use crate::bxdfs::{
    CoatedConductorBxDF, CoatedDiffuseBxDF, ConductorBxDF, DielectricBxDF, DiffuseBxDF, HairBxDF,
};
use crate::core::bsdf::BSDF;
use crate::core::bssrdf::BSSRDF;
use crate::core::bxdf::BxDF;
use crate::core::image::Image;
use crate::core::material::{Material, MaterialEvalContext, MaterialTrait};
use crate::core::scattering::TrowbridgeReitzDistribution;
use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::core::texture::{GPUFloatTexture, GPUSpectrumTexture, TextureEvaluator};
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr;
use crate::utils::hash::hash_float;
use crate::utils::math::clamp;

#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct MixMaterial {
    pub amount: Ptr<GPUFloatTexture>,
    pub materials: [Ptr<Material>; 2],
}

impl MixMaterial {
    pub fn choose_material<T: TextureEvaluator>(
        &self,
        tex_eval: &T,
        ctx: &MaterialEvalContext,
    ) -> Option<&Material> {
        let amt = tex_eval.evaluate_float(&self.amount, ctx);

        let index = if amt <= 0.0 {
            0
        } else if amt >= 1.0 {
            1
        } else {
            let u = hash_float(&(ctx.p, ctx.wo));
            if amt < u { 0 } else { 1 }
        };

        self.materials[index].get()
    }
}

impl MaterialTrait for MixMaterial {
    fn get_bsdf<T: TextureEvaluator>(
        &self,
        tex_eval: &T,
        ctx: &MaterialEvalContext,
        lambda: &SampledWavelengths,
    ) -> BSDF {
        if let Some(mat) = self.choose_material(tex_eval, ctx) {
            mat.get_bsdf(tex_eval, ctx, lambda)
        } else {
            BSDF::default()
        }
    }

    fn get_bssrdf<T>(
        &self,
        _tex_eval: &T,
        _ctx: &MaterialEvalContext,
        _lambda: &SampledWavelengths,
    ) -> Option<BSSRDF> {
        None
    }

    fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool {
        tex_eval.can_evaluate(&[self.amount], &[])
    }

    fn get_normal_map(&self) -> Option<&Image> {
        None
    }

    fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
        panic!(
            "MixMaterial::get_displacement() shouldn't be called. \
               Displacement is not supported on Mix materials directly."
        );
    }

    fn has_subsurface_scattering(&self) -> bool {
        false
    }
}
