use crate::{make_case_elim, make_proj_elim};
use rand::{seq::SliceRandom, thread_rng, Rng};
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};

make_proj_elim!(
    body,
    #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
    pub struct Body {
        pub species: Species,
        pub body_type: BodyType,
        #[typed(pure)]
        pub hair_style: u8,
        #[typed(pure)]
        pub beard: u8,
        #[typed(pure)]
        pub eyes: u8,
        #[typed(pure)]
        pub accessory: u8,
        #[typed(pure)]
        pub hair_color: u8,
        #[typed(pure)]
        pub skin: u8,
        #[typed(pure)]
        pub eye_color: u8,
    }
);

impl Body {
    pub fn random() -> Self {
        let mut rng = thread_rng();
        let species = *(&ALL_SPECIES).choose(&mut rng).unwrap();
        Self::random_with(&mut rng, &species)
    }

    #[inline]
    pub fn random_with(rng: &mut impl Rng, &species: &Species) -> Self {
        let body_type = *(&ALL_BODY_TYPES).choose(rng).unwrap();
        Self {
            species,
            body_type,
            hair_style: rng.gen_range(0..species.num_hair_styles(body_type)),
            beard: rng.gen_range(0..species.num_beards(body_type)),
            accessory: rng.gen_range(0..species.num_accessories(body_type)),
            hair_color: rng.gen_range(0..species.num_hair_colors()),
            skin: rng.gen_range(0..species.num_skin_colors()),
            eye_color: rng.gen_range(0..species.num_eye_colors()),
            eyes: rng.gen_range(0..1), /* TODO Add a way to set specific head-segments for NPCs
                                        * with the default being a random one */
        }
    }

    pub fn validate(&mut self) {
        self.hair_style = self
            .hair_style
            .min(self.species.num_hair_styles(self.body_type) - 1);
        self.beard = self.beard.min(self.species.num_beards(self.body_type) - 1);
        self.hair_color = self.hair_color.min(self.species.num_hair_colors() - 1);
        self.skin = self.skin.min(self.species.num_skin_colors() - 1);
        self.eyes = self.eyes.min(self.species.num_eyes(self.body_type) - 1);
        self.eye_color = self.eye_color.min(self.species.num_eye_colors() - 1);
        self.accessory = self
            .accessory
            .min(self.species.num_accessories(self.body_type) - 1);
    }
}

impl From<Body> for super::Body {
    fn from(body: Body) -> Self { super::Body::Humanoid(body) }
}

make_case_elim!(
    species,
    #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
    #[repr(u32)]
    pub enum Species {
        Danari = 0,
        Dwarf = 1,
        Elf = 2,
        Human = 3,
        Orc = 4,
        Undead = 5,
    }
);

/// Data representing per-species generic data.
///
/// NOTE: Deliberately don't (yet?) implement serialize.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AllSpecies<SpeciesMeta> {
    pub danari: SpeciesMeta,
    pub dwarf: SpeciesMeta,
    pub elf: SpeciesMeta,
    pub human: SpeciesMeta,
    pub orc: SpeciesMeta,
    pub undead: SpeciesMeta,
}

impl<'a, SpeciesMeta> core::ops::Index<&'a Species> for AllSpecies<SpeciesMeta> {
    type Output = SpeciesMeta;

    #[inline]
    fn index(&self, &index: &'a Species) -> &Self::Output {
        match index {
            Species::Danari => &self.danari,
            Species::Dwarf => &self.dwarf,
            Species::Elf => &self.elf,
            Species::Human => &self.human,
            Species::Orc => &self.orc,
            Species::Undead => &self.undead,
        }
    }
}

pub const ALL_SPECIES: [Species; 6] = [
    Species::Danari,
    Species::Dwarf,
    Species::Elf,
    Species::Human,
    Species::Orc,
    Species::Undead,
];

impl<'a, SpeciesMeta: 'a> IntoIterator for &'a AllSpecies<SpeciesMeta> {
    type IntoIter = std::iter::Copied<std::slice::Iter<'static, Self::Item>>;
    type Item = Species;

    fn into_iter(self) -> Self::IntoIter { ALL_SPECIES.iter().copied() }
}

