use super::{causal_triangulation2d::CausalTriangulation2D, triangulation2d::Triangulation2D};

pub struct TriangulationHalfEdge {
    adjacency: Vec<usize>,
}

macro_rules! impl_from_triangulation {
    ($fn_name:ident, $triangulation_type:ty) => {
        impl TriangulationHalfEdge {
            // Returns the half edge adjacencies of the `triangulation`
            //
            // Associate three half-edges to each triangle in counter clockwise order, such that:
            // given a triangle `i` the three edges have (in order) labels `[3*i, 3*i+1, 3*i+2]`,
            // where the ordering is the same as the corresponding `neighbours` of that triangle.
            // Hence given a half-edge `j`, the corresponding triangle is `j / 3`;
            // the next half-edge will be `3*(j/3) + (j+1) % 3`; and the previous `3*(j/3) + (j+2) % 3`
            pub fn $fn_name(triangulation: &$triangulation_type) -> Self {
                let triangles = &triangulation.triangles;
                let mut adjacency = vec![usize::MAX; triangles.len() * 3];

                for (s0, triangle) in triangles.iter().enumerate() {
                    let [s1, s2, s3] = triangle.neighbours;
                    // TODO: there logic statements can be combined
                    if s1 == s0 || s2 == s0 || s3 == s0 {
                        // Tadpole
                        match (s1 == s0, s2 == s0, s3 == s0) {
                            (true, true, true) => panic!("Impossible triangulation structure"),
                            (false, false, false) => unreachable!(),
                            (true, true, false) => {
                                adjacency[s0 * 3] = 3 * s0 + 1;
                                adjacency[s0 * 3 + 1] = 3 * s0;
                                adjacency[s0 * 3 + 2] =
                                    3 * s3 + get_nbr_index(s0, &triangles[s3].neighbours);
                            }
                            (true, false, true) => {
                                adjacency[s0 * 3] = 3 * s0 + 2;
                                adjacency[s0 * 3 + 1] =
                                    3 * s2 + get_nbr_index(s0, &triangles[s2].neighbours);
                                adjacency[s0 * 3 + 2] = 3 * s0;
                            }
                            (false, true, true) => {
                                adjacency[s0 * 3] =
                                    3 * s1 + get_nbr_index(s0, &triangles[s1].neighbours);
                                adjacency[s0 * 3 + 1] = 3 * s0 + 2;
                                adjacency[s0 * 3 + 2] = 3 * s0 + 1;
                            }
                            _ => panic!("Missing connection's opposite"),
                        }
                    } else if s1 != s2 && s1 != s3 && s2 != s3 {
                        // All different, typical setting
                        adjacency[s0 * 3] = 3 * s1 + get_nbr_index(s0, &triangles[s1].neighbours);
                        adjacency[s0 * 3 + 1] =
                            3 * s2 + get_nbr_index(s0, &triangles[s2].neighbours);
                        adjacency[s0 * 3 + 2] =
                            3 * s3 + get_nbr_index(s0, &triangles[s3].neighbours);
                    } else if s1 == s2 && s1 == s3 && s2 == s3 {
                        // Trivial minimal triangulations
                        assert_eq!(
                            triangles.len(),
                            2,
                            "Triangle connections can only happen if trivial minimal triangulation,\
                            `ntriangles` must be 2 or triangulation structure is incorrect."
                        );
                        let [v1, v2, v3] = triangle.vertices;
                        if v1 == v2 && v1 == v3 {
                            // Smallest possible torus
                            adjacency[s0 * 3] = 3 * s1 + 2;
                            adjacency[s0 * 3 + 1] = 3 * s1;
                            adjacency[s0 * 3 + 2] = 3 * s1 + 1;
                        } else if v1 != v2 && v1 != v3 && v2 != v3 {
                            // Cushion
                            adjacency[s0 * 3] = 3 * s1 + 2;
                            adjacency[s0 * 3 + 1] = 3 * s1 + 1;
                            adjacency[s0 * 3 + 2] = 3 * s1
                        }
                    } else {
                        // Two the same, need vertex info to determine connection
                        let (nbr_label, v0_index) = match (s1 == s2, s1 == s3, s2 == s3) {
                            (true, false, false) => (s1, 1),
                            (false, true, false) => (s3, 0),
                            (false, false, true) => (s2, 2),
                            (false, false, false) => unreachable!(),
                            (true, true, true) => unreachable!(),
                            (true, true, false) => {
                                unreachable!("Logically equivalent to `(true, true, true)`")
                            }
                            (true, false, true) => {
                                unreachable!("Logically equivalent to `(true, true, true)`")
                            }
                            (false, true, true) => {
                                unreachable!("Logically equivalent to `(true, true, true)`")
                            }
                        };

                        let v0 = triangle.vertices[v0_index];
                        let nbr_triangle = &triangles[nbr_label];
                        let nbr_opposite_index =
                            get_nonmatching_nbr_index(s0, &nbr_triangle.neighbours);
                        let v1 = nbr_triangle.vertices[nbr_opposite_index];
                        if v1 == v0 {
                            // Torus-like connection
                            adjacency[s0 * 3] = 3 * s1 + (nbr_opposite_index + 1) % 3;
                            adjacency[s0 * 3 + 1] = 3 * s1 + (nbr_opposite_index + 2) % 3;
                        } else {
                            // Party hat-like connection
                            adjacency[s0 * 3] = 3 * s1 + (nbr_opposite_index + 2) % 3;
                            adjacency[s0 * 3 + 1] = 3 * s1 + (nbr_opposite_index + 1) % 3;
                        }
                        adjacency[s0 * 3 + 2] =
                            3 * s3 + get_nbr_index(s0, &triangles[s3].neighbours);
                    }
                }

                // Check if all half-edges have been correctly set
                assert!(
                    adjacency.iter().all(|&v| v != usize::MAX),
                    "Not all half-edges have been set"
                );

                TriangulationHalfEdge { adjacency }
            }
        }
    };
}
impl_from_triangulation!(from_triangulation, Triangulation2D);
impl_from_triangulation!(from_causal_triangulation, CausalTriangulation2D);

