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::{Op, OpSet, OpSetType, Options, _c};
use crate::geometry::Line;
use crate::renderer::_double_line;

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

impl<F, P> PatternFiller<F, P> for ZigZagLineFiller<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 zig_zag_offset = o
            .zigzag_offset
            .map(_c::<F>)
            .unwrap_or_else(|| _c::<F>(-1.0));
        if zig_zag_offset < F::zero() {
            zig_zag_offset = gap;
        }
        o.set_hachure_gap(Some((gap + zig_zag_offset).to_f32().unwrap()));
        let lines = polygon_hachure_lines(polygon_list.borrow_mut(), o);
        OpSet {
            op_set_type: OpSetType::FillSketch,
            ops: ZigZagLineFiller::zig_zag_lines(&lines, zig_zag_offset, o),
            size: None,
            path: None,
        }
    }
}

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

    fn zig_zag_lines(lines: &[Line<F>], zig_zag_offset: F, o: &mut Options) -> Vec<Op<F>> {
        let mut ops = vec![];
        for line in lines.iter() {
            let length = line.length();
            let count = length / (_c::<F>(2.0) * zig_zag_offset);
            let mut p1 = line.start_point;
            let mut p2 = line.end_point;
            if p1.x > p2.x {
                p1 = line.end_point;
                p2 = line.start_point;
            }

            let alpha = ((p2.y - p1.y) / (p2.x - p1.x)).atan();

            for i in 0..(count.to_isize().unwrap()) {
                let lstart = _c::<F>(i as f32) * _c::<F>(2.0) * zig_zag_offset;
                let lend = _c::<F>((i + 1) as f32) * _c::<F>(2.0) * zig_zag_offset;
                let dz = (zig_zag_offset.powi(2) * _c::<F>(2.0)).sqrt();
                let start: Point2D<F> = point2(
                    p1.x + lstart * num_traits::Float::cos(alpha),
                    p1.y + lstart * num_traits::Float::sin(alpha),
                );
                let end: Point2D<F> = point2(
                    p1.x + lend * num_traits::Float::cos(alpha),
                    p1.y + lend * num_traits::Float::sin(alpha),
                );
                let middle: Point2D<F> = point2(
                    start.x + dz * num_traits::Float::cos(alpha + _c::<F>(f32::PI() / 4.0)),
                    start.y + dz * num_traits::Float::sin(alpha + _c::<F>(f32::PI() / 4.0)),
                );
                ops.extend(_double_line(start.x, start.y, middle.x, middle.y, o, false));

                ops.extend(_double_line(middle.x, middle.y, end.x, end.y, o, false));
            }
        }
        ops
    }
}
