use std::convert::{From, Into, TryFrom, TryInto};
use std::collections::HashMap;

use libpso::packet::ship::{UpdateConfig, WriteInfoboard};
use crate::entity::item::tech::Technique;
use crate::entity::account::UserAccountId;

#[derive(Copy, Clone, Hash, PartialEq, Eq)]
pub enum CharacterClass {
    HUmar,
    HUnewearl,
    HUcast,
    HUcaseal,
    RAmar,
    RAmarl,
    RAcast,
    RAcaseal,
    FOmar,
    FOmarl,
    FOnewm,
    FOnewearl,
}

// TODO: TryFrom
impl From<u8> for CharacterClass {
    fn from(f: u8) -> CharacterClass {
        match f {
            0 => CharacterClass::HUmar,
            1 => CharacterClass::HUnewearl,
            2 => CharacterClass::HUcast,
            3 => CharacterClass::RAmar,
            4 => CharacterClass::RAcast,
            5 => CharacterClass::RAcaseal,
            6 => CharacterClass::FOmarl,
            7 => CharacterClass::FOnewm,
            8 => CharacterClass::FOnewearl,
            9 => CharacterClass::HUcaseal,
            10 => CharacterClass::FOmar,
            11 => CharacterClass::RAmarl,
            _ => panic!("unknown class")
        }
    }
}

impl Into<u8> for CharacterClass {
    fn into(self) -> u8 {
        match self {
             CharacterClass::HUmar => 0,
             CharacterClass::HUnewearl => 1,
             CharacterClass::HUcast => 2,
             CharacterClass::RAmar => 3,
             CharacterClass::RAcast => 4,
             CharacterClass::RAcaseal => 5,
             CharacterClass::FOmarl => 6,
             CharacterClass::FOnewm => 7,
             CharacterClass::FOnewearl => 8,
             CharacterClass::HUcaseal => 9,
             CharacterClass::FOmar => 10,
             CharacterClass::RAmarl => 11,
        }
    }
}



#[derive(Copy, Clone, Hash, PartialEq, Eq, derive_more::Display)]
pub enum SectionID {
    Viridia,
    Greenill,
    Skyly,
    Bluefull,
    Purplenum,
    Pinkal,
    Redria,
    Oran,
    Yellowboze,
    Whitill,
}

impl From<u8> for SectionID {
    fn from(id: u8) -> SectionID {
        match id {
            0 => SectionID::Viridia,
            1 => SectionID::Greenill,
            2 => SectionID::Skyly,
            3 => SectionID::Bluefull,
            4 => SectionID::Purplenum,
            5 => SectionID::Pinkal,
            6 => SectionID::Redria,
            7 => SectionID::Oran,
            8 => SectionID::Yellowboze,
            9 => SectionID::Whitill,
            _ => panic!(),
        }
    }
}

impl Into<u8> for SectionID {
    fn into(self) -> u8 {
        match self {
            SectionID::Viridia => 0,
            SectionID::Greenill => 1,
            SectionID::Skyly => 2,
            SectionID::Bluefull => 3,
            SectionID::Purplenum => 4,
            SectionID::Pinkal => 5,
            SectionID::Redria => 6,
            SectionID::Oran => 7,
            SectionID::Yellowboze => 8,
            SectionID::Whitill => 9,
        }
    }
}


#[derive(Clone, Debug, Default)]
pub struct CharacterAppearance {
    pub costume: u16,
    pub skin: u16,
    pub face: u16,
    pub head: u16,
    pub hair: u16,
    pub hair_r: u16,
    pub hair_g: u16,
    pub hair_b: u16,
    pub prop_x: f32,
    pub prop_y: f32,
}


#[derive(Clone, Debug)]
pub struct TechLevel(pub u8);

#[derive(Clone, Debug)]
pub struct CharacterTechniques {
    techs: HashMap<Technique, TechLevel>
}

impl CharacterTechniques {
    fn new() -> CharacterTechniques {
        CharacterTechniques {
            techs: HashMap::new(),
        }
    }

    pub fn set_tech(&mut self, tech: Technique, level: TechLevel) {
        self.techs.insert(tech, level);
    }

    // from_bytes

    pub fn as_bytes(&self) -> [u8; 20] {
        self.techs.iter()
            .fold([0xFF; 20], |mut techlist, (tech, level)| {
                let index = match tech {
                    Technique::Foie => 0,
                    Technique::Gifoie => 1,
                    Technique::Rafoie => 2,
                    Technique::Barta => 3,
                    Technique::Gibarta => 4,
                    Technique::Rabarta => 5,
                    Technique::Zonde => 6,
                    Technique::Gizonde => 7,
                    Technique::Razonde => 8,
                    Technique::Grants => 9,
                    Technique::Deband => 10,
                    Technique::Jellen => 11,
                    Technique::Zalure => 12,
                    Technique::Shifta => 13,
                    Technique::Ryuker => 14,
                    Technique::Resta => 15,
                    Technique::Anti => 16,
                    Technique::Reverser => 17,
                    Technique::Megid => 18,
                };

                techlist[index] = level.0 - 1;
                techlist
            })
    }
}


#[derive(Clone)]
pub struct CharacterConfig {
    raw_data: [u8; 0xE8],
}

impl CharacterConfig {
    fn new() -> CharacterConfig {
        CharacterConfig {
            raw_data: [0; 0xE8],
        }
    }

    pub fn update(&mut self, new_config: &UpdateConfig) {
        self.raw_data = new_config.config;
    }

    pub fn as_bytes(&self) -> [u8; 0xE8] {
        self.raw_data
    }
}

#[derive(Clone)]
pub struct CharacterInfoboard {
    board: [u16; 172],
}

impl CharacterInfoboard {
    fn new() -> CharacterInfoboard {
        CharacterInfoboard {
            board: [0; 172]
        }
    }

    pub fn as_bytes(&self) -> [u16; 172] {
        self.board
    }

    pub fn update_infoboard(&mut self, new_board: &WriteInfoboard) {
        self.board = libpso::utf8_to_utf16_array!(new_board.message, 172);
    }
}

#[derive(Clone, Default)]
pub struct CharacterGuildCard {
    pub description: String,
}


#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct CharacterEntityId(pub u32);

#[derive(Clone)]
pub struct CharacterEntity {
    pub id: CharacterEntityId,
    pub user_id: UserAccountId,
    pub slot: u32,

    pub name: String,
    pub exp: u32,

    pub char_class: CharacterClass,
    pub section_id: SectionID,

    pub appearance: CharacterAppearance,
    pub techs: CharacterTechniques,
    pub config: CharacterConfig,
    pub info_board: CharacterInfoboard,
    pub guildcard: CharacterGuildCard,
}

impl std::default::Default for CharacterEntity {
    fn default() -> CharacterEntity {
        CharacterEntity {
            id: CharacterEntityId(0),
            user_id: UserAccountId(0),
            slot: 0,
            name: "".into(),
            exp: 0,
            char_class: CharacterClass::HUmar,
            section_id: SectionID::Viridia,
            appearance: CharacterAppearance::default(),
            techs: CharacterTechniques::new(),
            config: CharacterConfig::new(),
            info_board: CharacterInfoboard::new(),
            guildcard: CharacterGuildCard::default(),
        }
    }
}