use std::convert::{From, Into};
use std::collections::HashMap;
use serde::{Serialize, Deserialize};

use libpso::packet::ship::{UpdateConfig, WriteInfoboard, KeyboardConfig};
use libpso::character::character::{DEFAULT_PALETTE_CONFIG, DEFAULT_TECH_MENU, DEFAULT_KEYBOARD_CONFIG1};
use crate::entity::item::tech::Technique;
use crate::entity::account::UserAccountId;

#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, enum_utils::FromStr, derive_more::Display, Serialize, Deserialize, Default)]
pub enum CharacterClass {
    #[default]
    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 From<CharacterClass> for u8 {
    fn from(other: CharacterClass) -> u8 {
        match other {
             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,
        }
    }
}

impl CharacterClass {
    pub fn is_human(&self) -> bool {
        matches!(self,
                 CharacterClass::HUmar |
                 CharacterClass::RAmar |
                 CharacterClass::RAmarl |
                 CharacterClass::FOmar |
                 CharacterClass::FOmarl)
    }
    
    pub fn is_newman(&self) -> bool {
        matches!(self,
                 CharacterClass::HUnewearl |
                 CharacterClass::FOnewm |
                 CharacterClass::FOnewearl)
    }
    
    pub fn is_android(&self) -> bool {
        matches!(self,
                 CharacterClass::HUcast |
                 CharacterClass::HUcaseal |
                 CharacterClass::RAcast |
                 CharacterClass::RAcaseal)
    }
}


#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, enum_utils::FromStr, derive_more::Display, Serialize, Deserialize, Default)]
pub enum SectionID {
    #[default]
    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 From<SectionID> for u8 {
    fn from(other: SectionID) -> u8 {
        match other {
            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, Default)]
pub struct CharacterTechniques {
    pub techs: HashMap<Technique, TechLevel>
}

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

    // from_bytes

    pub fn as_bytes(&self) -> [u8; 20] {
        self.techs.iter()
            .fold([0xFF; 20], |mut techlist, (tech, level)| {
                let index = tech.as_value();
                techlist[index as usize] = level.0;
                techlist
            })
    }
}


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

impl Default for CharacterConfig {
    fn default() -> CharacterConfig {
        CharacterConfig {
            raw_data: DEFAULT_PALETTE_CONFIG,
        }
    }
}

impl CharacterConfig {
    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(Debug, Clone)]
pub struct CharacterInfoboard {
    pub board: [u16; 172],
}

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

impl CharacterInfoboard {
    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(Debug, Clone, Default)]
pub struct CharacterGuildCard {
    pub description: String,
}

#[derive(Debug, Clone)]
pub struct CharacterTechMenu {
    pub tech_menu: [u8; 40],
}

impl Default for CharacterTechMenu {
    fn default() -> CharacterTechMenu {
        CharacterTechMenu {
            tech_menu: DEFAULT_TECH_MENU,
        }
    }
}

impl CharacterTechMenu {
    pub fn as_bytes(&self) -> [u8; 40] {
        self.tech_menu
    }
}

#[derive(Clone, Default, Debug)]
pub struct CharacterMaterials {
    pub power: u32,
    pub mind: u32,
    pub def: u32,
    pub evade: u32,
    pub luck: u32,
    pub hp: u32,
    pub tp: u32,
}

#[derive(Clone)]
pub struct CharacterKeyboardConfig {
    pub keyboard: [u8; 0x16C],
}

impl CharacterKeyboardConfig {
    fn new() -> CharacterKeyboardConfig {
        CharacterKeyboardConfig {
            keyboard: DEFAULT_KEYBOARD_CONFIG1,
        }
    }

    pub fn update(&mut self, new_config: &KeyboardConfig) {
        self.keyboard = new_config.keyboard_config;
    }

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


#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default, derive_more::Display)]
pub struct CharacterEntityId(pub u32);

#[derive(Clone)]
pub struct NewCharacterEntity {
    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,
    pub materials: CharacterMaterials,

    pub tech_menu: CharacterTechMenu,
    pub option_flags: u32,
}

impl NewCharacterEntity {
    pub fn new(user: UserAccountId) -> NewCharacterEntity {
        NewCharacterEntity {
            user_id: user,
            slot: 0,
            name: "".into(),
            exp: 0,
            char_class: CharacterClass::HUmar,
            section_id: SectionID::Viridia,
            appearance: CharacterAppearance::default(),
            techs: CharacterTechniques::default(),
            config: CharacterConfig::default(),
            info_board: CharacterInfoboard::default(),
            guildcard: CharacterGuildCard::default(),
            materials: CharacterMaterials::default(),
            tech_menu: CharacterTechMenu::default(),
            option_flags: 0,
        }
    }
}

#[derive(Clone, Default, Debug)]
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,
    pub materials: CharacterMaterials,

    pub tech_menu: CharacterTechMenu,
    pub option_flags: u32,
}