#![allow(dead_code, unused_must_use)]
use std::net::Ipv4Addr;
use std::collections::HashMap;

use rand::Rng;
use thiserror::Error;

use libpso::packet::ship::*;
use libpso::packet::login::{Login, LoginResponse, Session};
use libpso::packet::messages::*;
use libpso::{PacketParseError, PSOPacket};
use libpso::crypto::bb::PSOBBCipher;

use libpso::packet::ship::{BLOCK_MENU_ID, ROOM_MENU_ID};

use crate::common::cipherkeys::{ELSEWHERE_PRIVATE_KEY, ELSEWHERE_PARRAY};
use crate::common::serverstate::{SendServerPacket, RecvServerPacket, ServerState, OnConnect, ClientId};
use crate::common::leveltable::CharacterLevelTable;
use crate::common::interserver::{AuthToken, Ship, ServerId, InterserverActor, LoginMessage, ShipMessage};

use crate::entity::gateway::EntityGateway;
use crate::entity::account::{UserAccountEntity, UserSettingsEntity};
use crate::entity::character::{CharacterEntity, SectionID};

use crate::ship::location::{ClientLocation, RoomLobby, MAX_ROOMS, ClientLocationError};

use crate::ship::items;
use crate::ship::room;
use crate::ship::quests;
use crate::ship::map::{MapsError, MapAreaError, MapArea};
use crate::ship::packet::handler;
use crate::ship::shops::{ShopType, WeaponShop, ToolShop, ArmorShop, WeaponShopItem, ToolShopItem, ArmorShopItem};

pub const SHIP_PORT: u16 = 23423;
pub const QUEST_CATEGORY_MENU_ID: u32 = 0xA2;
pub const QUEST_SELECT_MENU_ID: u32 = 0xA3;
pub type Rooms = [Option<room::RoomState>; MAX_ROOMS];
pub type Clients = HashMap<ClientId, ClientState>;

