#![allow(dead_code, unused_must_use)]
use std::collections::HashMap;
use std::time::SystemTime;
use thiserror::Error;
use crate::common::serverstate::ClientId;

pub const MAX_ROOMS: usize = 128;

pub enum AreaType {
    Room,
    Lobby,
}


#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct LobbyId(pub usize);

#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, derive_more::Display)]
pub struct RoomId(pub usize);

impl LobbyId {
    pub fn id(&self) -> u8 {
        self.0 as u8
    }
}


#[derive(Error, Debug, PartialEq, Eq)]
#[error("create room")]
pub enum CreateRoomError {
    NoOpenSlots,
    ClientInAreaAlready,
    JoinError,
}

#[derive(Error, Debug, PartialEq, Eq)]
#[error("join room")]
pub enum JoinRoomError {
    RoomDoesNotExist,
    RoomFull,
    ClientInAreaAlready,
}

#[derive(Error, Debug, PartialEq, Eq)]
#[error("join lobby")]
pub enum JoinLobbyError {
    LobbyDoesNotExist,
    LobbyFull,
    ClientInAreaAlready,
}

#[derive(Error, Debug, PartialEq, Eq)]
#[error("get area")]
pub enum GetAreaError {
    NotInRoom,
    NotInLobby,
    InvalidClient,
}

#[derive(Error, Debug, PartialEq, Eq)]
#[error("client removal")]
pub enum ClientRemovalError {
    ClientNotInArea,
    InvalidArea,
}

#[derive(Error, Debug, PartialEq, Eq)]
#[error("get clients")]
pub enum GetClientsError {
    InvalidClient,
    InvalidArea,
}

#[derive(Error, Debug, PartialEq, Eq)]
#[error("get neighbor")]
pub enum GetNeighborError {
    InvalidClient,
    InvalidArea,
}

#[derive(Error, Debug, PartialEq, Eq)]
#[error("get leader")]
pub enum GetLeaderError {
    InvalidClient,
    InvalidArea,
    NoClientInArea,
}

