use super::{
    DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES, RGBColorSpace,
    SampledSpectrum, SampledWavelengths,
};
use crate::core::color::{RGB, RGBSigmoidPolynomial, XYZ};
use crate::core::spectrum::SpectrumTrait;
use crate::utils::Ptr;

use crate::Float;

#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct RGBAlbedoSpectrum {
    pub rsp: RGBSigmoidPolynomial,
}

impl RGBAlbedoSpectrum {
    pub fn new(cs: &RGBColorSpace, rgb: RGB) -> Self {
        Self {
            rsp: cs.to_rgb_coeffs(rgb),
        }
    }
}

impl SpectrumTrait for RGBAlbedoSpectrum {
    fn evaluate(&self, lambda: Float) -> Float {
        self.rsp.evaluate(lambda)
    }

    fn max_value(&self) -> Float {
        self.rsp.max_value()
    }
}

#[repr(C)]
#[derive(Debug, Default, Clone, Copy)]
pub struct UnboundedRGBSpectrum {
    pub scale: Float,
    pub rsp: RGBSigmoidPolynomial,
}

impl UnboundedRGBSpectrum {
    pub fn new(cs: RGBColorSpace, rgb: RGB) -> Self {
        let m = rgb.r.max(rgb.g).max(rgb.b);
        let scale = 2.0 * m;
        let scaled_rgb = if scale != 0.0 {
            rgb / scale
        } else {
            RGB::new(0.0, 0.0, 0.0)
        };
        Self {
            scale,
            rsp: cs.to_rgb_coeffs(scaled_rgb),
        }
    }
}

impl SpectrumTrait for UnboundedRGBSpectrum {
    fn evaluate(&self, lambda: Float) -> Float {
        self.scale * self.rsp.evaluate(lambda)
    }

    fn max_value(&self) -> Float {
        self.scale * self.rsp.max_value()
    }
}

#[derive(Debug, Clone, Copy, Default)]
pub struct RGBIlluminantSpectrum {
    pub scale: Float,
    pub rsp: RGBSigmoidPolynomial,
    pub illuminant: Ptr<DenselySampledSpectrum>,
}

impl RGBIlluminantSpectrum {
    pub fn new(cs: &RGBColorSpace, rgb: RGB) -> Self {
        let illuminant = cs.illuminant;
        let m = rgb.max_component_value();
        let scale = 2. * m;
        let rsp = cs.to_rgb_coeffs(if scale == 1. {
            rgb / scale
        } else {
            RGB::new(0., 0., 0.)
        });
        Self {
            scale,
            rsp,
            illuminant,
        }
    }
}

impl SpectrumTrait for RGBIlluminantSpectrum {
    fn evaluate(&self, lambda: Float) -> Float {
        if self.illuminant.is_null() {
            return 0.;
        }
        self.scale * self.rsp.evaluate(lambda) * self.illuminant.evaluate(lambda)
    }

    fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
        if self.illuminant.is_null() {
            return SampledSpectrum::new(0.);
        }
        SampledSpectrum::from_fn(|i| self.scale * self.rsp.evaluate(lambda[i]))
    }

    fn max_value(&self) -> Float {
        if self.illuminant.is_null() {
            return 0.;
        }
        self.scale * self.rsp.max_value() * self.illuminant.max_value()
    }
}

#[derive(Debug, Clone, Copy)]
pub struct RGBSpectrum {
    pub c: [Float; 3],
}

#[derive(Debug, Clone, Copy)]
pub struct RGBUnboundedSpectrum {
    pub scale: Float,
    pub rsp: RGBSigmoidPolynomial,
}

impl Default for RGBUnboundedSpectrum {
    fn default() -> Self {
        Self {
            scale: 0.0,
            rsp: RGBSigmoidPolynomial::default(),
        }
    }
}

impl RGBUnboundedSpectrum {
    pub fn new(cs: &RGBColorSpace, rgb: RGB) -> Self {
        let m = rgb.max_component_value();

        let scale = 2.0 * m;

        let rgb_norm = if scale > 0.0 {
            rgb / scale
        } else {
            RGB::new(0.0, 0.0, 0.0)
        };

        let rsp = cs.to_rgb_coeffs(rgb_norm);

        Self { scale, rsp }
    }
}

impl SpectrumTrait for RGBUnboundedSpectrum {
    fn evaluate(&self, lambda: Float) -> Float {
        self.scale * self.rsp.evaluate(lambda)
    }

    fn max_value(&self) -> Float {
        self.scale * self.rsp.max_value()
    }
}
