#pragma once

#include "td/Defines.h"
#include "Towers.h"
#include "Types.h"
#include "Team.h"

#include "td/misc/ObjectNotifier.h"

#include <vector>
#include <memory>

namespace td {
namespace game {

struct WalkableTile;

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;
	Vec2f 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, Vec2f 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 Vec2f& 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
	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
	float m_HitCooldown;

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

	TeamCastle* m_CastleTarget;
	utils::CooldownTimer m_AttackTimer;

public:
	Mob(MobID id, MobLevel level, PlayerID sender) : m_ID(id), m_Sender(sender), m_Level(level),
		m_HitCooldown(0), m_EffectFireTimer(1000), m_EffectPoisonTimer(1000),
		m_EffectHealTimer(1000), m_CastleTarget(nullptr), m_AttackTimer(1000) {

	}

	virtual MobType GetType() const = 0;

	virtual void Tick(std::uint64_t delta, World* world);

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

	MobID GetMobID() const { return m_ID; }
	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); }
	void SetHealth(float newHealth) { m_Health = newHealth; }
	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_CastleTarget != nullptr; }

	void Damage(float dmg, const Tower* damager) {
		m_Health = std::max(0.0f, m_Health - dmg);
		m_LastDamage = damager;
		m_HitCooldown = 0.1;
	}

	void Heal(float heal) {
		m_Health = std::min(static_cast<float>(GetStats()->GetMaxLife()), m_Health + heal);
	}

	void SetMobReachedCastle(TeamCastle* castle) { m_CastleTarget = castle; } // used when mob is in front of the castle

	bool IsImmuneTo(TowerType type);

	bool IsImmuneTo(EffectType type);
	void AddEffect(EffectType type, float durationSec, Tower* tower);
	bool HasEffect(EffectType type);

	bool HasTakenDamage() { return m_HitCooldown > 0; }

	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, World* world);
	void AttackCastle(std::uint64_t delta, World* world);
	void Move(std::uint64_t delta, World* world);
	void Walk(std::uint64_t delta, World* world);
	void MoveBack(const TeamCastle& castle, World* world);
	void ChangeDirection(const WalkableTile& tile, World* world);
	bool IsTouchingCastle(const TeamCastle& castle) const;
	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);

}

class MobListener {
public:
	virtual void OnMobSpawn(Mob* mob) {}
	virtual void OnMobDie(Mob* mob) {}

	virtual void OnMobDamage(Mob* target, float damage, Tower* damager) {}

	virtual void OnMobTouchCastle(Mob* damager, TeamCastle* enemyCastle) {}
	virtual void OnMobCastleDamage(Mob* damager, TeamCastle* enemyCastle, float damage) {}
};

typedef utils::ObjectNotifier<MobListener> MobNotifier;

} // namespace game
} // namespace td

