use std::collections::HashMap;
use crate::ship::items::ClientItemId;
use crate::entity::item::{Meseta, ItemEntityId, ItemDetail, ItemEntity, ItemType, InventoryEntity, InventoryItemEntity, EquippedEntity, ItemNote};
use std::cell::{RefMut, RefCell};
use std::ops::{Deref, DerefMut};
use std::convert::{From, Into};
use std::future::Future;
use std::pin::Pin;
use async_std::sync::{Arc, Mutex};
use std::borrow::BorrowMut;

use crate::ship::location::{AreaClient, RoomId};
use crate::entity::character::{CharacterEntity, CharacterEntityId, TechLevel};
use crate::entity::gateway::{EntityGateway, GatewayError};
use crate::entity::gateway::entitygateway::EntityGatewayTransaction;
use crate::entity::item::tool::{Tool, ToolType};
use crate::ship::drops::ItemDrop;

pub enum TriggerCreateItem {
    Yes,
    No
}


#[derive(thiserror::Error, Debug)]
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),
}


#[async_trait::async_trait]
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>;
}


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,
{
    fn act<O, F>(self, f: F) -> ItemActionStage<O, ItemStateAction<T, S, E>, F, S, E>
    where
        F: Fn(S, ()) -> Pin<Box<dyn Future<Output=Result<(S, O), E>> + Send>> + Send + Sync
    {
        ItemActionStage {
            _s: Default::default(),
            _e: std::marker::PhantomData,
            prev: self,
            actionf: f,
        }
    }
}

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

#[async_trait::async_trait]
impl<O, P: ItemAction, F, S, E> ItemAction for ItemActionStage<O, P, F, S, E>
where
    P: ItemAction + ItemAction<Start = S, Error = E> + Send + Sync,
    F: Fn(S, P::Output) -> Pin<Box<dyn Future<Output=Result<(S, O), E>> + Send>> + Send + Sync,
    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, S, E> ItemActionStage<O, P, F, S, E>
where
    P: ItemAction<Start = S, Error = E> + Send + Sync,
    F: Fn(S, P::Output) -> Pin<Box<dyn Future<Output=Result<(S, O), E>> + Send>> + Send + Sync,
    S: Send + Sync,
    P::Output: Send + Sync,
    E: Send + Sync,
    O: Send + Sync,
    P::Error: Send + Sync,
{
    fn act<O2, G>(self, g: G) -> ItemActionStage<O2, ItemActionStage<O, P, F, S, E>, G, S, E>
    where
        S: Send + Sync,
        G: Fn(S, <ItemActionStage<O, P, F, S, E> as ItemAction>::Output) -> Pin<Box<dyn Future<Output=Result<(S, O2), E>> + Send>> + Send + Sync,
        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)]
struct IndividualItemDetail {
    entity_id: ItemEntityId,
    item: ItemDetail,
}

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

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

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


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

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

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

#[derive(Clone)]
struct FloorItem {
    item_id: ClientItemId,
    item: FloorItemDetail,
}

// meseta is a floor item
/*
impl Into<InventoryItem> for FloorItem {
    fn into(&self) -> InventoryItem {
        InventoryItem {
            item_id: self.item_id,
            item:
        }
    }
}
*/

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


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

enum AddItemResult {
    NewItem,
    AddToStack,
    Meseta,
}





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

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


/*
impl Deref for InventoryState {
    type Target = Inventory;
    
    fn deref(&self) -> &Self::Target {
        &self.inventory
    }
}


impl DerefMut for InventoryState {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.inventory
    }
}
*/

impl InventoryState {
    fn add_floor_item(&mut self, item: FloorItem) -> Result<AddItemResult, InventoryError> {
        match item.item {
            FloorItemDetail::Individual(iitem) => {
                if self.inventory.0.len() >= 30 {
                    return 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() {
                            return Err(InventoryError::StackFull)
                        }
                        else {
                            existing_stack.entity_ids.append(&mut sitem.entity_ids.clone());
                            Ok(AddItemResult::AddToStack)
                        }
                    },
                    None => {
                        if self.inventory.0.len() >= 30 {
                            return 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)
                }
            },
        }
    }
}

struct FloorState {
    character_id: CharacterEntityId,
    local: LocalFloor,
    shared: SharedFloor,
}

impl FloorState {
    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()
        })
    }
}


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,
        }
    }
}


