use crate::Float;
use crate::core::geometry::{Point2f, VectorLike};
use crate::core::texture::{
    GPUFloatTexture, GPUSpectrumTexture, TextureEvalContext, TextureMapping2D,
};
use crate::spectra::sampled::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr;
use crate::utils::math::square;
use crate::utils::noise::noise_2d;
use num_traits::Float as NumFloat;

fn inside_polka_dot(st: Point2f) -> bool {
    let s_cell = (st[0] + 0.5).floor();
    let t_cell = (st[1] + 0.5).floor();
    if noise_2d(s_cell + 0.5, t_cell + 0.5) > 0. {
        let radius = 0.35;
        let max_shift = 0.5 + radius;
        let s_center = s_cell + max_shift * noise_2d(s_cell + 1.5, t_cell + 2.8);
        let t_center = t_cell + max_shift * noise_2d(s_cell + 4.5, t_cell + 9.8);
        let dst = st - Point2f::new(s_center, t_center);
        if dst.norm_squared() < square(radius) {
            return true;
        }
    }
    return false;
}

#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct FloatDotsTexture {
    pub mapping: TextureMapping2D,
    pub outside_dot: Ptr<GPUFloatTexture>,
    pub inside_dot: Ptr<GPUFloatTexture>,
}

impl FloatDotsTexture {
    pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
        let c = self.mapping.map(ctx);
        let target_texture = if inside_polka_dot(c.st) {
            self.inside_dot
        } else {
            self.outside_dot
        };

        if !target_texture.is_null() {
            target_texture.evaluate(ctx)
        } else {
            0.0
        }
    }
}

#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct SpectrumDotsTexture {
    pub mapping: TextureMapping2D,
    pub outside_dot: Ptr<GPUSpectrumTexture>,
    pub inside_dot: Ptr<GPUSpectrumTexture>,
}

impl SpectrumDotsTexture {
    pub fn evaluate(
        &self,
        ctx: &TextureEvalContext,
        lambda: &SampledWavelengths,
    ) -> SampledSpectrum {
        let c = self.mapping.map(ctx);

        let target_texture = if inside_polka_dot(c.st) {
            self.inside_dot
        } else {
            self.outside_dot
        };

        if !target_texture.is_null() {
            target_texture.evaluate(ctx, lambda)
        } else {
            SampledSpectrum::new(0.0)
        }
    }
}
