use crate::gui::Ancestry;
use crate::gui::Class;
use bracket_lib::prelude::*;
use serde::{ Deserialize, Serialize };
use specs::error::NoError;
use specs::prelude::*;
use specs::saveload::{ ConvertSaveload, Marker };
use specs_derive::*;
use std::collections::{ HashMap, HashSet };

// Serialization helper code. We need to implement ConvertSaveload for each type that contains an
// Entity.
pub struct SerializeMe;
// Special component that exists to help serialize the game data
#[derive(Component, Serialize, Deserialize, Clone)]
pub struct SerializationHelper {
    pub map: super::map::Map,
}

#[derive(Component, Serialize, Deserialize, Clone)]
pub struct DMSerializationHelper {
    pub map: super::map::MasterDungeonMap,
    pub log: std::collections::BTreeMap<i32, Vec<crate::gamelog::LogFragment>>,
    pub event_counts: HashMap<String, i32>,
    pub events: HashMap<u32, Vec<String>>,
}

#[derive(Component, ConvertSaveload, Clone)]
pub struct Position {
    pub x: i32,
    pub y: i32,
}

#[derive(Component, Serialize, Deserialize, Clone)]
pub struct OtherLevelPosition {
    pub x: i32,
    pub y: i32,
    pub id: i32,
}

#[derive(Debug, Component, ConvertSaveload, Clone)]
pub struct Renderable {
    pub glyph: FontCharType, // Legacy, and for drawing the morgue map.
    pub sprite: String,
    pub sprite_alt: Option<String>,
    pub fg: RGB,
    pub fg_alt: Option<RGB>,
    pub render_order: i32,
    pub render_order_alt: Option<i32>,
    pub offset: (f32, f32),
    pub offset_alt: Option<(f32, f32)>,
    // 0 = always on top: particle effects
    // 1 = things that should appear infront of the player: railings, etc.
    // 2 = the player
    // 3 = other mobs
    // 4 = interactable items
    // 5 = other props: table, etc.
}

impl Renderable {
    pub fn new(glyph: FontCharType, sprite: String, fg: RGB, render_order: i32) -> Self {
        Self {
            glyph,
            sprite,
            sprite_alt: None,
            fg,
            fg_alt: None,
            render_order,
            render_order_alt: None,
            offset: (0.0, 0.0),
            offset_alt: None,
        }
    }
    pub fn swap(&mut self) {
        let sprite = self.swap_sprite();
        let fg = self.swap_fg();
        let render_order = self.swap_render_order();
        let offset = self.swap_offset();
        let did_something = sprite || fg || render_order || offset;
        if !did_something {
            unreachable!(
                ".swap() was called on a Renderable component, but nothing happened. {:?}",
                self
            );
        }
    }
    pub fn swap_sprite(&mut self) -> bool {
        if let Some(sprite_alt) = &mut self.sprite_alt {
            std::mem::swap(&mut self.sprite, sprite_alt);
            return true;
        }
        false
    }
    pub fn swap_fg(&mut self) -> bool {
        if let Some(fg_alt) = &mut self.fg_alt {
            std::mem::swap(&mut self.fg, fg_alt);
            return true;
        }
        false
    }
    pub fn swap_render_order(&mut self) -> bool {
        if let Some(render_order_alt) = &mut self.render_order_alt {
            std::mem::swap(&mut self.render_order, render_order_alt);
            return true;
        }
        false
    }
    pub fn swap_offset(&mut self) -> bool {
        if let Some(offset_alt) = &mut self.offset_alt {
            std::mem::swap(&mut self.offset, offset_alt);
            return true;
        }
        false
    }
}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Bleeds {
    pub colour: RGB,
}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Player {}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Faction {
    pub name: String,
}

