use std::cmp::Ordering;
use std::collections::HashMap;
use libpso::character::character;
use crate::ship::items::ClientItemId;
use crate::entity::item::{Meseta, ItemEntityId, ItemDetail, ItemEntity, InventoryEntity, InventoryItemEntity, BankEntity, BankItemEntity, BankName, EquippedEntity};
use std::future::Future;

use crate::ship::map::MapArea;
use crate::ship::location::{AreaClient, RoomId};
use crate::entity::character::{CharacterEntity, CharacterEntityId};
use crate::entity::gateway::{EntityGateway, GatewayError};
use crate::entity::item::tool::{Tool, ToolType};
use crate::entity::item::mag::Mag;
use crate::ship::drops::ItemDrop;
use crate::ship::shops::{ShopItem, ArmorShopItem, ToolShopItem, WeaponShopItem};

// TODO: Commit trait that ItemStateProxy and EntityTransaction implement that .commit requires and acts on upon everything succeeding (like 3 less lines of code!)

#[derive(thiserror::Error, Debug)]
pub enum ItemStateError {
    #[error("character {0} not found")]
    NoCharacter(CharacterEntityId),
    #[error("room {0} not found")]
    NoRoom(RoomId),
    #[error("floor item {0} not found")]
    NoFloorItem(ClientItemId),

    #[error("expected {0} to be a tool")]
    NotATool(ClientItemId),

    #[error("bank item {0} not found")]
    NoBankItem(ClientItemId),

