use std::collections::HashMap;
use std::fs::File;
use serde_json::Value;
use crate::entity::character::CharacterClass;

#[derive(Default, Copy, Clone, Debug, PartialEq)]
pub struct CharacterStats {
    pub hp: u16,
    pub atp: u16,
    pub mst: u16,
    pub evp: u16,
    pub dfp: u16,
    pub ata: u16,
    pub lck: u16,
}

#[derive(Default, Copy, Clone, Debug)]
struct CharacterLevelEntry {
    hp: u32,
    atp: u32,
    mst: u32,
    evp: u32,
    dfp: u32,
    ata: u32,
    lck: u32,
    exp: u32,
}

pub struct CharacterLevelTable {
    table: HashMap<CharacterClass, [CharacterLevelEntry; 200]>,
}


impl CharacterLevelTable {
    pub fn new() -> CharacterLevelTable {
        let file = File::open("data/char_stats.json").unwrap();
        let json: Value = serde_json::from_reader(file).unwrap();
        let mut table = HashMap::new();

        for it in json.as_object().unwrap().iter(){
            let cl = match it.0.as_str() {
                "HUmar" => CharacterClass::HUmar,
                "HUnewearl" => CharacterClass::HUnewearl,
                "HUcast" => CharacterClass::HUcast,
                "HUcaseal" => CharacterClass::HUcaseal,
                "RAmar" => CharacterClass::RAmar,
                "RAmarl" => CharacterClass::RAmarl,
                "RAcast" => CharacterClass::RAcast,
                "RAcaseal" => CharacterClass::RAcaseal,
                "FOmar" => CharacterClass::FOmar,
                "FOmarl" => CharacterClass::FOmarl,
                "FOnewm" => CharacterClass::FOnewm,
                "FOnewearl" => CharacterClass::FOnewearl,
                _ => panic!("unexpected class in char stats"),
            };

            let mut statlist = [CharacterLevelEntry::default(); 200];
            for (i, stat) in it.1.as_array().unwrap().into_iter().enumerate() {
                statlist[i] = CharacterLevelEntry {
                    hp: stat["hp"].as_i64().unwrap() as u32,
                    atp: stat["atp"].as_i64().unwrap() as u32,
                    mst: stat["mst"].as_i64().unwrap() as u32,
                    evp: stat["evp"].as_i64().unwrap() as u32,
                    dfp: stat["dfp"].as_i64().unwrap() as u32,
                    ata: stat["ata"].as_i64().unwrap() as u32,
                    lck: stat["lck"].as_i64().unwrap() as u32,
                    exp: stat["xp"].as_i64().unwrap() as u32,
                }
            }

            table.insert(cl, statlist);
        }

        CharacterLevelTable {
            table: table,
        }
    }

    pub fn get_level_from_exp(&self, ch_class: CharacterClass, exp: u32) -> u32 {
        if let Some(statlist) = self.table.get(&ch_class) {
            statlist
                .iter()
                .filter(|stat| {
                    stat.exp <= exp
                })
                .count() as u32
        }
        else {
            0
        }
    }

    pub fn get_stats_from_exp(&self, ch_class: CharacterClass, exp: u32) -> (u32, CharacterStats) {
        if let Some(statlist) = self.table.get(&ch_class) {
            statlist
                .iter()
                .filter(|stat| {
                    stat.exp <= exp
                })
                .fold((0, CharacterStats::default()), |acc, &k| {
                    (acc.0 + 1, CharacterStats {
                        hp: acc.1.hp + k.hp as u16,
                        atp: acc.1.atp + k.atp as u16,
                        mst: acc.1.mst + k.mst as u16,
                        evp: acc.1.evp + k.evp as u16,
                        dfp: acc.1.dfp + k.dfp as u16,
                        ata: acc.1.ata + k.ata as u16,
                        lck: acc.1.lck + k.lck as u16,
                    })
                })
        }
        else {
            (0, CharacterStats::default())
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn test_stat_levels() {
        let table = CharacterLevelTable::new();
        assert!(table.get_stats_from_exp(CharacterClass::FOmarl, 0) == (1, CharacterStats { hp: 20, atp: 13, mst: 53, evp: 35, dfp: 10, ata: 15, lck: 10 }));
        assert!(table.get_stats_from_exp(CharacterClass::FOmarl, 1 << 17) == (36, CharacterStats { hp: 125, atp: 114, mst: 219, evp: 182, dfp: 42, ata: 213, lck: 10 }));
    }

    #[test]
    fn test_levels() {
        let table = CharacterLevelTable::new();
        assert!(table.get_level_from_exp(CharacterClass::FOmarl, 0) == 1);
        assert!(table.get_level_from_exp(CharacterClass::FOmarl, 3000) == 8);
        assert!(table.get_level_from_exp(CharacterClass::FOmarl, 3200) == 9);
    }
}