#pragma once

#include <string>
#include <memory>

#include "td/misc/Time.h"
#include "td/misc/Shapes.h"

#include "td/game/Types.h"

namespace td {
namespace game {

class World;
class Mob;

typedef std::shared_ptr<Mob> MobPtr;

enum class TowerType : std::uint8_t {
	Archer = 0,
	Ice,
	Sorcerer,
	Zeus,
	Mage,
	Artillery,
	Quake,
	Poison,

	Leach,
	Turret,
	Necromancer,

	TowerCount
};

enum class TowerSize : std::uint8_t {
	Little = 3, // 3x3
	Big = 5,        // 5x5
};

enum class TowerPath : std::uint8_t {
	Top = 0,
	Base, // Base Path
	Bottom
};

class TowerStats {
private:
	float m_Rate;
	float m_Damage;
	std::uint8_t m_Range;
public:
	TowerStats(float rate, float damage, std::uint8_t range) : m_Rate(rate), m_Damage(damage),
		m_Range(range) {
	}

	float GetDamageRate() const { return m_Rate; }
	float GetDamage() const { return m_Damage; }
	std::uint8_t GetRange() const { return m_Range; }
};

class TowerLevel {
private:
	// 1, 2, 3, 4
	std::uint8_t m_Level : 3;
	// 0 : base path       1 : top path (if there is bottom path)     2 : bottom path (if there is top path)
	TowerPath m_Path : 2;
public:
	TowerLevel() : m_Level(1), m_Path(TowerPath::Base) {}
	TowerLevel(std::uint8_t level, TowerPath path) : m_Level(level), m_Path(path) {}

	std::uint8_t GetLevel() const { return m_Level; }
	TowerPath GetPath() const { return m_Path; }

	void SetLevel(std::uint8_t level) { m_Level = level; }
	void SetPath(TowerPath path) { m_Path = path; }

	// operator to sort maps
	friend bool operator<(const TowerLevel& level, const TowerLevel& other) {
		return level.GetLevel() + static_cast<std::uint8_t>(level.GetPath()) * 4 <
			other.GetLevel() + static_cast<std::uint8_t>(other.GetPath()) * 4;
	}
};

const TowerStats* GetTowerStats(TowerType type, TowerLevel level);

typedef std::uint16_t TowerID;

class Tower : public utils::shape::Circle {
private:
	TowerID m_ID;
	TowerType m_Type;
	TowerLevel m_Level{};
	PlayerID m_Builder;
protected:
	utils::CooldownTimer m_Timer;
public:
	Tower(TowerID id, TowerType type, std::int32_t x, std::int32_t y, PlayerID builder) : utils::shape::Circle(x + 0.5f, y + 0.5f, 0), m_ID(id), m_Type(type), m_Builder(builder),
		m_Timer(GetStats()->GetDamageRate() * 1000) { // converting seconds to millis
		SetRadius(GetStats()->GetRange());
	}

	virtual TowerType GetType() const = 0;
	virtual TowerSize GetSize() const = 0;
	virtual void Tick(std::uint64_t delta, World* world) = 0;

	void Upgrade(std::uint8_t level, TowerPath path) {
		m_Level.SetLevel(level);
		m_Level.SetPath(path);
		m_Timer.SetCooldown(GetStats()->GetDamageRate() * 1000); // converting seconds to millis
		m_Timer.Reset();
		SetRadius(GetStats()->GetRange());
	}

	std::uint16_t GetID() const { return m_ID; }
	const TowerLevel& GetLevel() const { return m_Level; }
	const TowerStats* GetStats() const { return GetTowerStats(m_Type, m_Level); }
	PlayerID GetBuilder() const { return m_Builder; }

	bool IsMobInRange(MobPtr mob);
};

typedef std::shared_ptr<Tower> TowerPtr;

namespace TowerFactory {

TowerPtr CreateTower(TowerType type, TowerID id, std::int32_t x, std::int32_t y, PlayerID builder);
std::string GetTowerName(TowerType type);

} // namespace TowerFactory


class TowerInfo {
private:
	std::string m_Name, m_Description;
	bool m_IsBigTower;
public:
	TowerInfo(std::string&& name, std::string&& description, bool big) : m_Name(std::move(name)),
		m_Description(std::move(description)), m_IsBigTower(big) {
	}

	const std::string& GetName() const { return m_Name; }
	const std::string& GetDescription() const { return m_Description; }

	bool IsBigTower() const { return m_IsBigTower; }
};

const TowerInfo& GetTowerInfo(TowerType type);

// ---------- Little Towers ----------

class LittleTower : public Tower {
public:
	LittleTower(TowerID id, TowerType type, std::uint16_t x, std::uint16_t y, PlayerID builder) : Tower(id, type, x, y, builder) {}

