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::{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::DistantLight;
use shared::spectra::RGBColorSpace;
use shared::utils::{Ptr, Transform};
use shared::Float;

trait CreateDistantLight {
    fn new(render_from_light: Transform, le: Spectrum, scale: Float) -> Self;
}

impl CreateDistantLight for DistantLight {
    fn new(render_from_light: Transform, le: Spectrum, scale: Float) -> Self {
        let base = LightBase::new(
            LightType::DeltaDirection,
            render_from_light,
            MediumInterface::empty(),
        );
        let lemit = lookup_spectrum(&le);
        Self {
            base,
            lemit: Ptr::from(&*lemit),
            scale,
            scene_center: Point3f::default(),
            scene_radius: 0.,
        }
    }
}

pub fn create(
    render_from_light: Transform,
    _medium: Option<Medium>,
    parameters: &ParameterDictionary,
    _loc: &FileLoc,
    _shape: &Shape,
    _alpha_text: &FloatTexture,
    colorspace: Option<&RGBColorSpace>,
    _arena: &Arena,
) -> Result<Light> {
    let default_cs = crate::spectra::default_colorspace();
    let cs = colorspace.unwrap_or(&default_cs);
    let l = parameters
        .get_one_spectrum(
            "L",
            Some(Spectrum::Dense(cs.illuminant)),
            SpectrumType::Illuminant,
        )
        .unwrap();
    let mut scale = parameters.get_one_float("scale", 1.)?;

    let from = parameters.get_one_point3f("from", Point3f::new(0., 0., 0.))?;
    let to = parameters.get_one_point3f("to", Point3f::new(0., 0., 1.))?;
    let w = (from - to).normalize();
    let (v1, v2) = w.coordinate_system();
    let m: [Float; 16] = [
        v1.x(),
        v2.x(),
        w.x(),
        0.,
        v1.y(),
        v2.y(),
        w.y(),
        0.,
        v1.z(),
        v2.z(),
        w.z(),
        0.,
        0.,
        0.,
        0.,
        1.,
    ];
    let t = Transform::from_flat(&m).expect("Could not create transform for DistantLight");
    let final_render = render_from_light * t;
    scale /= spectrum_to_photometric(l);
    // Adjust scale to meet target illuminance value
    let e_v = parameters.get_one_float("illuminance", -1.)?;
    if e_v > 0. {
        scale *= e_v;
    }

    let specific = DistantLight::new(final_render, l, scale);

    Ok(Light::Distant(specific))
}
