use std::collections::HashMap;
use log::warn;
use libpso::packet::ship::*;
use libpso::packet::messages::*;
use crate::common::serverstate::ClientId;
use crate::common::leveltable::CharacterLevelTable;
use crate::ship::ship::{SendShipPacket, ShipError, ClientState, Clients, Rooms};
use crate::ship::character::{CharacterBytesBuilder, FullCharacterBytesBuilder};
use crate::ship::location::{ClientLocation, LobbyId, RoomId, RoomLobby, MAX_ROOMS, ClientLocationError};
use crate::ship::room::RoomState;
use crate::ship::drops::{ItemDrop, ItemDropType};
use crate::ship::items::ActiveItemDatabase;
use libpso::character::character;
use crate::entity::gateway::EntityGateway;
use libpso::{utf8_to_array, utf8_to_utf16_array};
use crate::ship::packet::builder;

fn send_to_client(id: ClientId, target: u8, msg: DirectMessage, client_location: &ClientLocation)
                  -> Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send> {
    Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter()
             .filter(move |client| client.local_client.id() == target)
             .map(move |client| {
                 (client.client, SendShipPacket::DirectMessage(msg.clone()))
             }))
}

pub fn guildcard_send(id: ClientId,
                      guildcard_send: &GuildcardSend,
                      target: u32,
                      client_location: &ClientLocation,
                      clients: &Clients)
                      -> Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send> {
    let client = clients.get(&id).unwrap();
    let msg = DirectMessage{
        flag: target,
        msg: GameMessage::GuildcardRecv(GuildcardRecv {
            client: guildcard_send.client,
            target: guildcard_send.target,
            guildcard: client.user.id.0,
            name: utf8_to_utf16_array!(client.character.name, 0x18),
            team: [0; 0x10], // TODO: teams not yet implemented
            desc: utf8_to_utf16_array!(client.character.guildcard.description, 0x58),
            one: 1,
            language: 0, // TODO: add language flag to character
            section_id: client.character.section_id.into(),
            class: client.character.char_class.into(),
        }),
    };
    send_to_client(id, target as u8, msg, &client_location)
}

pub fn request_item<EG>(id: ClientId,
                        request_item: &RequestItem,
                        entity_gateway: &mut EG,
                        client_location: &ClientLocation,
                        clients: &mut Clients,
                        rooms: &mut Rooms,
                        active_items: &mut ActiveItemDatabase)
                        -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError>
where EG: EntityGateway {
    let room_id = client_location.get_room(id).map_err(|err| -> ClientLocationError { err.into() })?;
    let mut room = rooms.get_mut(room_id.0)
        .ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?
        .as_mut()
        .ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?;

    let monster = room.maps.enemy_by_id(request_item.enemy_id as usize)?;
    if monster.dropped_item {
        return Err(ShipError::MonsterAlreadyDroppedItem(id, request_item.enemy_id))
    }

    let area_client = client_location.get_local_client(id).map_err(|err| -> ClientLocationError { err.into() })?;
    let clients_in_area = client_location.get_clients_in_room(room_id).map_err(|err| -> ClientLocationError { err.into() })?;

    let item_drop_packets = clients_in_area.into_iter()
        .filter_map(|area_client| {
            room.drop_table.get_drop(&monster.map_area, &monster.monster).map(|item_drop_type| {
                warn!("drop is? {:?}", item_drop_type);
                (area_client, item_drop_type)
            })
        })
        .map(|(area_client, item_drop_type)| -> Result<_, ShipError> {
            let item_drop = ItemDrop {
                map_area: monster.map_area,
                x: request_item.x,
                y: request_item.y,
                z: request_item.z,
                item: item_drop_type,
            };

            let activated_item = active_items.activate_item_drop(entity_gateway, item_drop)?;
            let mut client = clients.get_mut(&area_client.client).ok_or(ShipError::ClientNotFound(area_client.client))?;
            let item_drop_msg = builder::message::item_drop(request_item.client, request_item.target, &activated_item)?;
            client.floor_items.push(activated_item);
            Ok((area_client.client, SendShipPacket::Message(Message::new(GameMessage::ItemDrop(item_drop_msg)))))
        })
        .filter_map(|item_drop_pkt| {
            // TODO: log errors here
            item_drop_pkt.ok()
        })
        .collect::<Vec<_>>(); // TODO: can EntityGateway be Sync?

    Ok(Box::new(item_drop_packets.into_iter()))
}