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

use crate::entity::item::ItemDetail;
use crate::entity::item::unit::{UnitType, Unit, UnitModifier};
use crate::ship::room::{Difficulty, Episode};
use crate::ship::map::MapVariantType;
use crate::entity::character::SectionID;
use crate::ship::drops::load_data_file;
use crate::ship::item_stats::{unit_stats, UnitStats};



#[derive(Debug, Serialize, Deserialize)]
struct UnitLevels {
    area1: u32,
    area2: u32,
    area3: u32,
    area4: u32,
    area5: u32,
    area6: u32,
    area7: u32,
    area8: u32,
    area9: u32,
    area10: u32,
}

impl UnitLevels {
    fn level_by_area(&self, map_area: &MapVariantType) -> u32 {
        match map_area.area_value().unwrap() {
            0 => self.area1,
            1 => self.area2,
            2 => self.area3,
            3 => self.area1,
            4 => self.area1,
            5 => self.area6,
            6 => self.area7,
            7 => self.area8,
            8 => self.area9,
            9 => self.area10,
            _ => panic!()
        }
    }
}

pub struct GenericUnitTable {
    unit_levels: UnitLevels,
    unit_stats: BTreeMap<UnitType, UnitStats>,
}



impl GenericUnitTable {
    pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> GenericUnitTable {
        GenericUnitTable {
            unit_levels: load_data_file(episode, difficulty, section_id, "unit_rate.toml"),
            unit_stats: unit_stats(),
        }
    }

    fn unit_type_and_modifier<R: Rng>(&self, area_map: &MapVariantType, rng: &mut R) -> Option<(UnitType, Option<UnitModifier>)> {
        let level = self.unit_levels.level_by_area(area_map) as i32;
        if level == 0 {
            return None;
        }
        let units = self.unit_stats
            .iter()
            .filter(|(_, stats)| {
                stats.stars < 9
            })
            .filter_map(|(utype, stats)| {
                match level - stats.stars as i32 {
                    -1 if stats.modifier != 0 => Some(vec![(*utype, Some(UnitModifier::Minus)), (*utype, Some(UnitModifier::MinusMinus))]),
                    0  => Some(vec![(*utype, None)]),
                    1 if stats.modifier != 0 => Some(vec![(*utype, Some(UnitModifier::Plus)), (*utype, Some(UnitModifier::PlusPlus))]),
                    _ => None,
                }
            })
            .flatten();

        Some(units.choose(rng).unwrap())
    }

    pub fn get_drop<R: Rng>(&self, area_map: &MapVariantType, rng: &mut R) -> Option<ItemDetail> {
        let unit_type_modifier = self.unit_type_and_modifier(area_map, rng);

        unit_type_modifier.map(|(unit_type, unit_modifier)| {
            ItemDetail::Unit(Unit {
                unit: unit_type,
                modifier: unit_modifier,
            })
        })
    }
}


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

        let gut = GenericUnitTable::new(Episode::One, Difficulty::Normal, SectionID::Skyly);
        assert!(gut.get_drop(&MapVariantType::Forest1, &mut rng) == None);

        let gut = GenericUnitTable::new(Episode::One, Difficulty::Hard, SectionID::Skyly);

        let unit_tests = vec![(MapVariantType::Forest1, UnitType::ResistFreeze, Some(UnitModifier::PlusPlus)),
                              (MapVariantType::Caves3, UnitType::GeneralTp, None),
                              (MapVariantType::Mines2, UnitType::ResistEvil, Some(UnitModifier::PlusPlus)),
                              (MapVariantType::DarkFalz, UnitType::DragonHp, Some(UnitModifier::Minus))];
        for (area, unit, umod) in unit_tests {
            assert!(gut.get_drop(&area, &mut rng) == Some(ItemDetail::Unit(Unit {
                unit: unit,
                modifier: umod,
            })));
        }
    }
}