use crate::core::filter::{FilterSampler, FilterSample, FilterTrait};
use crate::core::geometry::{Point2f, Vector2f};
use crate::utils::math::{lerp, windowed_sinc};
use crate::utils::rng::Rng;
use crate::Float;

#[repr(C)]
#[derive(Clone, Debug)]
pub struct LanczosSincFilter {
    pub radius: Vector2f,
    pub tau: Float,
    pub sampler: FilterSampler,
    pub integral: Float,
}

impl LanczosSincFilter {
    pub fn new(radius: Vector2f, tau: Float) -> Self {
        let evaluate = move |p: Point2f| -> Float {
            windowed_sinc(p.x(), radius.x(), tau) * windowed_sinc(p.y(), radius.y(), tau)
        };

        let sampler = FilterSampler::new(radius, evaluate);

        let sqrt_samples = 64u32;
        let n_samples = sqrt_samples * sqrt_samples;
        let area = (2.0 * radius.x()) * (2.0 * radius.y());
        let mut sum = 0.0;
        let mut rng = Rng::new(0);
        for y in 0..sqrt_samples {
            for x in 0..sqrt_samples {
                let u = Point2f::new(
                    (x as Float + rng.uniform::<Float>()) / sqrt_samples as Float,
                    (y as Float + rng.uniform::<Float>()) / sqrt_samples as Float,
                );
                let p = Point2f::new(
                    lerp(u.x(), -radius.x(), radius.x()),
                    lerp(u.y(), -radius.y(), radius.y()),
                );
                sum += evaluate(p);
            }
        }
        let integral = sum / n_samples as Float * area;

        Self { radius, tau, sampler, integral }
    }
}

impl FilterTrait for LanczosSincFilter {
    fn radius(&self) -> Vector2f {
        self.radius
    }

    fn evaluate(&self, p: Point2f) -> Float {
        windowed_sinc(p.x(), self.radius.x(), self.tau)
            * windowed_sinc(p.y(), self.radius.y(), self.tau)
    }

    fn integral(&self) -> Float {
        self.integral
    }

    fn sample(&self, u: Point2f) -> FilterSample {
        self.sampler.sample(u)
    }
}
