use crate::utils::sampling::PiecewiseConstant2D;
use crate::utils::{FileLoc, ParameterDictionary};
use shared::Float;
use shared::core::filter::{Filter, FilterSampler};
use shared::core::geometry::{Bounds2f, Point2f, Vector2f};
use shared::filters::*;
use shared::utils::containers::Array2D;

pub trait FilterFactory {
    fn create(name: &str, params: &ParameterDictionary, loc: &FileLoc) -> Result<Filter, String>;
}

impl FilterFactory for Filter {
    fn create(name: &str, params: &ParameterDictionary, loc: &FileLoc) -> Result<Self, String> {
        match name {
            "box" => {
                let xw = params.get_one_float("xradius", 0.5);
                let yw = params.get_one_float("yradius", 0.5);
                let filter = BoxFilter::new(Vector2f::new(xw, yw));
                Ok(Filter::Box(filter))
            }
            "gaussian" => {
                let xw = params.get_one_float("xradius", 1.5);
                let yw = params.get_one_float("yradius", 1.5);
                let sigma = params.get_one_float("sigma", 0.5);
                let filter = GaussianFilter::new(Vector2f::new(xw, yw), sigma);
                Ok(Filter::Gaussian(filter))
            }
            "mitchell" => {
                let xw = params.get_one_float("xradius", 2.);
                let yw = params.get_one_float("yradius", 2.);
                let b = params.get_one_float("B", 1. / 3.);
                let c = params.get_one_float("C", 1. / 3.);
                let filter = MitchellFilter::new(Vector2f::new(xw, yw), b, c);
                Ok(Filter::Mitchell(filter))
            }
            "sinc" => {
                let xw = params.get_one_float("xradius", 4.);
                let yw = params.get_one_float("yradius", 4.);
                let tau = params.get_one_float("tau", 3.);
                let filter = LanczosSincFilter::new(Vector2f::new(xw, yw), tau);
                Ok(Filter::LanczosSinc(filter))
            }
            "triangle" => {
                let xw = params.get_one_float("xradius", 2.);
                let yw = params.get_one_float("yradius", 2.);
                let filter = TriangleFilter::new(Vector2f::new(xw, yw));
                Ok(Filter::Triangle(filter))
            }
            _ => Err(format!("Film type '{}' unknown at {}", name, loc)),
        }
    }
}

pub trait CreateFilterSampler {
    fn new<F>(radius: Vector2f, func: F) -> Self
    where
        F: Fn(Point2f) -> Float;
}

impl CreateFilterSampler for FilterSampler {
    fn new<F>(radius: Vector2f, func: F) -> Self
    where
        F: Fn(Point2f) -> Float,
    {
        let domain = Bounds2f::from_points(
            Point2f::new(-radius.x(), -radius.y()),
            Point2f::new(radius.x(), radius.y()),
        );

        let nx = (32.0 * radius.x()) as usize;
        let ny = (32.0 * radius.y()) as usize;

        let mut f = Array2D::new_with_dims(nx, ny);
        for y in 0..f.y_size() {
            for x in 0..f.x_size() {
                let p = domain.lerp(Point2f::new(
                    (x as Float + 0.5) / f.x_size() as Float,
                    (y as Float + 0.5) / f.y_size() as Float,
                ));
                f[(x as i32, y as i32)] = func(p);
            }
        }
        let distrib = PiecewiseConstant2D::new_with_bounds(&f, domain);
        Self { domain, f, distrib }
    }
}
