use crate::core::color::RGB;
use crate::core::geometry::{
    Bounds2f, Bounds3f, DirectionCone, Normal3f, Point2f, Point2i, Point3f, Point3fi, Ray,
    Vector3f, VectorLike, cos_theta,
};
use crate::core::interaction::{
    Interaction, InteractionBase, InteractionTrait, MediumInteraction, SimpleInteraction,
    SurfaceInteraction,
};
use crate::core::medium::MediumInterface;
use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::lights::*;
use crate::spectra::{
    DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGBColorSpace, RGBIlluminantSpectrum,
    SampledSpectrum, SampledWavelengths,
};
use crate::utils::Transform;
use crate::utils::math::{equal_area_sphere_to_square, radians, safe_sqrt, smooth_step, square};
use crate::{Float, PI};
use bitflags::bitflags;

use enum_dispatch::enum_dispatch;

bitflags! {
    #[repr(C)]
    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
    pub struct LightType: u32 {
        const DeltaPosition  = 1;
        const DeltaDirection = 2;
        const Area           = 4;
        const Infinite       = 8;
    }
}

impl LightType {
    pub fn is_infinite(&self) -> bool {
        self.contains(LightType::Infinite)
    }

    pub fn is_delta_light(&self) -> bool {
        self.contains(LightType::DeltaPosition) || self.contains(LightType::DeltaDirection)
    }
}

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct LightLeSample {
    pub l: SampledSpectrum,
    pub ray: Ray,
    pub pdf_pos: Float,
    pub pdf_dir: Float,
    pub intr: Interaction,
}

#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct LightLiSample {
    pub l: SampledSpectrum,
    pub wi: Vector3f,
    pub pdf: Float,
    pub p_light: Interaction,
}

#[cfg(not(target_os = "cuda"))]
impl LightLiSample {
    pub fn new(l: SampledSpectrum, wi: Vector3f, pdf: Float, p_light: Interaction) -> Self {
        Self {
            l,
            wi,
            pdf,
            p_light,
        }
    }
}
#[repr(C)]
#[derive(Debug, Copy, Default, Clone)]
pub struct LightSampleContext {
    pub pi: Point3fi,
    pub n: Normal3f,
    pub ns: Normal3f,
}

impl LightSampleContext {
    pub fn new(pi: Point3fi, n: Normal3f, ns: Normal3f) -> Self {
        Self { pi, n, ns }
    }

    pub fn p(&self) -> Point3f {
        self.pi.into()
    }
}

impl From<&SurfaceInteraction> for LightSampleContext {
    fn from(si: &SurfaceInteraction) -> Self {
        Self {
            pi: si.common.pi,
            n: si.common.n,
            ns: si.shading.n,
        }
    }
}

impl From<&MediumInteraction> for LightSampleContext {
    fn from(mi: &MediumInteraction) -> Self {
        Self {
            pi: mi.common.pi,
            n: Normal3f::default(),
            ns: Normal3f::default(),
        }
    }
}

impl From<&Interaction> for LightSampleContext {
    fn from(intr: &Interaction) -> Self {
        match intr {
            Interaction::Surface(si) => Self {
                pi: si.common.pi,
                n: si.common.n,
                ns: si.shading.n,
            },

            Interaction::Medium(mi) => Self {
                pi: mi.common.pi,
                n: mi.common.n,
                ns: mi.common.n,
            },

            Interaction::Simple(sim) => Self {
                pi: sim.common.pi,
                n: sim.common.n,
                ns: sim.common.n,
            },
        }
    }
}

#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct LightBase {
    pub render_from_light: Transform,
    pub light_type: LightType,
    pub medium_interface: MediumInterface,
}

impl LightBase {
    pub fn new(
        light_type: LightType,
        render_from_light: Transform,
        medium_interface: MediumInterface,
    ) -> Self {
        Self {
            light_type,
            render_from_light,
            medium_interface,
        }
    }

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

    pub fn light_type(&self) -> LightType {
        self.light_type
    }

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

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct LightBounds {
    pub bounds: Bounds3f,
    pub phi: Float,
    pub w: Vector3f,
    pub cos_theta_o: Float,
    pub cos_theta_e: Float,
    pub two_sided: bool,
}

#[cfg(not(target_os = "cuda"))]
impl LightBounds {
    pub fn new(
        bounds: &Bounds3f,
        w: Vector3f,
        phi: Float,
        cos_theta_o: Float,
        cos_theta_e: Float,
        two_sided: bool,
    ) -> Self {
        Self {
            bounds: *bounds,
            phi,
            w,
            cos_theta_o,
            cos_theta_e,
            two_sided,
        }
    }
}

impl LightBounds {
    pub fn centroid(&self) -> Point3f {
        self.bounds.p_min + Vector3f::from(self.bounds.p_max) / 2.
    }

