use std::collections::HashMap;
use serde::{Serialize, Deserialize};
use crate::entity::item::tool::ToolType;
use std::io::Read;

#[derive(Debug, Deserialize)]
struct MagStats {
    feed_table: u32,
}

#[derive(Debug, Deserialize)]
struct MagFeedTable {
    def: i32,
    pow: i32,
    dex: i32,
    mnd: i32,
    iq: i32,
    syn: i32,
}

lazy_static::lazy_static! {
    static ref MAG_STATS: HashMap<MagType, MagStats> = {
        let mut f = std::fs::File::open("data/item_stats/mag_stats.toml").unwrap();
        let mut s = String::new();
        f.read_to_string(&mut s).unwrap();

        let mag_stats: HashMap<String, MagStats> = toml::from_str(&s).unwrap();
        mag_stats.into_iter()
            .map(|(name, stats)| {
                (name.parse().unwrap(), stats)
            })
            .collect::<HashMap<MagType, MagStats>>()
    };

    static ref MAG_FEEDING_TABLES: Vec<HashMap<ToolType, MagFeedTable>> = {
        let mut f = std::fs::File::open("data/item_stats/mag_feed_table.toml").unwrap();
        let mut s = String::new();
        f.read_to_string(&mut s).unwrap();

        let mut feed: HashMap<String, Vec<HashMap<String, MagFeedTable>>> = toml::from_str(&s).unwrap();
        let feed = feed.remove("feedtable".into()).unwrap();
        feed.into_iter()
            .map(|table| {
                table.into_iter()
                    .map(|(tool, stats)| {
                        (tool.parse().unwrap(), stats)
                    })
                    .collect()
            })
            .collect::<Vec<HashMap<ToolType, MagFeedTable>>>()
    };
}



#[derive(Debug, Copy, Clone)]
pub enum ItemParseError {
    InvalidMagType,
    InvalidMagBytes,
}

#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, enum_utils::FromStr, derive_more::Display)]
pub enum MagType {
    Mag,
    Varuna,
    Mitra,
    Surya,
    Vayu,
    Varaha,
    Kama,
    Ushasu,
    Apsaras,
    Kumara,
    Kaitabha,
    Tapas,
    Bhirava,
    Kalki,
    Rudra,
    Marutah,
    Yaksa,
    Sita,
    Garuda,
    Nandin,
    Ashvinau,
    Ribhava,
    Soma,
    Ila,
    Durga,
    Vritra,
    Namuci,
    Sumba,
    Naga,
    Pitri,
    Kabanda,
    Ravana,
    Marica,
    Soniti,
    Preta,
    Andhaka,
    Bana,
    Naraka,
    Madhu,
    Churel,
    Robochao,
    OpaOpa,
    Pian,
    Chao,
    ChuChu,
    KapuKapu,
    AngelsWing,
    DevilsWing,
    Elenor,
    MarkIII,
    MasterSystem,
    Genesis,
    SegaSaturn,
    Dreamcast,
    Hamburger,
    PanzersTail,
    DevilsTail,
    Deva,
    Rati,
    Savitri,
    Rukmin,
    Pushan,
    Diwari,
    Sato,
    Bhima,
    Nidra,
    GeungSi,
    Tellusis,
    StrikerUnit,
    Pioneer,
    Puyo,
    Moro,
    Rappy,
    Yahoo,
    GaelGiel,
    Agastya,
}