#[derive(Error, Debug, PartialEq, Eq)]
#[error("clientlocation")]
pub enum ClientLocationError {
    CreateRoomError(#[from] CreateRoomError),
    JoinRoomError(#[from] JoinRoomError),
    JoinLobbyError(#[from] JoinLobbyError),
    GetAreaError(#[from] GetAreaError),
    ClientRemovalError(#[from] ClientRemovalError),
    GetClientsError(#[from] GetClientsError),
    GetNeighborError(#[from] GetNeighborError),
    GetLeaderError(#[from] GetLeaderError)
}


#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct LocalClientId(usize);

impl LocalClientId {
    pub fn id(&self) -> u8 {
        self.0 as u8
    }
}

impl PartialEq<u8> for LocalClientId {
    fn eq(&self, other: &u8) -> bool {
        self.0 == *other as usize
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct AreaClient {
    pub client: ClientId,
    pub local_client: LocalClientId,
    time_join: SystemTime,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
struct Lobby([Option<AreaClient>; 12]);
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
struct Room([Option<AreaClient>; 4]);

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum RoomLobby {
    Room(RoomId),
    Lobby(LobbyId),
}

pub struct ClientLocation {
    lobbies: [Lobby; 15],
    rooms: [Option<Room>; MAX_ROOMS],
    client_location: HashMap<ClientId, RoomLobby>,
}

impl Default for ClientLocation {
    fn default() -> ClientLocation {
        ClientLocation {
            lobbies: [Lobby([None; 12]); 15],
            rooms: [None; MAX_ROOMS],
            client_location: HashMap::new(),
        }
    }
}


impl ClientLocation {
    pub fn add_client_to_lobby(&mut self, id: ClientId, lobby: LobbyId) -> Result<(), JoinLobbyError> {
        let l = self.lobbies.get_mut(lobby.0).ok_or(JoinLobbyError::LobbyDoesNotExist)?;
        if l.0.iter().filter(|c| c.is_none()).count() == 0 {
            return Err(JoinLobbyError::LobbyFull);
        }
        self.remove_client_from_area(id);

        let l = self.lobbies.get_mut(lobby.0).ok_or(JoinLobbyError::LobbyDoesNotExist)?;
        let (index, empty_slot) = l.0.iter_mut()
            .enumerate()
            .find(|(_, k)| k.is_none())
            .ok_or(JoinLobbyError::LobbyFull)?;
        *empty_slot = Some(AreaClient {
            client: id,
            local_client: LocalClientId(index),
            time_join: SystemTime::now(),
        });
        self.client_location.insert(id, RoomLobby::Lobby(lobby));
        Ok(())
    }

    pub fn add_client_to_next_available_lobby(&mut self, id: ClientId, lobby: LobbyId) -> Result<LobbyId, JoinLobbyError> {
        let l = (0..15)
            .map(|lobby_index| {
                let new_lobby = LobbyId((lobby.0 + lobby_index) % 15);
                (new_lobby, self.add_client_to_lobby(id, new_lobby))
            })
            .find(|(_, lobby_option)| {
                lobby_option.is_ok()
            })
            .ok_or(JoinLobbyError::LobbyFull)?;

        Ok(l.0)
    }

    pub fn create_new_room(&mut self, id: ClientId) -> Result<RoomId, CreateRoomError> {
        let (index, empty_slot) = self.rooms.iter_mut()
            .enumerate()
            .find(|(_, r)| r.is_none())
            .ok_or(CreateRoomError::NoOpenSlots)?;
        *empty_slot = Some(Room([None; 4]));
        self.add_client_to_room(id, RoomId(index)).map_err(|_err| CreateRoomError::JoinError)?;

        Ok(RoomId(index))
    }

    pub fn add_client_to_room(&mut self, id: ClientId, room: RoomId) -> Result<(), JoinRoomError> {
        let r = self.rooms.get_mut(room.0)
            .ok_or(JoinRoomError::RoomDoesNotExist)?
            .as_mut()
            .ok_or(JoinRoomError::RoomDoesNotExist)?;
        let (index, empty_slot) = r.0.iter_mut()
            .enumerate()
            .find(|(_, k)| k.is_none())
            .ok_or(JoinRoomError::RoomFull)?;
        *empty_slot = Some(AreaClient {
            client: id,
            local_client: LocalClientId(index),
            time_join: SystemTime::now(),
        });
        self.remove_client_from_area(id);
        self.client_location.insert(id, RoomLobby::Room(room));
        Ok(())
    }

    pub fn get_all_clients_by_client(&self, id: ClientId) -> Result<Vec<AreaClient>, GetNeighborError> {
        let area = self.client_location.get(&id).ok_or(GetNeighborError::InvalidClient)?;
        match area {
            RoomLobby::Room(room) => {
                Ok(self.get_clients_in_room(*room).map_err(|_| GetNeighborError::InvalidArea)?
                   .into_iter()
                   .collect())
            },
            RoomLobby::Lobby(lobby) => {
                Ok(self.get_clients_in_lobby(*lobby).map_err(|_| GetNeighborError::InvalidArea)?
                   .into_iter()
                   .collect())
            }
        }
    }

    pub fn get_client_neighbors(&self, id: ClientId) -> Result<Vec<AreaClient>, GetNeighborError> {
        let area = self.client_location.get(&id).ok_or(GetNeighborError::InvalidClient)?;
        match area {
            RoomLobby::Room(room) => {
                Ok(self.get_clients_in_room(*room).map_err(|_| GetNeighborError::InvalidArea)?
                   .into_iter()
                   .filter(|c| c.client != id)
                   .collect())
            },
            RoomLobby::Lobby(lobby) => {
                Ok(self.get_clients_in_lobby(*lobby).map_err(|_| GetNeighborError::InvalidArea)?
                   .into_iter()
                   .filter(|c| c.client != id)
                   .collect())
            }
        }
    }

    pub fn get_room_leader(&self, room: RoomId) -> Result<AreaClient, GetLeaderError> {
        let mut r = self.rooms[room.0]
            .as_ref()
            .ok_or(GetLeaderError::InvalidArea)?
            .0.iter().flatten()
            .collect::<Vec<_>>();
        r.sort_by_key(|k| k.time_join);
        let c = r.get(0).ok_or(GetLeaderError::NoClientInArea)?;
        Ok(**c)
    }

    pub fn get_lobby_leader(&self, lobby: LobbyId) -> Result<AreaClient, GetLeaderError> {
        let mut l = self.lobbies[lobby.0]
            .0.iter().flatten()
            .collect::<Vec<_>>();
        l.sort_by_key(|k| k.time_join);
        let c = l.get(0).ok_or(GetLeaderError::NoClientInArea)?;
        Ok(**c)
    }

    pub fn get_area_leader(&self, roomlobby: RoomLobby) -> Result<AreaClient, GetLeaderError> {
        match roomlobby {
            RoomLobby::Room(room) => {
                self.get_room_leader(room)
            },
            RoomLobby::Lobby(lobby) => {
                self.get_lobby_leader(lobby)
            }
        }
    }

    pub fn get_leader_by_client(&self, id: ClientId) -> Result<AreaClient, GetLeaderError> {
        let area = self.client_location.get(&id).ok_or(GetLeaderError::InvalidClient)?;
        match area {
            RoomLobby::Room(room) => {
                self.get_room_leader(*room)
            },
            RoomLobby::Lobby(lobby) => {
                self.get_lobby_leader(*lobby)
            }
        }
    }

    pub fn get_clients_in_lobby(&self, lobby: LobbyId) -> Result<Vec<AreaClient>, GetClientsError> {
        Ok(self.lobbies.get(lobby.0).ok_or(GetClientsError::InvalidArea)?.0
           .iter()
           .filter_map(|client| {
               client.map(|c| {
                   c
               })
           }).collect())
    }

    pub fn get_clients_in_room(&self, room: RoomId) -> Result<Vec<AreaClient>, GetClientsError> {
        Ok(self.rooms.get(room.0)
           .ok_or(GetClientsError::InvalidArea)?
           .ok_or(GetClientsError::InvalidArea)?.0
           .iter()
           .filter_map(|client| {
               client.map(|c| {
                   c
               })
           }).collect())
    }

    pub fn get_local_client(&self, id: ClientId) -> Result<AreaClient, GetClientsError> {
        let area = self.client_location.get(&id).ok_or(GetClientsError::InvalidClient)?;
        match area {
            RoomLobby::Room(room) => {
                self.get_clients_in_room(*room).map_err(|_| GetClientsError::InvalidArea)?
                    .into_iter()
                    .find(|c| c.client == id)
                    .ok_or(GetClientsError::InvalidClient)
            },
            RoomLobby::Lobby(lobby) => {
                self.get_clients_in_lobby(*lobby).map_err(|_| GetClientsError::InvalidArea)?
                    .into_iter()
                    .find(|c| c.client == id)
                    .ok_or(GetClientsError::InvalidClient)
            }
        }
    }

    pub fn get_area(&self, id: ClientId) -> Result<RoomLobby, GetAreaError> {
        self.client_location.get(&id)
            .ok_or(GetAreaError::InvalidClient)
            .map(Clone::clone)
    }

    pub fn get_room(&self, id: ClientId) -> Result<RoomId, GetAreaError> {
        if let RoomLobby::Room(room) = self.client_location.get(&id).ok_or(GetAreaError::InvalidClient)? {
            Ok(*room)
        }
        else {
            Err(GetAreaError::NotInRoom)
        }
    }

    pub fn get_lobby(&self, id: ClientId) -> Result<LobbyId, GetAreaError> {
        if let RoomLobby::Lobby(lobby) = self.client_location.get(&id).ok_or(GetAreaError::InvalidClient)? {
            Ok(*lobby)
        }
        else {
            Err(GetAreaError::NotInLobby)
        }
    }

    pub fn remove_client_from_area(&mut self, id: ClientId) -> Result<(), ClientRemovalError> {
        let area = self.client_location.get_mut(&id).ok_or(ClientRemovalError::ClientNotInArea)?;
        let client_list = match area {
            RoomLobby::Room(room) => {
                self.rooms[room.0]
                    .as_mut()
                    .map(|r| {
                        r.0.iter_mut()
                    })
            },
            RoomLobby::Lobby(lobby) => {
                Some(self.lobbies[lobby.0].0.iter_mut())
            }
        };

        client_list
            .ok_or(ClientRemovalError::InvalidArea)?
            .filter(|client| {
                client.map_or(false, |c| {
                    c.client == id
                })
            })
            .for_each(|client| {
                *client = None
            });
        Ok(())
    }
}








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

    #[test]
    fn test_add_client_to_lobby() {
        let mut cl = ClientLocation::default();
        cl.add_client_to_lobby(ClientId(12), LobbyId(0));
        cl.add_client_to_lobby(ClientId(13), LobbyId(1));
        cl.add_client_to_lobby(ClientId(14), LobbyId(0));

        assert!(cl.get_clients_in_lobby(LobbyId(0)).into_iter().flatten().map(|c| (c.client, c.local_client)).collect::<Vec<_>>() == vec![
            (ClientId(12), LocalClientId(0)),
            (ClientId(14), LocalClientId(1)),
        ]);
    }

    #[test]
    fn test_add_client_to_full_lobby() {
        let mut cl = ClientLocation::default();
        (0..12).for_each(|i| {
            cl.add_client_to_lobby(ClientId(i), LobbyId(0));
        });
        assert!(cl.add_client_to_lobby(ClientId(99), LobbyId(0)) == Err(JoinLobbyError::LobbyFull));
    }

    #[test]
    fn test_add_client_to_next_available_lobby() {
        let mut cl = ClientLocation::default();
        (1..4).for_each(|lobby| {
            (0..12).for_each(|i| {
                cl.add_client_to_lobby(ClientId(lobby*12+i), LobbyId(lobby));
            });
        });
        assert!(cl.add_client_to_next_available_lobby(ClientId(99), LobbyId(1)) == Ok(LobbyId(4)));
    }

    #[test]
    fn test_add_to_lobby_when_all_are_full() {
        let mut cl = ClientLocation::default();
        (0..15).for_each(|lobby| {
            (0..12).for_each(|i| {
                cl.add_client_to_lobby(ClientId(lobby*12+i), LobbyId(lobby));
            });
        });
        assert!(cl.add_client_to_next_available_lobby(ClientId(99), LobbyId(1)) == Err(JoinLobbyError::LobbyFull));
    }

    #[test]
    fn test_new_room() {
        let mut cl = ClientLocation::default();
        assert!(cl.create_new_room(ClientId(12)) == Ok(RoomId(0)));
    }

    #[test]
    fn test_add_client_to_room() {
        let mut cl = ClientLocation::default();
        let room = cl.create_new_room(ClientId(12)).unwrap();
        assert!(cl.add_client_to_room(ClientId(234), room) == Ok(()));
        assert!(cl.get_clients_in_room(room).unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::<Vec<_>>() == vec![
            (ClientId(12), LocalClientId(0)),
            (ClientId(234), LocalClientId(1)),
        ]);
    }

    #[test]
    fn test_no_new_room_slots() {
        let mut cl = ClientLocation::default();
        for i in 0..128 {
            cl.create_new_room(ClientId(i));
        }
        assert!(cl.create_new_room(ClientId(234)) == Err(CreateRoomError::NoOpenSlots));
    }

    #[test]
    fn test_joining_full_room() {
        let mut cl = ClientLocation::default();
        let room = cl.create_new_room(ClientId(0)).unwrap();
        assert!(cl.add_client_to_room(ClientId(1), room) == Ok(()));
        assert!(cl.add_client_to_room(ClientId(2), room) == Ok(()));
        assert!(cl.add_client_to_room(ClientId(3), room) == Ok(()));
        assert!(cl.add_client_to_room(ClientId(234), room) == Err(JoinRoomError::RoomFull));
    }

    #[test]
    fn test_adding_client_to_room_removes_from_lobby() {
        let mut cl = ClientLocation::default();
        cl.add_client_to_lobby(ClientId(93), LobbyId(0));
        cl.add_client_to_lobby(ClientId(23), LobbyId(0));
        cl.add_client_to_lobby(ClientId(51), LobbyId(0));
        cl.add_client_to_lobby(ClientId(12), LobbyId(0));

        let room = cl.create_new_room(ClientId(51)).unwrap();
        assert!(cl.add_client_to_room(ClientId(93), room) == Ok(()));
        assert!(cl.get_clients_in_lobby(LobbyId(0)).unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::<Vec<_>>() == vec![
            (ClientId(23), LocalClientId(1)),
            (ClientId(12), LocalClientId(3)),
        ]);
        assert!(cl.get_clients_in_room(room).unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::<Vec<_>>() == vec![
            (ClientId(51), LocalClientId(0)),
            (ClientId(93), LocalClientId(1)),
        ]);
    }

    #[test]
    fn test_getting_neighbors() {
        let mut cl = ClientLocation::default();
        cl.add_client_to_lobby(ClientId(93), LobbyId(0));
        cl.add_client_to_lobby(ClientId(23), LobbyId(0));
        cl.add_client_to_lobby(ClientId(51), LobbyId(0));
        cl.add_client_to_lobby(ClientId(12), LobbyId(0));

        assert!(cl.get_client_neighbors(ClientId(23)).unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::<Vec<_>>() == vec![
            (ClientId(93), LocalClientId(0)),
            (ClientId(51), LocalClientId(2)),
            (ClientId(12), LocalClientId(3)),
        ]);
    }

    #[test]
    fn test_failing_to_join_lobby_does_not_remove_from_current_area() {
        let mut cl = ClientLocation::default();
        (0..12).for_each(|i| {
            cl.add_client_to_lobby(ClientId(i), LobbyId(0));
        });
        cl.add_client_to_lobby(ClientId(99), LobbyId(1));
        cl.add_client_to_lobby(ClientId(99), LobbyId(0));
        assert!(cl.get_clients_in_lobby(LobbyId(0)).unwrap().len() == 12);
        assert!(cl.get_clients_in_lobby(LobbyId(1)).unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::<Vec<_>>() == vec![
            (ClientId(99), LocalClientId(0)),
        ]);
    }

    #[test]
    fn test_get_leader() {
        let mut cl = ClientLocation::default();
        cl.add_client_to_lobby(ClientId(93), LobbyId(0));
        cl.add_client_to_lobby(ClientId(23), LobbyId(0));
        cl.add_client_to_lobby(ClientId(51), LobbyId(0));
        cl.add_client_to_lobby(ClientId(12), LobbyId(0));

        assert!(cl.get_leader_by_client(ClientId(51)).map(|c| (c.client, c.local_client)) == Ok((ClientId(93), LocalClientId(0))));
    }

    #[test]
    fn test_remove_client_from_room() {
        let mut cl = ClientLocation::default();
        let room = cl.create_new_room(ClientId(51)).unwrap();
        cl.add_client_to_room(ClientId(93), room);
        cl.add_client_to_room(ClientId(23), room);
        cl.remove_client_from_area(ClientId(51));
        cl.add_client_to_room(ClientId(12), room);

        assert!(cl.get_clients_in_room(room).unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::<Vec<_>>() == vec![
            (ClientId(12), LocalClientId(0)),
            (ClientId(93), LocalClientId(1)),
            (ClientId(23), LocalClientId(2)),
        ]);
    }

    #[test]
    fn test_leader_changes_on_leader_leaving() {
        let mut cl = ClientLocation::default();
        let room = cl.create_new_room(ClientId(51)).unwrap();
        cl.add_client_to_room(ClientId(93), room);
        cl.add_client_to_room(ClientId(23), room);
        cl.remove_client_from_area(ClientId(51));
        cl.add_client_to_room(ClientId(12), room);
        cl.remove_client_from_area(ClientId(23));
        cl.add_client_to_room(ClientId(99), room);

        assert!(cl.get_leader_by_client(ClientId(12)).map(|c| (c.client, c.local_client)) == Ok((ClientId(93), LocalClientId(1))));
    }
}