use libpso::packet::ship::*;
use crate::common::serverstate::ClientId;
use crate::ship::ship::{Clients, ShipEvent};
use crate::ship::location::{ClientLocation, LobbyId, ClientLocationError};
use crate::ship::packet::builder::{player_info};
use crate::ship::items::state::ItemState;

use futures::future::join_all;

pub async fn join_lobby(id: ClientId,
                        lobby: LobbyId,
                        client_location: &ClientLocation,
                        clients: &Clients,
                        item_state: &ItemState,
                        event: ShipEvent)
                        -> Result<JoinLobby, anyhow::Error> {
    let lobby_clients = client_location.get_clients_in_lobby(lobby).await.map_err(|err| -> ClientLocationError { err.into() })?;

    let playerinfo = join_all(
        lobby_clients.into_iter()
            .map(|area_client| {
                let item_state = item_state.clone();
                async move {
                    clients.with(area_client.client, |client| Box::pin(async move {
                        let inventory = item_state.get_character_inventory(&client.character).await?;
                        Ok(player_info(0x100, client, &area_client, &inventory).await)
                    })).await?
                }}))
        .await
        .into_iter()
        .collect::<Result<Vec<_>, anyhow::Error>>()?;

    let client_block = clients.with(id, |client| Box::pin(async move {
        client.block as u16
    })).await?;
    let area_client = client_location.get_local_client(id).await.map_err(|err| -> ClientLocationError { err.into() })?;
    let leader = client_location.get_lobby_leader(lobby).await.map_err(|err| -> ClientLocationError { err.into() })?;
    Ok(JoinLobby {
        client: area_client.local_client.id(),
        leader: leader.local_client.id(),
        one: 1,
        lobby: lobby.id(),
        block: client_block,
        event: event.into(),
        padding: 0,
        playerinfo,
    })
}

pub async fn add_to_lobby(id: ClientId,
                          lobby: LobbyId,
                          client_location: &ClientLocation,
                          clients: &Clients,
                          item_state: &ItemState,
                          event: ShipEvent)
                          -> Result<AddToLobby, anyhow::Error> {
    let area_client = client_location.get_local_client(id).await.map_err(|err| -> ClientLocationError { err.into() })?;
    let leader = client_location.get_lobby_leader(lobby).await.map_err(|err| -> ClientLocationError { err.into() })?;
    clients.with(id, |client| {
        let item_state = item_state.clone();
        Box::pin(async move {
            let inventory = item_state.get_character_inventory(&client.character).await?;
            Ok(AddToLobby {
                flag: 1,
                client: area_client.local_client.id(),
                leader: leader.local_client.id(),
                one: 1,
                lobby: lobby.id(),
                block: client.block as u16,
                event: event.into(),
                padding: 0,
                playerinfo: player_info(0x100, client, &area_client, &inventory).await,
            })
        })}).await?
}

pub async fn remove_from_lobby(id: ClientId,
                               client_location: &ClientLocation)
                               -> Result<LeaveLobby, anyhow::Error> {
    let prev_area_index = client_location.get_local_client(id).await.map_err(|err| -> ClientLocationError { err.into() })?.local_client.id();
    let prev_area_leader_index = client_location
        .get_area_leader(client_location
                         .get_area(id)
                         .await
                         .map_err(|err| -> ClientLocationError { err.into() })?)
        .await
        .map_err(|err| -> ClientLocationError { err.into() })?.local_client.id();
    Ok(LeaveLobby {
        client: prev_area_index,
        leader: prev_area_leader_index,
        _padding: 0,
    })
}