use crate::core::color::{RGB, XYZ};
use crate::core::geometry::*;
use crate::core::image::Image;
use crate::core::interaction::{
    Interaction, InteractionTrait, MediumInteraction, SurfaceInteraction,
};
use crate::core::light::{
    LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType,
};
use crate::core::medium::MediumInterface;
use crate::core::shape::{Shape, ShapeSampleContext, ShapeTrait};
use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::core::texture::{
    GPUFloatTexture, TextureEvalContext, TextureEvaluator, UniversalTextureEvaluator,
};
use crate::spectra::*;
use crate::utils::hash::hash_float;
use crate::utils::{Ptr, Transform};
use crate::{Float, PI};
use num_traits::Float as NumFloat;

#[repr(C)]
#[derive(Clone, Debug, Copy)]
pub struct DiffuseAreaLight {
    pub base: LightBase,
    pub shape: Ptr<Shape>,
    pub alpha: Ptr<GPUFloatTexture>,
    pub colorspace: Ptr<RGBColorSpace>,
    pub lemit: Ptr<DenselySampledSpectrum>,
    pub image: Ptr<Image>,
    pub area: Float,
    pub two_sided: bool,
    pub scale: Float,
}

unsafe impl Send for DiffuseAreaLight {}
unsafe impl Sync for DiffuseAreaLight {}

impl DiffuseAreaLight {
    fn l_base(&self, n: Normal3f, wo: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum {
        if !self.two_sided && n.dot(wo.into()) <= 0.0 {
            return SampledSpectrum::new(0.0);
        }
        self.lemit.sample(lambda) * self.scale
    }

    fn alpha_masked(&self, intr: &Interaction) -> bool {
        if self.alpha.is_null() {
            return false;
        };
        let ctx = TextureEvalContext::from(intr);
        let a = UniversalTextureEvaluator.evaluate_float(&self.alpha, &ctx);
        if a >= 1.0 {
            return false;
        }
        if a <= 0.0 {
            return true;
        }
        hash_float(&intr.p()) > a
    }
}

impl LightTrait for DiffuseAreaLight {
    fn base(&self) -> &LightBase {
        &self.base
    }

    fn sample_li(
        &self,
        ctx: &LightSampleContext,
        u: Point2f,
        lambda: &SampledWavelengths,
        _allow_incomplete_pdf: bool,
    ) -> Option<LightLiSample> {
        let shape_ctx = ShapeSampleContext::new(ctx.pi, ctx.n, ctx.ns, 0.0);
        let ss = self.shape.sample_from_context(&shape_ctx, u)?;
        let mut intr = ss.intr;
        intr.set_medium_interface(self.base.medium_interface);
        let p = intr.p();
        let n = intr.n();
        let uv = intr.get_common().uv;

        // let generic_intr = Interaction::Surface(intr);
        if self.alpha_masked(&intr) {
            return None;
        }

        let wi = (p - ctx.p()).normalize();
        let le = self.l(p, n, uv, -wi, lambda);

        if le.is_black() {
            return None;
        }

        Some(LightLiSample::new(le, wi, ss.pdf, intr))
    }

    fn pdf_li(&self, ctx: &LightSampleContext, wi: Vector3f, _allow_incomplete_pdf: bool) -> Float {
        let shape_ctx = ShapeSampleContext::new(ctx.pi, ctx.n, ctx.ns, 0.);
        self.shape.pdf_from_context(&shape_ctx, wi)
    }

    fn l(
        &self,
        p: Point3f,
        n: Normal3f,
        mut uv: Point2f,
        w: Vector3f,
        lambda: &SampledWavelengths,
    ) -> SampledSpectrum {
        if self.two_sided && n.dot(w.into()) < 0. {
            return SampledSpectrum::new(0.);
        }
        let intr = Interaction::Surface(SurfaceInteraction::new_minimal(
            Point3fi::new_from_point(p),
            uv,
        ));

        if self.alpha_masked(&intr) {
            return SampledSpectrum::new(0.);
        }
        if !self.image.is_null() {
            let mut rgb = RGB::default();
            uv[1] = 1. - uv[1];
            for c in 0..3 {
                rgb[c] = self.image.bilerp_channel(uv, c as i32);
            }

            let cs_ref = unsafe { self.colorspace.as_ref() };
            let spec = RGBIlluminantSpectrum::new(cs_ref, rgb.clamp_zero());

            self.scale * spec.sample(lambda)
        } else {
            self.scale * self.lemit.sample(lambda)
        }
    }

    fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
        todo!()
    }

    #[cfg(not(target_os = "cuda"))]
    fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
        let mut l = SampledSpectrum::new(0.);
        if !self.image.is_null() {
            for y in 0..self.image.resolution().y() {
                for x in 0..self.image.resolution().x() {
                    let mut rgb = RGB::default();
                    for c in 0..3 {
                        rgb[c] = self.image.get_channel(Point2i::new(x, y), c as i32);
                    }

                    let cs_ref = unsafe { self.colorspace.as_ref() };
                    l += RGBIlluminantSpectrum::new(cs_ref, rgb.clamp_zero()).sample(&lambda);
                }
            }
            l *= self.scale / (self.image.resolution().x() * self.image.resolution().y()) as Float;
        } else {
            l = self.lemit.sample(&lambda) * self.scale;
        }
        let two_side = if self.two_sided { 2. } else { 1. };
        PI * two_side * self.area * l
    }

    #[cfg(not(target_os = "cuda"))]
    fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
        unimplemented!()
    }

    #[cfg(not(target_os = "cuda"))]
    fn bounds(&self) -> Option<LightBounds> {
        let mut phi = 0.;
        if !self.image.is_null() {
            for y in 0..self.image.resolution().y() {
                for x in 0..self.image.resolution().x() {
                    for c in 0..3 {
                        phi += self.image.get_channel(Point2i::new(x, y), c);
                    }
                }
            }
        } else {
            phi = self.lemit.max_value();
        }

        let nb = self.shape.normal_bounds();
        Some(LightBounds::new(
            &self.shape.bounds(),
            nb.w,
            phi,
            nb.cos_theta,
            (PI / 2.).cos(),
            self.two_sided,
        ))
    }
}
