use crate::core::fill_rule::FillRule;
use crate::string::line::IntLine;
use crate::string::overlay::StringOverlay;
use crate::string::rule::StringRule;
use i_float::int::point::IntPoint;
use i_shape::int::path::IntPath;
use i_shape::int::shape::{IntShape, IntShapes};

pub trait IntSlice {
    fn slice_by_line(&self, line: IntLine, fill_rule: FillRule) -> IntShapes;
    fn slice_by_lines(&self, lines: &[IntLine], fill_rule: FillRule) -> IntShapes;
    fn slice_by_path(&self, path: &IntPath, fill_rule: FillRule) -> IntShapes;
    fn slice_by_paths(&self, paths: &[IntPath], fill_rule: FillRule) -> IntShapes;
}

impl IntSlice for IntShapes {
    #[inline]
    fn slice_by_line(&self, line: IntLine, fill_rule: FillRule) -> IntShapes {
        let mut overlay = StringOverlay::with_shapes(self);
        overlay.add_string_line(line);
        overlay
            .build_graph_view(fill_rule)
            .map(|graph| graph.extract_shapes(StringRule::Slice))
            .unwrap_or_default()
    }

    #[inline]
    fn slice_by_lines(&self, lines: &[IntLine], fill_rule: FillRule) -> IntShapes {
        let mut overlay = StringOverlay::with_shapes(self);
        overlay.add_string_lines(lines);
        overlay
            .build_graph_view(fill_rule)
            .map(|graph| graph.extract_shapes(StringRule::Slice))
            .unwrap_or_default()
    }

    #[inline]
    fn slice_by_path(&self, path: &IntPath, fill_rule: FillRule) -> IntShapes {
        let mut overlay = StringOverlay::with_shapes(self);
        overlay.add_string_path(path);
        overlay
            .build_graph_view(fill_rule)
            .map(|graph| graph.extract_shapes(StringRule::Slice))
            .unwrap_or_default()
    }

    #[inline]
    fn slice_by_paths(&self, paths: &[IntPath], fill_rule: FillRule) -> IntShapes {
        let mut overlay = StringOverlay::with_shapes(self);
        overlay.add_string_paths(paths);
        overlay
            .build_graph_view(fill_rule)
            .map(|graph| graph.extract_shapes(StringRule::Slice))
            .unwrap_or_default()
    }
}

impl IntSlice for IntShape {
    #[inline]
    fn slice_by_line(&self, line: IntLine, fill_rule: FillRule) -> IntShapes {
        let mut overlay = StringOverlay::with_shape(self);
        overlay.add_string_line(line);
        overlay
            .build_graph_view(fill_rule)
            .map(|graph| graph.extract_shapes(StringRule::Slice))
            .unwrap_or_default()
    }

    #[inline]
    fn slice_by_lines(&self, lines: &[IntLine], fill_rule: FillRule) -> IntShapes {
        let mut overlay = StringOverlay::with_shape(self);
        overlay.add_string_lines(lines);
        overlay
            .build_graph_view(fill_rule)
            .map(|graph| graph.extract_shapes(StringRule::Slice))
            .unwrap_or_default()
    }

    #[inline]
    fn slice_by_path(&self, path: &IntPath, fill_rule: FillRule) -> IntShapes {
        let mut overlay = StringOverlay::with_shape(self);
        overlay.add_string_path(path);
        overlay
            .build_graph_view(fill_rule)
            .map(|graph| graph.extract_shapes(StringRule::Slice))
            .unwrap_or_default()
    }

    #[inline]
    fn slice_by_paths(&self, paths: &[IntPath], fill_rule: FillRule) -> IntShapes {
        let mut overlay = StringOverlay::with_shape(self);
        overlay.add_string_paths(paths);
        overlay
            .build_graph_view(fill_rule)
            .map(|graph| graph.extract_shapes(StringRule::Slice))
            .unwrap_or_default()
    }
}

impl IntSlice for [IntPoint] {
    #[inline]
    fn slice_by_line(&self, line: IntLine, fill_rule: FillRule) -> IntShapes {
        let mut overlay = StringOverlay::with_shape_contour(self);
        overlay.add_string_line(line);
        overlay
            .build_graph_view(fill_rule)
            .map(|graph| graph.extract_shapes(StringRule::Slice))
            .unwrap_or_default()
    }