// Skin colors
pub const DANARI_SKIN_COLORS: [Skin; 4] = [
    Skin::DanariOne,
    Skin::DanariTwo,
    Skin::DanariThree,
    Skin::DanariFour,
];
pub const DWARF_SKIN_COLORS: [Skin; 14] = [
    Skin::Skin1,
    Skin::Skin2,
    Skin::Skin3,
    Skin::Skin4,
    Skin::Skin5,
    Skin::Skin6,
    Skin::Skin7,
    Skin::Skin8,
    Skin::Skin9,
    Skin::Skin10,
    Skin::Skin11,
    Skin::Skin12,
    Skin::Iron,
    Skin::Steel,
];
pub const ELF_SKIN_COLORS: [Skin; 13] = [
    Skin::Skin1,
    Skin::Skin2,
    Skin::Skin3,
    Skin::Skin5,
    Skin::Skin6,
    Skin::Skin7,
    Skin::Skin8,
    Skin::Skin9,
    Skin::Skin10,
    Skin::Skin11,
    Skin::Skin12,
    Skin::ElfOne,
    Skin::ElfTwo,
    //Skin::ElfThree,
];
pub const HUMAN_SKIN_COLORS: [Skin; 18] = [
    Skin::Skin1,
    Skin::Skin2,
    Skin::Skin3,
    Skin::Skin4,
    Skin::Skin5,
    Skin::Skin6,
    Skin::Skin7,
    Skin::Skin8,
    Skin::Skin9,
    Skin::Skin10,
    Skin::Skin11,
    Skin::Skin12,
    Skin::Skin13,
    Skin::Skin14,
    Skin::Skin15,
    Skin::Skin16,
    Skin::Skin17,
    Skin::Skin18,
];
pub const ORC_SKIN_COLORS: [Skin; 4] = [Skin::OrcOne, Skin::OrcTwo, Skin::OrcThree, Skin::OrcFour];
pub const UNDEAD_SKIN_COLORS: [Skin; 3] = [Skin::UndeadOne, Skin::UndeadTwo, Skin::UndeadThree];

// Eye colors
pub const DANARI_EYE_COLORS: [EyeColor; 3] = [
    EyeColor::CuriousGreen,
    EyeColor::LoyalBrown,
    EyeColor::ViciousRed,
];
pub const DWARF_EYE_COLORS: [EyeColor; 4] = [
    EyeColor::CuriousGreen,
    EyeColor::LoyalBrown,
    EyeColor::NobleBlue,
    EyeColor::CornflowerBlue,
];
pub const ELF_EYE_COLORS: [EyeColor; 4] = [
    EyeColor::NobleBlue,
    EyeColor::CornflowerBlue,
    EyeColor::CuriousGreen,
    EyeColor::LoyalBrown,
];
pub const HUMAN_EYE_COLORS: [EyeColor; 4] = [
    EyeColor::NobleBlue,
    EyeColor::CornflowerBlue,
    EyeColor::CuriousGreen,
    EyeColor::LoyalBrown,
];
pub const ORC_EYE_COLORS: [EyeColor; 5] = [
    EyeColor::LoyalBrown,
    EyeColor::ExoticPurple,
    EyeColor::AmberOrange,
    EyeColor::PineGreen,
    EyeColor::CornflowerBlue,
];
pub const UNDEAD_EYE_COLORS: [EyeColor; 5] = [
    EyeColor::ViciousRed,
    EyeColor::PumpkinOrange,
    EyeColor::GhastlyYellow,
    EyeColor::MagicPurple,
    EyeColor::ToxicGreen,
];