/*
struct ProxiedItemState {
    character_inventory: RefCell<HashMap<CharacterEntityId, RefCell<Inventory>>>,
    //character_bank: HashMap<CharacterEntityId, RefCell<Bank>>,
    //character_meseta: HashMap<CharacterEntityId, RefCell<Meseta>>,
    //bank_meseta: HashMap<CharacterEntityId, RefCell<Meseta>>,

    character_room: RefCell<HashMap<CharacterEntityId, RefCell<RoomId>>>,
    character_floor: RefCell<HashMap<CharacterEntityId, RefCell<RoomFloorItems>>>,
    room_floor: RefCell<HashMap<RoomId, RefCell<RoomFloorItems>>>,

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

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

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(),
        }
    }
}

struct ItemStateProxy<'a> {
    item_state: &'a mut ItemState,
    //entity_gateway: &'a mut dyn EntityGateway,
    transaction: Box<dyn EntityGatewayTransaction>,
    //entity_gateway: &'a mut Box<dyn EntityGateway>,
    //entity_gateway: &'a mut Box<dyn EntityGateway>,
    proxied_state: ProxiedItemState,

    gateway_actions: Vec<GatewayActions>,

    //_eg: std::marker::PhantomData<EG>,
}

/*
fn get_or_clone<'a,  K, V>(master: &HashMap<K, V>, proxy: &'a RefCell<HashMap<K, RefCell<V>>>, key: K, err: fn(K) -> ItemStateError) -> Result<impl Deref<Target = V> + 'a, ItemStateError>
where
    K: Eq + std::hash::Hash + Copy,
    V: Clone
{
    let existing_element = master.get(&key).ok_or_else(|| err(key))?;
    Ok(proxy.borrow_mut().entry(key)
        .or_insert(RefCell::new(existing_element.clone()))
        .borrow_mut())

}
*/


/*
fn get_or_clone<'a,  K, V>(master: &HashMap<K, V>, proxy: &'a mut HashMap<K, RefCell<V>>, key: K, err: fn(K) -> ItemStateError) -> Result<impl Deref<Target = V> + 'a, 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(RefCell::new(existing_element.clone()))
        .borrow_mut())

}
*/

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(existing_element.clone()).clone())

}

impl<'a> ItemStateProxy<'a> {
    //fn new(item_state: &'a mut ItemState, entity_gateway: &'a mut EG) -> Self {
    //fn new(item_state: &'a mut ItemState, entity_gateway: &'a mut dyn EntityGateway) -> Self {
    fn new(item_state: &'a mut ItemState, transaction: Box<dyn EntityGatewayTransaction>) -> Self {
        ItemStateProxy {
            item_state,
            //entity_gateway,
            transaction,
            proxied_state: Default::default(),
            gateway_actions: Vec::new(),
            //_eg: std::marker::PhantomData,
        }
    }

    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, |c| ItemStateError::NoCharacter(c))?,
            meseta: get_or_clone(&self.item_state.character_meseta, &mut self.proxied_state.character_meseta, *character_id, |c| ItemStateError::NoCharacter(c))?,
        })
    }

    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);
    }

    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, |c| ItemStateError::NoCharacter(c))?;
        Ok(FloorState {
            character_id: *character_id,
            local: get_or_clone(&self.item_state.character_floor, &mut self.proxied_state.character_floor, *character_id, |c| ItemStateError::NoCharacter(c))?,
            shared: get_or_clone(&self.item_state.room_floor, &mut self.proxied_state.room_floor, room_id, |r| ItemStateError::NoRoom(r))?,
        })
    }

    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, |c| ItemStateError::NoCharacter(c)).unwrap();
        self.proxied_state.character_floor.insert(floor.character_id, floor.local);
        self.proxied_state.room_floor.insert(room_id, floor.shared);
    }

    fn new_item_id(&mut self) -> Result<ClientItemId, ItemStateError> {
        //let room_id = get_or_clone(&self.item_state.character_room, &mut self.proxied_state.character_room, *character_id, |c| ItemStateError::NoCharacter(c))?;
        self.item_state.room_item_id_counter += 1;
        Ok(ClientItemId(self.item_state.room_item_id_counter))
        /*
        self.item_state.room_item_id_counter
            .borrow_mut()
            .get_mut(&room_id)
            .ok_or(ItemStateError::NoRoom(room_id))
            .map(|f| f())
         */
    }
}


fn take_item_from_floor(character_id: CharacterEntityId, item_id: ClientItemId)
                        -> impl for<'a> Fn(ItemStateProxy<'a>, ())
                                           -> Pin<Box<dyn Future<Output=Result<(ItemStateProxy, FloorItem), ItemStateError>> + Send + 'a>> {
    move |mut item_state, _| {
        Box::pin(async move {
            let mut floor = item_state.floor(&character_id)?;
            let item = floor.take_item(&item_id).ok_or(ItemStateError::NoFloorItem(item_id))?;
            item_state.set_floor(floor);
            Ok((item_state, item))
        })
    }
}

