use shared::Float;
use shared::core::spectrum::Spectrum;
use shared::spectra::cie::{CIE_S_LAMBDA, CIE_S0, CIE_S1, CIE_S2, N_CIES};
use shared::spectra::{
    BlackbodySpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, PiecewiseLinearSpectrum,
    SampledSpectrum, SampledWavelengths,
};
use shared::utils::math::square;

pub struct DenselySampledSpectrumBuffer {
    pub device: DenselySampledSpectrum,
    pub _storage: Vec<Float>,
}

impl std::ops::Deref for DenselySampledSpectrumBuffer {
    type Target = DenselySampledSpectrum;
    fn deref(&self) -> &Self::Target {
        &self.device
    }
}

impl DenselySampledSpectrumBuffer {
    pub fn new(lambda_min: i32, lambda_max: i32, values: Vec<Float>) -> Self {
        let view = DenselySampledSpectrum {
            lambda_min,
            lambda_max,
            values: values.as_ptr(),
        };
        Self {
            device: view,
            _storage: values,
        }
    }

    pub fn new_zero(lambda_min: i32, lambda_max: i32) -> Self {
        let n_values = (lambda_max - lambda_min + 1).max(0) as usize;
        let values = vec![0.0; n_values];
        Self::new(lambda_min, lambda_max, values)
    }

    pub fn from_spectrum(spec: &Spectrum) -> Self {
        let lambda_min = LAMBDA_MIN;
        let lambda_max = LAMBDA_MAX;

        let mut values = Vec::with_capacity((lambda_max - lambda_min + 1) as usize);

        for lambda in lambda_min..=lambda_max {
            values.push(spec.evaluate(lambda as Float));
        }

        Self::new(lambda_min, lambda_max, values)
    }

    pub fn from_function<F>(f: F, lambda_min: i32, lambda_max: i32) -> Self
    where
        F: Fn(Float) -> Float,
    {
        let mut values = Vec::with_capacity((lambda_max - lambda_min + 1) as usize);

        for lambda in lambda_min..=lambda_max {
            values.push(f(lambda as Float));
        }

        Self::new(lambda_min, lambda_max, values)
    }

    pub fn generate_cie_d(temperature: Float) -> Self {
        let cct = temperature * 1.4388 / 1.4380;

        if cct < 4000.0 {
            return Self::from_function(
                |lambda| BlackbodySpectrum::new(cct).evaluate(lambda),
                LAMBDA_MIN,
                LAMBDA_MAX,
            );
        }

        let x = if cct < 7000. {
            -4.607 * 1e9 / cct.powi(3) + 2.9678 * 1e6 / square(cct) + 0.09911 * 1e3 / cct + 0.244063
        } else {
            -2.0064 * 1e9 / cct.powi(3) + 1.9018 * 1e6 / square(cct) + 0.24748 * 1e3 / cct + 0.23704
        };
        let y = -3. * x + 2.87 * x - 0.275;
        let m = 0.0241 + 0.2562 * x - 0.7341 * y;
        let m1 = (-1.3515 - 1.7703 * x + 5.9114 * y) / m;
        let m2 = (0.0300 - 31.4424 * x + 30.0717 * y) / m;

        let coarse_values: Vec<Float> = (0..N_CIES)
            .map(|i| (CIE_S0[i] + CIE_S1[i] * m1 + CIE_S2[i] * m2) * 0.01)
            .collect();

        let temp_pls = PiecewiseLinearSpectrum {
            lambdas: CIE_S_LAMBDA.as_ptr(),
            values: coarse_values.as_ptr(),
            count: N_CIES as u32,
        };

        Self::from_function(|lambda| temp_pls.evaluate(lambda), LAMBDA_MIN, LAMBDA_MAX)
    }

    pub fn as_view(&self) -> DenselySampledSpectrum {
        DenselySampledSpectrum {
            lambda_min: self.lambda_min,
            lambda_max: self.lambda_max,
            values: self.samples.as_ptr(),
        }
    }

    pub fn scale(&mut self, s: Float) {
        for v in &mut self._storage {
            *v *= s;
        }
    }
}
