#pragma once

#include "misc/DataBuffer.h"
#include "game/World.h"
#include "game/BaseGame.h"

#include <memory>

namespace td {
namespace protocol {

class PacketHandler;

enum class PacketType : std::uint8_t {
    // client --> server
    PlayerLogin = 0,
    SelectTeam,
    SpawnMob,
    SendMobs,
    PlaceTower,

    // client <-- server
    PlayerJoin,
    PlayerLeave,
    WorldBeginData,
    WorldData,
    UpdateMoney,
    UpdateEXP,
    UpdateLobbyTime,
    UpdateGameState,
    PlayerList,
    ConnectionInfo,
    UpdatePlayerTeam,
    ServerTps,
    WorldAddTower,
    WorldRemoveTower,

    // client <--> server
    KeepAlive,
    Disconnect,
    UpgradeTower,
};

struct WorldHeader {
    game::TowerTileColorPalette m_TowerPlacePalette;
    game::Color m_WalkablePalette;
    std::vector<game::Color> m_DecorationPalette;

    game::SpawnColorPalette m_SpawnColorPalette;

    game::TilePalette m_TilePalette;

    game::Spawn m_RedSpawn, m_BlueSpawn;
    game::TeamCastle m_RedCastle, m_BlueCastle;

    const game::World* m_World;
};

struct WorldData {
    std::unordered_map<game::ChunkCoord, game::ChunkPtr> m_Chunks;
};

class Packet {
public:
    Packet() {}
    virtual ~Packet() {}

    virtual DataBuffer Serialize() const = 0;
    virtual void Deserialize(DataBuffer& data) = 0;
    virtual void Dispatch(PacketHandler* handler) const = 0;

    virtual PacketType getType() const = 0;
    std::uint8_t getID() const { return (std::uint8_t)getType(); }
};

typedef std::unique_ptr<Packet> PacketPtr;

class KeepAlivePacket : public Packet {
private:
    std::uint64_t m_AliveID;
public:
    KeepAlivePacket() {}
    KeepAlivePacket(std::uint64_t aliveID) : m_AliveID(aliveID) {}
    virtual ~KeepAlivePacket() {}

    virtual DataBuffer Serialize() const;
    virtual void Deserialize(DataBuffer& data);
    virtual void Dispatch(PacketHandler* handler) const;

    std::uint64_t getAliveID() const { return m_AliveID; }

    virtual PacketType getType() const { return PacketType::KeepAlive; }
};

class PlayerLoginPacket : public Packet {
private:
    std::string m_PlayerName;
public:
    PlayerLoginPacket() {}
    PlayerLoginPacket(std::string playerName) : m_PlayerName(playerName) {}
    virtual ~PlayerLoginPacket() {}

    virtual DataBuffer Serialize() const;
    virtual void Deserialize(DataBuffer& data);
    virtual void Dispatch(PacketHandler* handler) const;

    virtual PacketType getType() const { return PacketType::PlayerLogin; }

    const std::string& getPlayerName() const { return m_PlayerName; }
};

class WorldBeginDataPacket : public Packet {
private:
    WorldHeader m_Header;
public:
    WorldBeginDataPacket() {}
    WorldBeginDataPacket(const game::World* world) {
        m_Header.m_World = world;
    }
    virtual ~WorldBeginDataPacket() {}

    virtual DataBuffer Serialize() const;
    virtual void Deserialize(DataBuffer& data);
    virtual void Dispatch(PacketHandler* handler) const;

    virtual PacketType getType() const { return PacketType::WorldBeginData; }

    const game::TowerTileColorPalette& getTowerTilePalette() const { return m_Header.m_TowerPlacePalette; }
    const game::Color& getWalkableTileColor() const { return m_Header.m_WalkablePalette; }
    const std::vector<game::Color>& getDecorationPalette() const { return m_Header.m_DecorationPalette; }

    const game::Spawn& getRedSpawn() const { return m_Header.m_RedSpawn; }
    const game::Spawn& getBlueSpawn() const { return m_Header.m_BlueSpawn; }

    const game::SpawnColorPalette& getSpawnPalette() const { return m_Header.m_SpawnColorPalette; }

    const game::TeamCastle& getRedCastle() const { return m_Header.m_RedCastle; }
    const game::TeamCastle& getBlueCastle() const { return m_Header.m_BlueCastle; }

    const game::TilePalette getTilePalette() const { return m_Header.m_TilePalette; }

    DataBuffer SerializeCustom() const; // allow serialisation with invalid World member
    void setWorldHeader(const WorldHeader& header) { m_Header = header; }
};

class WorldDataPacket : public Packet {
private:
    WorldData m_WorldData;

