use crate::core::filter::{FilterSample, FilterSampler, FilterTrait};
use crate::core::geometry::{Point2f, Vector2f};
use crate::Float;
use num_traits::Float as NumFloat;

#[repr(C)]
#[derive(Clone, Debug)]
pub struct MitchellFilter {
    pub radius: Vector2f,
    pub b: Float,
    pub c: Float,
    pub sampler: FilterSampler,
}

pub fn mitchell_1d_eval(b: Float, c: Float, x: Float) -> Float {
    let x = x.abs();
    if x <= 1.0 {
        ((12.0 - 9.0 * b - 6.0 * c) * x.powi(3)
            + (-18.0 + 12.0 * b + 6.0 * c) * x.powi(2)
            + (6.0 - 2.0 * b))
            * (1.0 / 6.0)
    } else if x <= 2.0 {
        ((-b - 6.0 * c) * x.powi(3)
            + (6.0 * b + 30.0 * c) * x.powi(2)
            + (-12.0 * b - 48.0 * c) * x
            + (8.0 * b + 24.0 * c))
            * (1.0 / 6.0)
    } else {
        0.0
    }
}

impl MitchellFilter {
    pub fn new(radius: Vector2f, b: Float, c: Float) -> Self {
        let sampler = FilterSampler::new(radius, move |p: Point2f| {
            mitchell_1d_eval(p.x() / radius.x(), b, c) * mitchell_1d_eval(p.y() / radius.y(), b, c)
        });
        Self {
            radius,
            b,
            c,
            sampler,
        }
    }

    fn mitchell_1d(&self, x: Float) -> Float {
        mitchell_1d_eval(self.b, self.c, x)
    }
}

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

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

    fn integral(&self) -> Float {
        self.radius.x() * self.radius.y() / 4.0
    }

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