use std::collections::HashMap;
use std::fs::File;
use std::io::Read;
use std::path::PathBuf;
use std::convert::TryInto;
use std::cmp::Ordering;
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::ItemDetail;
use crate::entity::item::weapon::{Weapon, WeaponType, WeaponSpecial, Attribute, WeaponAttribute};
use crate::ship::shops::ShopItem;
use crate::ship::item_stats::WEAPON_STATS;


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 WeaponShopItem {
    weapon: WeaponType,
    special: Option<WeaponSpecial>,
    grind: usize,
    attributes: [Option<WeaponAttribute>; 3],
}

impl PartialEq for WeaponShopItem {
    fn eq(&self, other: &Self) -> bool {
        self.weapon == other.weapon &&
            self.special == other.special &&
            self.grind == other.grind &&
            self.attributes == other.attributes
    }
}

impl Eq for WeaponShopItem {}

impl Ord for WeaponShopItem {
    fn cmp(&self, other: &Self) -> Ordering {
        self.weapon.value().cmp(&other.weapon.value())
    }
}

impl PartialOrd for WeaponShopItem {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}


fn special_stars(special: &WeaponSpecial) -> usize {
    match special {
        WeaponSpecial::Draw => 1,
        WeaponSpecial::Drain => 2,
        WeaponSpecial::Fill => 3,
        WeaponSpecial::Gush => 4,
        WeaponSpecial::Heart => 1,
        WeaponSpecial::Mind => 2,
        WeaponSpecial::Soul => 3,
        WeaponSpecial::Geist => 4,
        WeaponSpecial::Masters => 2,
        WeaponSpecial::Lords => 3,
        WeaponSpecial::Kings => 4,
        WeaponSpecial::Charge => 2,
        WeaponSpecial::Spirit => 3,
        WeaponSpecial::Berserk => 4,
        WeaponSpecial::Ice => 1,
        WeaponSpecial::Frost => 2,
        WeaponSpecial::Freeze => 3,
        WeaponSpecial::Blizzard => 4,
        WeaponSpecial::Bind => 1,
        WeaponSpecial::Hold => 2,
        WeaponSpecial::Seize => 3,
        WeaponSpecial::Arrest => 4,
        WeaponSpecial::Heat => 1,
        WeaponSpecial::Fire => 2,
        WeaponSpecial::Flame => 3,
        WeaponSpecial::Burning => 4,
        WeaponSpecial::Shock => 1,
        WeaponSpecial::Thunder => 2,
        WeaponSpecial::Storm => 3,
        WeaponSpecial::Tempest => 4,
        WeaponSpecial::Dim => 1,
        WeaponSpecial::Shadow => 2,
        WeaponSpecial::Dark => 3,
        WeaponSpecial::Hell => 4,
        WeaponSpecial::Panic => 1,
        WeaponSpecial::Riot => 2,
        WeaponSpecial::Havoc => 3,
        WeaponSpecial::Chaos => 4,
        WeaponSpecial::Devils => 3,
        WeaponSpecial::Demons => 4,
    }

}

impl ShopItem for WeaponShopItem {
    fn price(&self) -> usize {
        WEAPON_STATS.get(&self.weapon)
            .map(|weapon_stat| {
                let mut price = weapon_stat.atp_max as f32;
                price += self.grind as f32;
                price = (price * (price * 3.0)) / weapon_stat.shop_multiplier;

                let percent = self.attributes.iter()
                    .fold(0.0, |acc, attr| {
                        acc + attr.map(|a| a.value).unwrap_or(0) as f32
                    });

                price = price + ((price / 300.0) * percent);

                let special = self.special.map(|special| {
                    special_stars(&special) as f32
                }).unwrap_or(0.0);

                price += special * special * 1000.0;
                price as usize
            })
            .unwrap_or(0xFFFF)
    }

    fn as_bytes(&self) -> [u8; 12] {
        self.as_item().as_client_bytes()[0..12].try_into().unwrap()
    }

    fn as_item(&self) -> ItemDetail {
        ItemDetail::Weapon(
            Weapon {
                weapon: self.weapon,
                special: self.special,
                grind: self.grind as u8,
                attrs: [self.attributes[0], self.attributes[1], None],
                tekked: true,
            }
        )
    }
}

impl WeaponShopItem {
    pub fn weapon_from_item(w: &Weapon) -> WeaponShopItem {
        WeaponShopItem {
            weapon: w.weapon,
            special: w.special,
            grind: w.grind as usize,
            attributes: w.attrs,
        }
    }
}


#[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").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").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").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").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").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").unwrap())
}

fn number_of_weapons_to_generate(character_level: usize) -> usize {
    if character_level <= 10 {
        10
    }
    else if character_level <= 42 {
        12
    }
    else {
        16
    }
}

#[derive(Debug)]
pub 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,
            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()
            .find(|t| t.level <= level)
            .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,
            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,
            value: percent as i8,
        })
    }

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

    fn generate_weapon(&mut self, level: usize) -> WeaponShopItem {
        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)
                }
            }
        };

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

    pub fn generate_weapon_list(&mut self, level: usize) -> Vec<WeaponShopItem> {
        let mut x = (0..number_of_weapons_to_generate(level))
            .map(|_| {
                self.generate_weapon(level)
            })
            .collect::<Vec<WeaponShopItem>>();
            x.sort();
            x
    }
}

#[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_list(i);
        }
    }
}