use std::convert::TryInto;
use futures::future::{join_all, BoxFuture, LocalBoxFuture};
use futures::stream::{FuturesOrdered, StreamExt};
use thiserror::Error;
use rand::{Rng, SeedableRng};
use rand::distributions::{WeightedIndex, Distribution};
use crate::entity::gateway::{EntityGateway, GatewayError};
use crate::entity::character::CharacterEntity;
use crate::entity::item::mag::{MagType, MagCell, MagCellError};
use crate::entity::item::tool::{Tool, ToolType};
use crate::entity::item::{ItemDetail, ItemEntityId};
use crate::ship::items::state::{ItemStateProxy, ItemStateError};
use crate::ship::items::inventory::{InventoryItem, InventoryItemDetail};


#[derive(Error, Debug)]
pub enum ApplyItemError {
    #[error("no character")]
    NoCharacter,
    #[error("item not equipped")]
    ItemNotEquipped,
    #[error("invalid item")]
    InvalidItem,
    #[error("gateway error {0}")]
    GatewayError(#[from] GatewayError),

    #[error("itemstate error {0}")]
    ItemStateError(Box<ItemStateError>),

    #[error("magcell error {0}")]
    MagCellError(#[from] MagCellError),
}

#[derive(Debug, Clone)]
pub enum ApplyItemAction {
    UpdateCharacter(Box<CharacterEntity>),
    CreateItem(ItemDetail),
    //TransformItem,
    //RemoveItem,
}

impl From<ItemStateError> for ApplyItemError {
    fn from(other: ItemStateError) -> ApplyItemError {
        ApplyItemError::ItemStateError(Box::new(other))
    }
}

async fn power_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, ApplyItemError> {
    character.materials.power += 1;
    entity_gateway.save_character(character).await?;
    Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))])
}

async fn mind_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, ApplyItemError> {
    character.materials.mind += 1;
    entity_gateway.save_character(character).await.unwrap();
    Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))])
}

async fn evade_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, ApplyItemError> {
    character.materials.evade += 1;
    entity_gateway.save_character(character).await.unwrap();
    Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))])
}

async fn def_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, ApplyItemError> {
    character.materials.def += 1;
    entity_gateway.save_character(character).await.unwrap();
    Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))])
}

async fn luck_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, ApplyItemError> {
    character.materials.luck += 1;
    entity_gateway.save_character(character).await.unwrap();
    Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))])
}

async fn hp_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, ApplyItemError> {
    character.materials.hp += 1;
    entity_gateway.save_character(character).await.unwrap();
    Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))])
}

async fn tp_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, ApplyItemError> {
    character.materials.tp += 1;
    entity_gateway.save_character(character).await.unwrap();
    Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))])
}

/*
async fn mag_cell<EG: EntityGateway>(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory, mag_cell_type: MagCell) -> Result<(), ApplyItemError> {
    let mut mag_handle = inventory.get_equipped_mag_handle().ok_or(ApplyItemError::ItemNotEquipped)?;
    let mag_item = mag_handle.item_mut()
        .ok_or(ApplyItemError::InvalidItem)?;
    let actual_mag = mag_item
        .individual_mut()
        .ok_or(ApplyItemError::InvalidItem)?
        .mag_mut()
        .ok_or(ApplyItemError::InvalidItem)?;
    actual_mag.apply_mag_cell(mag_cell_type);
    for mag_entity_id in mag_item.entity_ids() {
        for cell_entity_id in used_cell.entity_ids() {
            entity_gateway.use_mag_cell(&mag_entity_id, &cell_entity_id).await.unwrap();
        }
    }

    Ok(())
}
 */


async fn mag_cell<'a, EG>(item_state: &mut ItemStateProxy,
                          entity_gateway: &mut EG,
                          character: &CharacterEntity,
                          cell_entity_id: ItemEntityId,
                          mag_cell_type: MagCell)
                          -> Result<Vec<ApplyItemAction>, ApplyItemError>
where
    EG: EntityGateway + ?Sized,
{
    let mut inventory = item_state.inventory(&character.id).await?;

    let (mag_entity_id, mag) = inventory.equipped_mag_mut()
        .ok_or(ApplyItemError::ItemNotEquipped)?;
    mag.apply_mag_cell(mag_cell_type)?;

    entity_gateway.use_mag_cell(&mag_entity_id, &cell_entity_id).await?;
    entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?;
    item_state.set_inventory(inventory).await;

    Ok(Vec::new())
}