    const game::World* m_World;
public:
    WorldDataPacket() {}
    WorldDataPacket(const game::World* world) : m_World(world) {}
    virtual ~WorldDataPacket() {}

    virtual DataBuffer Serialize() const;
    virtual void Deserialize(DataBuffer& data);
    virtual void Dispatch(PacketHandler* handler) const;

    virtual PacketType getType() const { return PacketType::WorldData; }

    const std::unordered_map<game::ChunkCoord, game::ChunkPtr>& getChunks() const { return m_WorldData.m_Chunks; }

    DataBuffer SerializeCustom() const; // allow serialisation with invalid World member
    void setWorldData(const WorldData& worldData) { m_WorldData = worldData; }
};

class UpdateMoneyPacket : public Packet {
private:
    std::uint32_t m_NewAmount;
public:
    UpdateMoneyPacket() {}
    UpdateMoneyPacket(std::uint32_t newAmount) : m_NewAmount(newAmount) {}
    virtual ~UpdateMoneyPacket() {}

    std::uint32_t getGold() const { return m_NewAmount; }

    virtual DataBuffer Serialize() const;
    virtual void Deserialize(DataBuffer& data);
    virtual void Dispatch(PacketHandler* handler) const;

    virtual PacketType getType() const { return PacketType::UpdateMoney; }
};

class UpdateExpPacket : public Packet {
private:
    std::uint32_t m_NewAmount;
public:
    UpdateExpPacket() {}
    UpdateExpPacket(std::uint32_t newAmount) : m_NewAmount(newAmount) {}
    virtual ~UpdateExpPacket() {}

    virtual DataBuffer Serialize() const;
    virtual void Deserialize(DataBuffer& data);
    virtual void Dispatch(PacketHandler* handler) const;

    virtual PacketType getType() const { return PacketType::UpdateEXP; }
};

class UpdateLobbyTimePacket : public Packet {
private:
    std::uint32_t m_RemainingTime;
public:
    UpdateLobbyTimePacket() {}
    UpdateLobbyTimePacket(std::uint32_t remainingTime) : m_RemainingTime(remainingTime) {}
    virtual ~UpdateLobbyTimePacket() {}

    virtual DataBuffer Serialize() const;
    virtual void Deserialize(DataBuffer& data);
    virtual void Dispatch(PacketHandler* handler) const;

    std::uint32_t getRemainingTime() const { return m_RemainingTime; }

    virtual PacketType getType() const { return PacketType::UpdateLobbyTime; }
};

class UpdateGameStatePacket : public Packet {
private:
    game::GameState m_GameState;
public:
    UpdateGameStatePacket() {}
    UpdateGameStatePacket(game::GameState gameState) : m_GameState(gameState) {}
    virtual ~UpdateGameStatePacket() {}

    virtual DataBuffer Serialize() const;
    virtual void Deserialize(DataBuffer& data);
    virtual void Dispatch(PacketHandler* handler) const;

    game::GameState getGameState() const { return m_GameState; }

    virtual PacketType getType() const { return PacketType::UpdateGameState; }
};

struct PlayerInfo {
    std::string name;
    game::TeamColor team;
};

class PlayerListPacket : public Packet {
private:
    std::map<std::uint8_t, PlayerInfo> m_Players;
public:
    PlayerListPacket() {}
    PlayerListPacket(std::map<std::uint8_t, PlayerInfo> players) : m_Players(players) {}
    virtual ~PlayerListPacket() {}

    virtual DataBuffer Serialize() const;
    virtual void Deserialize(DataBuffer& data);
    virtual void Dispatch(PacketHandler* handler) const;

    const std::map<std::uint8_t, PlayerInfo>& getPlayers() const { return m_Players; }

    virtual PacketType getType() const { return PacketType::PlayerList; }
};

class PlayerJoinPacket : public Packet {
private:
    std::uint8_t m_PlayerID;
    std::string m_PlayerName;
public:
    PlayerJoinPacket() {}
    PlayerJoinPacket(std::uint8_t playerID, const std::string& playerName) : m_PlayerID(playerID), m_PlayerName(playerName) {}
    virtual ~PlayerJoinPacket() {}

    virtual DataBuffer Serialize() const;
    virtual void Deserialize(DataBuffer& data);
    virtual void Dispatch(PacketHandler* handler) const;

    std::uint8_t getPlayerID() const { return m_PlayerID; }
    const std::string& getPlayerName() const { return m_PlayerName; }