    pub fn importance(&self, p: Point3f, n: Normal3f) -> Float {
        // Compute clamped squared distance to reference point
        let pc = self.centroid();
        let d2_raw = p.distance_squared(pc);
        let d2 = d2_raw.max(self.bounds.diagonal().norm()) / 2.;
        let cos_sub_clamped = |sin_theta_a: Float,
                               cos_theta_a: Float,
                               sin_theta_b: Float,
                               cos_theta_b: Float|
         -> Float {
            if cos_theta_a > cos_theta_b {
                return 1.;
            }
            cos_theta_a * cos_theta_b + sin_theta_a * sin_theta_b
        };

        let sin_sub_clamped = |sin_theta_a: Float,
                               cos_theta_a: Float,
                               sin_theta_b: Float,
                               cos_theta_b: Float|
         -> Float {
            if cos_theta_a > cos_theta_b {
                return 1.;
            }
            sin_theta_a * cos_theta_b - cos_theta_a * sin_theta_b
        };

        let wi = (p - pc).normalize();
        let mut cos_theta_w = self.w.dot(wi);
        if self.two_sided {
            cos_theta_w = cos_theta_w.abs();
        }
        let sin_theta_w = safe_sqrt(1. - square(cos_theta_w));
        let cos_theta_b = DirectionCone::bound_subtended_directions(&self.bounds, p).cos_theta;
        let sin_theta_b = safe_sqrt(1. - square(cos_theta_b));
        let sin_theta_o = safe_sqrt(1. - square(self.cos_theta_o));
        let cos_theta_x = cos_sub_clamped(sin_theta_w, cos_theta_w, sin_theta_o, self.cos_theta_o);
        let sin_theta_x = sin_sub_clamped(sin_theta_w, cos_theta_w, sin_theta_o, self.cos_theta_o);
        let cos_theta_p = cos_sub_clamped(sin_theta_x, cos_theta_x, sin_theta_b, cos_theta_b);

        if cos_theta_p <= self.cos_theta_e {
            return 0.;
        }

        let mut importance = self.phi * cos_theta_p / d2;
        if n != Normal3f::new(0., 0., 0.) {
            let cos_theta_i = wi.abs_dot(n.into());
            let sin_theta_i = safe_sqrt(1. - square(cos_theta_i));
            let cos_thetap_i = cos_sub_clamped(sin_theta_i, cos_theta_i, sin_theta_b, cos_theta_b);
            importance *= cos_thetap_i;
        }
        importance
    }

    pub fn union(a: &Self, b: &Self) -> Self {
        if a.phi == 0. {
            return a.clone();
        }
        if b.phi == 0. {
            return b.clone();
        }

        let a_cone = DirectionCone::new(a.w, a.cos_theta_o);
        let b_cone = DirectionCone::new(b.w, b.cos_theta_o);
        let cone = DirectionCone::union(&a_cone, &b_cone);
        let cos_theta_o = cone.cos_theta;
        let cos_theta_e = a.cos_theta_e.min(b.cos_theta_e);
        LightBounds::new(
            &a.bounds.union(b.bounds),
            cone.w,
            a.phi + b.phi,
            cos_theta_o,
            cos_theta_e,
            a.two_sided || b.two_sided,
        )
    }
}

#[enum_dispatch]
pub trait LightTrait {
    fn base(&self) -> &LightBase;

    fn sample_li(
        &self,
        ctx: &LightSampleContext,
        u: Point2f,
        lambda: &SampledWavelengths,
        allow_incomplete_pdf: bool,
    ) -> Option<LightLiSample>;

    fn pdf_li(&self, ctx: &LightSampleContext, wi: Vector3f, allow_incomplete_pdf: bool) -> Float;

    fn l(
        &self,
        p: Point3f,
        n: Normal3f,
        uv: Point2f,
        w: Vector3f,
        lambda: &SampledWavelengths,
    ) -> SampledSpectrum;

    fn le(&self, ray: &Ray, lambda: &SampledWavelengths) -> SampledSpectrum;

    fn light_type(&self) -> LightType {
        self.base().light_type
    }

    #[cfg(not(target_os = "cuda"))]
    fn bounds(&self) -> Option<LightBounds>;

    #[cfg(not(target_os = "cuda"))]
    fn preprocess(&mut self, scene_bounds: &Bounds3f);

    #[cfg(not(target_os = "cuda"))]
    fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum;
}

#[enum_dispatch(LightTrait)]
#[repr(C)]
#[derive(Debug, Clone, Copy)]
#[allow(clippy::large_enum_variant)]
pub enum Light {
    DiffuseArea(DiffuseAreaLight),
    Distant(DistantLight),
    Goniometric(GoniometricLight),
    InfiniteUniform(UniformInfiniteLight),
    InfiniteImage(ImageInfiniteLight),
    InfinitePortal(PortalInfiniteLight),
    Point(PointLight),
    Projection(ProjectionLight),
    Spot(SpotLight),
}
