use crate::core::geometry::{
    Bounds3f, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector3f, VectorLike,
};
use crate::core::interaction::{Interaction, InteractionBase, InteractionTrait, SimpleInteraction};
use crate::core::light::{LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait};
use crate::core::spectrum::SpectrumTrait;
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths};
use crate::{Float, PI, Ptr, Transform};
use num_traits::Float as NumFloat;

#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct SpotLight {
    pub base: LightBase,
    pub iemit: Ptr<DenselySampledSpectrum>,
    pub scale: Float,
    pub cos_falloff_start: Float,
    pub cos_falloff_end: Float,
}

impl SpotLight {
    pub fn i(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum {
        let cos_theta = w.z(); // assuming normalized in light space
        let falloff = crate::utils::math::smooth_step(
            cos_theta,
            self.cos_falloff_end,
            self.cos_falloff_start,
        );
        falloff * self.scale * self.iemit.sample(lambda)
    }
}

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

    fn sample_li(
        &self,
        ctx: &LightSampleContext,
        _u: Point2f,
        lambda: &SampledWavelengths,
        _allow_incomplete_pdf: bool,
    ) -> Option<LightLiSample> {
        let pi = self
            .base
            .render_from_light
            .apply_to_interval(&Point3fi::default());
        let p: Point3f = pi.into();
        let wi = (p - ctx.p()).normalize();
        let w_light = self.base.render_from_light.apply_inverse_vector(-wi);
        let li = self.i(w_light, lambda) / p.distance_squared(ctx.p());

        let base = InteractionBase::new_boundary(p, 0., self.base.medium_interface);
        let intr = SimpleInteraction::new(base);
        Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr)))
    }

    fn pdf_li(
        &self,
        _ctx: &LightSampleContext,
        _wi: Vector3f,
        _allow_incomplete_pdf: bool,
    ) -> Float {
        0.
    }

    fn l(
        &self,
        _p: Point3f,
        _n: Normal3f,
        _uv: Point2f,
        _w: Vector3f,
        _lambda: &SampledWavelengths,
    ) -> SampledSpectrum {
        todo!()
    }

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

    #[cfg(not(target_os = "cuda"))]
    fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
        self.scale
            * self.iemit.sample(&lambda)
            * 2.
            * PI
            * ((1. - self.cos_falloff_start) + (self.cos_falloff_start - self.cos_falloff_end) / 2.)
    }

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

    #[cfg(not(target_os = "cuda"))]
    fn bounds(&self) -> Option<LightBounds> {
        let p = self
            .base
            .render_from_light
            .apply_to_point(Point3f::default());
        let w = self
            .base
            .render_from_light
            .apply_to_vector(Vector3f::new(0., 0., 1.))
            .normalize();
        let phi = self.scale * self.iemit.max_value() * 4. * PI;
        let cos_theta_e = (self.cos_falloff_end.acos() - self.cos_falloff_start.acos()).cos();
        Some(LightBounds::new(
            &Bounds3f::from_points(p, p),
            w,
            phi,
            self.cos_falloff_start,
            cos_theta_e,
            false,
        ))
    }
}
