#pragma once

#include <memory>
#include <vector>
#include <map>
#include <unordered_map>
#include <array>

#include "Mobs.h"
#include "Team.h"

namespace td {
namespace game {

struct ChunkCoord {
	std::int16_t x, y;

	friend bool operator==(const td::game::ChunkCoord& first, const td::game::ChunkCoord& other) {
		return first.x == other.x && first.y == other.y;
	}
};


}
}


namespace std {
template <>
struct hash<td::game::ChunkCoord> {
	std::size_t operator()(const td::game::ChunkCoord& key) const noexcept {
		return std::hash<std::int16_t>()(key.x << 16 | key.y);
	}
};
}

namespace td {
namespace protocol {

class WorldBeginDataPacket;
class WorldDataPacket;

}

namespace game {

class Game;

enum class TileType : std::uint8_t {
	None = 0,
	Tower,
	Walk,
	Decoration,
	/*Heal,
	Lava,
	Bedrock,
	Freeze,
	Ice,*/
};

static constexpr Color BLACK{ 0, 0, 0 };
static constexpr Color WHITE{ 255, 255, 255 };

static constexpr Color RED{ 255, 0, 0 };
static constexpr Color GREEN{ 0, 255, 0 };
static constexpr Color BLUE{ 0, 0, 255 };

struct Tile {
	virtual TileType GetType() const = 0;
};

struct TowerTile : Tile {
	std::uint8_t color_palette_ref;
	TeamColor team_owner;

	virtual TileType GetType() const { return TileType::Tower; }
};

struct WalkableTile : Tile {
	Direction direction;

	virtual TileType GetType() const { return TileType::Walk; }
};

struct DecorationTile : Tile {
	std::uint16_t color_palette_ref;

	virtual TileType GetType() const { return TileType::Decoration; }
};

typedef std::shared_ptr<Tile> TilePtr;
typedef std::vector<std::uint16_t> ChunkPalette;

typedef std::shared_ptr<WalkableTile> WalkableTilePtr;

typedef std::uint32_t TileIndex;

//32 x 32 area
struct Chunk {
	enum { ChunkWidth = 32, ChunkHeight = 32, ChunkSize = ChunkWidth * ChunkHeight };
	typedef std::array<std::uint16_t, ChunkSize> ChunkData;

	// stores index of tile palette
	ChunkData tiles{ 0 };
	ChunkPalette palette;

	TileIndex GetTileIndex(std::uint16_t tileNumber) const {
		TileIndex chunkPaletteIndex = tiles.at(tileNumber);
		if (chunkPaletteIndex == 0)  // index 0 means empty tile     index 1 = first tile
			return 0;
		return palette.at(chunkPaletteIndex);
	}
};

typedef std::shared_ptr<Chunk> ChunkPtr;

typedef std::array<Color, 2> TowerTileColorPalette;

typedef std::vector<TilePtr> TilePalette;

typedef std::vector<MobPtr> MobList;

typedef std::array<Color, 2> SpawnColorPalette;

typedef std::vector<TowerPtr> TowerList;

class WorldListener {
public:
	WorldListener() {}

	virtual void OnTowerAdd(TowerPtr tower) {}
	virtual void OnTowerRemove(TowerPtr tower) {}

	virtual void OnArcherTowerShot(MobPtr target, ArcherTower* shooter) {}

	virtual void OnArrowShot(MobPtr target, bool fire, Tower* shooter) {}
	virtual void OnExplosion(utils::shape::Circle explosion, float centerDamage, Tower* shooter) {}
};

typedef utils::ObjectNotifier<WorldListener> WorldNotifier;

class World : public WorldListener, public MobListener {
protected:
	TowerTileColorPalette m_TowerPlacePalette;
	Color m_WalkablePalette;
	std::vector<Color> m_DecorationPalette;
	Color m_Background;

	std::unordered_map<ChunkCoord, ChunkPtr> m_Chunks;

	SpawnColorPalette m_SpawnColorPalette;

	TilePalette m_TilePalette;

	MobList m_Mobs;

	TowerList m_Towers;

	Game* m_Game;

	WorldNotifier m_WorldNotifier;
	MobNotifier m_MobNotifier;
public:
	World(Game* game);

	bool LoadMap(const protocol::WorldBeginDataPacket* worldHeader);
	bool LoadMap(const protocol::WorldDataPacket* worldData);

	bool LoadMapFromFile(const std::string& fileName);
	bool SaveMap(const std::string& fileName) const;

	void Tick(std::uint64_t delta);

	void Reset();  // clear mobs and towers

	void SpawnMobAt(MobID id, MobType type, std::uint8_t level, PlayerID sender, float x, float y, Direction dir);
	MobPtr RemoveMob(MobID id);

	TowerPtr PlaceTowerAt(TowerID id, TowerType type, std::int32_t x, std::int32_t y, PlayerID builder);
	TowerPtr RemoveTower(TowerID id);

	TilePtr GetTile(std::int32_t x, std::int32_t y) const;

	const TowerTileColorPalette& GetTowerTileColorPalette() const { return m_TowerPlacePalette; }
	const Color& GetWalkableTileColor() const { return m_WalkablePalette; }
	const std::vector<Color>& GetDecorationPalette() const { return m_DecorationPalette; }
	const Color& GetBackgroundColor() const { return m_Background; }

	const TilePalette& GetTilePalette() const { return m_TilePalette; }

	TilePtr GetTilePtr(TileIndex index) const {
		if (index == 0)
			return nullptr;
		return m_TilePalette.at(index - 1);
	}

	bool CanPlaceLittleTower(const Vec2f& worldPos, PlayerID player) const;
	bool CanPlaceBigTower(const Vec2f& worldPos, PlayerID player) const;

	TowerPtr GetTower(const Vec2f& position) const; // returns null if no tower is here

	const std::unordered_map<ChunkCoord, ChunkPtr>& GetChunks() const { return m_Chunks; }

	const Color& GetSpawnColor(TeamColor color) const { return m_SpawnColorPalette[static_cast<std::size_t>(color)]; }
	const SpawnColorPalette& GetSpawnColors() const { return m_SpawnColorPalette; }

	const MobList& GetMobList() const { return m_Mobs; }
	MobList& GetMobList() { return m_Mobs; }

	const Color* GetTileColor(TilePtr tile) const;

	Team& GetRedTeam();
	const Team& GetRedTeam() const;

	Team& GetBlueTeam();
	const Team& GetBlueTeam() const;

	Team& GetTeam(TeamColor team);
	const Team& GetTeam(TeamColor team) const;

	const TeamList& GetTeams() const;

	TowerList& GetTowers() { return m_Towers; }
	const TowerList& GetTowers() const { return m_Towers; }

	TowerPtr GetTowerById(TowerID tower);

	const Player* GetPlayerById(PlayerID id) const;

	WorldNotifier& GetWorldNotifier() { return m_WorldNotifier; }
	MobNotifier& GetMobNotifier() { return m_MobNotifier; }

	// WorldListener

	virtual void OnArcherTowerShot(MobPtr target, ArcherTower* shooter) override;
	virtual void OnArrowShot(MobPtr target, bool fire, Tower* shooter) override;
	virtual void OnExplosion(utils::shape::Circle explosion, float centerDamage, Tower* shooter) override;

	// MobListener

	virtual void OnMobDamage(Mob* target, float damage, Tower* source) override;
	virtual void OnMobCastleDamage(Mob* damager, TeamCastle* enemyCastle, float damage) override;

private:
	void TickMobs(std::uint64_t delta);
	void CleanDeadMobs();
};

} // namespace game
} // namespace td