	virtual TowerSize GetSize() const { return TowerSize::Little; }

	virtual TowerType GetType() const = 0;
	virtual void Tick(std::uint64_t delta, World* world) = 0;
};

class ArcherTower : public LittleTower {
public:
	ArcherTower(TowerID id, std::uint16_t x, std::uint16_t y, PlayerID builder) : LittleTower(id, GetType(), x, y, builder) {}

	constexpr static float ExplosionRadius = 1.5f;
	constexpr static float FireDurationSec = 10.0f;

	virtual TowerType GetType() const { return TowerType::Archer; }
	virtual void Tick(std::uint64_t delta, World* world);
};

class IceTower : public LittleTower {
public:
	IceTower(TowerID id, std::uint16_t x, std::uint16_t y, PlayerID builder) : LittleTower(id, GetType(), x, y, builder) {}

	virtual TowerType GetType() const { return TowerType::Ice; }
	virtual void Tick(std::uint64_t delta, World* world);
};

class MageTower : public LittleTower {
public:
	MageTower(TowerID id, std::uint16_t x, std::uint16_t y, PlayerID builder) : LittleTower(id, GetType(), x, y, builder) {}

	virtual TowerType GetType() const { return TowerType::Mage; }
	virtual void Tick(std::uint64_t delta, World* world);
};

class PoisonTower : public LittleTower {
public:
	PoisonTower(TowerID id, std::uint16_t x, std::uint16_t y, PlayerID builder) : LittleTower(id, GetType(), x, y, builder) {}

	virtual TowerType GetType() const { return TowerType::Poison; }
	virtual void Tick(std::uint64_t delta, World* world);
};

class QuakeTower : public LittleTower {
public:
	QuakeTower(TowerID id, std::uint16_t x, std::uint16_t y, PlayerID builder) : LittleTower(id, GetType(), x, y, builder) {}

	virtual TowerType GetType() const { return TowerType::Quake; }
	virtual void Tick(std::uint64_t delta, World* world);
};

class ArtilleryTower : public LittleTower {
public:
	ArtilleryTower(TowerID id, std::uint16_t x, std::uint16_t y, PlayerID builder) : LittleTower(id, GetType(), x, y, builder) {}

	virtual TowerType GetType() const { return TowerType::Artillery; }
	virtual void Tick(std::uint64_t delta, World* world);
};

class SorcererTower : public LittleTower {
public:
	SorcererTower(TowerID id, std::uint16_t x, std::uint16_t y, PlayerID builder) : LittleTower(id, GetType(), x, y, builder) {}

	virtual TowerType GetType() const { return TowerType::Sorcerer; }
	virtual void Tick(std::uint64_t delta, World* world);
};

class ZeusTower : public LittleTower {
public:
	ZeusTower(TowerID id, std::uint16_t x, std::uint16_t y, PlayerID builder) : LittleTower(id, GetType(), x, y, builder) {}

	virtual TowerType GetType() const { return TowerType::Zeus; }
	virtual void Tick(std::uint64_t delta, World* world);
};

// ---------- Big Towers ----------

class BigTower : public Tower {
public:
	BigTower(TowerID id, TowerType type, std::uint16_t x, std::uint16_t y, PlayerID builder) : Tower(id, type, x, y, builder) {}

	virtual TowerSize GetSize() const { return TowerSize::Big; }

	virtual TowerType GetType() const = 0;
	virtual void Tick(std::uint64_t delta, World* world) = 0;
};

class TurretTower : public BigTower {
public:
	TurretTower(TowerID id, std::uint16_t x, std::uint16_t y, PlayerID builder) : BigTower(id, GetType(), x, y, builder) {}

	virtual TowerType GetType() const { return TowerType::Turret; }
	virtual void Tick(std::uint64_t delta, World* world);
};

class NecromancerTower : public BigTower {
public:
	NecromancerTower(TowerID id, std::uint16_t x, std::uint16_t y, PlayerID builder) : BigTower(id, GetType(), x, y, builder) {}

	virtual TowerType GetType() const { return TowerType::Necromancer; }
	virtual void Tick(std::uint64_t delta, World* world);
};

class LeachTower : public BigTower {
public:
	LeachTower(TowerID id, std::uint16_t x, std::uint16_t y, PlayerID builder) : BigTower(id, GetType(), x, y, builder) {}

	virtual TowerType GetType() const { return TowerType::Leach; }
	virtual void Tick(std::uint64_t delta, World* world);
};

} // namespace game
} // namespace td
