use ahash::AHashMap;
use bevy::utils::FloatOrd;
use de_map::size::MapBounds;
use de_objects::EXCLUSION_OFFSET;
use parry2d::{
    math::{Point, Vector},
    shape::Triangle,
};
use spade::{handles::FixedVertexHandle, ConstrainedDelaunayTriangulation, Point2, Triangulation};
use crate::exclusion::ExclusionArea;
const MAP_OFFSET: Vector<f32> = Vector::new(EXCLUSION_OFFSET, EXCLUSION_OFFSET);
pub(crate) fn triangulate(bounds: &MapBounds, exclusions: &[ExclusionArea]) -> Vec<Triangle> {
    let mut triangulation = MapTriangulation::new();
    let (mins, maxs) = {
        let aabb = bounds.aabb();
        (aabb.mins + MAP_OFFSET, aabb.maxs - MAP_OFFSET)
    };
    triangulation.insert(Point::new(mins.x, mins.y), None);
    triangulation.insert(Point::new(mins.x, maxs.y), None);
    triangulation.insert(Point::new(maxs.x, maxs.y), None);
    triangulation.insert(Point::new(maxs.x, mins.y), None);
    for edge in MultipleAreaEdges::new(exclusions) {
        triangulation.insert(edge.a(), Some(edge.polygon_id()));
    }
    for edge in MultipleAreaEdges::new(exclusions) {
        triangulation.add_constraint(edge.a(), edge.b());
    }
    triangulation.collect()
}
struct MapTriangulation {
    triangulation: ConstrainedDelaunayTriangulation<Point2<f32>>,
    point_to_vertex: AHashMap<(FloatOrd, FloatOrd), Vertex>,
}
impl MapTriangulation {
    fn new() -> Self {
        Self {
            triangulation: ConstrainedDelaunayTriangulation::<Point2<f32>>::new(),
            point_to_vertex: AHashMap::new(),
        }
    }
    fn insert(&mut self, point: Point<f32>, polygon_id: Option<usize>) {
        let point2 = Point2::new(point.x, point.y);
        let handle = self.triangulation.insert(point2).unwrap();
        let old = self
            .point_to_vertex
            .insert(Self::point_to_key(point), Vertex::new(polygon_id, handle));
        debug_assert!(old.is_none());
    }
    fn add_constraint(&mut self, a: Point<f32>, b: Point<f32>) {
        let a = self.point_to_vertex.get(&Self::point_to_key(a)).unwrap();
        let b = self.point_to_vertex.get(&Self::point_to_key(b)).unwrap();
        self.triangulation.add_constraint(a.handle(), b.handle());
    }
    fn collect(self) -> Vec<Triangle> {
        self.triangulation
            .inner_faces()
            .filter_map(|f| {
                let vertices = f.vertices().map(|v| {
                    let v = v.as_ref();
                    Point::new(v.x, v.y)
                });
                let triangle = Triangle::new(vertices[0], vertices[1], vertices[2]);
                if self.is_excluded(&triangle) {
                    None
                } else {
                    Some(triangle)
                }
            })
            .collect()
    }
    fn point_to_key(point: Point<f32>) -> (FloatOrd, FloatOrd) {
        (FloatOrd(point.x), FloatOrd(point.y))
    }
    fn is_excluded(&self, triangle: &Triangle) -> bool {
        let vertices = triangle.vertices().map(|p| {
            self.point_to_vertex
                .get(&Self::point_to_key(p))
                .and_then(|v| v.polygon_id())
        });
        vertices[0].is_some() && vertices[0] == vertices[1] && vertices[1] == vertices[2]
    }
}
struct Vertex {
    polygon_id: Option<usize>,
    hanlde: FixedVertexHandle,
}
impl Vertex {
    fn new(polygon_id: Option<usize>, hanlde: FixedVertexHandle) -> Self {
        Self { polygon_id, hanlde }
    }
    fn polygon_id(&self) -> Option<usize> {
        self.polygon_id
    }
    fn handle(&self) -> FixedVertexHandle {
        self.hanlde
    }
}
struct MultipleAreaEdges<'a> {
    exclusions: &'a [ExclusionArea],
    index: usize,
    current: Option<SingleAreaEdges<'a>>,
}
impl<'a> MultipleAreaEdges<'a> {
    fn new(exclusions: &'a [ExclusionArea]) -> Self {
        Self {
            exclusions,
            index: 0,
            current: None,
        }
    }
}
impl<'a> Iterator for MultipleAreaEdges<'a> {
    type Item = ExclusionEdge;
    fn next(&mut self) -> Option<ExclusionEdge> {
        match self.current.as_mut().and_then(|c| c.next()) {
            Some(edge) => Some(edge),
            None => match self.exclusions.get(self.index) {
                Some(exclusion) => {
                    self.current = Some(SingleAreaEdges::new(exclusion, self.index));
                    self.index += 1;
                    self.current.as_mut().unwrap().next()
                }
                None => None,
            },
        }
    }
}
struct SingleAreaEdges<'a> {
    polygon: &'a ExclusionArea,
    polygon_id: usize,
    index: usize,
}
impl<'a> SingleAreaEdges<'a> {
    fn new(polygon: &'a ExclusionArea, polygon_id: usize) -> Self {
        Self {
            polygon,
            polygon_id,
            index: 0,
        }
    }
}
impl<'a> Iterator for SingleAreaEdges<'a> {
    type Item = ExclusionEdge;
    fn next(&mut self) -> Option<ExclusionEdge> {
        let points = self.polygon.points();
        if self.index >= points.len() {
            return None;
        }
        let a = points[self.index];
        self.index += 1;
        let b = points[self.index.rem_euclid(points.len())];
        Some(ExclusionEdge::new(self.polygon_id, a, b))
    }
}
struct ExclusionEdge {
    polygon_id: usize,
    a: Point<f32>,
    b: Point<f32>,
}
impl ExclusionEdge {
    fn new(polygon_id: usize, a: Point<f32>, b: Point<f32>) -> Self {
        Self { polygon_id, a, b }
    }
    fn polygon_id(&self) -> usize {
        self.polygon_id
    }
    fn a(&self) -> Point<f32> {
        self.a
    }
    fn b(&self) -> Point<f32> {
        self.b
    }
}
#[cfg(test)]
mod tests {
    use std::hash::Hash;
    use ahash::AHashSet;
    use glam::Vec2;
    use parry2d::shape::ConvexPolygon;
    use super::*;
    use crate::utils::HashableSegment;
    #[test]
    fn test_triangulation_empty() {
        let obstacles = Vec::new();
        let triangles = triangulate(
            &MapBounds::new(Vec2::new(
                19. + 2. * EXCLUSION_OFFSET,
                13. + 2. * EXCLUSION_OFFSET,
            )),
            &obstacles,
        );
        assert_eq!(triangles.len(), 2);
        let a = triangles[0];
        let b = triangles[1];
        assert_eq!(
            a,
            Triangle::new(
                Point::new(9.5, 6.5),
                Point::new(-9.5, 6.5),
                Point::new(-9.5, -6.5),
            )
        );
        assert_eq!(
            b,
            Triangle::new(
                Point::new(9.5, -6.5),
                Point::new(9.5, 6.5),
                Point::new(-9.5, -6.5),
            )
        );
    }
    #[test]
    fn test_triangulation() {
        let obstacles = vec![ExclusionArea::new(
            ConvexPolygon::from_convex_polyline(vec![
                Point::new(-0.1, 1.1),
                Point::new(-0.1, 1.3),
                Point::new(1.0, 1.3),
                Point::new(1.0, 1.1),
            ])
            .unwrap(),
        )];
        let triangles: AHashSet<HashableTriangle> = triangulate(
            &MapBounds::new(Vec2::new(
                19. + 2. * EXCLUSION_OFFSET,
                13. + 2. * EXCLUSION_OFFSET,
            )),
            &obstacles,
        )
        .iter()
        .map(HashableTriangle::new)
        .collect();
        let expected: AHashSet<HashableTriangle> = [
            Triangle::new(
                Point::new(-0.1, 1.1),
                Point::new(-9.5, 6.5),
                Point::new(-9.5, -6.5),
            ),
            Triangle::new(
                Point::new(-9.5, -6.5),
                Point::new(9.5, -6.5),
                Point::new(-0.1, 1.1),
            ),
            Triangle::new(
                Point::new(1.0, 1.1),
                Point::new(-0.1, 1.1),
                Point::new(9.5, -6.5),
            ),
            Triangle::new(
                Point::new(-0.1, 1.3),
                Point::new(9.5, 6.5),
                Point::new(-9.5, 6.5),
            ),
            Triangle::new(
                Point::new(-0.1, 1.3),
                Point::new(-9.5, 6.5),
                Point::new(-0.1, 1.1),
            ),
            Triangle::new(
                Point::new(9.5, 6.5),
                Point::new(-0.1, 1.3),
                Point::new(1.0, 1.3),
            ),
            Triangle::new(
                Point::new(9.5, -6.5),
                Point::new(9.5, 6.5),
                Point::new(1.0, 1.1),
            ),
            Triangle::new(
                Point::new(1.0, 1.3),
                Point::new(1.0, 1.1),
                Point::new(9.5, 6.5),
            ),
        ]
        .iter()
        .map(HashableTriangle::new)
        .collect();
        assert_eq!(triangles, expected);
    }
    #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
    struct HashableTriangle(HashableSegment, HashableSegment, HashableSegment);
    impl HashableTriangle {
        fn new(triangle: &Triangle) -> Self {
            let mut edges = triangle.edges().map(HashableSegment::new);
            edges.sort();
            HashableTriangle(edges[0], edges[1], edges[2])
        }
    }
}