use std::collections::HashMap;
use std::fs::File;
use std::io::Read;
use std::path::PathBuf;
use serde::Deserialize;
use rand::{Rng, SeedableRng};
use rand::distributions::{WeightedIndex, Distribution};
use rand::seq::{SliceRandom, IteratorRandom};
use crate::entity::character::SectionID;
use crate::ship::room::Difficulty;
use crate::entity::item::weapon::{WeaponType, WeaponSpecial, Attribute, WeaponAttribute};


const TIER1_SPECIAL: [WeaponSpecial; 8] = [WeaponSpecial::Draw, WeaponSpecial::Heart, WeaponSpecial::Ice, WeaponSpecial::Bind,
                                           WeaponSpecial::Heat, WeaponSpecial::Shock, WeaponSpecial::Dim, WeaponSpecial::Panic];

const TIER2_SPECIAL: [WeaponSpecial; 10] = [WeaponSpecial::Drain, WeaponSpecial::Mind, WeaponSpecial::Masters, WeaponSpecial::Charge, WeaponSpecial::Frost,
                                            WeaponSpecial::Hold, WeaponSpecial::Fire, WeaponSpecial::Thunder, WeaponSpecial::Shadow, WeaponSpecial::Riot];

#[derive(Debug)]
pub struct ShopWeapon {
    weapon: WeaponType,
    special: Option<WeaponSpecial>,
    grind: usize,
    attributes: [Option<WeaponAttribute>; 2],
}


#[derive(Debug, Deserialize)]
struct WeaponTableTierEntry {
    weapon: WeaponType,
    probability: usize,
}

#[derive(Debug, Deserialize)]
struct WeaponTableTier {
    level: usize,
    weapons: Vec<WeaponTableTierEntry>,
}

#[derive(Debug)]
struct WeaponTable(Vec<WeaponTableTier>);

#[derive(Debug, Deserialize)]
struct GrindTier {
    level: usize,
    min: usize,
    max: usize,
}

#[derive(Debug)]
struct GrindTable(Vec<GrindTier>);

#[derive(Debug, Deserialize)]
struct AttributeTier {
    level: usize,
    percent_min: isize,
    percent_max: isize,
    none: usize,
    native: usize,
    abeast: usize,
    machine: usize,
    dark: usize,
    hit: usize,
}

#[derive(Debug)]
struct AttributeTable(Vec<AttributeTier>);

#[derive(Debug, Deserialize)]
struct SpecialTierEntry {
    tier: usize,
    probability: usize,
}

#[derive(Debug, Deserialize)]
struct SpecialTier {
    level: usize,
    special: Vec<SpecialTierEntry>,
}

#[derive(Debug)]
struct SpecialTable(Vec<SpecialTier>);







/*
trait WeaponTableLoader {
    fn load(difficulty: Difficulty, section_id: SectionID) -> WeaponTable where Self::Sized;
}

struct WeaponTableLoaderImpl;
impl WeaponTableLoader for WeaponTableLoaderImpl {
    fn load(difficulty: Difficulty, section_id: SectionID) -> WeaponTable {
        let mut path = PathBuf::from("data/shops/");
        path.push(difficulty.to_string().to_lowercase());
        path.push(section_id.to_string().to_lowercase());
        path.push("weapon.toml");
        let mut f = File::open(path).unwrap();
        let mut s = String::new();
        f.read_to_string(&mut s);

        let table: Vec<WeaponTableTier> = toml::from_str(s.as_str()).unwrap();
        println!("table {:?}", table);

        WeaponTable {

        }
    }
}
*/

fn load_weapon_table(difficulty: Difficulty, section_id: SectionID) -> WeaponTable {
    let mut path = PathBuf::from("data/shops/");
    path.push(difficulty.to_string().to_lowercase());
    path.push(section_id.to_string().to_lowercase());
    path.push("weapon.toml");
    let mut f = File::open(path).unwrap();
    let mut s = String::new();
    f.read_to_string(&mut s).unwrap();

    let mut table: HashMap<String, Vec<WeaponTableTier>> = toml::from_str(s.as_str()).unwrap();

    WeaponTable(table.remove("weapon_tier".into()).unwrap())
}

fn load_special_table() -> SpecialTable {
    let path = PathBuf::from("data/shops/special.toml");
    let mut f = File::open(path).unwrap();
    let mut s = String::new();
    f.read_to_string(&mut s).unwrap();

    let mut table: HashMap<String, Vec<SpecialTier>> = toml::from_str(s.as_str()).unwrap();

    SpecialTable(table.remove("specials".into()).unwrap())
}

fn load_grind_table() -> GrindTable {
    let path = PathBuf::from("data/shops/grind.toml");
    let mut f = File::open(path).unwrap();
    let mut s = String::new();
    f.read_to_string(&mut s).unwrap();

    let mut table: HashMap<String, Vec<GrindTier>> = toml::from_str(s.as_str()).unwrap();

    GrindTable(table.remove("grind".into()).unwrap())
}

