use crate::equipment::EquipmentBlock;
use crate::statistics::StatBlock;
use crate::status::Status;
use rand::seq::SliceRandom;
use rand::Rng;

const MASC_NAMES: &'static [&'static str] = &["Antonio", "Brandon", "Charles", "Denzel", "Edgar"];
const FEMME_NAMES: &'static [&'static str] = &["Alice", "Bethany", "Cynthia", "Dawn", "Elizabeth"];
const NEUTR_NAMES: &'static [&'static str] = &["Aspen", "Blake", "Charlie", "Dekota", "Emery"];

#[derive(Debug, Copy, Clone)]
pub enum Gender {
    Man,
    Woman,
    Nonbinary,
    DemiGirl,
    DemiBoy,
    NoGender,
}

#[derive(Debug, Clone)]
pub struct Unit {
    name: String,
    level: u8,
    exp: u32,
    status: Option<(Option<Status>,Option<Status>)>,
    gender: Gender,
    base_stats: StatBlock,
    hp: u16,
    jel: u8,
    jel_total: u8,
    is_monster: bool,
    equipment: Option<EquipmentBlock>,
}

impl Unit {
    pub fn new() -> Unit {
        let mut rng = rand::thread_rng();
        let j = rng.gen_range(10..26);
        Unit {
            name: String::from("Basic Unit"),
            level: 1,
            exp: 0,
            status: None,
            gender: Gender::NoGender,
            base_stats: StatBlock::new(),
            hp: 18,
            jel: j,
            jel_total: j,
            is_monster: true,
            equipment: None,
        }
    }
    pub fn name(self) -> String {
        self.name
    }
    pub fn level(self) -> u8 {
        self.level
    }
    pub fn gender(self) -> Gender {
        self.gender
    }
    pub fn stats(self) -> StatBlock {
        self.base_stats + self.stat_bonus()
    }
    pub fn base_stats(self) -> StatBlock {
        self.base_stats
    }
    pub fn stat_bonus(self) -> StatBlock {
        match self.equipment() {
            Some(e) => e.bonuses(),
            None => StatBlock::new_zero(),
        }
    }
    pub fn hp(self) -> u16 {
        self.hp
    }
    pub fn hp_max(self) -> u16 {
        u16::from(
            18u16
                + (u16::from(self.level)
                    * u16::from(0u8.wrapping_add_signed(self.stats().vitality()))),
        )
    }
    pub fn jel(self) -> u8 {
        self.jel
    }
    pub fn jel_total(self) -> u8 {
        self.jel_total
    }
    pub fn is_monster(self) -> bool {
        self.is_monster
    }
    pub fn equipment(self) -> Option<EquipmentBlock> {
        if self.is_monster {
            return None;
        }
        self.equipment
    }

    pub fn generate_gender() -> Gender {
        let gender_variants = [
            Gender::Man,
            Gender::Woman,
            Gender::Nonbinary,
            Gender::DemiGirl,
            Gender::DemiBoy,
        ];
        match gender_variants.choose(&mut rand::thread_rng()) {
            Some(g) => g.to_owned(),
            None => Gender::NoGender,
        }
    }
    pub fn generate_name(g: &Gender) -> String {
        let names = match g {
            Gender::Man => MASC_NAMES.to_vec(),
            Gender::Woman => FEMME_NAMES.to_vec(),
            Gender::Nonbinary => NEUTR_NAMES.to_vec(),
            Gender::DemiGirl => [FEMME_NAMES, NEUTR_NAMES].concat(),
            Gender::DemiBoy => [MASC_NAMES, NEUTR_NAMES].concat(),
            Gender::NoGender => [MASC_NAMES, FEMME_NAMES, NEUTR_NAMES].concat(),
            _ => return String::from("MISSINGNO"),
        };
        match names.choose(&mut rand::thread_rng()) {
            Some(n) => String::from(n.clone()),
            None => String::from("MISSINGNO"),
        }
    }

    pub fn human_battle_maniac(lv: u8) -> Unit {
        let mut rng = rand::thread_rng();
        let mut j = rng.gen_range(10..26);
        if lv > 20 {
            j += rng.gen_range(10..26);
        }
        let g = Unit::generate_gender();
        let n = format!("Battle Maniac {}", Unit::generate_name(&g)); // Excuse me, but may I
                                                                      // borrow your gender for a
                                                                      // moment?
        let mut sb = StatBlock::new();
        for _i in 0..lv {
            match rng.gen_range(0..4) {
                0 => sb.add_strength(1),
                1 => sb.add_defense(1),
                2 => sb.add_vitality(1),
                3 => sb.add_intellect(1),
                _ => {
                    eprintln!("ERROR! human_battle_maniac() stat assign out of bounds!");
                    sb.add_vitality(1);
                },
            };
        }
        let mut equip = EquipmentBlock::new();

        //TODO: create equipment and equipment allocation algorithm
        Unit {
            name: n,
            level: lv,
            exp: 0, //TODO!! Fix this
            status: None,
            gender: g,
            base_stats: sb.clone(),
            hp: u16::from(
                18u16 + (u16::from(lv) * u16::from(0u8.wrapping_add_signed(sb.vitality()))),
            ),
            jel: j,
            jel_total: j,
            is_monster: false,
            equipment: Some(equip),
        }
    }
    pub fn human_street_samurai(lv: u8) -> Unit {
        let mut rng = rand::thread_rng();
        let mut j = rng.gen_range(10..26);
        if rng.gen_range(0..100) < 25 && lv > 20 {
            j += rng.gen_range(10..26);
        }
        let g = Unit::generate_gender();
        let n = format!("Street Samurai {}", Unit::generate_name(&g));

        let mut sb = StatBlock::new();
        for _i in 0..lv {
            match rng.gen_range(0..4) {
                0 => sb.add_strength(1),
                1 => sb.add_defense(1),
                2 => sb.add_vitality(1),
                3 => sb.add_agility(1),
                _ => {
                    eprintln!("ERROR! human_street_samurai() stat assign out of bounds!");
                    sb.add_vitality(1);
                },
            };
        }
        let mut equip = EquipmentBlock::new();
        //TODO: create equipment and equipment allocation algorithm

        Unit {
            name: n,
            level: lv,
            exp: 0, //TODO!! fix this
            status: None,
            gender: g,
            base_stats: sb.clone(),
            hp: u16::from(
                18u16 + (u16::from(lv) * u16::from(0u8.wrapping_add_signed(sb.vitality()))),
            ),
            jel: j,
            jel_total: j,
            is_monster: false,
            equipment: Some(equip),
        }
    }
}