use crate::globals::get_options;
use shared::Ptr;
use shared::bxdfs::DiffuseBxDF;
use shared::core::bsdf::BSDF;
use shared::core::bssrdf::BSSRDF;
use shared::core::bxdf::BxDF;
use shared::core::camera::Camera;
use shared::core::geometry::Ray;
use shared::core::interaction::{MediumInteraction, SimpleInteraction, SurfaceInteraction};
use shared::core::material::{Material, MaterialEvalContext, MaterialTrait};
use shared::core::sampler::{Sampler, SamplerTrait};
use shared::core::texture::UniversalTextureEvaluator;
use shared::spectra::SampledWavelengths;

pub trait InteractionGetter {
    fn get_bsdf(
        &mut self,
        r: &Ray,
        lambda: &SampledWavelengths,
        camera: &Camera,
        sampler: &mut Sampler,
    ) -> Option<BSDF>;

    fn get_bssrdf(
        &self,
        _ray: &Ray,
        lambda: &SampledWavelengths,
        _camera: &Camera,
    ) -> Option<BSSRDF>;
}

impl InteractionGetter for SurfaceInteraction {
    fn get_bsdf(
        &mut self,
        r: &Ray,
        lambda: &SampledWavelengths,
        camera: &Camera,
        sampler: &mut Sampler,
    ) -> Option<BSDF> {
        self.compute_differentials(r, camera, sampler.samples_per_pixel() as i32);
        let material = {
            let mut active_mat = unsafe { self.material.as_ref() };
            let tex_eval = UniversalTextureEvaluator;
            while let Material::Mix(mix) = active_mat {
                let ctx = MaterialEvalContext::from(&*self);
                active_mat = mix.choose_material(&tex_eval, &ctx)?;
            }
            active_mat.clone()
        };
        let ctx = MaterialEvalContext::from(&*self);
        let tex_eval = UniversalTextureEvaluator;
        let displacement = material.get_displacement();
        let normal_map = material
            .get_normal_map()
            .map(Ptr::from)
            .unwrap_or(Ptr::null());
        if !displacement.is_null() || !normal_map.is_null() {
            self.compute_bump_geom(&tex_eval, displacement, normal_map);
        }
        let mut bsdf = material.get_bsdf(&tex_eval, &ctx, lambda);
        if get_options().force_diffuse {
            let rho = bsdf.rho_wo(self.common.wo, &[sampler.get1d()], &[sampler.get2d()]);
            let diff_bxdf = BxDF::Diffuse(DiffuseBxDF::new(rho));
            bsdf = BSDF::new(self.shading.n, self.shading.dpdu, Ptr::from(&diff_bxdf));
        }
        Some(bsdf)
    }

    fn get_bssrdf(
        &self,
        _ray: &Ray,
        lambda: &SampledWavelengths,
        _camera: &Camera,
    ) -> Option<BSSRDF> {
        let mut active_mat = unsafe { self.material.as_ref() };
        let tex_eval = UniversalTextureEvaluator;
        while let Material::Mix(mix) = active_mat {
            let ctx = MaterialEvalContext::from(self);
            active_mat = mix.choose_material(&tex_eval, &ctx)?;
        }
        let material = active_mat.clone();
        let ctx = MaterialEvalContext::from(self);
        material.get_bssrdf(&tex_eval, &ctx, lambda)
    }
}

impl InteractionGetter for MediumInteraction {
    fn get_bsdf(
        &mut self,
        _r: &Ray,
        _lambda: &SampledWavelengths,
        _camera: &Camera,
        _sampler: &mut Sampler,
    ) -> Option<BSDF> {
        None
    }

    fn get_bssrdf(
        &self,
        _ray: &Ray,
        _lambda: &SampledWavelengths,
        _camera: &Camera,
    ) -> Option<BSSRDF> {
        None
    }
}

impl InteractionGetter for SimpleInteraction {
    fn get_bsdf(
        &mut self,
        _r: &Ray,
        _lambda: &SampledWavelengths,
        _camera: &Camera,
        _sampler: &mut Sampler,
    ) -> Option<BSDF> {
        None
    }

    fn get_bssrdf(
        &self,
        _ray: &Ray,
        _lambda: &SampledWavelengths,
        _camera: &Camera,
    ) -> Option<BSSRDF> {
        None
    }
}
