use std::collections::HashMap;
use rand::Rng;
use serde::{Serialize, Deserialize};
use crate::entity::item::weapon::{Weapon, WeaponType};
use crate::entity::item::armor::{Armor, ArmorType};
use crate::entity::item::shield::{Shield, ShieldType};
use crate::entity::item::unit::{Unit, UnitType};
use crate::entity::item::tool::{Tool, ToolType};
use crate::entity::item::mag::{Mag, MagType};
use crate::entity::character::SectionID;
use crate::ship::monster::MonsterType;
use crate::ship::room::{Difficulty, Episode};
use crate::ship::map::MapArea;
use crate::ship::drops::{ItemDropType, load_data_file};
use crate::ship::drops::generic_weapon::AttributeTable;
use crate::ship::drops::generic_armor::GenericArmorTable;
use crate::ship::drops::generic_shield::GenericShieldTable;


#[derive(Debug, Copy, Clone)]
pub enum RareDropItem {
    Weapon(WeaponType),
    Armor(ArmorType),
    Shield(ShieldType),
    Unit(UnitType),
    Tool(ToolType),
    Mag(MagType)
}

impl RareDropItem {
    pub fn from_string(name: String) -> RareDropItem {
        let parse_funcs: [Box<dyn Fn(&String) -> Option<RareDropItem>>; 6] = [
            Box::new(|i| Some(RareDropItem::Weapon(str::parse::<WeaponType>(&i).ok()?))),
            Box::new(|i| Some(RareDropItem::Armor(str::parse::<ArmorType>(&i).ok()?))),
            Box::new(|i| Some(RareDropItem::Shield(str::parse::<ShieldType>(&i).ok()?))),
            Box::new(|i| Some(RareDropItem::Unit(str::parse::<UnitType>(&i).ok()?))),
            Box::new(|i| Some(RareDropItem::Tool(str::parse::<ToolType>(&i).ok()?))),
            Box::new(|i| Some(RareDropItem::Mag(str::parse::<MagType>(&i).ok()?))),
        ];

        for parse in parse_funcs.iter() {
            match parse(&name) {
                Some(k) => return k,
                None => {},
            }
        }

        panic!()
    }
}


struct RareDropRate {
    rate: f32,
    item: RareDropItem
}



#[derive(Debug, Serialize, Deserialize)]
pub struct RareDropConfigEntity {
    pub rate: f32,
    pub item: String,
}


pub struct RareDropTable {
    rates: HashMap<MonsterType, Vec<RareDropRate>>,
    attribute_table: AttributeTable,
    armor_stats: GenericArmorTable,
    shield_stats: GenericShieldTable,
}

impl RareDropTable {
    pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> RareDropTable {
        let cfg: HashMap<String, Vec<RareDropConfigEntity>> = load_data_file(episode, difficulty, section_id, "rare_rate.toml");

        let rates = cfg.into_iter()
            .map(|(monster, drops)| {
                let monster = monster.parse().unwrap();
                let drops = drops.into_iter().map(|drop| {
                    RareDropRate {
                        rate: drop.rate,
                        item: RareDropItem::from_string(drop.item),
                    }
                }).collect();
                (monster, drops)
            }).collect();

        RareDropTable {
            rates: rates,
            attribute_table: AttributeTable::new(episode, difficulty, section_id),
            armor_stats: GenericArmorTable::new(episode, difficulty, section_id),
            shield_stats: GenericShieldTable::new(episode, difficulty, section_id),
        }
    }

    pub fn apply_item_stats<R: Rng>(&self, map_area: &MapArea, item: RareDropItem, rng: &mut R) -> ItemDropType {
        match item {
            RareDropItem::Weapon(weapon) => {
                ItemDropType::Weapon(Weapon {
                    weapon: weapon,
                    special: None,
                    grind: 0,
                    attrs: self.attribute_table.generate_rare_attributes(map_area, rng),
                    tekked: false,
                })

            },
            RareDropItem::Armor(armor) => {
                ItemDropType::Armor(Armor {
                    armor: armor,
                    dfp: self.armor_stats.dfp_modifier(&armor, rng) as u8,
                    evp: self.armor_stats.evp_modifier(&armor, rng) as u8,
                    slots: self.armor_stats.slots(map_area, rng) as u8,
                })
            },
            RareDropItem::Shield(shield) => {
                ItemDropType::Shield(Shield {
                    shield: shield,
                    dfp: self.shield_stats.dfp_modifier(&shield, rng) as u8,
                    evp: self.shield_stats.evp_modifier(&shield, rng) as u8,
                })
            },
            RareDropItem::Unit(unit) => {
                ItemDropType::Unit(Unit {
                    unit: unit,
                    modifier: None,
                })
            },
            RareDropItem::Tool(tool) => {
                ItemDropType::Tool(Tool {
                    tool: tool,
                })
            },
            RareDropItem::Mag(_mag) => {
                ItemDropType::Mag(Mag::baby_mag(rng.gen_range(0, 18)))
            }
        }
    }

    pub fn get_drop<R: Rng>(&self, map_area: &MapArea, monster: &MonsterType, rng: &mut R) -> Option<ItemDropType> {
        self.rates.get(monster)
            .and_then(|drop_rates| {
                drop_rates.iter()
                    .filter_map(|drop_rate| {
                        let rand: f32 = rng.gen();
                        if rand < drop_rate.rate {
                            Some(self.apply_item_stats(map_area, drop_rate.item, rng))
                        }
                        else {
                            None
                        }
                    }).nth(0)
            })
    }
}