fn add_floor_item_to_inventory(character_id: CharacterEntityId)
                               -> impl for<'a> Fn(ItemStateProxy<'a>, FloorItem)
                                                  -> Pin<Box<dyn Future<Output=Result<(ItemStateProxy, TriggerCreateItem), ItemStateError>> + Send + 'a>> {
    move |mut item_state, floor_item| Box::pin(async move {
        let mut inventory = item_state.inventory(&character_id)?;
        let add_result = inventory.add_floor_item(floor_item)?;
        item_state.set_inventory(inventory);
        Ok((item_state,
            match add_result {
                AddItemResult::NewItem => TriggerCreateItem::Yes,
                AddItemResult::AddToStack => TriggerCreateItem::No,
                AddItemResult::Meseta => TriggerCreateItem::No,
            }))
    })
}


#[fix_hidden_lifetime_bug]
async fn pick_up_item<'a, EG>(
    item_state: &'a mut ItemState,
    entity_gateway: &'a mut EG,
    character_id: &CharacterEntityId,
    item_id: &ClientItemId)
    -> Result<TriggerCreateItem, ItemStateError>
where
    'a: 'static,
    EG: EntityGateway,
{
    let result: Result<TriggerCreateItem, ItemStateError> = entity_gateway.with_transaction(|transaction| async move {
        let item_state_proxy = ItemStateProxy::new(item_state);
        let ((item_state_proxy, transaction), result) = ItemStateAction::default()
            .act(take_item_from_floor(*character_id, *item_id))
            .act(add_floor_item_to_inventory(*character_id))
            .commit((item_state_proxy, transaction))
            .await?;
        Ok((transaction, result))
    }).await;
    Ok(result?)
}


/*
fn record_item_drop(character_id: CharacterEntityId, item_drop: ItemDrop) -> impl Fn(&mut ItemStateProxy, ()) -> Result<(), ItemStateError> {
    move |item_state, _| {
        // how do I do this? I need EG to add_item_note but how should I kick that till later?
        // general purpose vec in item_state `misc_gateway_actions`?
        // can't quite use actions here as it relies on an ItemEntityId which at this point does not yet exist for the dropped item
        //item_state.gateway_actions.push(GatewayAction::ItemNote(item_drop.))
        Ok(())
    }
}
*/

/*
fn map_drop_to_floor_item(character_id: CharacterEntityId, item_drop: ItemDrop) -> impl Fn(&mut ItemStateProxy, ()) -> Result<FloorItem, ItemStateError> {
    move |item_state, _| {
        match item_drop.item {
            ItemDropType::Weapon(w) => FloorItem {
                item_id: item_state.new_item_id(&character_id)?,
                item: FloorItemDetail::Individual(item_drop.item)
            },
            ItemDropType::Armor(w) => ItemOrMeseta::Individual(ItemDetail::Armor(w)),
            ItemDropType::Shield(w) => ItemOrMeseta::Individual(ItemDetail::Shield(w)),
            ItemDropType::Unit(w) => ItemOrMeseta::Individual(ItemDetail::Unit(w)),
            ItemDropType::TechniqueDisk(w) => ItemOrMeseta::Individual(ItemDetail::TechniqueDisk(w)),
            ItemDropType::Mag(w) => ItemOrMeseta::Individual(ItemDetail::Mag(w)),
            //ItemDropType::IndividualTool(t) => ItemOrMeseta::Individual(ItemDetail::Tool(t)),
            //ItemDropType::StackedTool(t, _) => ItemOrMeseta::Stacked(t),
            ItemDropType::Tool(t) if t.tool.is_stackable() => ItemOrMeseta::Stacked(t),
            ItemDropType::Tool(t) if !t.tool.is_stackable() => ItemOrMeseta::Individual(ItemDetail::Tool(t)),
            ItemDropType::Meseta(m) => ItemOrMeseta::Meseta(Meseta(m)),
            _ => unreachable!() // rust isnt smart enough to see that the conditional on tool catches everything
        }
    }
}



fn enemy_drops_item<EG>(item_state: &mut ItemState,
                        entity_gateway: &mut EG,
                        character: &CharacterEntity,
                        item_drop: ItemDrop)
                        -> Result<FloorItem, ItemStateError>
where
    EG: EntityGateway,
{
    ItemStateAction::default()
        .act(record_item_drop(character.id, item_drop.clone()))
        .act(map_drop_to_floor_item(character.id, item_drop))
        .act(add_item_drop_to_floor(character.id))
        .commit(&mut ItemStateProxy::new(item_state))
}
*/

/*
fn sell_item<EG: EntityGateway>(item_state: &mut ItemState,
                                entity_gateway: &mut EG,
                                character_id: &CharacterEntityId,
                                item_id: &ClientItemId,
                                amount: usize)
                                -> Result<TriggerCreateItem, ItemStateError>
where
    EG: EntityGateway,
{
    ItemStateAction::default()
        .act(take_item_from_inventory(*character_id, *item_id))
        .act(sell_inventory_item(*character_id, *item_id))
        .exec_state(&mut ItemStateProxy::new(item_state))
        .exec_gateway(&mut entity_gateway)
}
*/