use crate::error::WumpError;
use anyhow::Result;
use slotmap::{new_key_type, Key, SlotMap};
use std::any::Any;
use std::fmt::Debug;

new_key_type! {
pub struct VertexKey;
}

pub trait Payload: Debug + Default {
    fn as_any(&self) -> &dyn Any;
}

#[derive(Debug, Default)]
pub struct Vertex<P: Payload> {
    key: VertexKey,
    neighbors: Vec<VertexKey>,
    _payload: P,
}

impl<P: Payload> Vertex<P> {
    pub fn _with_neighbors(key: VertexKey, payload: P, new_neighbors: &[VertexKey]) -> Self {
        // TODO: maybe convert to using `Result`
        assert!(new_neighbors.len() <= 3);
        let neighbors = new_neighbors.to_vec();
        Self {
            key,
            neighbors,
            _payload: payload,
        }
    }

    pub fn _new(key: VertexKey, payload: P) -> Self {
        Self::_with_neighbors(key, payload, &[])
    }

    pub fn join_neighbors(key_1: &VertexKey, key_2: &VertexKey, vertex_map: &mut VertexMap<P>) {
        Self::try_join_neighbors(key_1, key_2, vertex_map).unwrap();
    }
    pub fn try_join_neighbors(
        key_1: &VertexKey,
        key_2: &VertexKey,
        vertex_map: &mut VertexMap<P>,
    ) -> Result<()> {
        vertex_map[*key_1].try_add_neighbor_key(*key_2)?;
        vertex_map[*key_2].try_add_neighbor_key(*key_1)?;
        Ok(())
    }

    fn try_add_neighbor_key(&mut self, key: VertexKey) -> Result<()> {
        if self.neighbors.len() > 3 {
            Err(WumpError::OutOfBoundsError(self.neighbors.len()))?
        } else {
            self.neighbors.push(key);
            Ok(())
        }
    }

    fn _add_neighbor_key(&mut self, key: VertexKey) {
        self.try_add_neighbor_key(key).unwrap()
    }

    pub fn key(&self) -> VertexKey {
        self.key
    }

    pub fn neighbors(&self) -> &[VertexKey] {
        &self.neighbors
    }

    pub fn _payload(&self) -> &impl Payload {
        &self._payload
    }

    pub fn has_neighbor(&self, key: &VertexKey) -> bool {
        self.neighbors.contains(key)
    }
}

pub type VertexMap<P> = SlotMap<VertexKey, Vertex<P>>;

#[derive(Debug)]
struct Polygon<const N: usize> {
    vertices: [VertexKey; N],
}

impl<const N: usize> Polygon<N> {
    fn _try_new(incoming_vertices: &[VertexKey]) -> Result<Self> {
        let vertices: [VertexKey; N] = incoming_vertices.try_into()?;
        Ok(Self { vertices })
    }

    fn _new(incoming_vertices: &[VertexKey]) -> Self {
        Self::_try_new(incoming_vertices).unwrap()
    }

    fn create<P: Payload>(vertex_map: &mut VertexMap<P>) -> Self {
        let mut vertices = [VertexKey::null(); N];
        for index in 0..N {
            let key = vertex_map.insert(Default::default());
            vertex_map[key].key = key;
            vertices[index] = key;
        }
        let polygon = Self { vertices };
        polygon._connect_vertices(vertex_map);
        polygon
    }

    fn _connect_vertices<P: Payload>(&self, vertex_map: &mut VertexMap<P>) {
        let mut previous_vertex_id = self.vertices[N - 1];
        for vertex_id in self.vertices {
            Vertex::join_neighbors(&vertex_id, &previous_vertex_id, vertex_map);
            previous_vertex_id = vertex_id;
        }
    }

    fn _rotate(&mut self, increment: i32) {
        let n = i32::try_from(N).unwrap();
        let mut source: i32 = 0;
        let mut source_key = self.vertices[0];
        let mut dest: i32;
        let mut saved_key: VertexKey;
        for _ in 0..N {
            dest = (source + increment + n) % n;
            saved_key = self.vertices[dest as usize];
            self.vertices[dest as usize] = source_key;
            source = dest;
            source_key = saved_key;
        }
    }