/*
pub async fn cell_of_mag_502<EG: EntityGateway>(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> {
    mag_cell(entity_gateway, inventory, MagCell::CellOfMag502).await
}

pub async fn cell_of_mag_213<EG: EntityGateway>(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> {
    mag_cell(entity_gateway, used_cell, inventory, MagCell::CellOfMag213).await
}

pub async fn parts_of_robochao<EG: EntityGateway>(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> {
    mag_cell(entity_gateway, used_cell, inventory, MagCell::PartsOfRobochao).await
}

pub async fn heart_of_opaopa<EG: EntityGateway>(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> {
    mag_cell(entity_gateway, used_cell, inventory, MagCell::HeartOfOpaOpa).await
}

pub async fn heart_of_pian<EG: EntityGateway>(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> {
    mag_cell(entity_gateway, used_cell, inventory, MagCell::HeartOfPian).await
}

pub async fn heart_of_chao<EG: EntityGateway>(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> {
    mag_cell(entity_gateway, used_cell, inventory, MagCell::HeartOfChao).await
}

pub async fn heart_of_angel<EG: EntityGateway>(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> {
    mag_cell(entity_gateway, used_cell, inventory, MagCell::HeartOfAngel).await
}

pub async fn kit_of_hamburger<EG: EntityGateway>(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> {
    mag_cell(entity_gateway, used_cell, inventory, MagCell::KitOfHamburger).await
}

pub async fn panthers_spirit<EG: EntityGateway>(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> {
    mag_cell(entity_gateway, used_cell, inventory, MagCell::PanthersSpirit).await
}

pub async fn kit_of_mark3<EG: EntityGateway>(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> {
    mag_cell(entity_gateway, used_cell, inventory, MagCell::KitOfMark3).await
}

pub async fn kit_of_master_system<EG: EntityGateway>(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> {
    mag_cell(entity_gateway, used_cell, inventory, MagCell::KitOfMasterSystem).await
}

pub async fn kit_of_genesis<EG: EntityGateway>(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> {
    mag_cell(entity_gateway, used_cell, inventory, MagCell::KitOfGenesis).await
}

pub async fn kit_of_sega_saturn<EG: EntityGateway>(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> {
    mag_cell(entity_gateway, used_cell, inventory, MagCell::KitOfSegaSaturn).await
}

pub async fn kit_of_dreamcast<EG: EntityGateway>(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> {
    mag_cell(entity_gateway, used_cell, inventory, MagCell::KitOfDreamcast).await
}

pub async fn tablet<EG: EntityGateway>(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> {
    mag_cell(entity_gateway, used_cell, inventory, MagCell::Tablet).await
}

pub async fn dragon_scale<EG: EntityGateway>(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> {
    mag_cell(entity_gateway, used_cell, inventory, MagCell::DragonScale).await
}

pub async fn heaven_striker_coat<EG: EntityGateway>(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> {
    mag_cell(entity_gateway, used_cell, inventory, MagCell::HeavenStrikerCoat).await
}

pub async fn pioneer_parts<EG: EntityGateway>(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> {
    mag_cell(entity_gateway, used_cell, inventory, MagCell::PioneerParts).await
}

pub async fn amities_memo<EG: EntityGateway>(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> {
    mag_cell(entity_gateway, used_cell, inventory, MagCell::AmitiesMemo).await
}

pub async fn heart_of_morolian<EG: EntityGateway>(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> {
    mag_cell(entity_gateway, used_cell, inventory, MagCell::HeartOfMorolian).await
}

pub async fn rappys_beak<EG: EntityGateway>(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> {
    mag_cell(entity_gateway, used_cell, inventory, MagCell::RappysBeak).await
}

pub async fn yahoos_engine<EG: EntityGateway>(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> {
    mag_cell(entity_gateway, used_cell, inventory, MagCell::YahoosEngine).await
}

pub async fn d_photon_core<EG: EntityGateway>(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> {
    mag_cell(entity_gateway, used_cell, inventory, MagCell::DPhotonCore).await
}

pub async fn liberta_kit<EG: EntityGateway>(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> {
    mag_cell(entity_gateway, used_cell, inventory, MagCell::LibertaKit).await
}
*/