    virtual PacketType getType() const { return PacketType::PlayerJoin; }
};

class PlayerLeavePacket : public Packet {
private:
    std::uint8_t m_PlayerID;
public:
    PlayerLeavePacket() {}
    PlayerLeavePacket(std::uint8_t playerID) : m_PlayerID(playerID) {}
    virtual ~PlayerLeavePacket() {}

    virtual DataBuffer Serialize() const;
    virtual void Deserialize(DataBuffer& data);
    virtual void Dispatch(PacketHandler* handler) const;

    std::uint8_t getPlayerID() const { return m_PlayerID; }

    virtual PacketType getType() const { return PacketType::PlayerLeave; }
};

class ConnexionInfoPacket : public Packet {
private:
    std::uint8_t m_ConnectionID;
public:
    ConnexionInfoPacket() {}
    ConnexionInfoPacket(std::uint8_t connectionID) : m_ConnectionID(connectionID) {}
    virtual ~ConnexionInfoPacket() {}

    virtual DataBuffer Serialize() const;
    virtual void Deserialize(DataBuffer& data);
    virtual void Dispatch(PacketHandler* handler) const;

    std::uint8_t getConnectionID() const { return m_ConnectionID; }

    virtual PacketType getType() const { return PacketType::ConnectionInfo; }
};

class SelectTeamPacket : public Packet {
private:
    game::TeamColor m_SelectedTeam;
public:
    SelectTeamPacket() {}
    SelectTeamPacket(game::TeamColor selectedTeam) : m_SelectedTeam(selectedTeam) {}
    virtual ~SelectTeamPacket() {}

    virtual DataBuffer Serialize() const;
    virtual void Deserialize(DataBuffer& data);
    virtual void Dispatch(PacketHandler* handler) const;

    game::TeamColor getSelectedTeam() const { return m_SelectedTeam; }

    virtual PacketType getType() const { return PacketType::SelectTeam; }
};

class UpdatePlayerTeamPacket : public Packet {
private:
    std::uint8_t m_PlayerID;
    game::TeamColor m_SelectedTeam;
public:
    UpdatePlayerTeamPacket() {}
    UpdatePlayerTeamPacket(std::uint8_t playerID, game::TeamColor selectedTeam) : m_PlayerID(playerID), m_SelectedTeam(selectedTeam) {}
    virtual ~UpdatePlayerTeamPacket() {}

    virtual DataBuffer Serialize() const;
    virtual void Deserialize(DataBuffer& data);
    virtual void Dispatch(PacketHandler* handler) const;

    game::TeamColor getSelectedTeam() const { return m_SelectedTeam; }
    std::uint8_t getPlayerID() const { return m_PlayerID; }

    virtual PacketType getType() const { return PacketType::UpdatePlayerTeam; }
};

class DisconnectPacket : public Packet {
private:
    std::string m_Reason; // only when sent from server
public:
    DisconnectPacket() {}
    DisconnectPacket(std::string reason) : m_Reason(reason) {}
    virtual ~DisconnectPacket() {}

    virtual DataBuffer Serialize() const;
    virtual void Deserialize(DataBuffer& data);
    virtual void Dispatch(PacketHandler* handler) const;

    const std::string& getReason() const { return m_Reason; }

    virtual PacketType getType() const { return PacketType::Disconnect; }
};

class ServerTpsPacket : public Packet {
private:
    float m_TPS;
    std::uint64_t m_PacketSendTime; // used to calculate ping
public:
    ServerTpsPacket() {}
    ServerTpsPacket(float tps, std::uint64_t sendTime) : m_TPS(tps), m_PacketSendTime(sendTime) {}
    virtual ~ServerTpsPacket() {}

    virtual DataBuffer Serialize() const;
    virtual void Deserialize(DataBuffer& data);
    virtual void Dispatch(PacketHandler* handler) const;

    float getTPS() const { return m_TPS; }
    std::uint64_t getPacketSendTime() const { return m_PacketSendTime; }

    virtual PacketType getType() const { return PacketType::ServerTps; }
};

struct MobSend { // represents a mob send
    game::MobType mobType;
    game::MobLevel mobLevel;
    std::uint8_t mobCount; // the max is 12
};

class SendMobsPacket : public Packet {
private:
    std::vector<MobSend> m_MobSends;
public:
    SendMobsPacket() {}
    SendMobsPacket(const std::vector<MobSend>& mobSends) : m_MobSends(mobSends) {}
    virtual ~SendMobsPacket() {}

    virtual DataBuffer Serialize() const;
    virtual void Deserialize(DataBuffer& data);
    virtual void Dispatch(PacketHandler* handler) const;

    const std::vector<MobSend>& getMobSends() const { return m_MobSends; }