    #[error("inventory error {0}")]
    InventoryError(#[from] InventoryError),

    #[error("bank error {0}")]
    BankError(#[from] BankError),

    #[error("invalid item id {0}")]
    InvalidItemId(ClientItemId),

    #[error("invalid drop? {0:?} (this shouldn't occur)")]
    BadItemDrop(ItemDrop),

    #[error("idk")]
    Dummy,

    #[error("gateway")]
    GatewayError(#[from] GatewayError),

    #[error("tried to remove more meseta than exists: {0}")]
    InvalidMesetaRemoval(u32),

    #[error("tried to add meseta when there is no more room")]
    FullOfMeseta,

    #[error("stacked item")]
    StackedItemError(Vec<ItemEntity>),

    #[error("apply item {0}")]
    ApplyItemError(#[from] crate::ship::items::apply_item::ApplyItemError),

    #[error("item is not a mag {0}")]
    NotAMag(ClientItemId),

    #[error("item is not mag food {0}")]
    NotMagFood(ClientItemId),

    #[error("item is not sellable")]
    ItemNotSellable,
}

pub enum FloorType {
    Local,
    Shared,
}


#[async_trait::async_trait]
pub trait ItemAction {
    type Input;
    type Output;
    type Start;
    type Error;

    async fn action(&self, s: Self::Start, i: Self::Input) -> Result<(Self::Start, Self::Output), Self::Error>;
    async fn commit(&self, v: Self::Start) -> Result<(Self::Start, Self::Output), Self::Error>;
}


pub struct ItemStateAction<T, S, E> {
    _t: std::marker::PhantomData<T>,
    _s: std::marker::PhantomData<S>,
    _e: std::marker::PhantomData<E>,
}

impl<T, S, E> Default for ItemStateAction<T, S, E> {
    fn default() -> ItemStateAction<T, S, E> {
        ItemStateAction {
            _t: std::marker::PhantomData,
            _s: std::marker::PhantomData,
            _e: std::marker::PhantomData,
        }
    }
}

impl<T, S, E> ItemStateAction<T, S, E>
where
    T: Send + Sync,
    S: Send + Sync,
    E: Send + Sync,
{
    pub fn act<O, F, Fut>(self, f: F) -> ItemActionStage<O, ItemStateAction<T, S, E>, F, Fut, S, E>
    where
        F: Fn(S, ()) -> Fut + Send + Sync,
        Fut: Future<Output=Result<(S, O), E>> + Send
    {
        ItemActionStage {
            _s: Default::default(),
            _e: std::marker::PhantomData,
            prev: self,
            actionf: f,
        }
    }
}

pub struct ItemActionStage<O, P, F, Fut, S, E>
where
    P: ItemAction,
    F: Fn(S, P::Output) -> Fut + Send + Sync,
    Fut: Future<Output=Result<(S, O) , E>> + Send,
{
    _s: std::marker::PhantomData<S>,
    _e: std::marker::PhantomData<E>,
    prev: P,
    actionf: F,
}

#[async_trait::async_trait]
impl<O, P: ItemAction, F, Fut, S, E> ItemAction for ItemActionStage<O, P, F, Fut, S, E>
where
    P: ItemAction + ItemAction<Start = S, Error = E> + Send + Sync,
    F: Fn(S, P::Output) -> Fut + Send + Sync,
    Fut: Future<Output=Result<(S, O), E>> + Send,
    S: Send + Sync,
    P::Output: Send + Sync,
    E: Send + Sync,
    O: Send + Sync,
    P::Error: Send + Sync,
{
    type Input = P::Output;
    type Output = O;
    type Start = S;
    type Error = P::Error;

    async fn action(&self, s: Self::Start, i: Self::Input) -> Result<(Self::Start, Self::Output), Self::Error> {
        (self.actionf)(s, i).await
    }

    async fn commit(&self, i: Self::Start) -> Result<(Self::Start, Self::Output), Self::Error> {
        let (i, prev) = self.prev.commit(i).await?;
        self.action(i, prev).await
    }
}

impl<O, P: ItemAction, F, Fut, S, E> ItemActionStage<O, P, F, Fut, S, E>
where
    P: ItemAction<Start = S, Error = E> + Send + Sync,
    F: Fn(S, P::Output) -> Fut + Send + Sync,
    Fut: Future<Output=Result<(S, O), E>> + Send,
    S: Send + Sync,
    P::Output: Send + Sync,
    E: Send + Sync,
    O: Send + Sync,
    P::Error: Send + Sync,
{
    #[allow(clippy::type_complexity)]
    pub fn act<O2, G, GFut>(self, g: G) -> ItemActionStage<O2, ItemActionStage<O, P, F, Fut, S, E>, G, GFut, S, E>
    where
        S: Send + Sync,
        G: Fn(S, <ItemActionStage<O, P, F, Fut, S, E> as ItemAction>::Output) -> GFut + Send + Sync,
        GFut: Future<Output=Result<(S, O2), E>> + Send,
        O2: Send + Sync,
    {
        ItemActionStage {
            _s: Default::default(),
            _e: Default::default(),
            prev: self,
            actionf: g,
        }
    }
}

#[async_trait::async_trait]
impl<T, S, E> ItemAction for ItemStateAction<T, S, E>
where
    T: Send + Sync,
    S: Send + Sync,
    E: Send + Sync,
{
    type Input = T;
    type Output = ();
    type Start = T;
    type Error = E;

    async fn action(&self, s: Self::Start, _i: Self::Input) -> Result<(Self::Start, Self::Output), Self::Error> {
        Ok((s, ()))
    }

    async fn commit(&self, i: Self::Start) -> Result<(Self::Start, Self::Output), Self::Error> {
        Ok((i, ()))
    }
}


#[derive(Clone, Debug)]
pub struct IndividualItemDetail {
    pub entity_id: ItemEntityId,
    pub item: ItemDetail,
}

impl IndividualItemDetail {
    pub fn as_mag(&self) -> Option<&Mag> {
        match &self.item {
            ItemDetail::Mag(mag) => Some(mag),
            _ => None
        }
    }

    pub fn as_mag_mut(&mut self) -> Option<&mut Mag> {
        match &mut self.item {
            ItemDetail::Mag(mag) => Some(mag),
            _ => None
        }
    }
}

#[derive(Clone, Debug)]
pub struct StackedItemDetail {
    pub entity_ids: Vec<ItemEntityId>,
    pub tool: Tool,
}

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

#[derive(Clone, Debug)]
pub enum InventoryItemDetail {
    Individual(IndividualItemDetail),
    Stacked(StackedItemDetail),
}

impl InventoryItemDetail {
    // TODO: rename as_stacked for consistency
    pub fn stacked(&self) -> Option<&StackedItemDetail> {
        match self {
            InventoryItemDetail::Stacked(sitem) => Some(sitem),
            _ => None,
        }
    }
    // TODO: rename as_stacked_mut for consistency
    pub fn stacked_mut(&mut self) -> Option<&mut StackedItemDetail> {
        match self {
            InventoryItemDetail::Stacked(sitem) => Some(sitem),
            _ => None,
        }
    }

    pub fn as_individual(&self) -> Option<&IndividualItemDetail> {
        match self {
            InventoryItemDetail::Individual(iitem) => Some(iitem),
            _ => None,
        }
    }

    pub fn as_individual_mut(&mut self) -> Option<&mut IndividualItemDetail> {
        match self {
            InventoryItemDetail::Individual(iitem) => Some(iitem),
            _ => None,
        }
    }

    pub fn as_client_bytes(&self) -> [u8; 16] {
        match self {
            InventoryItemDetail::Individual(item) => {
                match &item.item {
                    ItemDetail::Weapon(w) => w.as_bytes(),
                    ItemDetail::Armor(a) => a.as_bytes(),
                    ItemDetail::Shield(s) => s.as_bytes(),
                    ItemDetail::Unit(u) => u.as_bytes(),
                    ItemDetail::Tool(t) => t.as_individual_bytes(),
                    ItemDetail::TechniqueDisk(d) => d.as_bytes(),
                    ItemDetail::Mag(m) => m.as_bytes(),
                    ItemDetail::ESWeapon(e) => e.as_bytes(),
                }
            },
            InventoryItemDetail::Stacked(item) => {
                item.tool.as_stacked_bytes(item.entity_ids.len())
            },
        }
    }

    // TODO: this should probably go somewhere a bit more fundamental like ItemDetail
    pub fn sell_price(&self) -> Result<u32, ItemStateError> {
        match self {
            InventoryItemDetail::Individual(individual_item) => {
                match &individual_item.item {
                    // TODO: can wrapped items be sold?
                    ItemDetail::Weapon(w) => {
                        if !w.tekked {
                            return Ok(1u32)
                        }
                        if w.is_rare_item() {
                            return Ok(10u32)
                        }
                        Ok((WeaponShopItem::from(w).price() / 8) as u32)
                    },
                    ItemDetail::Armor(a) => {
                        if a.is_rare_item() {
                            return Ok(10u32)
                        }
                        Ok((ArmorShopItem::from(a).price() / 8) as u32)
                    },
                    ItemDetail::Shield(s) => {
                        if s.is_rare_item() {
                            return Ok(10u32)
                        }
                        Ok((ArmorShopItem::from(s).price() / 8) as u32)
                    },
                    ItemDetail::Unit(u) => {
                        if u.is_rare_item() {
                            return Ok(10u32)
                        }
                        Ok((ArmorShopItem::from(u).price() / 8) as u32)
                    },
                    ItemDetail::Tool(t) => {
                        if !matches!(t.tool, ToolType::PhotonDrop | ToolType::PhotonSphere | ToolType::PhotonCrystal) && t.is_rare_item() {
                            return Ok(10u32)
                        }
                        Ok((ToolShopItem::from(t).price() / 8) as u32)
                    },
                    ItemDetail::TechniqueDisk(d) => {
                        Ok((ToolShopItem::from(d).price() / 8) as u32)
                    },
                    ItemDetail::Mag(_m) => {
                        Err(ItemStateError::ItemNotSellable)
                    },
                    ItemDetail::ESWeapon(_e) => {
                        Ok(10u32)
                    },
                }
            },
            // the number of stacked items sold is handled by the caller. this is just the price of 1
            InventoryItemDetail::Stacked(stacked_item) => {
                Ok(((ToolShopItem::from(&stacked_item.tool).price() / 8) as u32) * stacked_item.count() as u32)
            },
        }
    }

}


#[derive(Clone, Debug)]
pub struct InventoryItem {
    pub item_id: ClientItemId,
    pub item: InventoryItemDetail,
}

impl InventoryItem {
    pub async fn with_entity_id<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, ItemStateError>
    where
        F: FnMut(T, ItemEntityId) -> Fut,
        Fut: Future<Output=Result<T, ItemStateError>>,
    {
        match &self.item {
            InventoryItemDetail::Individual(individual_item) => {
                param = func(param, individual_item.entity_id).await?;
            },
            InventoryItemDetail::Stacked(stacked_item) => {
                for entity_id in &stacked_item.entity_ids {
                    param = func(param, *entity_id).await?;
                }
            }
        }

        Ok(param)
    }

    pub async fn with_mag<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, ItemStateError>
    where
        F: FnMut(T, ItemEntityId, Mag) -> Fut,
        Fut: Future<Output=Result<T, ItemStateError>>,
    {
        if let InventoryItemDetail::Individual(individual_item) = &self.item {
            if let ItemDetail::Mag(mag) = &individual_item.item {
                param = func(param, individual_item.entity_id, mag.clone()).await?;
            }
        }
        Ok(param)
    }
}


#[derive(Clone, Debug)]
pub enum BankItemDetail {
    Individual(IndividualItemDetail),
    Stacked(StackedItemDetail),
}

impl BankItemDetail {
    fn stacked_mut(&mut self) -> Option<&mut StackedItemDetail> {
        match self {
            BankItemDetail::Stacked(sitem) => Some(sitem),
            _ => None,
        }
    }

    pub fn as_client_bytes(&self) -> [u8; 16] {
        match self {
            BankItemDetail::Individual(item) => {
                match &item.item {
                    ItemDetail::Weapon(w) => w.as_bytes(),
                    ItemDetail::Armor(a) => a.as_bytes(),
                    ItemDetail::Shield(s) => s.as_bytes(),
                    ItemDetail::Unit(u) => u.as_bytes(),
                    ItemDetail::Tool(t) => t.as_individual_bytes(),
                    ItemDetail::TechniqueDisk(d) => d.as_bytes(),
                    ItemDetail::Mag(m) => m.as_bytes(),
                    ItemDetail::ESWeapon(e) => e.as_bytes(),
                }
            },
            BankItemDetail::Stacked(item) => {
                item.tool.as_stacked_bytes(item.entity_ids.len())
            },
        }
    }
}

#[derive(Clone, Debug)]
pub struct BankItem {
    pub item_id: ClientItemId,
    pub item: BankItemDetail,
}

impl BankItem {
    pub async fn with_entity_id<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, ItemStateError>
    where
        F: FnMut(T, ItemEntityId) -> Fut,
        Fut: Future<Output=Result<T, ItemStateError>>,
    {
        match &self.item {
            BankItemDetail::Individual(individual_item) => {
                param = func(param, individual_item.entity_id).await?;
            },
            BankItemDetail::Stacked(stacked_item) => {
                for entity_id in &stacked_item.entity_ids {
                    param = func(param, *entity_id).await?;
                }
            }
        }
        Ok(param)
    }
}

#[derive(Debug, Clone)]
pub enum FloorItemDetail {
    Individual(IndividualItemDetail),
    Stacked(StackedItemDetail),
    Meseta(Meseta),
}

impl FloorItemDetail {
    fn stacked(&self) -> Option<&StackedItemDetail> {
        match self {
            FloorItemDetail::Stacked(sitem) => Some(sitem),
            _ => None,
        }
    }
}

#[derive(Debug, Clone)]
pub struct FloorItem {
    pub item_id: ClientItemId,
    pub item: FloorItemDetail,
    pub map_area: MapArea,
    pub x: f32,
    pub y: f32,
    pub z: f32,
}

impl FloorItem {
    pub async fn with_entity_id<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, ItemStateError>
    where
        F: FnMut(T, ItemEntityId) -> Fut,
        Fut: Future<Output=Result<T, ItemStateError>>,
    {
        match &self.item {
            FloorItemDetail::Individual(individual_item) => {
                param = func(param, individual_item.entity_id).await?;
            },
            FloorItemDetail::Stacked(stacked_item) => {
                for entity_id in &stacked_item.entity_ids {
                    param = func(param, *entity_id).await?;
                }
            },
            FloorItemDetail::Meseta(_meseta) => {},
        }

        Ok(param)
    }

    pub async fn with_mag<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, ItemStateError>
    where
        F: FnMut(T, ItemEntityId, Mag) -> Fut,
        Fut: Future<Output=Result<T, ItemStateError>>,
    {
        if let FloorItemDetail::Individual(individual_item) = &self.item {
            if let ItemDetail::Mag(mag) = &individual_item.item {
                param = func(param, individual_item.entity_id, mag.clone()).await?;
            }
        }
        Ok(param)
    }

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


#[derive(Clone, Debug)]
pub struct Inventory(Vec<InventoryItem>);


#[derive(thiserror::Error, Debug)]
pub enum InventoryError {
    #[error("inventory full")]
    InventoryFull,
    #[error("stack full")]
    StackFull,
    #[error("meseta full")]
    MesetaFull,
}

#[derive(thiserror::Error, Debug)]
pub enum BankError {
    #[error("bank full")]
    BankFull,
    #[error("stack full")]
    StackFull,
    #[error("meseta full")]
    MesetaFull,
}

#[derive(Clone)]
pub enum AddItemResult {
    NewItem,
    AddToStack,
    Meseta,
}

#[derive(Debug, Clone, Default)]
pub struct LocalFloor(Vec<FloorItem>);
#[derive(Debug, Clone, Default)]
pub struct SharedFloor(Vec<FloorItem>);

#[derive(Clone)]
pub struct InventoryState {
    character_id: CharacterEntityId,
    item_id_counter: u32,
    pub inventory: Inventory,
    equipped: EquippedEntity,
    pub meseta: Meseta,
}

impl InventoryState {
    pub fn initialize_item_ids(&mut self, base_item_id: u32) {
        for (i, item) in self.inventory.0.iter_mut().enumerate() {
            item.item_id = ClientItemId(base_item_id + i as u32);
        }
        self.item_id_counter = base_item_id + self.inventory.0.len() as u32 + 1;
    }

    pub fn new_item_id(&mut self) -> ClientItemId {
        self.item_id_counter += 1;
        ClientItemId(self.item_id_counter)
    }

    pub fn count(&self) -> usize {
        self.inventory.0.len()
    }

    pub fn add_floor_item(&mut self, item: FloorItem) -> Result<AddItemResult, InventoryError> {
        match item.item {
            FloorItemDetail::Individual(iitem) => {
                if self.inventory.0.len() >= 30 {
                    Err(InventoryError::InventoryFull)
                }
                else {
                    self.inventory.0.push(InventoryItem {
                        item_id: item.item_id,
                        item: InventoryItemDetail::Individual(iitem)
                    });
                    Ok(AddItemResult::NewItem)
                }
            },
            FloorItemDetail::Stacked(sitem) => {
                let existing_stack = self.inventory.0
                    .iter_mut()
                    .filter_map(|item| item.item.stacked_mut())
                    .find(|item| {
                        item.tool == sitem.tool
                    });
                match existing_stack {
                    Some(existing_stack) => {
                        if existing_stack.entity_ids.len() + sitem.entity_ids.len() > sitem.tool.max_stack() {
                            Err(InventoryError::StackFull)
                        }
                        else {
                            existing_stack.entity_ids.append(&mut sitem.entity_ids.clone());
                            Ok(AddItemResult::AddToStack)
                        }
                    },
                    None => {
                        if self.inventory.0.len() >= 30 {
                            Err(InventoryError::InventoryFull)
                        }
                        else {
                            self.inventory.0.push(InventoryItem {
                                item_id: item.item_id,
                                item: InventoryItemDetail::Stacked(sitem)
                            });
                            Ok(AddItemResult::NewItem)
                        }
                    }
                }

            },
            FloorItemDetail::Meseta(meseta) => {
                if self.meseta == Meseta(999999) {
                    Err(InventoryError::MesetaFull)
                }
                else {
                    self.meseta.0 = std::cmp::min(self.meseta.0 + meseta.0, 999999);
                    Ok(AddItemResult::Meseta)
                }
            },
        }
    }

    pub fn add_item(&mut self, item: InventoryItem) -> Result<(AddItemResult, InventoryItem), InventoryError> {
        match &item.item {
            InventoryItemDetail::Individual(_) => {
                if self.inventory.0.len() >= 30 {
                    Err(InventoryError::InventoryFull)
                }
                else {
                    self.inventory.0.push(item);
                    Ok((
                        AddItemResult::NewItem,
                        self.inventory.0
                            .last()
                            .unwrap()
                            .clone()
                    ))
                }
            },
            InventoryItemDetail::Stacked(sitem) => {
                let existing_stack = self.inventory.0
                    .iter_mut()
                    .filter_map(|item| item.item.stacked_mut())
                    .find(|item| {
                        item.tool == sitem.tool
                    });
                match existing_stack {
                    Some(existing_stack) => {
                        if existing_stack.entity_ids.len() + sitem.entity_ids.len() > sitem.tool.max_stack() {
                            Err(InventoryError::StackFull)
                        }
                        else {
                            existing_stack.entity_ids.append(&mut sitem.entity_ids.clone());
                            Ok((
                                AddItemResult::AddToStack,
                                self.inventory.0[self.inventory.0
                                                 .iter()
                                                 .filter_map(|item| item.item.stacked())
                                                 .position(|item| item.tool == sitem.tool)
                                                 .unwrap()]
                                    .clone()
                            ))
                        }
                    },
                    None => {
                        if self.inventory.0.len() >= 30 {
                            Err(InventoryError::InventoryFull)
                        }
                        else {
                            self.inventory.0.push(item);
                            Ok((
                                AddItemResult::NewItem,
                                self.inventory.0
                                    .last()
                                    .unwrap()
                                    .clone()
                            ))
                        }
                    }
                }
            }
        }
    }

    pub fn take_item(&mut self, item_id: &ClientItemId, amount: u32) -> Option<InventoryItem> {
        let idx = self.inventory.0
            .iter()
            .position(|i| i.item_id == *item_id)?;
        match &mut self.inventory.0[idx].item {
            InventoryItemDetail::Individual(_individual_item) => {
                Some(self.inventory.0.remove(idx))
            },
            InventoryItemDetail::Stacked(stacked_item) => {
                let remove_all = (amount == 0) || match stacked_item.entity_ids.len().cmp(&(amount as usize)) {
                    Ordering::Equal => true,
                    Ordering::Greater => false,
                    Ordering::Less => return None,
                };

                if remove_all {
                    Some(self.inventory.0.remove(idx))
                }
                else {
                    let entity_ids = stacked_item.entity_ids.drain(..(amount as usize)).collect();
                    self.item_id_counter += 1;
                    Some(InventoryItem {
                        item_id: ClientItemId(self.item_id_counter),
                        item: InventoryItemDetail::Stacked(StackedItemDetail {
                            entity_ids: entity_ids,
                            tool: stacked_item.tool,
                        })})
                }
            }
        }
    }

    pub fn get_by_client_id(&self, item_id: &ClientItemId) -> Option<&InventoryItem> {
        self.inventory.0
            .iter()
            .find(|i| i.item_id == *item_id)
    }

    pub fn get_by_client_id_mut(&mut self, item_id: &ClientItemId) -> Option<&mut InventoryItem> {
        self.inventory.0
            .iter_mut()
            .find(|i| i.item_id == *item_id)
    }

    pub fn add_meseta(&mut self, amount: u32) -> Result<(), ItemStateError> {
        if self.meseta.0 == 999999 {
            return Err(ItemStateError::FullOfMeseta)
        }
        self.meseta.0 = std::cmp::min(self.meseta.0 + amount, 999999);
        Ok(())
    }

    pub fn add_meseta_no_overflow(&mut self, amount: u32) -> Result<(), ItemStateError> {
        if self.meseta.0 + amount > 999999 {
            return Err(ItemStateError::FullOfMeseta)
        }
        self.meseta.0 += amount;
        Ok(())
    }

    pub fn remove_meseta(&mut self, amount: u32) -> Result<(), ItemStateError> {
        if amount > self.meseta.0 {
            return Err(ItemStateError::InvalidMesetaRemoval(amount))
        }
        self.meseta.0 -= amount;
        Ok(())
    }

    pub fn equip(&mut self, item_id: &ClientItemId, equip_slot: u8) {
        for item in &self.inventory.0 {
            if let InventoryItemDetail::Individual(inventory_item) = &item.item {
                if item.item_id == *item_id {
                    match inventory_item.item {
                        ItemDetail::Weapon(_) => self.equipped.weapon = Some(inventory_item.entity_id),
                        ItemDetail::Armor(_) => self.equipped.armor = Some(inventory_item.entity_id),
                        ItemDetail::Shield(_) => self.equipped.shield = Some(inventory_item.entity_id),
                        ItemDetail::Unit(_) => {
                            if let Some(unit) = self.equipped.unit.get_mut(equip_slot as usize) {
                                *unit = Some(inventory_item.entity_id)
                            }
                        }
                        ItemDetail::Mag(_) => self.equipped.mag = Some(inventory_item.entity_id),
                        _ => {}
                    }
                }
            }
        }
    }

    pub fn unequip(&mut self, item_id: &ClientItemId) {
        for item in &self.inventory.0 {
            if let InventoryItemDetail::Individual(inventory_item) = &item.item {
                if item.item_id == *item_id {
                    match inventory_item.item {
                        ItemDetail::Weapon(_) => self.equipped.weapon = None,
                        ItemDetail::Armor(_) => {
                            self.equipped.armor = None;
                            self.equipped.unit = [None; 4];
                        }
                        ItemDetail::Shield(_) => self.equipped.shield = None,
                        ItemDetail::Unit(_) => {
                            for unit in self.equipped.unit.iter_mut() {
                                if *unit == Some(inventory_item.entity_id) {
                                    *unit = None
                                }
                            }
                        }
                        ItemDetail::Mag(_) => self.equipped.mag = Some(inventory_item.entity_id),
                        _ => {}
                    }
                }
            }
        }
    }

    pub fn equipped_mag_mut(&mut self) -> Option<(ItemEntityId, &mut Mag)> {
        let mag_id = self.equipped.mag?;
        self.inventory.0
            .iter_mut()
            .filter_map(|i| {
                let individual = i.item.as_individual_mut()?;
                let entity_id = individual.entity_id;
                Some((entity_id, individual.as_mag_mut()?))
            })
            .find(|(entity_id, _)| *entity_id == mag_id)
    }

    pub fn sort(&mut self, item_ids: &Vec<ClientItemId>) {
        self.inventory.0.sort_by(|a, b| {
            let a_index = item_ids.iter().position(|item_id| *item_id == a.item_id);
            let b_index = item_ids.iter().position(|item_id| *item_id == b.item_id);

            match (a_index, b_index) {
                (Some(a_index), Some(b_index)) => {
                    a_index.cmp(&b_index)
                },
                _ => Ordering::Equal
            }
        });
    }

    pub fn as_inventory_entity(&self, _character_id: &CharacterEntityId) -> InventoryEntity {
        InventoryEntity {
            items: self.inventory.0.iter()
                .map(|item| {
                    match &item.item {
                        InventoryItemDetail::Individual(item) => {
                            InventoryItemEntity::Individual(ItemEntity {
                                id: item.entity_id,
                                item: item.item.clone(),
                            })
                        },
                        InventoryItemDetail::Stacked(items) => {
                            InventoryItemEntity::Stacked(items.entity_ids.iter()
                                                         .map(|id| {
                                                             ItemEntity {
                                                                 id: *id,
                                                                 item: ItemDetail::Tool(items.tool)
                                                             }
                                                         })
                                                         .collect())
                        },
                    }
                })
                .collect()
        }
    }

    pub fn as_equipped_entity(&self) -> EquippedEntity {
        self.equipped.clone()
    }


    pub fn as_client_inventory_items(&self) -> [character::InventoryItem; 30] {
        self.inventory.0.iter()
            .enumerate()
            .fold([character::InventoryItem::default(); 30], |mut inventory, (slot, item)| {
                let bytes = item.item.as_client_bytes();
                inventory[slot].data1.copy_from_slice(&bytes[0..12]);
                inventory[slot].data2.copy_from_slice(&bytes[12..16]);
                inventory[slot].item_id = item.item_id.0;
                inventory[slot].equipped = 0;
                inventory[slot].flags = 0;

                if let InventoryItemDetail::Individual(individual_item) = &item.item {
                    if self.equipped.is_equipped(&individual_item.entity_id) {
                        if let ItemDetail::Unit(_) = individual_item.item {
                            inventory[slot].data1[4] = self.equipped.unit.iter()
                                .enumerate()
                                .find(|(_, u_id)| **u_id == Some(individual_item.entity_id))
                                .map(|(a, _)| a)
                                .unwrap_or(0) as u8
                        }
                        inventory[slot].equipped = 1;
                        inventory[slot].flags |= 8;
                    }
                }
                inventory
            })
    }
}

#[derive(Clone, Debug)]
pub struct Bank(Vec<BankItem>);

#[derive(Clone, Debug)]
pub struct BankState {
    character_id: CharacterEntityId,
    item_id_counter: u32,
    pub name: BankName,
    bank: Bank,
    pub meseta: Meseta,
}

impl BankState {
    pub fn new(character_id: CharacterEntityId, name: BankName, mut bank: Bank, meseta: Meseta) -> BankState {
        bank.0.sort();
        BankState {
            character_id,
            item_id_counter: 0,
            name,
            bank,
            meseta,
        }
    }

    pub fn count(&self) -> usize {
        self.bank.0.len()
    }

    pub fn initialize_item_ids(&mut self, base_item_id: u32) {
        for (i, item) in self.bank.0.iter_mut().enumerate() {
            item.item_id = ClientItemId(base_item_id + i as u32);
        }
        self.item_id_counter = base_item_id + self.bank.0.len() as u32;
    }

    pub fn add_meseta(&mut self, amount: u32) -> Result<(), ItemStateError> {
        if self.meseta.0 + amount > 999999 {
            return Err(ItemStateError::FullOfMeseta)
        }
        self.meseta.0 += self.meseta.0 + amount;
        Ok(())
    }

    pub fn remove_meseta(&mut self, amount: u32) -> Result<(), ItemStateError> {
        if amount > self.meseta.0 {
            return Err(ItemStateError::InvalidMesetaRemoval(amount))
        }
        self.meseta.0 -= amount;
        Ok(())
    }

    pub fn add_inventory_item(&mut self, item: InventoryItem) -> Result<AddItemResult, BankError> {
        match item.item {
            InventoryItemDetail::Individual(iitem) => {
                if self.bank.0.len() >= 30 {
                    Err(BankError::BankFull)
                }
                else {
                    self.bank.0.push(BankItem {
                        item_id: item.item_id,
                        item: BankItemDetail::Individual(iitem)
                    });
                    self.bank.0.sort();
                    Ok(AddItemResult::NewItem)
                }
            },
            InventoryItemDetail::Stacked(sitem) => {
                let existing_stack = self.bank.0
                    .iter_mut()
                    .filter_map(|item| item.item.stacked_mut())
                    .find(|item| {
                        item.tool == sitem.tool
                    });
                match existing_stack {
                    Some(existing_stack) => {
                        if existing_stack.entity_ids.len() + sitem.entity_ids.len() > sitem.tool.max_stack() {
                            Err(BankError::StackFull)
                        }
                        else {
                            existing_stack.entity_ids.append(&mut sitem.entity_ids.clone());
                            Ok(AddItemResult::AddToStack)
                        }
                    },
                    None => {
                        if self.bank.0.len() >= 30 {
                            Err(BankError::BankFull)
                        }
                        else {
                            self.bank.0.push(BankItem {
                                item_id: item.item_id,
                                item: BankItemDetail::Stacked(sitem)
                            });
                            self.bank.0.sort();
                            Ok(AddItemResult::NewItem)
                        }
                    }
                }
            }
        }
    }

    pub fn take_item(&mut self, item_id: &ClientItemId, amount: u32) -> Option<BankItem> {
        let idx = self.bank.0
            .iter()
            .position(|i| i.item_id == *item_id)?;
        match &mut self.bank.0[idx].item {
            BankItemDetail::Individual(_individual_item) => {
                Some(self.bank.0.remove(idx))
            },
            BankItemDetail::Stacked(stacked_item) => {
                let remove_all = match stacked_item.entity_ids.len().cmp(&(amount as usize)) {
                    Ordering::Equal => true,
                    Ordering::Greater => false,
                    Ordering::Less => return None,
                };

                if remove_all {
                    Some(self.bank.0.remove(idx))
                }
                else {
                    let entity_ids = stacked_item.entity_ids.drain(..(amount as usize)).collect();
                    self.item_id_counter += 1;
                    Some(BankItem {
                        item_id: ClientItemId(self.item_id_counter),
                        item: BankItemDetail::Stacked(StackedItemDetail {
                            entity_ids: entity_ids,
                            tool: stacked_item.tool,
                        })})
                }
            }
        }
    }

    pub fn as_client_bank_items(&self) -> character::Bank {
        self.bank.0.iter()
            .enumerate()
            .fold(character::Bank::default(), |mut bank, (slot, item)| {
                bank.item_count = (slot + 1) as u32;
                let bytes = item.item.as_client_bytes();
                bank.items[slot].data1.copy_from_slice(&bytes[0..12]);
                bank.items[slot].data2.copy_from_slice(&bytes[12..16]);
                bank.items[slot].item_id = item.item_id.0;

                bank
            })
    }

    pub fn as_client_bank_request(&self) -> Vec<character::BankItem> {
        self.bank.0.iter()
            .map(|item| {
                let bytes = item.item.as_client_bytes();
                let mut data1 = [0; 12];
                let mut data2 = [0; 4];
                data1.copy_from_slice(&bytes[0..12]);
                data2.copy_from_slice(&bytes[12..16]);
                let amount = match &item.item {
                    BankItemDetail::Individual(_individual_bank_item) => {
                        1
                    },
                    BankItemDetail::Stacked(stacked_bank_item) => {
                        stacked_bank_item.count()
                    },
                };
                character::BankItem {
                    data1,
                    data2,
                    item_id: item.item_id.0,
                    amount: amount as u16,
                    flags: 1,
                }
            })
            .collect()
    }

    pub fn as_bank_entity(&self) -> BankEntity {
        BankEntity {
            items: self.bank.0.iter()
                .map(|item| {
                    match &item.item {
                        BankItemDetail::Individual(item) => {
                            BankItemEntity::Individual(ItemEntity {
                                id: item.entity_id,
                                item: item.item.clone(),
                            })
                        },
                        BankItemDetail::Stacked(items) => {
                            BankItemEntity::Stacked(items.entity_ids.iter()
                                                    .map(|id| {
                                                        ItemEntity {
                                                            id: *id,
                                                            item: ItemDetail::Tool(items.tool)
                                                        }
                                                    })
                                                    .collect())
                        },
                    }
                })
                .collect()
        }
    }
}

impl std::cmp::PartialEq for BankItem {
    fn eq(&self, other: &BankItem) -> bool {
        let mut self_bytes = [0u8; 4];
        let mut other_bytes = [0u8; 4];
        self_bytes.copy_from_slice(&self.item.as_client_bytes()[0..4]);
        other_bytes.copy_from_slice(&other.item.as_client_bytes()[0..4]);

        let self_value = u32::from_be_bytes(self_bytes);
        let other_value = u32::from_be_bytes(other_bytes);

        self_value.eq(&other_value)
    }
}

impl std::cmp::Eq for BankItem {}

impl std::cmp::PartialOrd for BankItem {
    fn partial_cmp(&self, other: &BankItem) -> Option<std::cmp::Ordering> {
        let mut self_bytes = [0u8; 4];
        let mut other_bytes = [0u8; 4];
        self_bytes.copy_from_slice(&self.item.as_client_bytes()[0..4]);
        other_bytes.copy_from_slice(&other.item.as_client_bytes()[0..4]);


        let self_value = u32::from_be_bytes(self_bytes);
        let other_value = u32::from_be_bytes(other_bytes);

        self_value.partial_cmp(&other_value)
    }
}

impl std::cmp::Ord for BankItem {
    fn cmp(&self, other: &BankItem) -> std::cmp::Ordering {
        let mut self_bytes = [0u8; 4];
        let mut other_bytes = [0u8; 4];
        self_bytes.copy_from_slice(&self.item.as_client_bytes()[0..4]);
        other_bytes.copy_from_slice(&other.item.as_client_bytes()[0..4]);


        let self_value = u32::from_le_bytes(self_bytes);
        let other_value = u32::from_le_bytes(other_bytes);

        self_value.cmp(&other_value)
    }
}

#[derive(Debug)]
pub struct FloorState {
    character_id: CharacterEntityId,
    local: LocalFloor,
    shared: SharedFloor,
}

impl FloorState {
    pub fn take_item(&mut self, item_id: &ClientItemId) -> Option<FloorItem> {
        let item = self.local.0
            .drain_filter(|item| {
                item.item_id == *item_id
            })
            .next();
        item.or_else(|| {
            self.shared.0
                .drain_filter(|item| {
                    item.item_id == *item_id
                })
                .next()
        })
    }

    pub fn add_inventory_item(&mut self, inventory_item: InventoryItem, map_area: MapArea, position: (f32, f32, f32)) -> &FloorItem {
        let floor_item = FloorItem {
            item_id: inventory_item.item_id,
            item: match inventory_item.item {
                InventoryItemDetail::Individual(individual_item) => FloorItemDetail::Individual(individual_item),
                InventoryItemDetail::Stacked(stacked_item) => FloorItemDetail::Stacked(stacked_item),
            },
            map_area: map_area,
            x: position.0,
            y: position.1,
            z: position.2,
        };

        self.shared.0.push(floor_item);
        &self.shared.0[self.shared.0.len()-1]
    }

    pub fn add_shared_item(&mut self, floor_item: FloorItem) -> &FloorItem {
        self.shared.0.push(floor_item);
        &self.shared.0[self.shared.0.len()-1]
    }

    pub fn add_local_item(&mut self, floor_item: FloorItem) -> &FloorItem {
        self.local.0.push(floor_item);
        &self.local.0[self.local.0.len()-1]
    }
}


pub struct ItemState {
    character_inventory: HashMap<CharacterEntityId, InventoryState>,
    character_bank: HashMap<CharacterEntityId, BankState>,

    character_room: HashMap<CharacterEntityId, RoomId>,
    character_floor: HashMap<CharacterEntityId, LocalFloor>,
    room_floor: HashMap<RoomId, SharedFloor>,

    room_item_id_counter: u32,
}

impl Default for ItemState {
    fn default() -> ItemState {
        ItemState {
            character_inventory: HashMap::new(),
            character_bank: HashMap::new(),
            character_room: HashMap::new(),
            character_floor: HashMap::new(),
            room_floor: HashMap::new(),
            room_item_id_counter: 0x00810000,
        }
    }
}

impl ItemState {
    pub fn get_character_inventory(&self, character: &CharacterEntity) -> Result<&InventoryState, ItemStateError> {
        Ok(self.character_inventory.get(&character.id)
           .ok_or(ItemStateError::NoCharacter(character.id))?)
    }

    pub fn get_character_bank(&self, character: &CharacterEntity) -> Result<&BankState, ItemStateError> {
        Ok(self.character_bank.get(&character.id)
           .ok_or(ItemStateError::NoCharacter(character.id))?)
    }
}

impl ItemState {
    fn new_item_id(&mut self) -> Result<ClientItemId, ItemStateError> {
        self.room_item_id_counter += 1;
        Ok(ClientItemId(self.room_item_id_counter))
    }

    pub async fn load_character<EG: EntityGateway>(&mut self, entity_gateway: &mut EG, character: &CharacterEntity) -> Result<(), ItemStateError> {
        let inventory = entity_gateway.get_character_inventory(&character.id).await?;
        let bank = entity_gateway.get_character_bank(&character.id, &BankName("".into())).await?;
        let equipped = entity_gateway.get_character_equips(&character.id).await?;

        let inventory_items = inventory.items.into_iter()
            .map(|item| -> Result<InventoryItem, ItemStateError> {
                Ok(match item {
                    InventoryItemEntity::Individual(item) => {
                        InventoryItem {
                            item_id: ClientItemId(0),
                            item: InventoryItemDetail::Individual(IndividualItemDetail {
                                entity_id: item.id,
                                item: item.item,
                            }),
                        }
                    },
                    InventoryItemEntity::Stacked(items) => {
                        InventoryItem {
                            item_id: ClientItemId(0),
                            item: InventoryItemDetail::Stacked(StackedItemDetail {
                                entity_ids: items.iter().map(|i| i.id).collect(),
                                tool: items.get(0)
                                    .ok_or_else(|| ItemStateError::StackedItemError(items.clone()))?
                                    .item
                                    .clone()
                                    .as_tool()
                                    .ok_or_else(|| ItemStateError::StackedItemError(items.clone()))?
                            })
                        }
                    },
                })
            })
            .collect::<Result<Vec<_>, _>>()?;

        let character_meseta = entity_gateway.get_character_meseta(&character.id).await?;
        let inventory_state = InventoryState {
            character_id: character.id,
            item_id_counter: 0,
            inventory: Inventory(inventory_items),
            equipped: equipped,
            meseta: character_meseta,
        };

        let bank_items = bank.items.into_iter()
            .map(|item| -> Result<BankItem, ItemStateError> {
                Ok(match item {
                    BankItemEntity::Individual(item) => {
                        BankItem {
                            item_id: self.new_item_id()?,
                            item: BankItemDetail::Individual(IndividualItemDetail {
                                entity_id: item.id,
                                item: item.item,
                            })
                        }
                    },
                    BankItemEntity::Stacked(items) => {
                        BankItem {
                            item_id: self.new_item_id()?,
                            item:  BankItemDetail::Stacked(StackedItemDetail {
                            entity_ids: items.iter().map(|i| i.id).collect(),
                            tool: items.get(0)
                                .ok_or_else(|| ItemStateError::StackedItemError(items.clone()))?
                                .item
                                .clone()
                                .as_tool()
                                .ok_or_else(|| ItemStateError::StackedItemError(items.clone()))?
                            })
                        }
                    },
                })
            })
            .collect::<Result<Vec<_>, _>>()?;

        let bank_meseta = entity_gateway.get_bank_meseta(&character.id, &BankName("".into())).await?;
        let bank_state = BankState::new(character.id, BankName("".into()), Bank(bank_items), bank_meseta);

        self.character_inventory.insert(character.id, inventory_state);
        self.character_bank.insert(character.id, bank_state);
        Ok(())
    }

    pub fn add_character_to_room(&mut self, room_id: RoomId, character: &CharacterEntity, area_client: AreaClient) {
        let base_inventory_id = ((area_client.local_client.id() as u32) << 21) | 0x10000;
        let inventory = self.character_inventory.get_mut(&character.id).unwrap();
        inventory.initialize_item_ids(base_inventory_id);
        let base_bank_id = ((area_client.local_client.id() as u32) << 21) | 0x20000;
        let default_bank = self.character_bank.get_mut(&character.id);
        if let Some(default_bank ) = default_bank {
            default_bank.initialize_item_ids(base_bank_id);
        }
        self.character_room.insert(character.id, room_id);
        self.character_floor.insert(character.id, LocalFloor::default());
        self.room_floor.entry(room_id).or_insert_with(SharedFloor::default);
    }

    pub fn remove_character_from_room(&mut self, character: &CharacterEntity) {
        self.character_inventory.remove(&character.id);
        self.character_floor.remove(&character.id);
        if let Some(room) = self.character_room.remove(&character.id).as_ref() {
            if self.character_room.iter().any(|(_, r)| r == room) {
                self.room_floor.remove(room);
            }
        }
    }

    pub fn get_floor_item(&self, character_id: &CharacterEntityId, item_id: &ClientItemId) -> Result<(&FloorItem, FloorType), ItemStateError> {
        let local_floor = self.character_floor.get(character_id).ok_or(ItemStateError::NoCharacter(*character_id))?;
        let room = self.character_room.get(character_id).ok_or(ItemStateError::NoCharacter(*character_id))?;
        let shared_floor = self.room_floor.get(room).ok_or(ItemStateError::NoCharacter(*character_id))?;

        local_floor.0
            .iter()
            .find(|item| item.item_id == *item_id)
            .map(|item| (item, FloorType::Local))
            .or_else(|| {
                shared_floor.0
                    .iter()
                    .find(|item| item.item_id == *item_id)
                    .map(|item| (item, FloorType::Shared))
            })
            .ok_or_else(|| ItemStateError::NoFloorItem(*item_id))
    }
}


#[derive(Default)]
struct ProxiedItemState {
    character_inventory: HashMap<CharacterEntityId, InventoryState>,
    character_bank: HashMap<CharacterEntityId, BankState>,

    character_room: HashMap<CharacterEntityId, RoomId>,
    character_floor: HashMap<CharacterEntityId, LocalFloor>,
    room_floor: HashMap<RoomId, SharedFloor>,
}

pub struct ItemStateProxy<'a> {
    item_state: &'a mut ItemState,
    proxied_state: ProxiedItemState,
}

impl<'a> ItemStateProxy<'a> {
    pub fn commit(self) {
        self.item_state.character_inventory.extend(self.proxied_state.character_inventory.clone());
        self.item_state.character_bank.extend(self.proxied_state.character_bank.clone());
        self.item_state.character_room.extend(self.proxied_state.character_room.clone());
        self.item_state.character_floor.extend(self.proxied_state.character_floor.clone());
        self.item_state.room_floor.extend(self.proxied_state.room_floor.clone());
    }
}


fn get_or_clone<K, V>(master: &HashMap<K, V>, proxy: &mut HashMap<K, V>, key: K, err: fn(K) -> ItemStateError) -> Result<V, ItemStateError>
where
    K: Eq + std::hash::Hash + Copy,
    V: Clone
{
    let existing_element = master.get(&key).ok_or_else(|| err(key))?;
    Ok(proxy.entry(key)
        .or_insert_with(|| existing_element.clone()).clone())

}

impl<'a> ItemStateProxy<'a> {
    pub fn new(item_state: &'a mut ItemState) -> Self {
        ItemStateProxy {
            item_state,
            proxied_state: Default::default(),
        }
    }

    pub fn inventory(&mut self, character_id: &CharacterEntityId) -> Result<InventoryState, ItemStateError> {
        get_or_clone(&self.item_state.character_inventory, &mut self.proxied_state.character_inventory, *character_id, ItemStateError::NoCharacter)
    }

    pub fn set_inventory(&mut self, inventory: InventoryState) {
        self.proxied_state.character_inventory.insert(inventory.character_id, inventory);
    }

    pub fn bank(&mut self, character_id: &CharacterEntityId) -> Result<BankState, ItemStateError> {
        get_or_clone(&self.item_state.character_bank, &mut self.proxied_state.character_bank, *character_id, ItemStateError::NoCharacter)
    }

    pub fn set_bank(&mut self, bank: BankState) {
        self.proxied_state.character_bank.insert(bank.character_id, bank);
    }

    pub fn floor(&mut self, character_id: &CharacterEntityId) -> Result<FloorState, ItemStateError> {
        let room_id = get_or_clone(&self.item_state.character_room, &mut self.proxied_state.character_room, *character_id, ItemStateError::NoCharacter)?;
        Ok(FloorState {
            character_id: *character_id,
            local: get_or_clone(&self.item_state.character_floor, &mut self.proxied_state.character_floor, *character_id, ItemStateError::NoCharacter)?,
            shared: get_or_clone(&self.item_state.room_floor, &mut self.proxied_state.room_floor, room_id, ItemStateError::NoRoom)?,
        })
    }

    pub fn set_floor(&mut self, floor: FloorState) {
        let room_id = get_or_clone(&self.item_state.character_room, &mut self.proxied_state.character_room, floor.character_id, ItemStateError::NoCharacter).unwrap();
        self.proxied_state.character_floor.insert(floor.character_id, floor.local);
        self.proxied_state.room_floor.insert(room_id, floor.shared);
    }

    pub fn new_item_id(&mut self) -> Result<ClientItemId, ItemStateError> {
        self.item_state.new_item_id()
    }
}