    #[inline]
    fn slice_by_lines(&self, lines: &[IntLine], fill_rule: FillRule) -> IntShapes {
        let mut overlay = StringOverlay::with_shape_contour(self);
        overlay.add_string_lines(lines);
        overlay
            .build_graph_view(fill_rule)
            .map(|graph| graph.extract_shapes(StringRule::Slice))
            .unwrap_or_default()
    }

    #[inline]
    fn slice_by_path(&self, path: &IntPath, fill_rule: FillRule) -> IntShapes {
        let mut overlay = StringOverlay::with_shape_contour(self);
        overlay.add_string_path(path);
        overlay
            .build_graph_view(fill_rule)
            .map(|graph| graph.extract_shapes(StringRule::Slice))
            .unwrap_or_default()
    }

    #[inline]
    fn slice_by_paths(&self, paths: &[IntPath], fill_rule: FillRule) -> IntShapes {
        let mut overlay = StringOverlay::with_shape_contour(self);
        overlay.add_string_paths(paths);
        overlay
            .build_graph_view(fill_rule)
            .map(|graph| graph.extract_shapes(StringRule::Slice))
            .unwrap_or_default()
    }
}

#[cfg(test)]
mod tests {
    use crate::core::fill_rule::FillRule;
    use crate::string::slice::IntSlice;
    use alloc::vec;
    use i_float::int::point::IntPoint;

    #[test]
    fn test_empty_input() {
        #[rustfmt::skip]
        let shapes = [].slice_by_line(
            [IntPoint::new(0, 0), IntPoint::new(0, 0)],
            FillRule::NonZero,
        );

        assert_eq!(shapes.len(), 0);
    }

    #[test]
    fn test_0() {
        let paths = vec![vec![
            IntPoint::new(-10, 10),
            IntPoint::new(-10, -10),
            IntPoint::new(10, -10),
            IntPoint::new(10, 10),
        ]];

        let result = paths.slice_by_line([IntPoint::new(-20, 0), IntPoint::new(20, 0)], FillRule::NonZero);

        assert_eq!(result.len(), 2);
        assert_eq!(result[0].len(), 1);
        assert_eq!(result[0][0].len(), 4);
        assert_eq!(result[1].len(), 1);
        assert_eq!(result[1][0].len(), 4);
    }

    #[test]
    fn test_1() {
        let paths = vec![
            vec![
                IntPoint::new(-10, 10),
                IntPoint::new(-10, -10),
                IntPoint::new(10, -10),
                IntPoint::new(10, 10),
            ],
            vec![
                IntPoint::new(-5, -5),
                IntPoint::new(-5, 5),
                IntPoint::new(5, 5),
                IntPoint::new(5, -5),
            ],
        ];

        let result = paths.slice_by_line([IntPoint::new(-20, 0), IntPoint::new(20, 0)], FillRule::NonZero);

        assert_eq!(result.len(), 2);
        assert_eq!(result[0].len(), 1);
        assert_eq!(result[0][0].len(), 8);
        assert_eq!(result[1].len(), 1);
        assert_eq!(result[1][0].len(), 8);
    }

    #[test]
    fn test_2() {
        let paths = vec![
            vec![
                IntPoint::new(0, 0),
                IntPoint::new(35, 0),
                IntPoint::new(35, 20),
                IntPoint::new(0, 20),
            ],
            vec![
                IntPoint::new(5, 5),
                IntPoint::new(5, 15),
                IntPoint::new(15, 15),
                IntPoint::new(15, 5),
            ],
            vec![
                IntPoint::new(20, 5),
                IntPoint::new(20, 15),
                IntPoint::new(30, 15),
                IntPoint::new(30, 5),
            ],
        ];

        let result = paths.slice_by_line([IntPoint::new(15, 10), IntPoint::new(20, 10)], FillRule::NonZero);

        assert_eq!(result.len(), 1);
        assert_eq!(result[0].len(), 3);
    }

    #[test]
    fn test_3() {
        let paths = vec![
            vec![
                IntPoint::new(0, 0),
                IntPoint::new(35, 0),
                IntPoint::new(35, 20),
                IntPoint::new(0, 20),
            ],
            vec![
                IntPoint::new(5, 5),
                IntPoint::new(5, 15),
                IntPoint::new(15, 15),
                IntPoint::new(15, 5),
            ],
            vec![
                IntPoint::new(20, 5),
                IntPoint::new(20, 15),
                IntPoint::new(30, 15),
                IntPoint::new(30, 5),
            ],
        ];

        let result = paths.slice_by_lines(
            &vec![
                [IntPoint::new(15, 5), IntPoint::new(20, 5)],
                [IntPoint::new(15, 15), IntPoint::new(20, 15)],
            ],
            FillRule::NonZero,
        );

        assert_eq!(result.len(), 2);
        assert_eq!(result[0].len(), 2);
        assert_eq!(result[1].len(), 1);
    }