impl Species {
    fn skin_colors(self) -> &'static [Skin] {
        match self {
            Species::Danari => &DANARI_SKIN_COLORS,
            Species::Dwarf => &DWARF_SKIN_COLORS,
            Species::Elf => &ELF_SKIN_COLORS,
            Species::Human => &HUMAN_SKIN_COLORS,
            Species::Orc => &ORC_SKIN_COLORS,
            Species::Undead => &UNDEAD_SKIN_COLORS,
        }
    }

    fn eye_colors(self) -> &'static [EyeColor] {
        match self {
            Species::Danari => &DANARI_EYE_COLORS,
            Species::Dwarf => &DWARF_EYE_COLORS,
            Species::Elf => &ELF_EYE_COLORS,
            Species::Human => &HUMAN_EYE_COLORS,
            Species::Orc => &ORC_EYE_COLORS,
            Species::Undead => &UNDEAD_EYE_COLORS,
        }
    }

    /// FIXME: This is a hack!  The only reason we need to do this is because
    /// hair colors are currently just indices into an array, not enum
    /// variants.  Once we have proper variants for hair colors, we won't
    /// need to do this anymore, since we will use locally defined arrays to
    /// represent per-species stuff (or have some other solution for validity).
    pub fn num_hair_colors(self) -> u8 {
        match self {
            Species::Danari => 12,
            Species::Dwarf => 21,
            Species::Elf => 24,
            Species::Human => 22,
            Species::Orc => 11,
            Species::Undead => 22,
        }
    }

    pub fn skin_color(self, val: u8) -> Skin {
        self.skin_colors()
            .get(val as usize)
            .copied()
            .unwrap_or(Skin::Skin3)
    }

    pub fn num_skin_colors(self) -> u8 { self.skin_colors().len() as u8 }

    pub fn eye_color(self, val: u8) -> EyeColor {
        self.eye_colors()
            .get(val as usize)
            .copied()
            .unwrap_or(EyeColor::NobleBlue)
    }

    pub fn num_eye_colors(self) -> u8 { self.eye_colors().len() as u8 }

    pub fn num_hair_styles(self, body_type: BodyType) -> u8 {
        match (self, body_type) {
            (Species::Danari, BodyType::Female) => 4,
            (Species::Danari, BodyType::Male) => 4,
            (Species::Dwarf, BodyType::Female) => 8,
            (Species::Dwarf, BodyType::Male) => 4,
            (Species::Elf, BodyType::Female) => 21,
            (Species::Elf, BodyType::Male) => 4,
            (Species::Human, BodyType::Female) => 19,
            (Species::Human, BodyType::Male) => 17,
            (Species::Orc, BodyType::Female) => 7,
            (Species::Orc, BodyType::Male) => 8,
            (Species::Undead, BodyType::Female) => 6,
            (Species::Undead, BodyType::Male) => 5,
        }
    }

    pub fn num_accessories(self, body_type: BodyType) -> u8 {
        match (self, body_type) {
            (Species::Danari, BodyType::Female) => 1,
            (Species::Danari, BodyType::Male) => 1,
            (Species::Dwarf, BodyType::Female) => 7,
            (Species::Dwarf, BodyType::Male) => 7,
            (Species::Elf, BodyType::Female) => 2,
            (Species::Elf, BodyType::Male) => 1,
            (Species::Human, BodyType::Female) => 1,
            (Species::Human, BodyType::Male) => 1,
            (Species::Orc, BodyType::Female) => 4,
            (Species::Orc, BodyType::Male) => 6,
            (Species::Undead, BodyType::Female) => 2,
            (Species::Undead, BodyType::Male) => 2,
        }
    }

    pub fn num_eyebrows(self, _body_type: BodyType) -> u8 { 1 }

    pub fn num_eyes(self, body_type: BodyType) -> u8 {
        match (self, body_type) {
            (Species::Danari, BodyType::Female) => 6,
            (Species::Danari, BodyType::Male) => 8,
            (Species::Dwarf, BodyType::Female) => 6,
            (Species::Dwarf, BodyType::Male) => 8,
            (Species::Elf, BodyType::Female) => 6,
            (Species::Elf, BodyType::Male) => 7,
            (Species::Human, BodyType::Female) => 6,
            (Species::Human, BodyType::Male) => 7,
            (Species::Orc, BodyType::Female) => 6,
            (Species::Orc, BodyType::Male) => 2,
            (Species::Undead, BodyType::Female) => 3,
            (Species::Undead, BodyType::Male) => 4,
        }
    }

    pub fn num_beards(self, body_type: BodyType) -> u8 {
        match (self, body_type) {
            (Species::Danari, BodyType::Female) => 1,
            (Species::Danari, BodyType::Male) => 2,
            (Species::Dwarf, BodyType::Female) => 1,
            (Species::Dwarf, BodyType::Male) => 20,
            (Species::Elf, BodyType::Female) => 1,
            (Species::Elf, BodyType::Male) => 1,
            (Species::Human, BodyType::Female) => 1,
            (Species::Human, BodyType::Male) => 4,
            (Species::Orc, BodyType::Female) => 1,
            (Species::Orc, BodyType::Male) => 3,
            (Species::Undead, BodyType::Female) => 1,
            (Species::Undead, BodyType::Male) => 1,
        }
    }
}

make_case_elim!(
    body_type,
    #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
    #[repr(u32)]
    pub enum BodyType {
        Female = 0,
        Male = 1,
    }
);

pub const ALL_BODY_TYPES: [BodyType; 2] = [BodyType::Female, BodyType::Male];

make_case_elim!(
    eye_color,
    #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize_repr, Deserialize_repr)]
    #[repr(u32)]
    pub enum EyeColor {
        VigorousBlack = 0,
        NobleBlue = 1,
        CuriousGreen = 2,
        LoyalBrown = 3,
        ViciousRed = 4,
        PumpkinOrange = 5,
        GhastlyYellow = 6,
        MagicPurple = 7,
        ToxicGreen = 8,
        ExoticPurple = 9,
        SulfurYellow = 10,
        AmberOrange = 11,
        PineGreen = 12,
        CornflowerBlue = 13,
    }
);

make_case_elim!(
    skin,
    #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize_repr, Deserialize_repr)]
    #[repr(u32)]
    pub enum Skin {
        Skin1 = 0,
        Skin2 = 1,
        Skin3 = 2,
        Skin4 = 3,
        Skin5 = 4,
        Skin6 = 5,
        Iron = 6,
        Steel = 7,
        DanariOne = 8,
        DanariTwo = 9,
        DanariThree = 10,
        DanariFour = 11,
        ElfOne = 12,
        ElfTwo = 13,
        //ElfThree = 14,
        OrcOne = 14,
        OrcTwo = 15,
        OrcThree = 16,
        UndeadOne = 17,
        UndeadTwo = 18,
        UndeadThree = 19,
        Skin7 = 20,
        Skin8 = 21,
        Skin9 = 22,
        Skin10 = 23,
        Skin11 = 24,
        Skin12 = 25,
        Skin13 = 26,
        Skin14 = 27,
        Skin15 = 28,
        Skin16 = 29,
        Skin17 = 30,
        Skin18 = 31,
        OrcFour = 32,
    }
);
