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

use crate::ship::map::MapArea;
use crate::ship::location::RoomId;
use crate::entity::character::CharacterEntityId;
use crate::entity::gateway::GatewayError;
use crate::entity::item::tool::Tool;
use crate::entity::item::mag::Mag;
use crate::ship::drops::ItemDrop;


#[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("inventory error {0}")]
    InventoryError(#[from] InventoryError),

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

    #[error("idk")]
    Dummy,

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

    #[error("tried to drop more meseta than in inventory: {0}")]
    InvalidMesetaDrop(u32),
}


#[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 {
    entity_id: ItemEntityId,
    item: ItemDetail,
}

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

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

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


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

impl InventoryItem {
    pub async fn with_entity_id<F, Fut, T>(&self, mut param: T, mut func: F) -> T
    where
        F: FnMut(T, ItemEntityId) -> Fut,
        Fut: Future<Output=T>,
    {
        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;
                }
            }
        }

        param
    }
}

#[derive(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(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) -> T
    where
        F: FnMut(T, ItemEntityId) -> Fut,
        Fut: Future<Output=T>,
    {
        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) => {},
        }

        param
    }

    pub async fn with_mag<F, Fut, T>(&self, mut param: T, mut func: F) -> T
    where
        F: FnMut(T, ItemEntityId, Mag) -> Fut,
        Fut: Future<Output=T>,
    {
        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;
            }
        }
        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,
}

pub enum AddItemResult {
    NewItem,
    AddToStack,
    Meseta,
}

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

pub struct InventoryState {
    character_id: CharacterEntityId,
    inventory: Inventory,
    pub meseta: Meseta,
}

impl InventoryState {
    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 take_item(&mut self, item_id: &ClientItemId) -> Option<InventoryItem> {
        self.inventory.0
            .drain_filter(|i| i.item_id == *item_id)
            .next()
    }

    pub fn take_partial_item(&mut self, item_id: &ClientItemId, amount: u32) -> Option<StackedItemDetail> {
        let amount = amount as usize;
        let (idx, _, stacked_item) = self.inventory.0
            .iter_mut()
            .enumerate()
            .filter_map(|(k, item)| {
                match item.item {
                    InventoryItemDetail::Stacked(ref mut stacked_item) => Some((k, item.item_id, stacked_item)),
                    _ => None
                }
            })
            .find(|(_, id, _)| *id == *item_id)?;


        let remove_all = match stacked_item.entity_ids.len().cmp(&amount) {
            Ordering::Equal => true,
            Ordering::Greater => false,
            Ordering::Less => return None,
        };

        if remove_all {
            let stacked_item = stacked_item.clone();
            self.inventory.0.remove(idx);
            Some(stacked_item)
        }
        else {
            let entity_ids = stacked_item.entity_ids.drain(..amount).collect();
            Some(StackedItemDetail {
                entity_ids: entity_ids,
                tool: stacked_item.tool,
            })
        }
    }

    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 remove_meseta(&mut self, amount: u32) -> Result<(), ItemStateError> {
        if amount > self.meseta.0 {
            return Err(ItemStateError::InvalidMesetaDrop(amount))
        }
        self.meseta.0 -= amount;
        Ok(())
    }
}

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_item(&mut self, floor_item: FloorItem) -> &FloorItem {
        self.shared.0.push(floor_item);
        &self.shared.0[self.shared.0.len()-1]
    }
}


pub struct ItemState {
    character_inventory: HashMap<CharacterEntityId, Inventory>,
    //character_bank: HashMap<CharacterEntityId, Bank>,
    character_meseta: HashMap<CharacterEntityId, Meseta>,
    //bank_meseta: HashMap<CharacterEntityId, Meseta>,

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

    //room_item_id_counter: Arc<RefCell<HashMap<RoomId, Box<dyn FnMut() -> ClientItemId + Send + Sync>>>>,
    room_item_id_counter: u32,
}

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


#[derive(Default)]
struct ProxiedItemState {
    character_inventory: HashMap<CharacterEntityId, Inventory>,
    //character_bank: HashMap<CharacterEntityId, RefCell<Bank>>,
    character_meseta: HashMap<CharacterEntityId, Meseta>,
    //bank_meseta: HashMap<CharacterEntityId, RefCell<Meseta>>,

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

    //room_item_id_counter: HashMap<RoomId, Box<dyn FnMut() -> ClientItemId + Send>>,

}

/*
impl Default for ProxiedItemState {
    fn default() -> Self {
        ProxiedItemState {
            character_inventory: HashMap::new(),
            //character_bank: HashMap::new(),
            character_meseta: HashMap::new(),
            //bank_meseta: HashMap::new(),
            character_floor: HashMap::new(),
            character_room: HashMap::new(),
            room_floor: HashMap::new(),
            //room_item_id_counter: HashMap::new(),
        }
    }
}
*/

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_meseta.extend(self.proxied_state.character_meseta.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> {
        Ok(InventoryState {
            character_id: *character_id,
            inventory: get_or_clone(&self.item_state.character_inventory, &mut self.proxied_state.character_inventory, *character_id, ItemStateError::NoCharacter)?,
            meseta: get_or_clone(&self.item_state.character_meseta, &mut self.proxied_state.character_meseta, *character_id, ItemStateError::NoCharacter)?,
        })
    }

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

    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.room_item_id_counter += 1;
        Ok(ClientItemId(self.item_state.room_item_id_counter))
    }
}