fn jack_o_lantern() -> Result<Vec<ApplyItemAction>, ApplyItemError>
{
    let mag_rate = WeightedIndex::new(&[13, 13, 13, 13, 12, 12, 12, 12]).unwrap();
    let mag_type = match mag_rate.sample(&mut rand_chacha::ChaChaRng::from_entropy()) {
        0 => ToolType::CellOfMag502,
        1 => ToolType::CellOfMag213,
        2 => ToolType::HeartOfChuChu,
        3 => ToolType::HeartOfKapuKapu,
        4 => ToolType::PartsOfRobochao,
        5 => ToolType::HeartOfOpaOpa,
        6 => ToolType::HeartOfPian,
        7 => ToolType::HeartOfChao,
        _ => unreachable!(),
    };

    Ok(vec![ApplyItemAction::CreateItem(ItemDetail::Tool(Tool {tool: mag_type}))])
}

async fn apply_tool<'a, EG>(item_state: &mut ItemStateProxy,
                            entity_gateway: &mut EG,
                            character: &mut CharacterEntity,
                            entity_id: ItemEntityId,
                            tool: ToolType)
                            -> Result<Vec<ApplyItemAction>, ApplyItemError>
where
    EG: EntityGateway + ?Sized,
{
    match tool {
        ToolType::PowerMaterial => power_material(entity_gateway, character).await,
        ToolType::MindMaterial => mind_material(entity_gateway, character).await,
        ToolType::EvadeMaterial => evade_material(entity_gateway, character).await,
        ToolType::DefMaterial => def_material(entity_gateway, character).await,
        ToolType::LuckMaterial => luck_material(entity_gateway, character).await,
        ToolType::HpMaterial => hp_material(entity_gateway, character).await,
        ToolType::TpMaterial => tp_material(entity_gateway, character).await,
        ToolType::Monomate => Ok(Vec::new()),
        ToolType::Dimate => Ok(Vec::new()),
        ToolType::Trimate => Ok(Vec::new()),
        ToolType::Monofluid => Ok(Vec::new()),
        ToolType::Difluid => Ok(Vec::new()),
        ToolType::Trifluid => Ok(Vec::new()),
        ToolType::HuntersReport => Ok(Vec::new()),
        ToolType::CellOfMag502
            | ToolType::CellOfMag213
            | ToolType::PartsOfRobochao
            | ToolType::HeartOfOpaOpa
            | ToolType::HeartOfPian
            | ToolType::HeartOfChao
            | ToolType::HeartOfAngel
            | ToolType::KitOfHamburger
            | ToolType::PanthersSpirit
            | ToolType::KitOfMark3
            | ToolType::KitOfMasterSystem
            | ToolType::KitOfGenesis
            | ToolType::KitOfSegaSaturn
            | ToolType::KitOfDreamcast
            | ToolType::Tablet
            | ToolType::DragonScale
            | ToolType::HeavenStrikerCoat
            | ToolType::PioneerParts
            | ToolType::AmitiesMemo
            | ToolType::HeartOfMorolian
            | ToolType::RappysBeak
            | ToolType::YahoosEngine
            | ToolType::DPhotonCore
            | ToolType::LibertaKit => {
                mag_cell(item_state, entity_gateway, character, entity_id, tool.try_into()?).await
            }
        ToolType::JackOLantern => jack_o_lantern(),
        // TODO: rest of these
        _ => Err(ApplyItemError::InvalidItem)
    }
    
}


pub async fn apply_item<'a, EG>(item_state: &mut ItemStateProxy,
                                entity_gateway: &mut EG,
                                character: &mut CharacterEntity,
                                item: InventoryItem
) -> Result<Vec<ApplyItemAction>, ApplyItemError>
where
    EG: EntityGateway + ?Sized + Clone + 'static
{
    match item.item {
        InventoryItemDetail::Individual(individual_item) => {
            match individual_item.item {
                ItemDetail::Tool(tool) => apply_tool(item_state, entity_gateway, character, individual_item.entity_id, tool.tool).await,
                _ => Err(ApplyItemError::InvalidItem)
            }
        },
        InventoryItemDetail::Stacked(stacked_item) => {
            Ok(join_all(stacked_item.entity_ids.iter()
                        .map(|entity_id| {
                            let mut entity_gateway = entity_gateway.clone();
                            let mut character = character.clone();
                            let mut item_state = item_state.clone();
                            async move {
                                apply_tool(&mut item_state, &mut entity_gateway, &mut character, *entity_id, stacked_item.tool.tool).await
                            }
                        })
                        .collect::<Vec<_>>())
               .await
               .into_iter()
               .collect::<Result<Vec<Vec<_>>, _>>()?
               .into_iter()
               .flatten()
               .collect())
        },
    }
}