use std::collections::HashMap;
use serde::{Serialize, Deserialize};
use rand::{Rng, SeedableRng};
use rand::distributions::{WeightedIndex, Distribution};

use crate::entity::item::armor::{ArmorType, Armor};
use crate::ship::room::{Difficulty, Episode};
use crate::ship::map::MapVariantType;
use crate::entity::character::SectionID;
use crate::ship::drops::{ItemDropType, load_data_file};
use crate::ship::item_stats::{armor_stats, ArmorStats};


#[derive(Debug, Serialize, Deserialize)]
struct ArmorRankRates {
    rank0: u32,
    rank1: u32,
    rank2: u32,
    rank3: u32,
    rank4: u32,
}

#[derive(Debug, Serialize, Deserialize)]
struct ArmorSlotRanks {
    slot0: u32,
    slot1: u32,
    slot2: u32,
    slot3: u32,
    slot4: u32,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct GenericArmorTable {
    rank_rates: ArmorRankRates,
    slot_rates: ArmorSlotRanks,
    armor_set: u32,
    #[serde(skip)]
    armor_stats: HashMap<ArmorType, ArmorStats>,
}

impl GenericArmorTable {
    pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> GenericArmorTable {
        let mut gat: GenericArmorTable = load_data_file(episode, difficulty, section_id, "armor_rate.toml");
        gat.armor_stats = armor_stats();
        gat
    }

    fn armor_type<R: Rng>(&self, area_map: &MapVariantType, rng: &mut R) -> ArmorType {
        let rank_weights = WeightedIndex::new(&[self.rank_rates.rank0, self.rank_rates.rank1, self.rank_rates.rank2,
                                                self.rank_rates.rank3, self.rank_rates.rank4]).unwrap();
        let rank = rank_weights.sample(rng) as i32;
        let armor_level = std::cmp::max(0i32, self.armor_set as i32 - 3i32 + rank + area_map.area_value().unwrap_or(0) as i32);
        match armor_level {
            0x00 => ArmorType::Frame,
            0x01 => ArmorType::Armor,
            0x02 => ArmorType::PsyArmor,
            0x03 => ArmorType::GigaFrame,
            0x04 => ArmorType::SoulFrame,
            0x05 => ArmorType::CrossArmor,
            0x06 => ArmorType::SolidFrame,
            0x07 => ArmorType::BraveArmor,
            0x08 => ArmorType::HyperFrame,
            0x09 => ArmorType::GrandArmor,
            0x0A => ArmorType::ShockFrame,
            0x0B => ArmorType::KingsFrame,
            0x0C => ArmorType::DragonFrame,
            0x0D => ArmorType::AbsorbArmor,
            0x0E => ArmorType::ProtectFrame,
            0x0F => ArmorType::GeneralArmor,
            0x10 => ArmorType::PerfectFrame,
            0x11 => ArmorType::ValiantFrame,
            0x12 => ArmorType::ImperialArmor,
            0x13 => ArmorType::HolinessArmor,
            0x14 => ArmorType::GuardianArmor,
            0x15 => ArmorType::DivinityArmor,
            0x16 => ArmorType::UltimateFrame,
            0x17 => ArmorType::CelestialArmor,
            _ => panic!()
        }
    }

    pub fn slots<R: Rng>(&self, area_map: &MapVariantType, rng: &mut R) -> usize {
        let slot_weights = WeightedIndex::new(&[self.slot_rates.slot0, self.slot_rates.slot1, self.slot_rates.slot2,
                                                self.slot_rates.slot3, self.slot_rates.slot4]).unwrap();
        slot_weights.sample(rng)
    }

    pub fn dfp_modifier<R: Rng>(&self, armor_type: &ArmorType, rng: &mut R) -> u32 {
        let stats = self.armor_stats.get(armor_type).unwrap();
        rng.gen_range(0, stats.dfp_modifier)
    }

    pub fn evp_modifier<R: Rng>(&self, armor_type: &ArmorType, rng: &mut R) -> u32 {
        let stats = self.armor_stats.get(armor_type).unwrap();
        rng.gen_range(0, stats.evp_modifier)
    }

    pub fn get_drop<R: Rng>(&self, area_map: &MapVariantType, rng: &mut R) -> Option<ItemDropType> {
        let armor_type = self.armor_type(area_map, rng);
        let slots = self.slots(area_map, rng);
        let dfp_modifier = self.dfp_modifier(&armor_type, rng);
        let evp_modifier = self.dfp_modifier(&armor_type, rng);


        Some(ItemDropType::Armor(Armor {
            armor: armor_type,
            dfp: dfp_modifier as u8,
            evp: evp_modifier as u8,
            slots: slots as u8,
        }))
    }
}


#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn test_armor_generation() {
        let mut rng = rand_chacha::ChaCha20Rng::from_seed([23;32]);

        let gat = GenericArmorTable::new(Episode::One, Difficulty::Ultimate, SectionID::Skyly);
        assert!(gat.get_drop(&MapVariantType::Mines1, &mut rng) == Some(ItemDropType::Armor(Armor {
            armor: ArmorType::GeneralArmor,
            dfp: 0,
            evp: 0,
            slots: 1,
        })));
        assert!(gat.get_drop(&MapVariantType::Caves3, &mut rng) == Some(ItemDropType::Armor(Armor {
            armor: ArmorType::AbsorbArmor,
            dfp: 1,
            evp: 1,
            slots: 1,
        })));
        assert!(gat.get_drop(&MapVariantType::Forest2, &mut rng) == Some(ItemDropType::Armor(Armor {
            armor: ArmorType::HyperFrame,
            dfp: 0,
            evp: 0,
            slots: 0,
        })));
        assert!(gat.get_drop(&MapVariantType::DarkFalz, &mut rng) == Some(ItemDropType::Armor(Armor {
            armor: ArmorType::ImperialArmor,
            dfp: 2,
            evp: 1,
            slots: 0,
        })));
    }
}