impl TriangulationHalfEdge {
    /// Returns the number of half-edges in the triangulation.
    pub fn len(&self) -> usize {
        self.adjacency.len()
    }

    /// Returns the label of the adjacent `halfedge`.
    pub fn adjacent(&self, halfedge: usize) -> usize {
        self.adjacency[halfedge]
    }

    // /// Returns the label of the triangle the `halfedge` is part of.
    // ///
    // /// This is simply `halfedge / 3`
    // pub fn triangle(halfedge: usize) -> usize {
    //     halfedge / 3
    // }

    // /// Returns the index of the `halfedge` in the corresponding triangulation.
    // ///
    // /// This is simply `halfedge % 3` and means the index of neighbouring triangle
    // /// in the `neighbours` list of [`Triangle`] corresponding to the half-edge.
    // pub fn edge_index(halfedge: usize) -> usize {
    //     halfedge % 3
    // }

    // /// Returns the label of the next `halfedge`.
    // ///
    // /// This is simply `3 * (halfedge / 3) + (halfedge + 1) % 3`
    // pub fn next(halfedge: usize) -> usize {
    //     3 * (halfedge / 3) + (halfedge + 1) % 3
    // }

    // /// Returns the label of the previous `halfedge`.
    // ///
    // /// This is simply `3 * (halfedge / 3) + (halfedge + 2) % 3`
    // pub fn prev(halfedge: usize) -> usize {
    //     3 * (halfedge / 3) + (halfedge + 2) % 3
    // }
}

/// Get position of triangle `label` in `other` triangle's neighbour list
fn get_nbr_index(label: usize, other: &[usize; 3]) -> usize {
    let t0 = other[0] == label;
    let t1 = other[1] == label;
    let t2 = other[2] == label;

    match (t0, t1, t2) {
        (true, false, false) => 0,
        (false, true, false) => 1,
        (false, false, true) => 2,
        (false, false, false) => panic!("`label` not in `other`"),
        _ => panic!("`label` not unique in `other`"),
    }
}

/// Get position of the non-matching triangle `label` in `other` triangle's neighbour list,
/// meaning that the other two neighbours must match or this function will panic.
fn get_nonmatching_nbr_index(label: usize, other: &[usize; 3]) -> usize {
    let t0 = other[0] == label;
    let t1 = other[1] == label;
    let t2 = other[2] == label;

    match (t0, t1, t2) {
        (false, true, true) => 0,
        (true, false, true) => 1,
        (true, true, false) => 2,
        (true, true, true) => panic!("All are `label` in `other`"),
        (false, false, false) => panic!("`label` not in `other`"),
        _ => panic!("`label` not in `other` twice"),
    }
}
