use crate::ship::items::ClientItemId;
use std::collections::{HashMap, BTreeMap, BinaryHeap};
use std::cmp::{Ordering, PartialOrd, PartialEq};
use thiserror::Error;
use futures::future::join_all;
use libpso::character::character;//::InventoryItem;
use crate::entity::gateway::EntityGateway;
use crate::entity::character::{CharacterEntity, CharacterEntityId};
use crate::entity::item::{ItemEntityId, ItemEntity, ItemDetail, ItemLocation, BankName};
use crate::entity::item::{Meseta, NewItemEntity};
use crate::entity::item::tool::{Tool, ToolType};
use crate::ship::map::MapArea;
use crate::ship::ship::ItemDropLocation;
use crate::ship::drops::{ItemDrop, ItemDropType};
use crate::ship::location::{AreaClient, RoomId};
use crate::ship::items::inventory::{IndividualInventoryItem, StackedInventoryItem, InventoryItemHandle};


#[derive(Debug, Clone)]
pub struct IndividualFloorItem {
    pub entity_id: ItemEntityId,
    pub item_id: ClientItemId,
    pub item: ItemDetail,
    pub map_area: MapArea,
    pub x: f32,
    pub y: f32,
    pub z: f32,
}

#[derive(Debug, Clone)]
pub struct StackedFloorItem {
    pub entity_ids: Vec<ItemEntityId>,
    pub item_id: ClientItemId,
    pub tool: Tool,
    pub map_area: MapArea,
    pub x: f32,
    pub y: f32,
    pub z: f32,
}

impl StackedFloorItem {
    pub fn count(&self) -> usize {
        self.entity_ids.len()
    }

    pub fn as_client_bytes(&self) -> [u8; 16] {
        self.tool.as_stacked_bytes(self.count())
    }
}

#[derive(Debug, Clone)]
pub struct MesetaFloorItem {
    pub item_id: ClientItemId,
    pub meseta: Meseta,
    pub map_area: MapArea,
    pub x: f32,
    pub y: f32,
    pub z: f32,
}

#[derive(Debug, Clone)]
pub enum FloorItem {
    Individual(IndividualFloorItem),
    Stacked(StackedFloorItem),
    Meseta(MesetaFloorItem),
}

impl FloorItem {
    pub fn item_id(&self) -> ClientItemId {
        match self {
            FloorItem::Individual(individual_floor_item) => {
                individual_floor_item.item_id
            },
            FloorItem::Stacked(stacked_floor_item) => {
                stacked_floor_item.item_id
            },
            FloorItem::Meseta(meseta_floor_item) => {
                meseta_floor_item.item_id
            }
        }
    }

    pub fn x(&self) -> f32 {
        match self {
            FloorItem::Individual(individual_floor_item) => {
                individual_floor_item.x
            },
            FloorItem::Stacked(stacked_floor_item) => {
                stacked_floor_item.x
            },
            FloorItem::Meseta(meseta_floor_item) => {
                meseta_floor_item.x
            }
        }
    }

    pub fn y(&self) -> f32 {
        match self {
            FloorItem::Individual(individual_floor_item) => {
                individual_floor_item.y
            },
            FloorItem::Stacked(stacked_floor_item) => {
                stacked_floor_item.y
            },
            FloorItem::Meseta(meseta_floor_item) => {
                meseta_floor_item.y
            }
        }
    }

    pub fn z(&self) -> f32 {
        match self {
            FloorItem::Individual(individual_floor_item) => {
                individual_floor_item.z
            },
            FloorItem::Stacked(stacked_floor_item) => {
                stacked_floor_item.z
            },
            FloorItem::Meseta(meseta_floor_item) => {
                meseta_floor_item.z
            }
        }
    }

    pub fn map_area(&self) -> MapArea {
        match self {
            FloorItem::Individual(individual_floor_item) => {
                individual_floor_item.map_area
            },
            FloorItem::Stacked(stacked_floor_item) => {
                stacked_floor_item.map_area
            },
            FloorItem::Meseta(meseta_floor_item) => {
                meseta_floor_item.map_area
            }
        }
    }

