#![allow(dead_code, unused_variables)]
use rand::{Rng};
use rand::distributions::{WeightedIndex, Distribution};
use serde::{Serialize, Deserialize};
use entity::character::SectionID;
use maps::room::{Difficulty, Episode};
use maps::area::MapArea;
use crate::{ItemDropType, load_data_file};
use maps::object::{MapObject, MapObjectType, FixedBoxDropType};
use crate::rare_drop_table::{RareDropTable, RareDropItem};
use crate::generic_weapon::GenericWeaponTable;
use crate::generic_armor::GenericArmorTable;
use crate::generic_shield::GenericShieldTable;
use crate::generic_unit::GenericUnitTable;
use crate::tool_table::ToolTable;

#[derive(Debug, Serialize, Deserialize)]
struct BoxDropRate {
    weapon_rate: u32,
    armor_rate: u32,
    shield_rate: u32,
    unit_rate: u32,
    tool_rate: u32,
    meseta_rate: u32,
    nothing_rate: u32,
    min_meseta: u32,
    max_meseta: u32,
}

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

impl BoxDropRates {
    fn rates_by_area(&self, map_area: &MapArea) -> &BoxDropRate {
        match map_area.drop_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!()
        }
    }
}

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

#[derive(Debug, Serialize, Deserialize)]
struct BoxRareRatesRaw {
    area1: Vec<BoxRareRateRaw>,
    area2: Vec<BoxRareRateRaw>,
    area3: Vec<BoxRareRateRaw>,
    area4: Vec<BoxRareRateRaw>,
    area5: Vec<BoxRareRateRaw>,
    area6: Vec<BoxRareRateRaw>,
    area7: Vec<BoxRareRateRaw>,
    area8: Vec<BoxRareRateRaw>,
    area9: Vec<BoxRareRateRaw>,
    area10: Vec<BoxRareRateRaw>,
}


struct BoxRareRate {
    item: RareDropItem,
    rate: f32,
}

struct BoxRareRates {
    area1: Vec<BoxRareRate>,
    area2: Vec<BoxRareRate>,
    area3: Vec<BoxRareRate>,
    area4: Vec<BoxRareRate>,
    area5: Vec<BoxRareRate>,
    area6: Vec<BoxRareRate>,
    area7: Vec<BoxRareRate>,
    area8: Vec<BoxRareRate>,
    area9: Vec<BoxRareRate>,
    area10: Vec<BoxRareRate>,
}

impl BoxRareRates {
    fn new(raw: BoxRareRatesRaw) -> BoxRareRates {
        BoxRareRates {
            area1: raw.area1.into_iter().map(|i| { BoxRareRate {item: RareDropItem::from_string(i.item), rate: i.rate} }).collect(),
            area2: raw.area2.into_iter().map(|i| { BoxRareRate {item: RareDropItem::from_string(i.item), rate: i.rate} }).collect(),
            area3: raw.area3.into_iter().map(|i| { BoxRareRate {item: RareDropItem::from_string(i.item), rate: i.rate} }).collect(),
            area4: raw.area4.into_iter().map(|i| { BoxRareRate {item: RareDropItem::from_string(i.item), rate: i.rate} }).collect(),
            area5: raw.area5.into_iter().map(|i| { BoxRareRate {item: RareDropItem::from_string(i.item), rate: i.rate} }).collect(),
            area6: raw.area6.into_iter().map(|i| { BoxRareRate {item: RareDropItem::from_string(i.item), rate: i.rate} }).collect(),
            area7: raw.area7.into_iter().map(|i| { BoxRareRate {item: RareDropItem::from_string(i.item), rate: i.rate} }).collect(),
            area8: raw.area8.into_iter().map(|i| { BoxRareRate {item: RareDropItem::from_string(i.item), rate: i.rate} }).collect(),
            area9: raw.area9.into_iter().map(|i| { BoxRareRate {item: RareDropItem::from_string(i.item), rate: i.rate} }).collect(),
            area10: raw.area10.into_iter().map(|i| { BoxRareRate {item: RareDropItem::from_string(i.item), rate: i.rate} }).collect(),
        }
    }

