use crate::Float;
use crate::core::color::RGB;
use crate::core::geometry::{
    Bounds2f, Bounds3f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector3f, VectorLike, cos_theta,
};
use crate::core::image::Image;
use crate::core::light::{
    LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType,
};
use crate::core::medium::MediumInterface;
use crate::core::spectrum::SpectrumTrait;
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::math::{radians, square};
use crate::{
    spectra::{RGBColorSpace, RGBIlluminantSpectrum},
    utils::{Ptr, Transform, sampling::PiecewiseConstant2D},
};
use num_traits::Float as NumFloat;

#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct ProjectionLight {
    pub base: LightBase,
    pub scale: Float,
    pub hither: Float,
    pub screen_bounds: Bounds2f,
    pub screen_from_light: Transform,
    pub light_from_screen: Transform,
    pub a: Float,
    pub image: Ptr<Image>,
    pub distrib: Ptr<PiecewiseConstant2D>,
    pub image_color_space: Ptr<RGBColorSpace>,
}

impl ProjectionLight {
    pub fn i(&self, w: Vector3f, lambda: SampledWavelengths) -> SampledSpectrum {
        if w.z() < self.hither {
            return SampledSpectrum::new(0.);
        }
        let ps = self.screen_from_light.apply_to_point(w.into());
        if !self.screen_bounds.contains(Point2f::new(ps.x(), ps.y())) {
            return SampledSpectrum::new(0.);
        }
        let uv = Point2f::from(self.screen_bounds.offset(&Point2f::new(ps.x(), ps.y())));
        let mut rgb = RGB::default();
        for c in 0..3 {
            rgb[c] = self.image.lookup_nearest_channel(uv, c as i32);
        }
        let s = RGBIlluminantSpectrum::new(&*self.image_color_space, rgb.clamp_zero());
        self.scale * s.sample(&lambda)
    }
}

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

    fn sample_li(
        &self,
        _ctx: &LightSampleContext,
        _u: Point2f,
        _lambda: &SampledWavelengths,
        _allow_incomplete_pdf: bool,
    ) -> Option<LightLiSample> {
        todo!()
    }

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

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

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

    fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
        let mut sum = SampledSpectrum::new(0.);
        let res = self.image.resolution();
        for y in 0..res.y() {
            for x in 0..res.x() {
                let ps = self.screen_bounds.lerp(Point2f::new(
                    (x as Float + 0.5) / res.x() as Float,
                    (y as Float + 0.5) / res.y() as Float,
                ));
                let w_raw = Vector3f::from(self.light_from_screen.apply_to_point(Point3f::new(
                    ps.x(),
                    ps.y(),
                    0.,
                )));
                let w = w_raw.normalize();
                let dwda = cos_theta(w).powi(3);
                let mut rgb = RGB::default();
                for c in 0..3 {
                    rgb[c] = self.image.get_channel(Point2i::new(x, y), c as i32);
                }

                let s = RGBIlluminantSpectrum::new(&*self.image_color_space, rgb.clamp_zero());
                sum += s.sample(&lambda) * dwda;
            }
        }
        self.scale * self.a * sum / (res.x() * res.y()) as Float
    }

    fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
        todo!()
    }

    fn bounds(&self) -> Option<LightBounds> {
        todo!()
    }
}
