#pragma once

#include "Towers.h"
#include "Types.h"
#include "Team.h"

#include <vector>
#include <memory>

#include <glm/glm.hpp>

namespace td {
namespace game {

enum class EffectType : std::uint8_t {
    Slowness = 0,
    Stun,
    Fire,
    Poison,
    Heal,
};

enum class MobType : std::uint8_t {
    Zombie = 0,
    Spider,
    Skeleton,
    Pigman,
    Creeper,
    Silverfish,
    Blaze,
    Witch,
    Slime,
    Giant,

    MOB_COUNT
};

typedef std::uint32_t MobID;
typedef std::uint8_t MobLevel;
typedef std::vector<TowerType> TowerImmunities;
typedef std::vector<EffectType> EffectImmunities;

class MobStats {
private:
    float m_Damage;
    float m_Speed;
    glm::vec2 m_Size;
    std::uint16_t m_MoneyCost;
    std::uint16_t m_ExpCost;
    std::uint16_t m_MaxLife;
    std::uint16_t m_ExpReward;
public:
    MobStats(float damage, float speed, glm::vec2 size, std::uint16_t moneyCost,
        std::uint16_t expCost, std::uint16_t expReward,
        std::uint16_t maxLife) : m_Damage(damage), m_Speed(speed),
        m_Size(size), m_MoneyCost(moneyCost), m_ExpCost(expCost),
        m_MaxLife(maxLife), m_ExpReward(expReward) {
    }

    float getDamage() const { return m_Damage; }
    float getMovementSpeed() const { return m_Speed; }
    const glm::vec2& getSize() const { return m_Size; }
    std::uint16_t getMoneyCost() const { return m_MoneyCost; }
    std::uint16_t getExpCost() const { return m_ExpCost; }
    std::uint16_t getExpReward() const { return m_ExpReward; }
    std::uint16_t getMaxLife() const { return m_MaxLife; }
};

struct EffectDuration {
    EffectType type;
    float duration; // in seconds
    const Tower* tower; // the tower that gived the effect
};

const MobStats* getMobStats(MobType type, std::uint8_t level);
const TowerImmunities& getMobTowerImmunities(MobType type, std::uint8_t level);
const EffectImmunities& getMobEffectImmunities(MobType type, std::uint8_t level);

class Mob : public utils::shape::Rectangle {
protected:
    float m_Health;
private:
    MobID m_ID;
    PlayerID m_Sender;
    MobLevel m_Level;
    Direction m_Direction;
    std::vector<EffectDuration> m_Effects;
    const Tower* m_LastDamage;   // the last tower that damaged the mob
    bool m_HasReachedCastle; // if true, no need to walk anymore

    utils::Timer m_EffectFireTimer;
    utils::Timer m_EffectPoisonTimer;
    utils::Timer m_EffectHealTimer;

public:
    Mob(MobID id, MobLevel level, PlayerID sender) : m_Sender(sender), m_Level(level),
        m_HasReachedCastle(false), m_EffectFireTimer(1000), m_EffectPoisonTimer(1000),
        m_EffectHealTimer(1000) {

    }

    virtual MobType getType() const = 0;

    virtual void tick(std::uint64_t delta);

    virtual bool OnDeath(World* world) { return true; }

    const TowerImmunities& getTowerImmunities() const { return getMobTowerImmunities(getType(), m_Level); }
    const EffectImmunities& getEffectImmunities() const { return getMobEffectImmunities(getType(), m_Level); }
    PlayerID getSender() const { return m_Sender; }
    MobLevel getLevel() const { return m_Level; }
    const MobStats* getStats() const { return getMobStats(getType(), m_Level); }
    float getHealth() const { return m_Health; }
    bool isDead() const { return m_Health <= 0; }
    bool isAlive() const { return m_Health > 0; }
    const Tower* getLastDamageTower() { return m_LastDamage; }
    bool hasReachedEnemyCastle() { return m_HasReachedCastle; }

    void damage(float dmg, const Tower* damager) { m_Health = std::max(0.0f, m_Health - dmg); m_LastDamage = damager; }
    void heal(float heal) { m_Health = std::min(static_cast<float>(getStats()->getMaxLife()), m_Health + heal); }
    void setMobReachedCastle() { m_HasReachedCastle = true; } // used when mob is in front of the castle

    bool isImmuneTo(TowerType type);

    bool isImmuneTo(EffectType type);
    void addEffect(EffectType type, float durationSec, const Tower* tower);
    bool hasEffect(EffectType type);

    float getTileX() { return getCenterX() - static_cast<float>(static_cast<std::int32_t>(getCenterX())); } // returns a float between 0 and 1 excluded
    float getTileY() { return getCenterY() - static_cast<float>(static_cast<std::int32_t>(getCenterY())); } // returns a float between 0 and 1 excluded

    Direction getDirection() const { return m_Direction; }
    void setDirection(Direction dir) { m_Direction = dir; }
protected:
    void initMob() {
        m_Health = static_cast<float>(getStats()->getMaxLife());
        setSize(getStats()->getSize().x, getStats()->getSize().y);
    }
private:
    void updateEffects(std::uint64_t delta);
    EffectDuration& getEffect(EffectType type);
};

typedef std::shared_ptr<Mob> MobPtr;

class Zombie : public Mob {
public:
    Zombie(MobID id, std::uint8_t level, PlayerID sender) : Mob(id, level, sender) { initMob(); }

    virtual MobType getType() const { return MobType::Zombie; }
};

class Spider : public Mob {
public:
    Spider(MobID id, std::uint8_t level, PlayerID sender) : Mob(id, level, sender) { initMob(); }

    virtual MobType getType() const { return MobType::Spider; }
};

class Skeleton : public Mob {
public:
    Skeleton(MobID id, std::uint8_t level, PlayerID sender) : Mob(id, level, sender) { initMob(); }

    virtual MobType getType() const { return MobType::Skeleton; }
};

class PigMan : public Mob {
public:
    PigMan(MobID id, std::uint8_t level, PlayerID sender) : Mob(id, level, sender) { initMob(); }

    virtual MobType getType() const { return MobType::Pigman; }
};

class Creeper : public Mob {
public:
    Creeper(MobID id, std::uint8_t level, PlayerID sender) : Mob(id, level, sender) { initMob(); }

    virtual MobType getType() const { return MobType::Creeper; }
};

class Silverfish : public Mob {
public:
    Silverfish(MobID id, std::uint8_t level, PlayerID sender) : Mob(id, level, sender) { initMob(); }

    virtual MobType getType() const { return MobType::Silverfish; }
};

class Blaze : public Mob {
public:
    Blaze(MobID id, std::uint8_t level, PlayerID sender) : Mob(id, level, sender) { initMob(); }

    virtual MobType getType() const { return MobType::Blaze; }
};

class Witch : public Mob {
public:
    Witch(MobID id, std::uint8_t level, PlayerID sender) : Mob(id, level, sender) { initMob(); }

    virtual MobType getType() const { return MobType::Witch; }
};

class Slime : public Mob {
public:
    Slime(MobID id, std::uint8_t level, PlayerID sender) : Mob(id, level, sender) { initMob(); }

    virtual MobType getType() const { return MobType::Slime; }
};

class Giant : public Mob {
public:
    Giant(MobID id, std::uint8_t level, PlayerID sender) : Mob(id, level, sender) { initMob(); }

    virtual MobType getType() const { return MobType::Giant; }
};

namespace MobFactory {
MobPtr createMob(MobID id, MobType type, std::uint8_t level, PlayerID sender);
std::string getMobName(MobType type);
}


} // namespace game
} // namespace td