#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
pub enum Movement {
    Static,
    Random,
    RandomWaypoint {
        path: Option<Vec<usize>>,
    },
}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct MoveMode {
    pub mode: Movement,
}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Prop {}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct LootTable {
    pub table: String,
    pub chance: f32,
}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Energy {
    pub current: i32,
    pub speed: i32,
}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Clock {}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct TakingTurn {}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Quips {
    pub available: Vec<String>,
}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Mind {}

#[derive(Component, ConvertSaveload, Clone)]
pub struct Viewshed {
    pub visible_tiles: Vec<Point>,
    pub range: i32,
    pub dirty: bool,
}

#[derive(Component, ConvertSaveload, Clone)]
pub struct Telepath {
    pub telepath_tiles: Vec<Point>,
    pub range: i32,
    pub dirty: bool,
}

#[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct Name {
    pub name: String,
    pub plural: String,
}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct BlocksTile {}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct BlocksVisibility {}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Door {
    pub open: bool,
    pub locked: bool,
    pub blocks_vis: bool,
    pub blocks_move: bool,
}

#[derive(Serialize, Deserialize, Copy, Clone, PartialEq)]
pub enum HungerState {
    Satiated,
    Normal,
    Hungry,
    Weak,
    Fainting,
    Starving,
}

#[derive(Component, Serialize, Deserialize, Clone)]
pub struct HungerClock {
    pub state: HungerState,
    pub duration: i32,
}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct ProvidesNutrition {}

#[derive(Component, Serialize, Deserialize, Clone)]
pub struct HasAncestry {
    pub name: Ancestry,
}