impl MagType {
    pub fn value(&self) -> [u8; 3] {
        match self {
            MagType::Mag => [0x02, 0x00, 0x00],
            MagType::Varuna => [0x02, 0x01, 0x00],
            MagType::Mitra => [0x02, 0x02, 0x00],
            MagType::Surya => [0x02, 0x03, 0x00],
            MagType::Vayu => [0x02, 0x04, 0x00],
            MagType::Varaha => [0x02, 0x05, 0x00],
            MagType::Kama => [0x02, 0x06, 0x00],
            MagType::Ushasu => [0x02, 0x07, 0x00],
            MagType::Apsaras => [0x02, 0x08, 0x00],
            MagType::Kumara => [0x02, 0x09, 0x00],
            MagType::Kaitabha => [0x02, 0x0A, 0x00],
            MagType::Tapas => [0x02, 0x0B, 0x00],
            MagType::Bhirava => [0x02, 0x0C, 0x00],
            MagType::Kalki => [0x02, 0x0D, 0x00],
            MagType::Rudra => [0x02, 0x0E, 0x00],
            MagType::Marutah => [0x02, 0x0F, 0x00],
            MagType::Yaksa => [0x02, 0x10, 0x00],
            MagType::Sita => [0x02, 0x11, 0x00],
            MagType::Garuda => [0x02, 0x12, 0x00],
            MagType::Nandin => [0x02, 0x13, 0x00],
            MagType::Ashvinau => [0x02, 0x14, 0x00],
            MagType::Ribhava => [0x02, 0x15, 0x00],
            MagType::Soma => [0x02, 0x16, 0x00],
            MagType::Ila => [0x02, 0x17, 0x00],
            MagType::Durga => [0x02, 0x18, 0x00],
            MagType::Vritra => [0x02, 0x19, 0x00],
            MagType::Namuci => [0x02, 0x1A, 0x00],
            MagType::Sumba => [0x02, 0x1B, 0x00],
            MagType::Naga => [0x02, 0x1C, 0x00],
            MagType::Pitri => [0x02, 0x1D, 0x00],
            MagType::Kabanda => [0x02, 0x1E, 0x00],
            MagType::Ravana => [0x02, 0x1F, 0x00],
            MagType::Marica => [0x02, 0x20, 0x00],
            MagType::Soniti => [0x02, 0x21, 0x00],
            MagType::Preta => [0x02, 0x22, 0x00],
            MagType::Andhaka => [0x02, 0x23, 0x00],
            MagType::Bana => [0x02, 0x24, 0x00],
            MagType::Naraka => [0x02, 0x25, 0x00],
            MagType::Madhu => [0x02, 0x26, 0x00],
            MagType::Churel => [0x02, 0x27, 0x00],
            MagType::Robochao => [0x02, 0x28, 0x00],
            MagType::OpaOpa => [0x02, 0x29, 0x00],
            MagType::Pian => [0x02, 0x2A, 0x00],
            MagType::Chao => [0x02, 0x2B, 0x00],
            MagType::ChuChu => [0x02, 0x2C, 0x00],
            MagType::KapuKapu => [0x02, 0x2D, 0x00],
            MagType::AngelsWing => [0x02, 0x2E, 0x00],
            MagType::DevilsWing => [0x02, 0x2F, 0x00],
            MagType::Elenor => [0x02, 0x30, 0x00],
            MagType::MarkIII => [0x02, 0x31, 0x00],
            MagType::MasterSystem => [0x02, 0x32, 0x00],
            MagType::Genesis => [0x02, 0x33, 0x00],
            MagType::SegaSaturn => [0x02, 0x34, 0x00],
            MagType::Dreamcast => [0x02, 0x35, 0x00],
            MagType::Hamburger => [0x02, 0x36, 0x00],
            MagType::PanzersTail => [0x02, 0x37, 0x00],
            MagType::DevilsTail => [0x02, 0x38, 0x00],
            MagType::Deva => [0x02, 0x39, 0x00],
            MagType::Rati => [0x02, 0x3A, 0x00],
            MagType::Savitri => [0x02, 0x3B, 0x00],
            MagType::Rukmin => [0x02, 0x3C, 0x00],
            MagType::Pushan => [0x02, 0x3D, 0x00],
            MagType::Diwari => [0x02, 0x3E, 0x00],
            MagType::Sato => [0x02, 0x3F, 0x00],
            MagType::Bhima => [0x02, 0x40, 0x00],
            MagType::Nidra => [0x02, 0x41, 0x00],
            MagType::GeungSi => [0x02, 0x42, 0x00],
            MagType::Tellusis => [0x02, 0x44, 0x00],
            MagType::StrikerUnit => [0x02, 0x45, 0x00],
            MagType::Pioneer => [0x02, 0x46, 0x00],
            MagType::Puyo => [0x02, 0x47, 0x00],
            MagType::Moro => [0x02, 0x48, 0x00],
            MagType::Rappy => [0x02, 0x49, 0x00],
            MagType::Yahoo => [0x02, 0x4A, 0x00],
            MagType::GaelGiel => [0x02, 0x4B, 0x00],
            MagType::Agastya => [0x02, 0x4C, 0x00],
        }
    }