    fn rates_by_area(&self, map_area: &MapArea) -> &Vec<BoxRareRate> {
        match map_area.drop_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 BoxDropTable {
    box_rates: BoxDropRates,
    rare_rates: BoxRareRates,
    rare_stats: RareDropTable,
    weapon_table: GenericWeaponTable,
    armor_table: GenericArmorTable,
    shield_table: GenericShieldTable,
    unit_table: GenericUnitTable,
    tool_table: ToolTable,
}

impl BoxDropTable {
    pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> BoxDropTable {
        let rates = load_data_file(episode, difficulty, section_id, "box_rare_rate.toml");

        BoxDropTable {
            box_rates: load_data_file(episode, difficulty, section_id, "box_drop_rate.toml"),
            rare_rates: BoxRareRates::new(rates),
            rare_stats: RareDropTable::new(episode, difficulty, section_id),
            weapon_table: GenericWeaponTable::new(episode, difficulty, section_id),
            armor_table: GenericArmorTable::new(episode, difficulty, section_id),
            shield_table: GenericShieldTable::new(episode, difficulty, section_id),
            unit_table: GenericUnitTable::new(episode, difficulty, section_id),
            tool_table: ToolTable::new(episode, difficulty, section_id),
        }
        
    }

    fn rare_drop<R: Rng>(&self, map_area: &MapArea, rng: &mut R) -> Option<ItemDropType> {
        self.rare_rates.rates_by_area(map_area).iter()
            .filter_map(|rate| {
                let rand: f32 = rng.gen();
                if rand < rate.rate {
                    Some(self.rare_stats.apply_item_stats(map_area, rate.item, rng))
                }
                else {
                    None
                }
            }).next()
    }
    
    fn random_box_drop<R: Rng>(&self, map_area: &MapArea, rng: &mut R) -> Option<ItemDropType> {
        self.rare_drop(map_area, rng).or_else(|| {
            let rate = self.box_rates.rates_by_area(map_area);
            let type_weights = WeightedIndex::new([rate.weapon_rate, rate.armor_rate, rate.shield_rate, rate.unit_rate,
                                                   rate.tool_rate, rate.meseta_rate, rate.nothing_rate]).unwrap();
            let btype = type_weights.sample(rng);
            match btype {
                0 => self.weapon_table.get_drop(map_area, rng),
                1 => self.armor_table.get_drop(map_area, rng),
                2 => self.shield_table.get_drop(map_area, rng),
                3 => self.unit_table.get_drop(map_area, rng),
                4 => self.tool_table.get_drop(map_area, rng),
                5 => Some(ItemDropType::Meseta(rng.gen_range(rate.min_meseta, rate.max_meseta))),
                _ => None,
            }
        })
    }
    
    fn fixed_box_drop<R: Rng>(&self, fixed_drop: FixedBoxDropType, map_area: &MapArea, rng: &mut R) -> Option<ItemDropType> {
        match fixed_drop {
            FixedBoxDropType::Weapon => self.weapon_table.get_drop(map_area, rng),
            FixedBoxDropType::Armor => self.armor_table.get_drop(map_area, rng), // TODO: should this drop shield?
            FixedBoxDropType::Tool => self.tool_table.get_drop(map_area, rng),
            FixedBoxDropType::Meseta => {
                let rate = self.box_rates.rates_by_area(map_area);
                Some(ItemDropType::Meseta(rng.gen_range(rate.min_meseta, rate.max_meseta)))
            },
            FixedBoxDropType::Random => self.random_box_drop(map_area, rng),
            FixedBoxDropType::Specific(value) => {
                let mut buf: [u8; 16] = [0; 16];
                buf[0..4].copy_from_slice(&u32::to_be_bytes(value));
                ItemDropType::parse_item_from_bytes(buf)
            },
        }
    }
    
    pub fn get_drop<R: Rng>(&self, map_area: &MapArea, object: &MapObject, rng: &mut R) -> Option<ItemDropType> {
        match object.object {
            MapObjectType::Box | MapObjectType::EnemyBox | MapObjectType::RuinsBox| MapObjectType::RuinsEnemyBox
                | MapObjectType::CcaBox => {
                    self.random_box_drop(map_area, rng)
            },
            MapObjectType::FixedBox(f) | MapObjectType::EnemyFixedBox(f) | MapObjectType::RuinsFixedBox(f)
                | MapObjectType::RuinsEnemyFixedBox(f) | MapObjectType::CcaFixedBox(f) => {
                    self.fixed_box_drop(f, map_area, rng)
                },
            _ => None,
        }
    }
}



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

        let bdt = BoxDropTable::new(Episode::One, Difficulty::Ultimate, SectionID::Skyly);

        println!("{:?}", bdt.get_drop(&MapArea::Forest1, &MapObject {object: MapObjectType::Box, map: MapArea::Forest1, dropped_item: false}, &mut rng));
    }
}