#[derive(Component, Serialize, Deserialize, Clone)]
pub struct HasClass {
    pub name: Class,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Pool {
    pub max: i32,
    pub current: i32,
}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Pools {
    pub hit_points: Pool,
    pub mana: Pool,
    pub xp: i32,
    pub bac: i32,
    pub level: i32,
    pub weight: f32,
    pub god: bool,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Attribute {
    pub base: i32,
    pub bonuses: i32,
    pub exercise: i32,
}

impl Attribute {
    pub fn new(base: i32) -> Self {
        Self {
            base,
            bonuses: 0,
            exercise: 0,
        }
    }
    // Raw attribute score. e.g. 10 base, + 4 from armour: 14 strength.
    pub fn current(&self) -> i32 {
        self.base + self.bonuses
    }
    // Attribute bonus. e.g. 14 strength = +2, 8 strength = -1
    pub fn modifier(&self) -> i32 {
        crate::gamesystem::attr_bonus(self.current())
    }
    pub fn improve(&mut self) {
        if self.exercise < 50 {
            self.exercise += 1;
        }
    }
    pub fn abuse(&mut self) {
        if self.exercise > -50 {
            self.exercise -= 1;
        }
    }
}

#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
pub enum Skill {
    Melee,
    Defence,
    Magic,
}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Skills {
    pub skills: HashMap<Skill, i32>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct KnownSpell {
    pub display_name: String,
    pub mana_cost: i32,
}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct KnownSpells {
    pub list: Vec<KnownSpell>,
}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct GrantsSpell {
    pub spell: String,
}

// TODO: GrantsIntrinsic, Intrinsics, etc. ? Done the same way as spells?
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Attributes {
    pub strength: Attribute,
    pub dexterity: Attribute,
    pub constitution: Attribute,
    pub intelligence: Attribute,
    pub wisdom: Attribute,
    pub charisma: Attribute,
}

impl Attributes {
    pub const STR: i32 = 0;
    pub const DEX: i32 = 1;
    pub const CON: i32 = 2;
    pub const INT: i32 = 3;
    pub const WIS: i32 = 4;
    pub const CHA: i32 = 5;
    pub fn default() -> Self {
        Self {
            strength: Attribute::new(10),
            dexterity: Attribute::new(10),
            constitution: Attribute::new(10),
            intelligence: Attribute::new(10),
            wisdom: Attribute::new(10),
            charisma: Attribute::new(10),
        }
    }
    pub fn with_stats(str: i32, dex: i32, con: i32, int: i32, wis: i32, cha: i32) -> Self {
        Self {
            strength: Attribute::new(str),
            dexterity: Attribute::new(dex),
            constitution: Attribute::new(con),
            intelligence: Attribute::new(int),
            wisdom: Attribute::new(wis),
            charisma: Attribute::new(cha),
        }
    }
    pub fn attr_from_index(&self, attr: i32) -> &Attribute {
        match attr {
            Self::STR => &self.strength,
            Self::DEX => &self.dexterity,
            Self::CON => &self.constitution,
            Self::INT => &self.intelligence,
            Self::WIS => &self.wisdom,
            Self::CHA => &self.charisma,
            _ => unreachable!("Tried to get an attribute that doesn't exist."),
        }
    }
    pub fn exercise(&mut self, attr: i32, improve: bool) {
        match attr {
            Self::STR => {
                if improve {
                    self.strength.improve();
                } else {
                    self.strength.abuse();
                }
            }
            Self::DEX => {
                if improve {
                    self.dexterity.improve();
                } else {
                    self.dexterity.abuse();
                }
            }
            Self::CON => {
                if improve {
                    self.constitution.improve();
                } else {
                    self.constitution.abuse();
                }
            }
            Self::INT => {
                if improve {
                    self.intelligence.improve();
                } else {
                    self.intelligence.abuse();
                }
            }
            Self::WIS => {
                if improve {
                    self.wisdom.improve();
                } else {
                    self.wisdom.abuse();
                }
            }
            Self::CHA => {
                if improve {
                    self.charisma.improve();
                } else {
                    self.charisma.abuse();
                }
            }
            _ => unreachable!("Tried to exercise an attribute that doesn't exist."),
        }
    }
}

#[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct WantsToMelee {
    pub target: Entity,
}

#[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct GrantsXP {
    pub amount: i32,
}

#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
pub enum BUC {
    Cursed,
    Uncursed,
    Blessed,
}

impl BUC {
    pub fn noncursed(&self) -> bool {
        match self {
            BUC::Cursed => false,
            _ => true,
        }
    }
}

#[derive(Component, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Clone)]
pub struct Beatitude {
    pub buc: BUC,
    pub known: bool,
}

#[derive(Debug, Serialize, Deserialize, Copy, Clone, PartialEq, Eq)]
pub enum ItemType {
    Amulet,
    Weapon,
    Armour,
    Comestible,
    Scroll,
    Spellbook,
    Potion,
    Ring,
    Wand,
}

impl ItemType {
    pub fn string(&self) -> &str {
        match self {
            ItemType::Amulet => "Amulets",
            ItemType::Weapon => "Weapons",
            ItemType::Armour => "Armour",
            ItemType::Comestible => "Comestibles",
            ItemType::Scroll => "Scrolls",
            ItemType::Spellbook => "Spellbooks",
            ItemType::Potion => "Potions",
            ItemType::Ring => "Rings",
            ItemType::Wand => "Wands",
        }
    }
}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Item {
    pub weight: f32, // in lbs
    pub value: f32, // base
    pub category: ItemType,
}

#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
pub enum MagicItemClass {
    Common,
    Uncommon,
    Rare,
    VeryRare,
    Legendary,
}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct MagicItem {
    pub class: MagicItemClass,
}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct ObfuscatedName {
    pub name: String,
    pub plural: String,
}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct IdentifiedItem {
    pub name: String,
}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Stackable {}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct EquipmentChanged {}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct WantsToRemoveKey {}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct WantsToDelete {}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Key {
    pub idx: usize,
}

#[derive(PartialEq, Copy, Clone, Serialize, Deserialize)]
pub enum BurdenLevel {
    Burdened,
    Strained,
    Overloaded,
}

#[derive(Component, Serialize, Deserialize, Clone)]
pub struct Burden {
    pub level: BurdenLevel,
}

#[derive(PartialEq, Copy, Clone, Serialize, Deserialize)]
pub enum EquipmentSlot {
    Melee,
    Shield,
    Head,
    Body,
    Hands,
    Feet,
    Neck,
    Back,
}

#[derive(PartialEq, Copy, Clone, Serialize, Deserialize)]
pub enum WeaponAttribute {
    Strength,
    Dexterity,
    Finesse,
}

#[derive(Component, Serialize, Deserialize, Clone)]
pub struct MeleeWeapon {
    pub damage_type: DamageType,
    pub attribute: WeaponAttribute,
    pub damage_n_dice: i32,
    pub damage_die_type: i32,
    pub damage_bonus: i32,
    pub hit_bonus: i32,
}

#[derive(Serialize, Deserialize, Clone)]
pub struct NaturalAttack {
    pub name: String,
    pub damage_type: DamageType,
    pub damage_n_dice: i32,
    pub damage_die_type: i32,
    pub damage_bonus: i32,
    pub hit_bonus: i32,
}

#[derive(Component, Serialize, Deserialize, Clone)]
pub struct NaturalAttacks {
    pub attacks: Vec<NaturalAttack>,
}

#[derive(Component, ConvertSaveload, Clone)]
pub struct ArmourClassBonus {
    pub amount: i32,
}

#[derive(Component, ConvertSaveload, Clone)]
pub struct ToHitBonus {
    pub amount: i32,
}

#[derive(Component, Serialize, Deserialize, Clone)]
pub struct Equippable {
    pub slot: EquipmentSlot,
}

#[derive(Component, ConvertSaveload, Clone)]
pub struct Equipped {
    pub owner: Entity,
    pub slot: EquipmentSlot,
}

#[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct ProvidesHealing {
    pub n_dice: i32,
    pub sides: i32,
    pub modifier: i32,
}