    pub fn as_client_bytes(&self) -> [u8; 16] {
        match self {
            FloorItem::Individual(individual_floor_item) => {
                individual_floor_item.item.as_client_bytes()
            },
            FloorItem::Stacked(stacked_floor_item) => {
                stacked_floor_item.as_client_bytes()
            },
            FloorItem::Meseta(meseta_floor_item) => {
                meseta_floor_item.meseta.as_bytes()
            }
        }
    }
}



pub struct FloorItemHandle<'a> {
    floor: &'a mut RoomFloorItems,
    index: usize,
}

impl<'a> FloorItemHandle<'a> {
    pub fn item(&'a self) -> Option<&'a FloorItem> {
        self.floor.0.get(self.index)
    }

    pub fn remove_from_floor(self) {
        self.floor.0.remove(self.index);
    }
}

// TODO: floors should keep track of their own item_ids
#[derive(Debug)]
pub struct RoomFloorItems(Vec<FloorItem>);

impl RoomFloorItems {
    pub fn new() -> RoomFloorItems {
        RoomFloorItems(Vec::new())
    }

    pub fn add_item(&mut self, item: FloorItem) {
        self.0.push(item);
    }

    pub fn get_item_by_id(&self, item_id: ClientItemId) -> Option<&FloorItem> {
        self.0.iter().find(|item| item.item_id() == item_id)
    }

    pub fn get_item_handle_by_id(&mut self, item_id: ClientItemId) -> Option<FloorItemHandle> {
        let index = self.0.iter().position(|item| item.item_id() == item_id)?;
        Some(FloorItemHandle {
            floor: self,
            index: index,
        })
    }
    
    pub fn take_item_by_id(&mut self, item_id: ClientItemId) -> Option<FloorItem> {
        self.0
            .drain_filter(|i| i.item_id() == item_id)
            .nth(0)
    }

    pub fn drop_individual_inventory_item(&mut self, individual_inventory_item: IndividualInventoryItem, item_drop_location: (MapArea, f32, f32, f32)) -> &IndividualFloorItem {
        self.0.push(FloorItem::Individual(IndividualFloorItem {
            entity_id: individual_inventory_item.entity_id,
            item_id: individual_inventory_item.item_id,
            item: individual_inventory_item.item,
            map_area: item_drop_location.0,
            x: item_drop_location.1,
            y: item_drop_location.2,
            z: item_drop_location.3,
        }));

        match self.0.last().unwrap() {
            FloorItem::Individual(item) => item,
            _ => unreachable!(),
        }
    }

    pub fn drop_stacked_inventory_item(&mut self, stacked_inventory_item: StackedInventoryItem, item_drop_location: (MapArea, f32, f32, f32)) -> &StackedFloorItem {
        self.0.push(FloorItem::Stacked(StackedFloorItem {
            entity_ids: stacked_inventory_item.entity_ids,
            item_id: stacked_inventory_item.item_id,
            tool: stacked_inventory_item.tool,
            map_area: item_drop_location.0,
            x: item_drop_location.1,
            y: item_drop_location.2,
            z: item_drop_location.3,
        }));
            
        match self.0.last().unwrap() {
            FloorItem::Stacked(item) => item,
            _ => unreachable!(),
        }
    }

    // TODO: Result
    // TODO: if consumed_item is not a tool items do not get placed back into inventory (should I care?)
    pub fn drop_partial_stacked_inventory_item(&mut self, inventory_item: InventoryItemHandle, amount: usize, new_item_id: ClientItemId, item_drop_location: (MapArea, f32, f32, f32)) -> Option<&StackedFloorItem> {
        let consumed_item = inventory_item.consume(amount).ok()?;

        if let ItemDetail::Tool(tool) = consumed_item.item {
            self.0.push(FloorItem::Stacked(StackedFloorItem {
                entity_ids: consumed_item.entity_ids,
                item_id: new_item_id,
                tool: tool,
                map_area: item_drop_location.0,
                x: item_drop_location.1,
                y: item_drop_location.2,
                z: item_drop_location.3,
            }))
        }
        else {
            return None
        }

        match self.0.last().unwrap() {
            FloorItem::Stacked(item) => Some(item),
            _ => unreachable!(),
        }
    }
}