fn load_alt_grind_table() -> GrindTable {
    let path = PathBuf::from("data/shops/alt_grind.toml");
    let mut f = File::open(path).unwrap();
    let mut s = String::new();
    f.read_to_string(&mut s).unwrap();

    let mut table: HashMap<String, Vec<GrindTier>> = toml::from_str(s.as_str()).unwrap();

    GrindTable(table.remove("grind".into()).unwrap())
}

fn load_attribute1_table() -> AttributeTable {
    let path = PathBuf::from("data/shops/attribute1.toml");
    let mut f = File::open(path).unwrap();
    let mut s = String::new();
    f.read_to_string(&mut s).unwrap();

    let mut table: HashMap<String, Vec<AttributeTier>> = toml::from_str(s.as_str()).unwrap();

    AttributeTable(table.remove("attributes".into()).unwrap())
}

fn load_attribute2_table() -> AttributeTable {
    let path = PathBuf::from("data/shops/attribute2.toml");
    let mut f = File::open(path).unwrap();
    let mut s = String::new();
    f.read_to_string(&mut s).unwrap();

    let mut table: HashMap<String, Vec<AttributeTier>> = toml::from_str(s.as_str()).unwrap();

    AttributeTable(table.remove("attributes".into()).unwrap())
}

#[derive(Debug)]
struct WeaponShop<R: Rng + SeedableRng> {
    difficulty: Difficulty,
    section_id: SectionID,
    weapon: WeaponTable,
    special: SpecialTable,
    grind: GrindTable,
    alt_grind: GrindTable,
    attr1: AttributeTable,
    attr2: AttributeTable,
    rng: R,
}

impl<R: Rng + SeedableRng> WeaponShop<R> {
    pub fn new(difficulty: Difficulty, section_id: SectionID) -> WeaponShop<R> {
        WeaponShop {
            difficulty: difficulty,
            section_id: section_id,
            weapon: load_weapon_table(difficulty, section_id),
            special: load_special_table(),
            grind: load_grind_table(),
            alt_grind: load_alt_grind_table(),
            attr1: load_attribute1_table(),
            attr2: load_attribute2_table(),
            rng: R::from_entropy(),
        }
    }


    fn generate_type(&mut self, level: usize) -> WeaponType {
        let tier = self.weapon.0.iter()
            .filter(|t| t.level <= level)
            .last()
            .unwrap();

        let weapon_choice = WeightedIndex::new(tier.weapons.iter().map(|t| t.probability)).unwrap();
        tier.weapons.get(weapon_choice.sample(&mut self.rng)).unwrap().weapon
    }

    fn generate_special(&mut self, level: usize) -> Option<WeaponSpecial> {
        let tier = self.special.0.iter()
            .filter(|t| t.level <= level)
            .last()
            .unwrap();

        let special_tier = WeightedIndex::new(tier.special.iter().map(|t| t.probability)).unwrap();
        match special_tier.sample(&mut self.rng) {
            1 => TIER1_SPECIAL.choose(&mut self.rng).cloned(),
            2 => TIER2_SPECIAL.choose(&mut self.rng).cloned(),
            _ => None
        }
    }

    fn generate_grind(&mut self, level: usize) -> usize {
        let tier = self.grind.0.iter()
            .filter(|t| t.level <= level)
            .last()
            .unwrap();

        self.rng.gen_range(tier.min, tier.max+1)
    }

    fn generate_alt_grind(&mut self, level: usize) -> usize {
        let tier = self.alt_grind.0.iter()
            .filter(|t| t.level <= level)
            .nth(0)
            .unwrap();

        self.rng.gen_range(tier.min, tier.max+1)
    }

    fn generate_attribute1(&mut self, level: usize) -> Option<WeaponAttribute> {
        let tier = self.attr1.0.iter()
            .filter(|t| t.level <= level)
            .last()
            .unwrap();

        let attr_choice = WeightedIndex::new(&[tier.none, tier.native, tier.abeast, tier.machine, tier.dark, tier.hit]).unwrap();
        let attr = match attr_choice.sample(&mut self.rng) {
            0 => return None,
            1 => Attribute::Native,
            2 => Attribute::ABeast,
            3 => Attribute::Machine,
            4 => Attribute::Dark,
            5 => Attribute::Hit,
            _ => panic!()
        };

        let percent = (tier.percent_min..tier.percent_max+1)
            .filter(|p| p % 5 == 0)
            .choose(&mut self.rng)?;

        Some(WeaponAttribute {
            attr: attr,
            value: percent as i8,
        })
    }