    pub fn parse_type(data: [u8; 3]) -> Result<MagType, ItemParseError> {
        match data {
            [0x02, 0x00, 0x00] => Ok(MagType::Mag),
            [0x02, 0x01, 0x00] => Ok(MagType::Varuna),
            [0x02, 0x02, 0x00] => Ok(MagType::Mitra),
            [0x02, 0x03, 0x00] => Ok(MagType::Surya),
            [0x02, 0x04, 0x00] => Ok(MagType::Vayu),
            [0x02, 0x05, 0x00] => Ok(MagType::Varaha),
            [0x02, 0x06, 0x00] => Ok(MagType::Kama),
            [0x02, 0x07, 0x00] => Ok(MagType::Ushasu),
            [0x02, 0x08, 0x00] => Ok(MagType::Apsaras),
            [0x02, 0x09, 0x00] => Ok(MagType::Kumara),
            [0x02, 0x0A, 0x00] => Ok(MagType::Kaitabha),
            [0x02, 0x0B, 0x00] => Ok(MagType::Tapas),
            [0x02, 0x0C, 0x00] => Ok(MagType::Bhirava),
            [0x02, 0x0D, 0x00] => Ok(MagType::Kalki),
            [0x02, 0x0E, 0x00] => Ok(MagType::Rudra),
            [0x02, 0x0F, 0x00] => Ok(MagType::Marutah),
            [0x02, 0x10, 0x00] => Ok(MagType::Yaksa),
            [0x02, 0x11, 0x00] => Ok(MagType::Sita),
            [0x02, 0x12, 0x00] => Ok(MagType::Garuda),
            [0x02, 0x13, 0x00] => Ok(MagType::Nandin),
            [0x02, 0x14, 0x00] => Ok(MagType::Ashvinau),
            [0x02, 0x15, 0x00] => Ok(MagType::Ribhava),
            [0x02, 0x16, 0x00] => Ok(MagType::Soma),
            [0x02, 0x17, 0x00] => Ok(MagType::Ila),
            [0x02, 0x18, 0x00] => Ok(MagType::Durga),
            [0x02, 0x19, 0x00] => Ok(MagType::Vritra),
            [0x02, 0x1A, 0x00] => Ok(MagType::Namuci),
            [0x02, 0x1B, 0x00] => Ok(MagType::Sumba),
            [0x02, 0x1C, 0x00] => Ok(MagType::Naga),
            [0x02, 0x1D, 0x00] => Ok(MagType::Pitri),
            [0x02, 0x1E, 0x00] => Ok(MagType::Kabanda),
            [0x02, 0x1F, 0x00] => Ok(MagType::Ravana),
            [0x02, 0x20, 0x00] => Ok(MagType::Marica),
            [0x02, 0x21, 0x00] => Ok(MagType::Soniti),
            [0x02, 0x22, 0x00] => Ok(MagType::Preta),
            [0x02, 0x23, 0x00] => Ok(MagType::Andhaka),
            [0x02, 0x24, 0x00] => Ok(MagType::Bana),
            [0x02, 0x25, 0x00] => Ok(MagType::Naraka),
            [0x02, 0x26, 0x00] => Ok(MagType::Madhu),
            [0x02, 0x27, 0x00] => Ok(MagType::Churel),
            [0x02, 0x28, 0x00] => Ok(MagType::Robochao),
            [0x02, 0x29, 0x00] => Ok(MagType::OpaOpa),
            [0x02, 0x2A, 0x00] => Ok(MagType::Pian),
            [0x02, 0x2B, 0x00] => Ok(MagType::Chao),
            [0x02, 0x2C, 0x00] => Ok(MagType::ChuChu),
            [0x02, 0x2D, 0x00] => Ok(MagType::KapuKapu),
            [0x02, 0x2E, 0x00] => Ok(MagType::AngelsWing),
            [0x02, 0x2F, 0x00] => Ok(MagType::DevilsWing),
            [0x02, 0x30, 0x00] => Ok(MagType::Elenor),
            [0x02, 0x31, 0x00] => Ok(MagType::MarkIII),
            [0x02, 0x32, 0x00] => Ok(MagType::MasterSystem),
            [0x02, 0x33, 0x00] => Ok(MagType::Genesis),
            [0x02, 0x34, 0x00] => Ok(MagType::SegaSaturn),
            [0x02, 0x35, 0x00] => Ok(MagType::Dreamcast),
            [0x02, 0x36, 0x00] => Ok(MagType::Hamburger),
            [0x02, 0x37, 0x00] => Ok(MagType::PanzersTail),
            [0x02, 0x38, 0x00] => Ok(MagType::DevilsTail),
            [0x02, 0x39, 0x00] => Ok(MagType::Deva),
            [0x02, 0x3A, 0x00] => Ok(MagType::Rati),
            [0x02, 0x3B, 0x00] => Ok(MagType::Savitri),
            [0x02, 0x3C, 0x00] => Ok(MagType::Rukmin),
            [0x02, 0x3D, 0x00] => Ok(MagType::Pushan),
            [0x02, 0x3E, 0x00] => Ok(MagType::Diwari),
            [0x02, 0x3F, 0x00] => Ok(MagType::Sato),
            [0x02, 0x40, 0x00] => Ok(MagType::Bhima),
            [0x02, 0x41, 0x00] => Ok(MagType::Nidra),
            [0x02, 0x42, 0x00] => Ok(MagType::GeungSi),
            [0x02, 0x44, 0x00] => Ok(MagType::Tellusis),
            [0x02, 0x45, 0x00] => Ok(MagType::StrikerUnit),
            [0x02, 0x46, 0x00] => Ok(MagType::Pioneer),
            [0x02, 0x47, 0x00] => Ok(MagType::Puyo),
            [0x02, 0x48, 0x00] => Ok(MagType::Moro),
            [0x02, 0x49, 0x00] => Ok(MagType::Rappy),
            [0x02, 0x4A, 0x00] => Ok(MagType::Yahoo),
            [0x02, 0x4B, 0x00] => Ok(MagType::GaelGiel),
            [0x02, 0x4C, 0x00] => Ok(MagType::Agastya),
            _ => Err(ItemParseError::InvalidMagType),
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
pub enum MagModifier {
    FeedMag{
        food: ToolType,
    },
    BankMag, // when putting a mag in the bank it truncates the values which has applications when raising degenerate mags
    MagCell(ToolType),
}

#[derive(Debug, Copy, Clone, PartialEq)]
pub enum PhotonBlast {
    Farlla,
    Estlla,
    Leilla,
    Pilla,
    Golla,
    MyllaYoulla,
}

#[derive(Debug, Clone, PartialEq)]
pub struct Mag {
    pub mag: MagType,
    pub def: u16,
    pub pow: u16,
    pub dex: u16,
    pub mnd: u16,
    pub synchro: u8,
    pub iq: u8,
    pub photon_blast: [Option<PhotonBlast>; 3],
    pub color: u8,
    pub modifiers: Vec<MagModifier>,
}


impl Mag {
    pub fn as_bytes(&self) -> [u8; 16] {
        let mut result = [0; 16];
        result[0..3].copy_from_slice(&self.mag.value());
        result[3] = self.photon_blast_value();
        result[4..6].copy_from_slice(&self.def.to_le_bytes());
        result[6..8].copy_from_slice(&self.pow.to_le_bytes());
        result[8..10].copy_from_slice(&self.dex.to_le_bytes());
        result[10..12].copy_from_slice(&self.mnd.to_le_bytes());
        result[12] = self.synchro;
        result[13] = self.iq;
        result[14] = self.photon_blast_count();
        result[15] = self.color;
        result
    }

    fn photon_blast_value(&self) -> u8 {
        let mut photon_blast_list = vec![PhotonBlast::Farlla,
                                         PhotonBlast::Estlla,
                                         PhotonBlast::Golla,
                                         PhotonBlast::Pilla,
                                         PhotonBlast::Leilla,
                                         PhotonBlast::MyllaYoulla];
        let mut photon_blast: u8 = 0;

        if let Some(ref pb_mid) = self.photon_blast[0] {
            match *pb_mid {
                PhotonBlast::Farlla => {},
                PhotonBlast::Estlla =>      photon_blast |= 1,
                PhotonBlast::Golla =>       photon_blast |= 2,
                PhotonBlast::Pilla =>       photon_blast |= 3,
                PhotonBlast::Leilla =>      photon_blast |= 4,
                PhotonBlast::MyllaYoulla => photon_blast |= 5,
            }

            photon_blast_list.retain(|k| k != pb_mid);
        }
        if let Some(ref pb_right) = self.photon_blast[1] {
            match *pb_right {
                PhotonBlast::Farlla => {}
                PhotonBlast::Estlla =>      photon_blast |= 1 << 3,
                PhotonBlast::Golla =>       photon_blast |= 2 << 3,
                PhotonBlast::Pilla =>       photon_blast |= 3 << 3,
                PhotonBlast::Leilla =>      photon_blast |= 4 << 3,
                PhotonBlast::MyllaYoulla => photon_blast |= 5 << 3,
            }

            photon_blast_list.retain(|k| k != pb_right);
        }
        if let Some(ref pb_left) = self.photon_blast[2] {
            if let Some(pos) = photon_blast_list.iter().position(|k| k == pb_left) {
                photon_blast |= (pos as u8) << 6;
            };
        }

        photon_blast
    }

    fn photon_blast_count(&self) -> u8 {
        let mut count = 0;
        for i in 0..3 {
            if let Some(_) = self.photon_blast[i] {
                count |= 1 << i
            };
        }
        count
    }

    pub fn from_bytes(data: [u8; 16]) -> Result<Mag, ItemParseError> {
        let m = MagType::parse_type([data[0], data[1], data[2]]); 
        if m.is_ok() {
            let mut def = u16::from_le_bytes([data[4], data[5]]);
            let mut pow = u16::from_le_bytes([data[6], data[7]]);
            let mut dex = u16::from_le_bytes([data[8], data[9]]);
            let mut mind = u16::from_le_bytes([data[10], data[11]]);

            if (def/100 + dex/100 + pow/100 + mind/100) > 200 {
                def = 0;
                pow = 0;
                dex = 0;
                mind = 0;
            }

            let sync = data[12] % 121; // TODO: handle invalid values.
            let iq = data[13] % 201; // TODO: handle invalid values.

            Ok(Mag{
                mag: m.unwrap(),
                def: def,
                pow: pow,
                dex: dex,
                mnd: mind,
                synchro: sync,
                iq: iq,
                photon_blast: [None, None, None], // TODO: actually get PBs from bytes
                color: data[15] % 18,
                modifiers: Vec::new(),
            })
        }
        else {
            Err(ItemParseError::InvalidMagBytes) // TODO: error handling if wrong bytes are given
        }
    }


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

    #[test]
    fn test_load_mag_stats() {
        let mut f = std::fs::File::open("data/item_stats/mag_stats.toml").unwrap();
        let mut s = String::new();
        f.read_to_string(&mut s).unwrap();

        let mags: HashMap<String, MagStats> = toml::from_str(&s).unwrap();
        let _mags = mags.into_iter()
            .map(|(name, stats)| {
                (name.parse().unwrap(), stats)
            })
            .collect::<HashMap<MagType, MagStats>>();
    }

    #[test]
    fn test_load_mag_feed_table() {
        let mut f = std::fs::File::open("data/item_stats/mag_feed_table.toml").unwrap();
        let mut s = String::new();
        f.read_to_string(&mut s).unwrap();

        let mut feed: HashMap<String, Vec<HashMap<String, MagFeedTable>>> = toml::from_str(&s).unwrap();
        let feed = feed.remove("feedtable".into()).unwrap();
        let _feed = feed.into_iter()
            .map(|table| {
                table.into_iter()
                    .map(|(tool, stats)| {
                        (tool.parse().unwrap(), stats)
                    })
                    .collect()
            })
            .collect::<Vec<HashMap<ToolType, MagFeedTable>>>();
    }

}