use crate::core::light::lookup_spectrum;
use crate::core::spectrum::spectrum_to_photometric;
use crate::core::texture::FloatTexture;
use crate::utils::{Arena, FileLoc, ParameterDictionary};
use anyhow::Result;
use shared::core::geometry::{Frame, Point3f, VectorLike};
use shared::core::light::{Light, LightBase, LightType};
use shared::core::medium::{Medium, MediumInterface};
use shared::core::shape::Shape;
use shared::core::spectrum::Spectrum;
use shared::core::texture::SpectrumType;
use shared::lights::SpotLight;
use shared::spectra::RGBColorSpace;
use shared::utils::math::radians;
use shared::utils::{Ptr, Transform};
use shared::{Float, Ptr, Transform, PI};

trait CreateSpotLight {
    fn new(
        render_from_light: Transform,
        _medium_interface: MediumInterface,
        le: Spectrum,
        scale: Float,
        cos_falloff_start: Float,
        total_width: Float,
    ) -> Self;
}

impl CreateSpotLight for SpotLight {
    fn new(
        render_from_light: Transform,
        _medium_interface: MediumInterface,
        le: Spectrum,
        scale: Float,
        cos_falloff_start: Float,
        total_width: Float,
    ) -> Self {
        let base = LightBase::new(
            LightType::DeltaPosition,
            render_from_light,
            MediumInterface::empty(),
        );

        let i = lookup_spectrum(&le);
        let iemit = arena.alloc(i);
        Self {
            base,
            iemit,
            scale,
            cos_falloff_end: radians(total_width).cos(),
            cos_falloff_start: radians(cos_falloff_start).cos(),
        }
    }
}

pub fn create(
    render_from_light: Transform,
    medium: Option<Medium>,
    parameters: &ParameterDictionary,
    _loc: &FileLoc,
    _shape: &Shape,
    _alpha_tex: &FloatTexture,
    colorspace: Option<&RGBColorSpace>,
    arena: &Arena,
) -> Result<Light> {
    let default_cs = crate::spectra::default_colorspace();
    let cs = colorspace.unwrap_or(&default_cs);
    let i = parameters
        .get_one_spectrum(
            "I",
            Some(Spectrum::Dense(cs.illuminant)),
            SpectrumType::Illuminant,
        )
        .expect("No spectrum");
    let mut scale = parameters.get_one_float("scale", 1.)?;
    let coneangle = parameters.get_one_float("coneangle", 30.)?;
    let conedelta = parameters.get_one_float("conedelta", 5.)?;
    let from = parameters.get_one_point3f("from", Point3f::zero())?;
    let to = parameters.get_one_point3f("to", Point3f::new(0., 0., 1.))?;
    let dir_to_z = Transform::from(Frame::from_z((to - from).normalize()));
    let t = Transform::translate(from.into()) * dir_to_z.inverse();
    let final_render = render_from_light * t;
    scale /= spectrum_to_photometric(i);

    let phi_v = parameters.get_one_float("power", -1.)?;
    if phi_v > 0. {
        let cos_falloff_end = radians(coneangle).cos();
        let cos_falloff_start = radians(coneangle - conedelta).cos();
        let k_e = 2. * PI * ((1. - cos_falloff_start) + (cos_falloff_start - cos_falloff_end) / 2.);
        scale *= phi_v / k_e;
    }

    let mi = match medium {
        Some(m) => {
            let ptr = arena.alloc(m);
            MediumInterface {
                inside: ptr,
                outside: ptr,
            }
        }
        None => MediumInterface::default(),
    };

    let specific = SpotLight::new(final_render, mi, i, scale, coneangle, coneangle - conedelta);
    arena.alloc(specific);
    Ok(Light::Spot(specific))
}
