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

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

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

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

impl<F, P> PatternFiller<F, P> for ZigZagFiller<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> {
        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 o2 = o.clone();
        o2.set_hachure_gap(Some(gap.to_f32().unwrap()));
        let lines = polygon_hachure_lines(polygon_list.borrow_mut(), &o2);
        let zig_zag_angle =
            (_c::<F>(f32::PI()) / _c::<F>(180.0)) * _c::<F>(o.hachure_angle.unwrap_or(0.0));
        let mut zig_zag_lines = vec![];
        let dgx = gap * _c::<F>(0.5) * Trig::cos(zig_zag_angle);
        let dgy = gap * _c::<F>(0.5) * Trig::sin(zig_zag_angle);

        for line in lines.iter() {
            if line.length() > _c::<F>(0.0) {
                zig_zag_lines.push(Line {
                    start_point: point2(line.start_point.x - dgx, line.start_point.y + dgy),
                    end_point: line.end_point,
                });
                zig_zag_lines.push(Line {
                    start_point: point2(line.start_point.x + dgx, line.start_point.y - dgy),
                    end_point: line.end_point,
                });
            }
        }

        let ops = ZigZagFiller::render_lines(zig_zag_lines, o);
        return OpSet {
            ops,
            op_set_type: OpSetType::FillSketch,
            size: None,
            path: None,
        };
    }
}

impl<F: Float + Trig + FromPrimitive> ZigZagFiller<F> {
    pub fn new() -> Self {
        ZigZagFiller { _phantom: PhantomData }
    }

    fn render_lines(lines: Vec<Line<F>>, o: &mut Options) -> Vec<crate::core::Op<F>> {
        let mut ops: Vec<crate::core::Op<F>> = vec![];
        lines.iter().for_each(|l| {
            ops.extend(crate::renderer::_double_line(
                l.start_point.x,
                l.start_point.y,
                l.end_point.x,
                l.end_point.y,
                o,
                true,
            ))
        });

        ops
    }
}