    #[test]
    fn test_4() {
        let paths = vec![
            vec![
                IntPoint::new(0, 0),
                IntPoint::new(35, 0),
                IntPoint::new(35, 35),
                IntPoint::new(0, 35),
            ],
            vec![
                IntPoint::new(5, 5),
                IntPoint::new(5, 15),
                IntPoint::new(15, 15),
                IntPoint::new(15, 5),
            ],
            vec![
                IntPoint::new(20, 5),
                IntPoint::new(20, 15),
                IntPoint::new(30, 15),
                IntPoint::new(30, 5),
            ],
            vec![
                IntPoint::new(5, 20),
                IntPoint::new(5, 30),
                IntPoint::new(15, 30),
                IntPoint::new(15, 20),
            ],
            vec![
                IntPoint::new(20, 20),
                IntPoint::new(20, 30),
                IntPoint::new(30, 30),
                IntPoint::new(30, 20),
            ],
        ];

        let result = paths.slice_by_lines(
            &vec![
                [IntPoint::new(10, 15), IntPoint::new(10, 20)],
                [IntPoint::new(25, 15), IntPoint::new(25, 20)],
                [IntPoint::new(15, 10), IntPoint::new(20, 10)],
                [IntPoint::new(15, 25), IntPoint::new(20, 25)],
            ],
            FillRule::NonZero,
        );

        assert_eq!(result.len(), 2);
        assert_eq!(result[0].len(), 2);
        assert_eq!(result[1].len(), 1);
    }

    #[test]
    fn test_5() {
        #[rustfmt::skip]
        let shapes = vec![
            IntPoint::new(-2, -2),
            IntPoint::new(2, -2),
            IntPoint::new(2, 2),
            IntPoint::new(-2, 2),
        ]
        .slice_by_line(
            [IntPoint::new(-5, 5), IntPoint::new(5, -5)],
            FillRule::NonZero,
        );

        assert_eq!(shapes.len(), 2);
        assert_eq!(shapes[0][0].len(), 3);
        assert_eq!(shapes[1][0].len(), 3);
    }

    #[test]
    fn test_6() {
        #[rustfmt::skip]
        let shapes = vec![
            IntPoint::new(-2, -2),
            IntPoint::new(2, -2),
            IntPoint::new(2, 2),
            IntPoint::new(-2, 2),
        ]
        .slice_by_line(
            [IntPoint::new(-5, 5), IntPoint::new(5, 5)],
            FillRule::NonZero,
        );

        assert_eq!(shapes.len(), 1);
    }

    #[test]
    fn test_7() {
        #[rustfmt::skip]
        let shapes = vec![
            IntPoint::new(0, 0),
            IntPoint::new(2, 0),
            IntPoint::new(2, 3),
            IntPoint::new(1, 1),
            IntPoint::new(0, 3),
        ]
        .slice_by_line(
            [IntPoint::new(-5, 2), IntPoint::new(5, 2)],
            FillRule::NonZero,
        );

        assert_eq!(shapes.len(), 3);
        assert_eq!(shapes[0][0].len(), 5);
        assert_eq!(shapes[1][0].len(), 3);
        assert_eq!(shapes[2][0].len(), 3);
    }

    #[test]
    fn test_8() {
        #[rustfmt::skip]
        let shapes = vec![
            IntPoint::new(-2, -2),
            IntPoint::new(2, -2),
            IntPoint::new(2, 2),
            IntPoint::new(-2, 2),
        ]
        .slice_by_line(
            [IntPoint::new(-2, 5), IntPoint::new(-2, -5)],
            FillRule::NonZero,
        );

        assert_eq!(shapes.len(), 1);
    }

    #[test]
    fn test_9() {
        #[rustfmt::skip]
        let shapes = vec![
            IntPoint::new(-2, 0),
            IntPoint::new(2, 0),
            IntPoint::new(0, 2),
        ]
        .slice_by_line(
            [IntPoint::new(-5, 2), IntPoint::new(5, 2)],
            FillRule::NonZero,
        );

        assert_eq!(shapes.len(), 1);
    }
}
