use std::borrow::BorrowMut;
use std::marker::PhantomData;

use euclid::default::Point2D;
use euclid::Trig;
use num_traits::{Float, FromPrimitive};

use super::scan_line_hachure::polygon_hachure_lines;
use super::traits::PatternFiller;
use crate::core::{OpSet, Options, _c, _cc};
use crate::geometry::Line;
use crate::renderer::ellipse;

pub struct DotFiller<F> {
    _phantom: PhantomData<F>,
}

impl<F, P> PatternFiller<F, P> for DotFiller<F>
where
    F: Float + Trig + FromPrimitive,
    P: BorrowMut<Vec<Vec<Point2D<F>>>>,
{
    fn fill_polygons(&self, mut polygon_list: P, o: &mut Options) -> crate::core::OpSet<F> {
        o.set_hachure_angle(Some(0.0));
        let lines = polygon_hachure_lines(polygon_list.borrow_mut(), o);
        let ops = DotFiller::dots_on_line(lines, o);
        OpSet {
            op_set_type: crate::core::OpSetType::FillSketch,
            ops,
            size: None,
            path: None,
        }
    }
}
impl<F: Float + Trig + FromPrimitive> DotFiller<F> {
    pub fn new() -> Self {
        DotFiller { _phantom: PhantomData }
    }

    fn dots_on_line(lines: Vec<Line<F>>, o: &mut Options) -> Vec<crate::core::Op<F>> {
        let mut ops = vec![];
        let mut gap = o.hachure_gap.map(_c::<F>).unwrap_or_else(|| _c::<F>(-1.0));
        if gap < F::zero() {
            gap = o.stroke_width.map(_c::<F>).unwrap_or_else(|| _c::<F>(1.0)) * _c::<F>(4.0);
        }
        gap = gap.max(_c::<F>(0.1));
        let mut fweight = o.fill_weight.map(_c::<F>).unwrap_or_else(|| _c::<F>(-1.0));
        if fweight < F::zero() {
            fweight = o.stroke_width.map(_c::<F>).unwrap_or_else(|| _c::<F>(1.0)) / _c::<F>(2.0);
        }

        let ro = gap / _c::<F>(4.0);
        for line in lines.iter() {
            let length = line.length();
            let dl = length / gap;
            let count = dl.ceil() - F::one();
            if count < F::zero() {
                continue;
            }
            let offset = length - (count * gap);
            let x = ((line.start_point.x + line.end_point.x) / _c::<F>(2.0)) - (gap / _c::<F>(4.0));
            let min_y = F::min(line.start_point.y, line.end_point.y);
            for i in 0..count.to_u64().unwrap() {
                let y = min_y + offset + (F::from(i).unwrap() * gap);
                let cx = (x - ro) + _cc::<F>(o.random()) * _c::<F>(2.0) * ro;
                let cy = (y - ro) + _cc::<F>(o.random()) * _c::<F>(2.0) * ro;
                let ellipse_ops = ellipse(cx, cy, fweight, fweight, o);
                ops.extend(ellipse_ops.ops);
            }
        }

        ops
    }
}

impl<F: Float + Trig + FromPrimitive> Default for DotFiller<F> {
    fn default() -> Self {
        Self::new()
    }
}