    virtual PacketType getType() const { return PacketType::SendMobs; }
};

class SpawnMobPacket : public Packet {
private:
    game::MobID m_MobID;
    game::MobType m_MobType;
    game::MobLevel m_MobLevel;
    game::Direction m_MobDirection;
    game::PlayerID m_Sender;
    float m_MobX, m_MobY;
public:
    SpawnMobPacket() {}
    SpawnMobPacket(game::MobID id, game::MobType type, std::uint8_t level, game::PlayerID sender,
        float x, float y, game::Direction dir) : m_MobID(id), m_MobType(type), m_MobLevel(level),
        m_MobDirection(dir), m_Sender(sender), m_MobX(x), m_MobY(y) {}
    virtual ~SpawnMobPacket() {}

    virtual DataBuffer Serialize() const;
    virtual void Deserialize(DataBuffer& data);
    virtual void Dispatch(PacketHandler* handler) const;

    game::MobID getMobID() const { return m_MobID; }
    game::MobType getMobType() const { return m_MobType; }
    game::MobLevel getMobLevel() const { return m_MobLevel; }
    game::Direction getMobDirection() const { return m_MobDirection; }
    game::PlayerID getSender() const { return m_Sender; }
    float getMobX() const { return m_MobX; }
    float getMobY() const { return m_MobY; }

    virtual PacketType getType() const { return PacketType::SpawnMob; }
};

class PlaceTowerPacket : public Packet {
private:
    std::int32_t m_TowerX, m_TowerY;
    game::TowerType m_TowerType;
public:
    PlaceTowerPacket() {}
    PlaceTowerPacket(std::int32_t x, std::int32_t y, game::TowerType type) :
        m_TowerX(x), m_TowerY(y), m_TowerType(type) {}
    virtual ~PlaceTowerPacket() {}

    virtual DataBuffer Serialize() const;
    virtual void Deserialize(DataBuffer& data);
    virtual void Dispatch(PacketHandler* handler) const;

    std::int32_t getTowerX() const { return m_TowerX; }
    std::int32_t getTowerY() const { return m_TowerY; }
    game::TowerType getTowerType() const { return m_TowerType; }

    virtual PacketType getType() const { return PacketType::PlaceTower; }
};

class WorldAddTowerPacket : public Packet {
private:
    game::TowerID m_TowerID;
    std::int32_t m_TowerX, m_TowerY;
    game::TowerType m_TowerType;
    game::PlayerID m_Builder;
public:
    WorldAddTowerPacket() {}
    WorldAddTowerPacket(game::TowerID id, std::int32_t x, std::int32_t y, game::TowerType type, game::PlayerID player) :
        m_TowerID(id), m_TowerX(x), m_TowerY(y), m_TowerType(type), m_Builder(player) {}
    virtual ~WorldAddTowerPacket() {}

    virtual DataBuffer Serialize() const;
    virtual void Deserialize(DataBuffer& data);
    virtual void Dispatch(PacketHandler* handler) const;

    game::TowerID getTowerID() const { return m_TowerID; }
    std::int32_t getTowerX() const { return m_TowerX; }
    std::int32_t getTowerY() const { return m_TowerY; }
    game::TowerType getTowerType() const { return m_TowerType; }
    game::PlayerID getBuilder() const { return m_Builder; }

    virtual PacketType getType() const { return PacketType::WorldAddTower; }
};

class WorldRemoveTowerPacket : public Packet {
private:
    game::TowerID m_TowerID;
public:
    WorldRemoveTowerPacket() {}
    WorldRemoveTowerPacket(game::TowerID id) : m_TowerID(id) {}
    virtual ~WorldRemoveTowerPacket() {}

    virtual DataBuffer Serialize() const;
    virtual void Deserialize(DataBuffer& data);
    virtual void Dispatch(PacketHandler* handler) const;

    game::TowerID getTowerID() const { return m_TowerID; }

    virtual PacketType getType() const { return PacketType::WorldRemoveTower; }
};

class UpgradeTowerPacket : public Packet {
private:
    game::TowerID m_TowerID;
    game::TowerLevel m_TowerLevel;
public:
    UpgradeTowerPacket() {}
    UpgradeTowerPacket(game::TowerID tower, game::TowerLevel level) : m_TowerID(tower), m_TowerLevel(level) {}
    virtual ~UpgradeTowerPacket() {}

    virtual DataBuffer Serialize() const;
    virtual void Deserialize(DataBuffer& data);
    virtual void Dispatch(PacketHandler* handler) const;

    game::TowerID getTowerID() const { return m_TowerID; }
    game::TowerLevel getTowerLevel() const { return m_TowerLevel; }

    virtual PacketType getType() const { return PacketType::UpgradeTower; }
};

}
}