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

use crate::entity::item::tool::{Tool, ToolType};
use crate::ship::room::{Difficulty, Episode};
use crate::ship::map::MapArea;
use crate::entity::character::SectionID;
use crate::ship::drops::{ItemDropType, load_data_file};
use crate::ship::drops::tech_table::TechniqueTable;


#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, enum_utils::FromStr)]
enum ToolRateType {
    Monomate,
    Dimate,
    Trimate,
    Monofluid,
    Difluid,
    Trifluid,
    Antidote,
    Antiparalysis,
    SolAtomizer,
    MoonAtomizer,
    StarAtomizer,
    Telepipe,
    TrapVision,
    Monogrinder,
    Digrinder,
    Trigrinder,
    PowerMaterial,
    MindMaterial,
    EvadeMaterial,
    HpMaterial,
    TpMaterial,
    DefMaterial,
    LuckMaterial,
    ScapeDoll,
    Technique,
    PhotonDrop,
}

/*#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
struct ToolRate {
    tool: ToolRateType,
    rate: u32,
}*/

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

struct ToolRates {
    area1: BTreeMap<ToolRateType, u32>,
    area2: BTreeMap<ToolRateType, u32>,
    area3: BTreeMap<ToolRateType, u32>,
    area4: BTreeMap<ToolRateType, u32>,
    area5: BTreeMap<ToolRateType, u32>,
    area6: BTreeMap<ToolRateType, u32>,
    area7: BTreeMap<ToolRateType, u32>,
    area8: BTreeMap<ToolRateType, u32>,
    area9: BTreeMap<ToolRateType, u32>,
    area10: BTreeMap<ToolRateType, u32>,
}

impl ToolRates {
    fn new(raw: ToolRatesRaw) -> ToolRates {
        ToolRates {
            area1: raw.area1.into_iter().map(|(name, rate)| (name.parse().unwrap(), rate)).collect(),
            area2: raw.area2.into_iter().map(|(name, rate)| (name.parse().unwrap(), rate)).collect(),
            area3: raw.area3.into_iter().map(|(name, rate)| (name.parse().unwrap(), rate)).collect(),
            area4: raw.area4.into_iter().map(|(name, rate)| (name.parse().unwrap(), rate)).collect(),
            area5: raw.area5.into_iter().map(|(name, rate)| (name.parse().unwrap(), rate)).collect(),
            area6: raw.area6.into_iter().map(|(name, rate)| (name.parse().unwrap(), rate)).collect(),
            area7: raw.area7.into_iter().map(|(name, rate)| (name.parse().unwrap(), rate)).collect(),
            area8: raw.area8.into_iter().map(|(name, rate)| (name.parse().unwrap(), rate)).collect(),
            area9: raw.area9.into_iter().map(|(name, rate)| (name.parse().unwrap(), rate)).collect(),
            area10: raw.area10.into_iter().map(|(name, rate)| (name.parse().unwrap(), rate)).collect(),
        }
    }
}

impl ToolRates {
    fn get_by_area<'a>(&'a self, map_area: &MapArea) -> &'a BTreeMap<ToolRateType, u32> {
        match map_area.drop_area_value().unwrap() {
            0 => &self.area1,
            1 => &self.area2,
            2 => &self.area3,
            3 => &self.area4,
            4 => &self.area5,
            5 => &self.area6,
            6 => &self.area7,
            7 => &self.area8,
            8 => &self.area9,
            _ => &self.area10,
        }
    }
}

pub struct ToolTable {
    rates: ToolRates,
    tech_table: TechniqueTable, 
}

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

        ToolTable {
            rates: ToolRates::new(rates),
            tech_table: TechniqueTable::new(episode, difficulty, section_id),
        }
    }

    pub fn get_drop<R: Rng>(&self, map_area: &MapArea, rng: &mut R) -> Option<ItemDropType> {
        let tool_rates = self.rates.get_by_area(map_area).iter();
        let tool_weights = WeightedIndex::new(tool_rates.clone().map(|(_, weights)| weights)).unwrap();
        
        let tool = tool_rates.map(|(ttype, _)| ttype).nth(tool_weights.sample(rng)).unwrap();
        let tool_type = match tool {
            ToolRateType::Monomate => ToolType::Monomate,
            ToolRateType::Dimate => ToolType::Dimate,
            ToolRateType::Trimate => ToolType::Trimate,
            ToolRateType::Monofluid => ToolType::Monofluid,
            ToolRateType::Difluid => ToolType::Difluid,
            ToolRateType::Trifluid => ToolType::Trifluid,
            ToolRateType::Antidote => ToolType::Antidote,
            ToolRateType::Antiparalysis => ToolType::Antiparalysis,
            ToolRateType::SolAtomizer => ToolType::SolAtomizer,
            ToolRateType::MoonAtomizer => ToolType::MoonAtomizer,
            ToolRateType::StarAtomizer => ToolType::StarAtomizer,
            ToolRateType::Telepipe => ToolType::Telepipe,
            ToolRateType::TrapVision => ToolType::TrapVision,
            ToolRateType::Monogrinder => ToolType::Monogrinder,
            ToolRateType::Digrinder => ToolType::Digrinder,
            ToolRateType::Trigrinder => ToolType::Trigrinder,
            ToolRateType::PowerMaterial => ToolType::PowerMaterial,
            ToolRateType::MindMaterial => ToolType::MindMaterial,
            ToolRateType::EvadeMaterial => ToolType::EvadeMaterial,
            ToolRateType::HpMaterial => ToolType::HpMaterial,
            ToolRateType::TpMaterial => ToolType::TpMaterial,
            ToolRateType::DefMaterial => ToolType::DefMaterial,
            ToolRateType::LuckMaterial => ToolType::LuckMaterial,
            ToolRateType::ScapeDoll => ToolType::ScapeDoll,
            ToolRateType::PhotonDrop => ToolType::PhotonDrop,
            ToolRateType::Technique => return self.tech_table.get_drop(map_area, rng),
        };

        Some(ItemDropType::Tool(Tool {
            tool: tool_type
        }))
    }
}