use crate::error::WumpError;
use crate::map::dodecahedron::{Dodecahedron, Payload, Polyhedron, Vertex};
pub use crate::map::dodecahedron::{VertexKey, VertexMap};
use crate::map::room::{initialize_room_map, RoomMap};
use crate::Args;
use anyhow::Result;
pub use room::Room;
use slotmap::{SecondaryMap, SlotMap};
use std::any::Any;

mod dodecahedron;
mod room;

impl Payload for () {
    fn as_any(&self) -> &dyn Any {
        self
    }
}

/// the main map
#[derive(Debug, Default)]
pub struct WumpusMap {
    room_map: RoomMap,
    vertex_map: VertexMap<()>,
    vertices: Vec<VertexKey>,
}

impl WumpusMap {
    pub fn new() -> Self {
        let mut vertex_map = SlotMap::with_key();
        let polyhedron = Dodecahedron::create(&mut vertex_map);
        let mut room_map = SecondaryMap::new();
        initialize_room_map(polyhedron.as_slice(), &mut room_map);
        let vertices = polyhedron.as_slice().to_vec();
        Self {
            vertex_map,
            room_map,
            vertices,
        }
    }

    pub fn try_get_vertex(&self, key: &VertexKey) -> Result<&Vertex<()>> {
        Ok(self
            .vertex_map
            .get(*key)
            .ok_or(WumpError::UnknownVertexKey(key.clone()))?)
    }

    pub fn get_vertex(&self, key: &VertexKey) -> &Vertex<()> {
        self.try_get_vertex(key).unwrap()
    }

    pub fn len(&self) -> usize {
        self.vertices.len()
    }

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

    pub fn _rooms(&self) -> Vec<Room> {
        self.vertices
            .iter()
            .map(|&key| self.room_map[key].clone())
            .collect()
    }
    pub fn _get_neighboring_rooms(&self, key: &VertexKey) -> Vec<Room> {
        self.try_get_neighboring_rooms(key).unwrap()
    }

    pub fn try_get_neighboring_rooms(&self, key: &VertexKey) -> Result<Vec<Room>> {
        let vertex = self.try_get_vertex(key)?;
        Ok(vertex
            .neighbors()
            .iter()
            .map(|v| self.try_get_room(v).cloned())
            .collect::<Result<Vec<_>>>()?)
    }

    pub fn get_room(&self, key: &VertexKey) -> &Room {
        self.try_get_room(key).unwrap()
    }

    pub fn try_get_room(&self, key: &VertexKey) -> Result<&Room> {
        Ok(self
            .room_map
            .get(*key)
            .ok_or(WumpError::UnknownVertexKey(key.clone()))?)
    }

    pub fn get_room_from_room_number(&self, room_number: usize) -> Result<&Room> {
        if let Some(vertex) = self.vertices.get(room_number - 1) {
            self.try_get_room(vertex)
        } else {
            Err(WumpError::UnknownRoom(room_number))?
        }
    }
}

pub fn generate_map(_config: &Args) -> WumpusMap {
    WumpusMap::new()
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_wumpus_map() {
        let wumpus_map = WumpusMap::new();
        let vertex_map = &wumpus_map.vertex_map;
        let room_map = &wumpus_map.room_map;
        assert_eq!(vertex_map.len(), 20);
        assert_eq!(room_map.len(), 20);
    }
}