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

use libpso::packet::ship::{UpdateConfig, WriteInfoboard};
use libpso::character::character::{DEFAULT_PALETTE_CONFIG, DEFAULT_TECH_MENU};
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 = tech.as_value();
                techlist[index as usize] = level.0 - 1;
                techlist
            })
    }
}


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

impl CharacterConfig {
    fn new() -> CharacterConfig {
        CharacterConfig {
            raw_data: DEFAULT_PALETTE_CONFIG,
        }
    }

    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(Clone)]
pub struct CharacterTechMenu {
    pub tech_menu: [u8; 40],
}

impl CharacterTechMenu {
    fn new() -> CharacterTechMenu {
        CharacterTechMenu {
            tech_menu: DEFAULT_TECH_MENU,
        }
    }

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

#[derive(Clone, Default)]
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(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
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 meseta: u32,
    pub bank_meseta: 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::new(),
            config: CharacterConfig::new(),
            info_board: CharacterInfoboard::new(),
            guildcard: CharacterGuildCard::default(),
            materials: CharacterMaterials::default(),
            tech_menu: CharacterTechMenu::new(),
            meseta: 0,
            bank_meseta: 0,
        }
    }
}

#[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,
    pub materials: CharacterMaterials,

    pub tech_menu: CharacterTechMenu,
    pub meseta: u32,
    // TODO: this should not be tied to the character
    pub bank_meseta: u32,
}