use crate::Float;
use crate::core::color::RGB;
use crate::core::geometry::{Point3f, Vector3f};
use crate::core::spectrum::SpectrumTrait;
use crate::core::texture::{TextureEvalContext, TextureMapping3D};
use crate::spectra::{RGBAlbedoSpectrum, RGBColorSpace, SampledSpectrum, SampledWavelengths};
use crate::utils::math::clamp;
use crate::utils::noise::fbm;
use crate::utils::ptr::Ptr;
use crate::utils::splines::evaluate_cubic_bezier;
use num_traits::Float as NumFloat;

#[repr(C)]
#[derive(Clone, Debug, Copy)]
pub struct MarbleTexture {
    pub mapping: TextureMapping3D,
    pub octaves: u32,
    pub omega: Float,
    pub scale: Float,
    pub variation: Float,
    // TODO: DO not forget to pass StandardColorSpace here!!
    pub colorspace: Ptr<RGBColorSpace>,
}

unsafe impl Send for MarbleTexture {}
unsafe impl Sync for MarbleTexture {}

impl MarbleTexture {
    pub fn evaluate(
        &self,
        ctx: &TextureEvalContext,
        lambda: &SampledWavelengths,
    ) -> SampledSpectrum {
        let mut c = self.mapping.map(ctx);
        c.p = Point3f::from(Vector3f::from(c.p) * self.scale);
        let marble = c.p.y()
            + self.variation
                * fbm(
                    c.p,
                    self.scale * c.dpdx,
                    self.scale * c.dpdy,
                    self.omega,
                    self.octaves,
                );
        let t = 0.5 + 0.5 * marble.sin();
        let colors: [Point3f; 9] = [
            Point3f::new(0.58, 0.58, 0.6),
            Point3f::new(0.58, 0.58, 0.6),
            Point3f::new(0.58, 0.58, 0.6),
            Point3f::new(0.5, 0.5, 0.5),
            Point3f::new(0.6, 0.59, 0.58),
            Point3f::new(0.58, 0.58, 0.6),
            Point3f::new(0.58, 0.58, 0.6),
            Point3f::new(0.2, 0.2, 0.33),
            Point3f::new(0.58, 0.58, 0.6),
        ];

        const N_SEG: i32 = 6; // (9 - 3)
        let t_clamped = clamp(t, 0.0, 1.0);
        let first = ((t_clamped * N_SEG as Float).floor() as i32).clamp(0, N_SEG - 1);
        let t_segment = t_clamped * N_SEG as Float - first as Float;
        let first_idx = first as usize;
        let (rgb_vec, _) = evaluate_cubic_bezier(&colors[first_idx..first_idx + 4], t_segment);
        let rgb = RGB::new(rgb_vec.x() * 1.5, rgb_vec.y() * 1.5, rgb_vec.z() * 1.5);

        RGBAlbedoSpectrum::new(&*self.colorspace, rgb).sample(lambda)
    }
}