    fn generate_attribute2(&mut self, level: usize) -> Option<WeaponAttribute> {
        let tier = self.attr2.0.iter()
            .filter(|t| t.level <= level)
            .last()
            .unwrap();

        let attr_choice = WeightedIndex::new(&[tier.none, tier.native, tier.abeast, tier.machine, tier.dark, tier.hit]).unwrap();
        let attr = match attr_choice.sample(&mut self.rng) {
            0 => return None,
            1 => Attribute::Native,
            2 => Attribute::ABeast,
            3 => Attribute::Machine,
            4 => Attribute::Dark,
            5 => Attribute::Hit,
            _ => panic!()
        };

        let percent = (tier.percent_min..tier.percent_max+1)
            .filter(|p| p % 5 == 0)
            .choose(&mut self.rng)?;

        Some(WeaponAttribute {
            attr: attr,
            value: percent as i8,
        })
    }

    fn is_alt_grind(&self, weapon: &WeaponType) -> bool {
        match (self.section_id, weapon) {
            (SectionID::Viridia, WeaponType::Shot) => true,
            (SectionID::Viridia, WeaponType::Spread) => true,
            (SectionID::Viridia, WeaponType::Cannon) => true,
            (SectionID::Viridia, WeaponType::Launcher) => true,
            (SectionID::Viridia, WeaponType::Arms) => true,
            (SectionID::Greenill, WeaponType::Rifle) => true,
            (SectionID::Greenill, WeaponType::Sniper) => true,
            (SectionID::Greenill, WeaponType::Blaster) => true,
            (SectionID::Greenill, WeaponType::Beam) => true,
            (SectionID::Greenill, WeaponType::Laser) => true,
            (SectionID::Skyly, WeaponType::Sword) => true,
            (SectionID::Skyly, WeaponType::Gigush) => true,
            (SectionID::Skyly, WeaponType::Breaker) => true,
            (SectionID::Skyly, WeaponType::Claymore) => true,
            (SectionID::Skyly, WeaponType::Calibur) => true,
            (SectionID::Bluefull, WeaponType::Partisan) => true,
            (SectionID::Bluefull, WeaponType::Halbert) => true,
            (SectionID::Bluefull, WeaponType::Glaive) => true,
            (SectionID::Bluefull, WeaponType::Berdys) => true,
            (SectionID::Bluefull, WeaponType::Gungnir) => true,
            (SectionID::Purplenum, WeaponType::Mechgun) => true,
            (SectionID::Purplenum, WeaponType::Assault) => true,
            (SectionID::Purplenum, WeaponType::Repeater) => true,
            (SectionID::Purplenum, WeaponType::Gatling) => true,
            (SectionID::Purplenum, WeaponType::Vulcan) => true,
            (SectionID::Pinkal, WeaponType::Cane) => true,
            (SectionID::Pinkal, WeaponType::Stick) => true,
            (SectionID::Pinkal, WeaponType::Mace) => true,
            (SectionID::Pinkal, WeaponType::Club) => true,
            (SectionID::Oran, WeaponType::Dagger) => true,
            (SectionID::Oran, WeaponType::Knife) => true,
            (SectionID::Oran, WeaponType::Blade) => true,
            (SectionID::Oran, WeaponType::Edge) => true,
            (SectionID::Oran, WeaponType::Ripper) => true,
            (SectionID::Whitill, WeaponType::Slicer) => true,
            (SectionID::Whitill, WeaponType::Spinner) => true,
            (SectionID::Whitill, WeaponType::Cutter) => true,
            (SectionID::Whitill, WeaponType::Sawcer) => true,
            (SectionID::Whitill, WeaponType::Diska) => true,
            _ => false,
        }
    }

    pub fn generate_weapon(&mut self, level: usize) -> ShopWeapon {
        let weapon = self.generate_type(level);
        let grind = if self.is_alt_grind(&weapon) {
            self.generate_alt_grind(level)
        } else {
            self.generate_grind(level)
        };
        let special = self.generate_special(level);
        let (attr1, attr2) = {
            match self.generate_attribute1(level) {
                Some(a1) => {
                    let a2 = loop {
                        let attr = self.generate_attribute2(level);
                        match attr {
                            Some(a2) => {
                                if a2.attr != a1.attr {
                                    break Some(a2);
                                }
                            },
                            None => break None,
                        }
                    };
                    (Some(a1), a2)
                },
                None => {
                    let a2 = self.generate_attribute2(level);
                    (a2, None)
                }
            }
        };

        ShopWeapon {
            weapon: weapon,
            grind: grind,
            special: special,
            attributes: [attr1, attr2],
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_loading_weapon_shop() {
        WeaponShop::<rand_chacha::ChaCha20Rng>::new(Difficulty::Ultimate, SectionID::Pinkal);
    }
    #[test]
    fn test_generating_some_weapons() {
        let mut ws = WeaponShop::<rand_chacha::ChaCha20Rng>::new(Difficulty::Ultimate, SectionID::Pinkal);
        for i in 0..200 {
            ws.generate_weapon(i);
        }
    }
}