    fn as_slice(&self) -> &[VertexKey] {
        return &self.vertices;
    }
}

pub trait Polyhedron {
    fn as_slice(&self) -> &[VertexKey];
    fn as_mut_slice(&mut self) -> &mut [VertexKey];
}

pub struct Dodecahedron {
    vertices: [VertexKey; 20],
}

impl Dodecahedron {
    fn try_new(incoming_vertices: &[VertexKey]) -> Result<Self> {
        let vertices: [VertexKey; 20] = incoming_vertices.try_into()?;
        Ok(Self { vertices })
    }

    fn new(vertices: &[VertexKey]) -> Self {
        Self::try_new(vertices).unwrap()
    }

    pub(crate) fn create<P: Payload>(vertex_map: &mut VertexMap<P>) -> Self {
        let top_pentagon: Polygon<5> = Polygon::create(vertex_map);
        let middle_decagon: Polygon<10> = Polygon::create(vertex_map);
        let bottom_pentagon: Polygon<5> = Polygon::create(vertex_map);
        // Adjustment for original game room numbering
        let middle_decagon_rotation: i32 = -2;
        let bottom_pentagon_rotation: i32 = 3;

        for index in 0..middle_decagon.vertices.len() {
            let pentagon_index = index / 2;
            let index_rotation = (index as i32 + 10 - middle_decagon_rotation) % 10;
            assert!(index_rotation >= 0);
            let vertex_key_rotation = &middle_decagon.vertices[index_rotation as usize];
            if index % 2 == 0 {
                Vertex::join_neighbors(
                    vertex_key_rotation,
                    &top_pentagon.vertices[pentagon_index],
                    vertex_map,
                );
            } else {
                let pentagon_index_rotation =
                    (pentagon_index as i32 + 5 - bottom_pentagon_rotation) % 5;
                Vertex::join_neighbors(
                    vertex_key_rotation,
                    &bottom_pentagon.vertices[pentagon_index_rotation as usize],
                    vertex_map,
                );
            }
        }
        let all_vertices: Vec<VertexKey> = top_pentagon
            .as_slice()
            .iter()
            .chain(middle_decagon.as_slice())
            .chain(bottom_pentagon.as_slice())
            .cloned()
            .collect();
        Self::new(&all_vertices)
    }
}

impl Polyhedron for Dodecahedron {
    fn as_slice(&self) -> &[VertexKey] {
        &self.vertices
    }