#[derive(Error, Debug)]
#[error("")]
pub enum ShipError {
    ClientNotFound(ClientId),
    NoCharacterInSlot(ClientId, u32),
    InvalidSlot(ClientId, u32),
    TooManyClients,
    ClientLocationError(#[from] ClientLocationError),
    MapsError(#[from] MapsError),
    MapAreaError(#[from] MapAreaError),
    InvalidRoom(u32),
    MonsterAlreadyDroppedItem(ClientId, u16),
    SliceError(#[from] std::array::TryFromSliceError),
    ItemError, // TODO: refine this
    PickUpInvalidItemId(u32),
    DropInvalidItemId(u32),
    ItemManagerError(#[from] items::ItemManagerError),
    ItemDropLocationNotSet,
    BoxAlreadyDroppedItem(ClientId, u16),
    InvalidQuestCategory(u32),
    InvalidQuest(u32),
    InvalidQuestFilename(String),
    IoError(#[from] std::io::Error),
    NotEnoughMeseta(ClientId, u32),
    ShopError,
}

#[derive(Debug)]
pub enum RecvShipPacket {
    Login(Login),
    MenuSelect(MenuSelect),
    RoomPasswordReq(RoomPasswordReq),
    CharData(CharData),
    Message(Message),
    DirectMessage(DirectMessage),
    PlayerChat(PlayerChat),
    CreateRoom(CreateRoom),
    RoomNameRequest(RoomNameRequest),
    UpdateConfig(UpdateConfig),
    ViewInfoboardRequest(ViewInfoboardRequest),
    WriteInfoboard(WriteInfoboard),
    RoomListRequest(RoomListRequest),
    Like62ButCooler(Like62ButCooler),
    ClientCharacterData(ClientCharacterData),
    DoneBursting(DoneBursting),
    DoneBursting2(DoneBursting2),
    LobbySelect(LobbySelect),
    RequestQuestList(RequestQuestList),
    MenuDetail(MenuDetail),
    QuestDetailRequest(QuestDetailRequest),
    QuestMenuSelect(QuestMenuSelect),
    QuestFileRequest(QuestFileRequest),
    QuestChunkAck(QuestChunkAck),
    DoneLoadingQuest(DoneLoadingQuest),
    FullCharacterData(Box<FullCharacterData>),
}

impl RecvServerPacket for RecvShipPacket {
    fn from_bytes(data: &[u8]) -> Result<RecvShipPacket, PacketParseError> {
        match u16::from_le_bytes([data[2], data[3]]) {
            0x93 => Ok(RecvShipPacket::Login(Login::from_bytes(data)?)),
            0x09 => match data[8] as u32 {
                QUEST_SELECT_MENU_ID => Ok(RecvShipPacket::QuestDetailRequest(QuestDetailRequest::from_bytes(data)?)),
                _ => Ok(RecvShipPacket::MenuDetail(MenuDetail::from_bytes(data)?)),
            }
            0x10 => match (data[0], data[8] as u32) {
                (16, QUEST_SELECT_MENU_ID) => Ok(RecvShipPacket::QuestMenuSelect(QuestMenuSelect::from_bytes(data)?)),
                (16, _) => Ok(RecvShipPacket::MenuSelect(MenuSelect::from_bytes(data)?)),
                (48, _) => Ok(RecvShipPacket::RoomPasswordReq(RoomPasswordReq::from_bytes(data)?)),
                _ => Err(PacketParseError::WrongPacketForServerType(u16::from_le_bytes([data[2], data[3]]), data.to_vec())),
            },
            0x13 => Ok(RecvShipPacket::QuestChunkAck(QuestChunkAck::from_bytes(data)?)),
            0x44 => Ok(RecvShipPacket::QuestFileRequest(QuestFileRequest::from_bytes(data)?)),
            0x61 => Ok(RecvShipPacket::CharData(CharData::from_bytes(data)?)),
            0x60 => Ok(RecvShipPacket::Message(Message::from_bytes(data)?)),
            0x62 => Ok(RecvShipPacket::DirectMessage(DirectMessage::from_bytes(data)?)),
            0x06 => Ok(RecvShipPacket::PlayerChat(PlayerChat::from_bytes(data)?)),
            0xC1 => Ok(RecvShipPacket::CreateRoom(CreateRoom::from_bytes(data)?)),
            0x8A => Ok(RecvShipPacket::RoomNameRequest(RoomNameRequest::from_bytes(data)?)),
            0x7ED => Ok(RecvShipPacket::UpdateConfig(UpdateConfig::from_bytes(data)?)),
            0xD8 => Ok(RecvShipPacket::ViewInfoboardRequest(ViewInfoboardRequest::from_bytes(data)?)),
            0xD9 => Ok(RecvShipPacket::WriteInfoboard(WriteInfoboard::from_bytes(data)?)),
            0x08 => Ok(RecvShipPacket::RoomListRequest(RoomListRequest::from_bytes(data)?)),
            0x6D => Ok(RecvShipPacket::Like62ButCooler(Like62ButCooler::from_bytes(data)?)),
            0x98 => Ok(RecvShipPacket::ClientCharacterData(ClientCharacterData::from_bytes(data)?)),
            0x6F => Ok(RecvShipPacket::DoneBursting(DoneBursting::from_bytes(data)?)),
            0x16F => Ok(RecvShipPacket::DoneBursting2(DoneBursting2::from_bytes(data)?)),
            0x84 => Ok(RecvShipPacket::LobbySelect(LobbySelect::from_bytes(data)?)),
            0xA2 => Ok(RecvShipPacket::RequestQuestList(RequestQuestList::from_bytes(data)?)),
            0xAC => Ok(RecvShipPacket::DoneLoadingQuest(DoneLoadingQuest::from_bytes(data)?)),
            0xE7 => Ok(RecvShipPacket::FullCharacterData(Box::new(FullCharacterData::from_bytes(data)?))),
            _ => Err(PacketParseError::WrongPacketForServerType(u16::from_le_bytes([data[2], data[3]]), data.to_vec()))
        }
    }
}

#[derive(Debug, Clone)]
pub enum SendShipPacket {
    ShipWelcome(ShipWelcome),
    LoginResponse(LoginResponse),
    ShipBlockList(ShipBlockList),
    FullCharacter(FullCharacter),
    CharDataRequest(CharDataRequest),
    JoinLobby(JoinLobby),
    AddToLobby(AddToLobby),
    Message(Message),
    DirectMessage(DirectMessage),
    PlayerChat(PlayerChat),
    SmallDialog(SmallDialog),
    JoinRoom(JoinRoom),
    AddToRoom(AddToRoom),
    LeaveLobby(LeaveLobby),
    LeaveRoom(LeaveRoom),
    RoomNameResponse(RoomNameResponse),
    ViewInfoboardResponse(ViewInfoboardResponse),
    RoomListResponse(RoomListResponse),
    Like62ButCooler(Like62ButCooler),
    BurstDone72(BurstDone72),
    DoneBursting(DoneBursting),
    DoneBursting2(DoneBursting2),
    LobbyList(LobbyList),
    QuestCategoryList(QuestCategoryList),
    QuestOptionList(QuestOptionList),
    QuestDetail(QuestDetail),
    QuestHeader(QuestHeader),
    QuestChunk(QuestChunk),
    DoneLoadingQuest(DoneLoadingQuest),
    BankItemList(BankItemList),
}

impl SendServerPacket for SendShipPacket {
    fn as_bytes(&self) -> Vec<u8> {
        match self {
            SendShipPacket::ShipWelcome(pkt) => pkt.as_bytes(),
            SendShipPacket::LoginResponse(pkt) => pkt.as_bytes(),
            SendShipPacket::ShipBlockList(pkt) => pkt.as_bytes(),
            SendShipPacket::FullCharacter(pkt) => pkt.as_bytes(),
            SendShipPacket::CharDataRequest(pkt) => pkt.as_bytes(),
            SendShipPacket::JoinLobby(pkt) => pkt.as_bytes(),
            SendShipPacket::AddToLobby(pkt) => pkt.as_bytes(),
            SendShipPacket::Message(pkt) => pkt.as_bytes(),
            SendShipPacket::DirectMessage(pkt) => pkt.as_bytes(),
            SendShipPacket::PlayerChat(pkt) => pkt.as_bytes(),
            SendShipPacket::SmallDialog(pkt) => pkt.as_bytes(),
            SendShipPacket::JoinRoom(pkt) => pkt.as_bytes(),
            SendShipPacket::AddToRoom(pkt) => pkt.as_bytes(),
            SendShipPacket::LeaveLobby(pkt) => pkt.as_bytes(),
            SendShipPacket::LeaveRoom(pkt) => pkt.as_bytes(),
            SendShipPacket::RoomNameResponse(pkt) => pkt.as_bytes(),
            SendShipPacket::ViewInfoboardResponse(pkt) => pkt.as_bytes(),
            SendShipPacket::RoomListResponse(pkt) => pkt.as_bytes(),
            SendShipPacket::Like62ButCooler(pkt) => pkt.as_bytes(),
            SendShipPacket::BurstDone72(pkt) => pkt.as_bytes(),
            SendShipPacket::DoneBursting(pkt) => pkt.as_bytes(),
            SendShipPacket::DoneBursting2(pkt) => pkt.as_bytes(),
            SendShipPacket::LobbyList(pkt) => pkt.as_bytes(),
            SendShipPacket::QuestCategoryList(pkt) => pkt.as_bytes(),
            SendShipPacket::QuestOptionList(pkt) => pkt.as_bytes(),
            SendShipPacket::QuestDetail(pkt) => pkt.as_bytes(),
            SendShipPacket::QuestHeader(pkt) => pkt.as_bytes(),
            SendShipPacket::QuestChunk(pkt) => pkt.as_bytes(),
            SendShipPacket::DoneLoadingQuest(pkt) => pkt.as_bytes(),
            SendShipPacket::BankItemList(pkt) => pkt.as_bytes(),
        }
    }
}

#[derive(Debug, Clone, Copy)]
pub struct ItemDropLocation {
    pub map_area: MapArea,
    pub x: f32,
    pub z: f32,
    pub item_id: items::ClientItemId,
}

pub struct LoadingQuest {
    pub header_bin: Option<QuestHeader>,
    pub header_dat: Option<QuestHeader>,
    //pub quest_chunk_bin: Option<Box<dyn Iterator<Item = >>>,
}

pub struct ClientState {
    pub user: UserAccountEntity,
    pub settings: UserSettingsEntity,
    pub character: CharacterEntity,
    session: Session,
    //guildcard: GuildCard,
    pub block: u32,
    pub item_drop_location: Option<ItemDropLocation>,
    pub done_loading_quest: bool,
    //pub loading_quest: Option<LoadingQuest>,
    pub area: Option<MapArea>,
    pub x: f32,
    pub y: f32,
    pub z: f32,
    pub weapon_shop: Vec<WeaponShopItem>,
    pub tool_shop: Vec<ToolShopItem>,
    pub armor_shop: Vec<ArmorShopItem>,
}

impl ClientState {
    pub fn new(user: UserAccountEntity, settings: UserSettingsEntity, character: CharacterEntity, session: Session) -> ClientState {
        ClientState {
            user: user,
            settings: settings,
            character: character,
            session: session,
            block: 1,
            item_drop_location: None,
            done_loading_quest: false,
            area: None,
            x: 0.0,
            y: 0.0,
            z: 0.0,
            weapon_shop: Vec::new(),
            tool_shop: Vec::new(),
            armor_shop: Vec::new(),
        }
    }
}

pub struct ItemShops {
    pub weapon_shop: HashMap<(room::Difficulty, SectionID), WeaponShop<rand_chacha::ChaCha20Rng>>,
    pub tool_shop: ToolShop<rand_chacha::ChaCha20Rng>,
    pub armor_shop: ArmorShop<rand_chacha::ChaCha20Rng>,
}

impl ItemShops {
    pub fn new() -> ItemShops {
        let difficulty = [room::Difficulty::Normal, room::Difficulty::Hard, room::Difficulty::VeryHard, room::Difficulty::Ultimate];
        let section_id = [SectionID::Viridia, SectionID::Greenill, SectionID::Skyly, SectionID::Bluefull, SectionID::Purplenum,
                          SectionID::Pinkal,  SectionID::Redria, SectionID::Oran, SectionID::Yellowboze, SectionID::Whitill];

        let mut weapon_shop = HashMap::new();
        for d in difficulty.iter() {
            for id in section_id.iter() {
                weapon_shop.insert((*d, *id), WeaponShop::new(*d, *id));
            }
        }

        ItemShops {
            weapon_shop: weapon_shop,
            tool_shop: ToolShop::new(),
            armor_shop: ArmorShop::new(),
        }
    }
}


pub struct ShipServerStateBuilder<EG: EntityGateway> {
    entity_gateway: Option<EG>,
    name: Option<String>,
    ip: Option<Ipv4Addr>,
    port: Option<u16>,
}

impl<EG: EntityGateway> ShipServerStateBuilder<EG> {
    pub fn new() -> ShipServerStateBuilder<EG> {
        ShipServerStateBuilder {
            entity_gateway: None,
            name: None,
            ip: None,
            port: None,
        }
    }

    pub fn gateway(mut self, entity_gateway: EG) -> ShipServerStateBuilder<EG> {
        self.entity_gateway = Some(entity_gateway);
        self
    }

    pub fn name(mut self, name: String) -> ShipServerStateBuilder<EG> {
        self.name = Some(name);
        self
    }

    pub fn ip(mut self, ip: Ipv4Addr) -> ShipServerStateBuilder<EG> {
        self.ip = Some(ip);
        self
    }

    pub fn port(mut self, port: u16) -> ShipServerStateBuilder<EG> {
        self.port = Some(port);
        self
    }

    pub fn build(self) -> ShipServerState<EG> {
        ShipServerState {
            entity_gateway: self.entity_gateway.unwrap(),
            clients: HashMap::new(),
            client_location: ClientLocation::new(),
            level_table: CharacterLevelTable::new(),
            name: self.name.unwrap_or("NAMENOTSET".into()),
            rooms: [None; MAX_ROOMS],
            item_manager: items::ItemManager::new(),
            quests: quests::load_quests("data/quests.toml".into()).unwrap(),
            ip: self.ip.unwrap_or(Ipv4Addr::new(127,0,0,1)),
            port: self.port.unwrap_or(SHIP_PORT),
            shops: Box::new(ItemShops::new()),
        }
    }
}

pub struct ShipServerState<EG: EntityGateway> {
    entity_gateway: EG,
    pub clients: Clients,
    client_location: ClientLocation,
    level_table: CharacterLevelTable,
    name: String,
    pub rooms: Rooms,
    item_manager: items::ItemManager,
    quests: quests::QuestList,
    ip: Ipv4Addr,
    port: u16,
    shops: Box<ItemShops>,
}

impl<EG: EntityGateway> ShipServerState<EG> {
    pub fn builder() -> ShipServerStateBuilder<EG> {
        ShipServerStateBuilder::new()
    }

    async fn message(&mut self, id: ClientId, msg: &Message) -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError> {
        match &msg.msg {
            GameMessage::RequestExp(request_exp) => {
                handler::message::request_exp(id, request_exp, &mut self.entity_gateway, &self.client_location, &mut self.clients, &mut self.rooms, &self.level_table).await
            },
            GameMessage::PlayerDropItem(player_drop_item) => {
                handler::message::player_drop_item(id, player_drop_item, &mut self.entity_gateway, &mut self.client_location, &mut self.clients, &mut self.rooms, &mut self.item_manager).await
            },
            GameMessage::DropCoordinates(drop_coordinates) => {
                handler::message::drop_coordinates(id, drop_coordinates, &self.client_location, &mut self.clients, &self.rooms)
            },
            GameMessage::PlayerNoLongerHasItem(no_longer_has_item) => {
                handler::message::split_item_stack(id, no_longer_has_item, &mut self.entity_gateway, &mut self.client_location, &mut self.clients, &mut self.item_manager).await
            },
            GameMessage::PlayerChangedMap(_) | GameMessage::PlayerChangedMap2(_) | GameMessage::TellOtherPlayerMyLocation(_) | 
            GameMessage::PlayerWarpingToFloor(_) | GameMessage::PlayerTeleported(_) | GameMessage::PlayerStopped(_) | 
            GameMessage::PlayerLoadedIn(_) | GameMessage::PlayerWalking(_) | GameMessage::PlayerRunning(_) | 
            GameMessage::PlayerWarped(_) | GameMessage::PlayerChangedFloor(_) | GameMessage::InitializeSpeechNpc(_) => {
                handler::message::update_player_position(id, &msg, &mut self.clients, &mut self.client_location, &self.rooms)
            },
            GameMessage::ChargeAttack(charge_attack) => {
                handler::message::charge_attack(id, charge_attack, &mut self.clients, &mut self.entity_gateway).await
            },
            GameMessage::PlayerUseItem(player_use_item) => {
                handler::message::use_item(id, player_use_item, &mut self.entity_gateway, &mut self.client_location, &mut self.clients, &mut self.item_manager).await
            },
            GameMessage::PlayerUsedMedicalCenter(player_used_medical_center) => {
                handler::message::player_used_medical_center(id, &player_used_medical_center, &mut self.entity_gateway, &mut self.clients).await
            },
            GameMessage::PlayerFeedMag(player_feed_mag) => {
                handler::message::player_feed_mag(id, &player_feed_mag, &mut self.entity_gateway, &mut self.client_location, &mut self.clients, &mut self.item_manager).await
            },
            _ => {
                let cmsg = msg.clone();
                Ok(Box::new(self.client_location.get_client_neighbors(id).unwrap().into_iter()
                    .map(move |client| {
                        (client.client, SendShipPacket::Message(cmsg.clone()))
                    })))
            },
        }
    }

    async fn direct_message(&mut self, id: ClientId, msg: &DirectMessage) -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError> {
        let target = msg.flag;
        match &msg.msg {
            GameMessage::GuildcardSend(guildcard_send) => {
                Ok(handler::direct_message::guildcard_send(id, guildcard_send, target, &self.client_location, &self.clients))
            },
            GameMessage::RequestItem(request_item) => {
                handler::direct_message::request_item(id, request_item, &mut self.entity_gateway, &mut self.client_location, &mut self.clients, &mut self.rooms, &mut self.item_manager).await
            },
            GameMessage::PickupItem(pickup_item) => {
                handler::direct_message::pickup_item(id, pickup_item, &mut self.entity_gateway, &mut self.client_location, &mut self.clients, &mut self.item_manager).await
            },
            GameMessage::BoxDropRequest(box_drop_request) => {
                handler::direct_message::request_box_item(id, box_drop_request, &mut self.entity_gateway, &mut self.client_location, &mut self.clients, &mut self.rooms, &mut self.item_manager).await
            },
            GameMessage::BankRequest(_bank_request) => {
                handler::direct_message::send_bank_list(id, &self.clients, &mut self.item_manager).await
            },
            GameMessage::BankInteraction(bank_interaction) => {
                handler::direct_message::bank_interaction(id, bank_interaction, &mut self.entity_gateway, &self.client_location, &mut self.clients, &mut self.item_manager).await
            },
            GameMessage::ShopRequest(shop_request) => {
                handler::direct_message::shop_request(id, shop_request, &self.client_location, &mut self.clients, &self.rooms, &self.level_table, &mut self.shops).await
            },
            GameMessage::BuyItem(buy_item) => {
                handler::direct_message::buy_item(id, buy_item, &mut self.entity_gateway, &self.client_location, &mut self.clients, &mut self.item_manager).await
            },
            _ => {
                let cmsg = msg.clone();
                Ok(Box::new(self.client_location.get_all_clients_by_client(id).unwrap().into_iter()
                    .filter(move |client| client.local_client.id() == target as u8)
                    .map(move |client| {
                        (client.client, SendShipPacket::DirectMessage(cmsg.clone()))
                    })))
            },
        }
    }
}

#[async_trait::async_trait]
impl<EG: EntityGateway> ServerState for ShipServerState<EG> {
    type SendPacket = SendShipPacket;
    type RecvPacket = RecvShipPacket;
    type PacketError = ShipError;

    fn on_connect(&mut self, _id: ClientId) -> Vec<OnConnect<Self::SendPacket>> {
        let mut rng = rand::thread_rng();

        let mut server_key = [0u8; 48];
        let mut client_key = [0u8; 48];
        rng.fill(&mut server_key[..]);
        rng.fill(&mut client_key[..]);

        vec![OnConnect::Packet(SendShipPacket::ShipWelcome(ShipWelcome::new(server_key, client_key))),
             OnConnect::Cipher((Box::new(PSOBBCipher::new(ELSEWHERE_PARRAY, ELSEWHERE_PRIVATE_KEY, client_key)),
                                Box::new(PSOBBCipher::new(ELSEWHERE_PARRAY, ELSEWHERE_PRIVATE_KEY, server_key))))
        ]
    }

    async fn handle(&mut self, id: ClientId, pkt: &RecvShipPacket)
              -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError> {
        Ok(match pkt {
            RecvShipPacket::Login(login) => {
                Box::new(handler::auth::validate_login(id, login, &mut self.entity_gateway, &mut self.clients, &mut self.item_manager, &self.name).await?.into_iter().map(move |pkt| (id, pkt)))
            },
            RecvShipPacket::QuestDetailRequest(questdetailrequest) => {
                match questdetailrequest.menu {
                    QUEST_SELECT_MENU_ID => handler::quest::quest_detail(id, questdetailrequest, &self.quests)?,
                    _ => unreachable!(),
                }
            },
            RecvShipPacket::MenuSelect(menuselect) => {
                match menuselect.menu {
                    BLOCK_MENU_ID => Box::new(handler::lobby::block_selected(id, menuselect, &mut self.clients, &self.item_manager, &self.level_table)?.into_iter().map(move |pkt| (id, pkt))),
                    ROOM_MENU_ID => handler::room::join_room(id, menuselect, &mut self.client_location, &mut self.clients, &mut self.item_manager, &self.level_table, &mut self.rooms)?,
                    QUEST_CATEGORY_MENU_ID => handler::quest::select_quest_category(id, menuselect, &self.quests)?,
                    _ => unreachable!(),
                }
            },
            RecvShipPacket::QuestMenuSelect(questmenuselect) => {
                handler::quest::load_quest(id, questmenuselect, &self.quests, &mut self.clients, &self.client_location, &mut self.rooms)?
            },
            RecvShipPacket::MenuDetail(_menudetail) => {
                unreachable!();
            },
            RecvShipPacket::RoomPasswordReq(room_password_req) => {
                if room_password_req.password == self.rooms[room_password_req.item as usize].as_ref()
                    .ok_or(ShipError::InvalidRoom(room_password_req.item))?
                    .password {
                    let menuselect = MenuSelect {
                        menu: room_password_req.menu,
                        item: room_password_req.item,
                    };
                    handler::room::join_room(id, &menuselect, &mut self.client_location, &mut self.clients, &mut self.item_manager, &self.level_table, &mut self.rooms)?
                }
                else {
                    Box::new(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("Incorrect password".into())))].into_iter())
                }
            },
            RecvShipPacket::CharData(chardata) => {
                Box::new(handler::lobby::send_player_to_lobby(id, chardata, &mut self.client_location, &self.clients, &self.item_manager, &self.level_table)?.into_iter())
            },
            RecvShipPacket::Message(msg) => {
                self.message(id, msg).await?
            },
            RecvShipPacket::DirectMessage(msg) => {
                self.direct_message(id, msg).await?
            },
            RecvShipPacket::PlayerChat(msg) => {
                Box::new(handler::communication::player_chat(id, msg, &self.client_location, &self.clients)?.into_iter())
            },
            RecvShipPacket::CreateRoom(create_room) => {
                handler::room::create_room(id, create_room, &mut self.client_location, &mut self.clients, &mut self.item_manager, &mut self.rooms)?
            },
            RecvShipPacket::RoomNameRequest(_req) => {
                handler::room::room_name_request(id, &self.client_location, &self.rooms)
            },
            RecvShipPacket::UpdateConfig(pkt) => {
                handler::settings::update_config(id, pkt, &mut self.clients, &mut self.entity_gateway).await
            },
            RecvShipPacket::ViewInfoboardRequest(_pkt) => {
                handler::communication::request_infoboard(id, &self.client_location, &self.clients)
            },
            RecvShipPacket::WriteInfoboard(pkt) => {
                handler::communication::write_infoboard(id, pkt, &mut self.clients, &mut self.entity_gateway).await
            },
            RecvShipPacket::RoomListRequest(_req) => {
                handler::room::request_room_list(id, &self.client_location, &self.rooms)
            },
            RecvShipPacket::Like62ButCooler(cool62) => {
                handler::room::cool_62(id, cool62, &self.client_location)
            },
            RecvShipPacket::ClientCharacterData(_) => {
                // TOOD: validate this in some way?
                Box::new(None.into_iter())
            },
            RecvShipPacket::DoneBursting(_) => {
                handler::room::done_bursting(id, &self.client_location, &mut self.rooms)
            },
            RecvShipPacket::DoneBursting2(_) => {
                handler::room::done_bursting(id, &self.client_location, &mut self.rooms)
            },
            RecvShipPacket::LobbySelect(pkt) => {
                Box::new(handler::lobby::change_lobby(id, pkt.lobby, &mut self.client_location, &self.clients, &mut self.item_manager, &self.level_table, &mut self.rooms, &mut self.entity_gateway).await?.into_iter())
            },
            RecvShipPacket::RequestQuestList(_) => {
                handler::quest::send_quest_category_list(id, &self.quests)?
            },
            RecvShipPacket::QuestFileRequest(quest_file_request) => {
                handler::quest::quest_file_request(id, quest_file_request, &self.quests)?
            },
            RecvShipPacket::QuestChunkAck(quest_chunk_ack) => {
                handler::quest::quest_chunk_ack(id, quest_chunk_ack, &self.quests)?
            },
            RecvShipPacket::DoneLoadingQuest(_) => {
                handler::quest::done_loading_quest(id, &mut self.clients, &self.client_location)?
            },
            RecvShipPacket::FullCharacterData(full_character_data) => {
                Box::new(None.into_iter())
            },
        })
    }

    fn on_disconnect(&mut self, id: ClientId) -> Vec<(ClientId, SendShipPacket)> {
        // TODO: don't unwrap!
        let client = self.clients.get(&id).unwrap();
        let area_client = self.client_location.get_local_client(id).unwrap();
        let neighbors = self.client_location.get_client_neighbors(id).unwrap();

        let pkt = match self.client_location.get_area(id).unwrap() {
            RoomLobby::Room(room) => {
                if neighbors.len() == 0 {
                    self.rooms[room.0] = None;
                }
                let leader = self.client_location.get_room_leader(room).unwrap();
                SendShipPacket::LeaveRoom(LeaveRoom::new(area_client.local_client.id(), leader.local_client.id()))
            },
            RoomLobby::Lobby(lobby) => {
                let leader = self.client_location.get_lobby_leader(lobby).unwrap();
                SendShipPacket::LeaveLobby(LeaveLobby::new(area_client.local_client.id(), leader.local_client.id()))
            }
        };

        self.client_location.remove_client_from_area(id);
        self.item_manager.remove_character_from_room(&client.character);

        neighbors.into_iter().map(|n| {
            (n.client, pkt.clone())
        }).collect()
    }
}


#[async_trait::async_trait]
impl<EG: EntityGateway> InterserverActor for ShipServerState<EG> {
    type SendMessage = ShipMessage;
    type RecvMessage = LoginMessage;
    type Error = ();

    async fn on_connect(&mut self, id: ServerId) -> Vec<(ServerId, Self::SendMessage)> {
        vec![ /* ShipMessage::Authenticate(AuthToken("hi".into())),  */ (id, ShipMessage::NewShip(Ship {
            name: self.name.clone(),
            ip: self.ip.clone(),
            port: self.port,
            block_count: 2,
        })) ]
    }

    async fn action(&mut self, id: ServerId, msg: Self::RecvMessage) -> Result<Vec<(ServerId, Self::SendMessage)>, Self::Error> {
        Ok(Vec::new())
    }

    async fn on_disconnect(&mut self, id: ServerId) -> Vec<(ServerId, Self::SendMessage)> {
        Vec::new()
    }
}