use crate::core::filter::{FilterSample, FilterSampler, FilterTrait};
use crate::core::geometry::{Point2f, Vector2f};
use crate::utils::math::{gaussian, gaussian_integral};
use crate::{Ptr, Float};

#[repr(C)]
#[derive(Clone, Debug)]
pub struct GaussianFilter {
    pub radius: Vector2f,
    pub sigma: Float,
    pub exp_x: Float,
    pub exp_y: Float,
    pub sampler: FilterSampler,
}

impl GaussianFilter {
    pub fn new(radius: Vector2f, sigma: Float) -> Self {
        let exp_x = gaussian(radius.x(), 0.0, sigma);
        let exp_y = gaussian(radius.y(), 0.0, sigma);
        let sampler = FilterSampler::new(radius, move |p: Point2f| {
            let gx = (gaussian(p.x(), 0.0, sigma) - exp_x).max(0.0);
            let gy = (gaussian(p.y(), 0.0, sigma) - exp_y).max(0.0);
            gx * gy
        });
        Self {
            radius,
            sigma,
            exp_x,
            exp_y,
            sampler,
        }
    }
}

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

    fn evaluate(&self, p: Point2f) -> Float {
        (gaussian(p.x(), 0.0, self.sigma) - self.exp_x).max(0.0)
            * (gaussian(p.y(), 0.0, self.sigma) - self.exp_y).max(0.0)
    }

    fn integral(&self) -> Float {
        (gaussian_integral(-self.radius.x(), self.radius.x(), 0.0, self.sigma)
            - 2.0 * self.radius.x() * self.exp_x)
            * (gaussian_integral(-self.radius.y(), self.radius.y(), 0.0, self.sigma)
                - 2.0 * self.radius.y() * self.exp_y)
    }

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