#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize)]
pub enum DamageType {
    Physical,
    Magic, // e.g. magic missiles, silvered weapons
    Fire, // e.g. fireball
    Cold, // e.g. cone of cold
    Poison, // e.g. poison gas
    Forced, // Bypasses any immunities. e.g. Hunger ticks.
}

impl DamageType {
    pub fn is_magic(&self) -> bool {
        match self {
            DamageType::Magic | DamageType::Fire | DamageType::Cold => true,
            _ => false,
        }
    }
}

#[derive(Debug, PartialEq, Copy, Clone, Serialize, Deserialize)]
pub enum DamageModifier {
    None,
    Weakness,
    Resistance,
    Immune,
}

impl DamageModifier {
    const NONE_MOD: f32 = 1.0;
    const WEAK_MOD: f32 = 2.0;
    const RESIST_MOD: f32 = 0.5;
    const IMMUNE_MOD: f32 = 0.0;

    pub fn multiplier(&self) -> f32 {
        match self {
            DamageModifier::None => Self::NONE_MOD,
            DamageModifier::Weakness => Self::WEAK_MOD,
            DamageModifier::Resistance => Self::RESIST_MOD,
            DamageModifier::Immune => Self::IMMUNE_MOD,
        }
    }
}

#[derive(Component, Serialize, Deserialize, Debug, Clone)]
pub struct HasDamageModifiers {
    pub modifiers: HashMap<DamageType, DamageModifier>,
}