    fn as_mut_slice(&mut self) -> &mut [VertexKey] {
        &mut self.vertices
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[derive(Copy, Clone, Debug, Default)]
    struct Tag(usize);
    impl Payload for Tag {
        fn as_any(&self) -> &dyn Any {
            self
        }
    }

    #[test]
    fn test_dodecahedron() {
        fn get_neighbors_tags(vertex_key: &VertexKey, vertex_map: &VertexMap<Tag>) -> Vec<Tag> {
            let neighbors = vertex_map[*vertex_key].neighbors.clone();
            let tags: Vec<Tag> = neighbors
                .iter()
                .map(|&n| {
                    vertex_map[n]
                        ._payload
                        .as_any()
                        .downcast_ref::<Tag>()
                        .unwrap()
                        .clone()
                })
                .collect();
            tags
        }

        fn get_tag(vertex_key: &VertexKey, vertex_map: &VertexMap<Tag>) -> Tag {
            vertex_map[*vertex_key]
                ._payload
                .as_any()
                .downcast_ref::<Tag>()
                .unwrap()
                .clone()
        }

        let mut vertex_map: VertexMap<Tag> = SlotMap::with_key();
        let dodecahedron = Dodecahedron::create(&mut vertex_map);
        for vertex_key in &dodecahedron.vertices {
            assert!(!vertex_key.is_null());
            let vertex = &vertex_map[*vertex_key];
            assert_eq!(vertex.key, *vertex_key);
            assert_eq!(vertex.neighbors.len(), 3);
            for neighbor_key in vertex.neighbors() {
                assert!(!neighbor_key.is_null());
                let neighbor = &vertex_map[*neighbor_key];
                assert!(neighbor.neighbors.contains(vertex_key));
            }
        }
        for (index, vertex_key) in dodecahedron.as_slice().iter().enumerate() {
            vertex_map[*vertex_key]._payload = Tag(index + 1);
        }
        let room_map = [
            (1, (5, 2, 8)),
            (2, (1, 3, 10)),
            (3, (2, 4, 12)),
            (4, (3, 5, 14)),
            (5, (4, 1, 6)),
            (6, (15, 7, 5)),
            (7, (6, 8, 17)),
            (8, (7, 9, 1)),
            (9, (8, 10, 18)),
            (10, (9, 11, 2)),
            (11, (10, 12, 19)),
            (12, (11, 13, 3)),
            (13, (12, 14, 20)),
            (14, (13, 15, 4)),
            (15, (14, 6, 16)),
            (16, (20, 17, 15)),
            (17, (16, 18, 7)),
            (18, (17, 19, 9)),
            (19, (18, 20, 11)),
            (20, (19, 16, 13)),
        ];
        for (index, vertex_key) in dodecahedron.as_slice().iter().enumerate() {
            let vertex_tag = get_tag(vertex_key, &vertex_map);
            let vertex_neighbors: Vec<_> = get_neighbors_tags(vertex_key, &vertex_map)
                .iter()
                .map(|x| x.0)
                .collect();
            let (room, neighbor_rooms) = room_map[index];

            assert_eq!(vertex_tag.0, room);
            assert_eq!(vertex_neighbors.len(), 3);

            assert!(vertex_neighbors.contains(&neighbor_rooms.0));
            assert!(vertex_neighbors.contains(&neighbor_rooms.1));
            assert!(vertex_neighbors.contains(&neighbor_rooms.2));
        }
    }

    #[test]
    fn test_polygon() {
        fn verify_neighbors(vertex_keys: &Vec<VertexKey>, vertex_map: &VertexMap<()>) {
            let n = vertex_keys.len();
            let mut previous_key = vertex_keys[n - 1];
            for (index, vertex_key) in vertex_keys.iter().enumerate() {
                let neighbors = &vertex_map[*vertex_key].neighbors;
                assert_eq!(neighbors.len(), 2);
                assert!(neighbors.contains(&previous_key));
                let next_key = vertex_keys[(index + 1) % n];
                assert!(neighbors.contains(&next_key));
                previous_key = *vertex_key;
            }
        }

        let mut vertex_map: VertexMap<()> = SlotMap::with_key();
        let mut pentagon: Polygon<5> = Polygon::create(&mut vertex_map);
        let pentagon_vertices = pentagon.as_slice();
        assert_eq!(pentagon_vertices.len(), 5);
        assert!(pentagon_vertices.iter().all(|&v| !v.is_null()));
        let vertices_0 = pentagon_vertices.to_vec();
        verify_neighbors(&vertices_0, &vertex_map);
        pentagon.rotate(-1);
        let vertices_1 = pentagon.as_slice().to_vec();
        verify_neighbors(&vertices_1, &vertex_map);
        assert_eq!(vertices_0[0], vertices_1[4]);
        assert_eq!(vertices_0[1], vertices_1[0]);
        assert_eq!(vertices_0[2], vertices_1[1]);
        assert_eq!(vertices_0[3], vertices_1[2]);
        assert_eq!(vertices_0[4], vertices_1[3]);
        pentagon.rotate(2);
        let vertices_2 = pentagon.as_slice().to_vec();
        verify_neighbors(&vertices_2, &vertex_map);
        assert_eq!(vertices_1[0], vertices_2[2]);
        assert_eq!(vertices_1[1], vertices_2[3]);
        assert_eq!(vertices_1[2], vertices_2[4]);
        assert_eq!(vertices_1[3], vertices_2[0]);
        assert_eq!(vertices_1[4], vertices_2[1]);
    }
}