impl HasDamageModifiers {
    pub fn modifier(&self, damage_type: &DamageType) -> &DamageModifier {
        self.modifiers.get(damage_type).unwrap_or(&DamageModifier::None)
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Intrinsic {
    Regeneration, // Regenerate 1 HP on every tick
    Speed, // 4/3x speed multiplier
}

impl Intrinsic {
    pub fn describe(&self) -> &str {
        match self {
            Intrinsic::Regeneration => "regenerates health",
            Intrinsic::Speed => "is hasted",
        }
    }
}

#[derive(Component, Serialize, Deserialize, Debug, Clone)]
pub struct Intrinsics {
    pub list: HashSet<Intrinsic>,
}

impl Intrinsics {
    pub fn describe(&self) -> String {
        let mut descriptions = Vec::new();
        for intrinsic in &self.list {
            descriptions.push(intrinsic.describe());
        }
        match descriptions.len() {
            0 =>
                unreachable!("describe() should never be called on an empty Intrinsics component."),
            1 => format!("It {}.", descriptions[0]),
            _ => {
                let last = descriptions.pop().unwrap();
                let joined = descriptions.join(", ");
                format!("It {}, and {}.", joined, last)
            }
        }
    }
}

#[derive(Component, Serialize, Deserialize, Debug, Clone)]
pub struct IntrinsicChanged {
    pub gained: HashSet<Intrinsic>,
    pub lost: HashSet<Intrinsic>,
}

#[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct InflictsDamage {
    pub damage_type: DamageType,
    pub n_dice: i32,
    pub sides: i32,
    pub modifier: i32,
}

#[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct Ranged {
    pub range: i32,
}

#[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct AOE {
    pub radius: i32,
}

#[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct Confusion {
    pub turns: i32,
}

#[derive(Component, Serialize, Deserialize, Clone)]
pub struct Blind {}

#[derive(Component, Serialize, Deserialize, Clone)]
pub struct MagicMapper {}

#[derive(Component, Debug, ConvertSaveload)]
pub struct InBackpack {
    pub owner: Entity,
}

#[derive(Component, Serialize, Deserialize, Clone)]
pub struct WantsToAssignKey {}

#[derive(Component, Debug, ConvertSaveload)]
pub struct WantsToPickupItem {
    pub collected_by: Entity,
    pub item: Entity,
}

#[derive(Component, Debug, ConvertSaveload)]
pub struct WantsToDropItem {
    pub item: Entity,
}

#[derive(Component, Debug, ConvertSaveload)]
pub struct WantsToRemoveItem {
    pub item: Entity,
}

#[derive(Component, Debug, ConvertSaveload)]
pub struct WantsToUseItem {
    pub item: Entity,
    pub target: Option<Point>,
}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct WantsToApproach {
    pub idx: i32,
}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct WantsToFlee {
    pub indices: Vec<usize>, // Dijkstra
}

#[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct Chasing {
    pub target: Entity,
}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Consumable {}

#[derive(Component, Debug, ConvertSaveload)]
pub struct Charges {
    pub uses: i32,
    pub max_uses: i32,
}

#[derive(Component, Serialize, Deserialize, Clone)]
pub struct SpawnParticleLine {
    pub glyph: FontCharType,
    pub sprite: String,
    pub tail_glyph: FontCharType,
    pub tail_sprite: String,
    pub colour: RGB,
    pub lifetime_ms: f32,
    pub trail_colour: RGB,
    pub trail_lifetime_ms: f32,
}

#[derive(Component, Serialize, Deserialize, Clone)]
pub struct SpawnParticleSimple {
    pub glyph: FontCharType,
    pub sprite: String,
    pub colour: RGB,
    pub lifetime_ms: f32,
}

#[derive(Component, Serialize, Deserialize, Clone)]
pub struct SpawnParticleBurst {
    pub glyph: FontCharType,
    pub sprite: String,
    pub head_glyph: FontCharType,
    pub head_sprite: String,
    pub tail_glyph: FontCharType,
    pub tail_sprite: String,
    pub colour: RGB,
    pub lerp: RGB,
    pub lifetime_ms: f32,
    pub trail_colour: RGB,
    pub trail_lifetime_ms: f32,
}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Destructible {}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct ProvidesRemoveCurse {}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct ProvidesIdentify {}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Digger {}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Hidden {}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct SingleActivation {}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct IdentifiedBeatitude {}

#[derive(Component, Clone, ConvertSaveload)]
pub struct ParticleLifetime {
    pub lifetime_ms: f32,
}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct EntryTrigger {}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct EntityMoved {}

#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct MultiAttack {}
