#include "protocol/PacketHandler.h"

#include <cmath>

#define REGISTER_DISPATCH_CLASS(className)  void className::Dispatch(PacketHandler* handler) const { \
                                                handler->HandlePacket(this);\
                                            }

namespace td {
namespace protocol {

const int BITS_IN_BYTE = 8;
const int BITS_IN_LONG = BITS_IN_BYTE * sizeof(std::uint64_t);

unsigned int countBits(unsigned int number) {
    // log function in base 2
    // take only integer part
    return static_cast<unsigned int>(std::log2(number) + 1);
}

void Packet::WritePacketID(DataBuffer& data, bool packetID) const {
    if (packetID) {
        data << GetID();
    }
}

DataBuffer& operator<<(DataBuffer& buffer, game::TilePtr tile) {
    buffer << tile->GetType();

    switch (tile->GetType()) {

    case game::TileType::Tower: {
        const game::TowerTile* towerTile = dynamic_cast<const game::TowerTile*>(tile.get());
        buffer << towerTile->color_palette_ref << towerTile->team_owner;
        break;
    }

    case game::TileType::Walk: {
        const game::WalkableTile* walkTile = dynamic_cast<const game::WalkableTile*>(tile.get());
        buffer << walkTile->direction;
        break;
    }

    case game::TileType::Decoration: {
        const game::DecorationTile* decoTile = dynamic_cast<const game::DecorationTile*>(tile.get());
        buffer << decoTile->color_palette_ref;
        break;
    }

    default:
        break;
    }

    return buffer;
}

DataBuffer& operator>>(DataBuffer& buffer, game::TilePtr& tile) {
    game::TileType tileType;
    buffer >> tileType;
    switch (tileType) {
    case game::TileType::Tower: {
        auto tilePtr = std::make_shared<game::TowerTile>();
        buffer >> tilePtr->color_palette_ref >> tilePtr->team_owner;
        tile = tilePtr;
        break;
    }
    case game::TileType::Walk: {
        auto tilePtr = std::make_shared<game::WalkableTile>();
        buffer >> tilePtr->direction;
        tile = tilePtr;
        break;
    }
    case game::TileType::Decoration: {
        auto tilePtr = std::make_shared<game::DecorationTile>();
        buffer >> tilePtr->color_palette_ref;
        tile = tilePtr;
        break;
    }
    default:
        break;
    }
    return buffer;
}

DataBuffer PlayerLoginPacket::Serialize(bool packetID) const {
    DataBuffer data;

    WritePacketID(data, packetID);
    data << m_PlayerName;
    return data;
}

void PlayerLoginPacket::Deserialize(DataBuffer& data) {
    data >> m_PlayerName;
}

DataBuffer WorldBeginDataPacket::Serialize(bool packetID) const {
    DataBuffer data;
    const game::TowerTileColorPalette& towerTilePalette = m_Header.m_World->GetTowerTileColorPalette();
    const std::vector<game::Color>& decoTilePalette = m_Header.m_World->GetDecorationPalette();

    WritePacketID(data, packetID);

    data << towerTilePalette << m_Header.m_World->GetWalkableTileColor()
        << (std::uint16_t)decoTilePalette.size();

    // deco color palette
    std::size_t bufferSize = data.GetSize();
    data.Resize(bufferSize + decoTilePalette.size() * sizeof(game::Color));

    memcpy(reinterpret_cast<std::uint8_t*>(data.data()) + bufferSize, decoTilePalette.data(), decoTilePalette.size() * sizeof(game::Color));

    data << m_Header.m_World->GetBackgroundColor();

    const game::Spawn& redSpawn = m_Header.m_World->GetRedTeam().GetSpawn(), blueSpawn = m_Header.m_World->GetBlueTeam().GetSpawn();
    const game::TeamCastle& redCastle = m_Header.m_World->GetRedTeam().GetCastle(), blueCastle = m_Header.m_World->GetBlueTeam().GetCastle();

    data << redSpawn << static_cast<utils::shape::Rectangle>(redCastle);
    data << blueSpawn << static_cast<utils::shape::Rectangle>(blueCastle);

    // tile palette
    data << static_cast<std::uint64_t>(m_Header.m_World->GetTilePalette().size());

    for (game::TilePtr tile : m_Header.m_World->GetTilePalette()) {
        data << tile;
    }

    data << m_Header.m_World->GetSpawnColors();

    return data;
}

void WorldBeginDataPacket::Deserialize(DataBuffer& data) {
    data >> m_Header.m_TowerPlacePalette >> m_Header.m_WalkablePalette;

    std::uint16_t decoPaletteSize;
    data >> decoPaletteSize;

    std::size_t decoPalletteSizeByte = decoPaletteSize * sizeof(game::Color);

    m_Header.m_DecorationPalette.resize(decoPaletteSize);

    memcpy(reinterpret_cast<std::uint8_t*>(m_Header.m_DecorationPalette.data()), data.data() + data.GetReadOffset(), decoPalletteSizeByte);

    data.SetReadOffset(data.GetReadOffset() + decoPalletteSizeByte);

    data >> m_Header.m_Background;

    utils::shape::Rectangle redCastle, blueCastle;

    data >> m_Header.m_RedSpawn >> redCastle;
    data >> m_Header.m_BlueSpawn >> blueCastle;

    m_Header.m_RedCastle.SetShape(redCastle);
    m_Header.m_BlueCastle.SetShape(blueCastle);

    std::uint64_t tilePaletteSize;
    data >> tilePaletteSize;

    m_Header.m_TilePalette.reserve(tilePaletteSize);

    for (std::uint64_t tileNumber = 0; tileNumber < tilePaletteSize; tileNumber++) {
        game::TilePtr tile;
        data >> tile;
        m_Header.m_TilePalette.push_back(tile);
    }

    data >> m_Header.m_SpawnColorPalette;
}

typedef std::vector<uint64_t> ChunkPackedData;

DataBuffer WorldDataPacket::Serialize(bool packetID) const {
    DataBuffer data;

    WritePacketID(data, packetID);

    data << m_World->GetChunks().size();
    for (const auto& pair : m_World->GetChunks()) {
        game::ChunkCoord coords = pair.first;
        game::ChunkPtr chunk = pair.second;

        data << coords.x << coords.y << static_cast<std::uint64_t>(chunk->palette.size());

        std::size_t bufferSize = data.GetSize();
        data.Resize(data.GetSize() + chunk->palette.size() * sizeof(game::ChunkPalette::value_type));
        memcpy(reinterpret_cast<std::uint8_t*>(data.data()) + bufferSize, chunk->palette.data(), chunk->palette.size() * sizeof(game::ChunkPalette::value_type));

        std::uint8_t bitsPerTile = countBits(chunk->palette.size());

        game::Chunk::ChunkData::value_type individualValueMask = ((1 << bitsPerTile) - 1);

        ChunkPackedData chunkData(game::Chunk::ChunkSize / (BITS_IN_BYTE * sizeof(ChunkPackedData::value_type) / bitsPerTile), 0);

        for (int tileNumber = 0; tileNumber < game::Chunk::ChunkSize; tileNumber++) {
            int startLong = (tileNumber * bitsPerTile) / BITS_IN_LONG;
            int startOffset = (tileNumber * bitsPerTile) % BITS_IN_LONG;
            int endLong = ((tileNumber + 1) * bitsPerTile - 1) / BITS_IN_LONG;

            std::uint64_t value = chunk->tiles[tileNumber];

            value &= individualValueMask;

            chunkData[startLong] |= (value << startOffset);

            if (startLong != endLong) {
                chunkData[endLong] = (value >> (BITS_IN_LONG - startOffset));
            }
        }

        bufferSize = data.GetSize();
        data.Resize(data.GetSize() + chunkData.size() * sizeof(ChunkPackedData::value_type));
        memcpy(reinterpret_cast<std::uint8_t*>(data.data()) + bufferSize, chunkData.data(), chunkData.size() * sizeof(ChunkPackedData::value_type));
    }
    return data;
}

void WorldDataPacket::Deserialize(DataBuffer& data) {
    std::uint64_t chunkCount;
    data >> chunkCount;

    for (std::uint64_t chunkNumber = 0; chunkNumber < chunkCount; chunkNumber++) {
        game::ChunkPtr chunk = std::make_shared<game::Chunk>();

        decltype(game::ChunkCoord::x) chunkX, chunkY;
        data >> chunkX >> chunkY;

        std::uint64_t chunkPaletteSize;
        data >> chunkPaletteSize;

        game::ChunkPalette chunkPalette(chunkPaletteSize);

        memcpy(reinterpret_cast<void*>(chunkPalette.data()), data.data() + data.GetReadOffset(), chunkPaletteSize * sizeof(game::ChunkPalette::value_type));
        data.SetReadOffset(data.GetReadOffset() + chunkPaletteSize * sizeof(game::ChunkPalette::value_type));

        chunk->palette = chunkPalette;

        std::uint8_t bitsPerTile = countBits(chunkPaletteSize);

        // A bitmask that contains bitsPerTile set bits
        game::Chunk::ChunkData::value_type individualValueMask = ((1 << bitsPerTile) - 1);

        ChunkPackedData chunkData(game::Chunk::ChunkSize / (BITS_IN_BYTE * sizeof(ChunkPackedData::value_type) / bitsPerTile), 0);

        memcpy(reinterpret_cast<void*>(chunkData.data()), data.data() + data.GetReadOffset(), chunkData.size() * sizeof(ChunkPackedData::value_type));
        data.SetReadOffset(data.GetReadOffset() + chunkData.size() * sizeof(ChunkPackedData::value_type));

        for (int tileNumber = 0; tileNumber < game::Chunk::ChunkSize; tileNumber++) {
            int startLong = (tileNumber * bitsPerTile) / BITS_IN_LONG;
            int startOffset = (tileNumber * bitsPerTile) % BITS_IN_LONG;
            int endLong = ((tileNumber + 1) * bitsPerTile - 1) / BITS_IN_LONG;

            game::Chunk::ChunkData::value_type value;
            if (startLong == endLong) {
                value = (chunkData[startLong] >> startOffset);
            } else {
                int endOffset = BITS_IN_LONG - startOffset;
                value = (chunkData[startLong] >> startOffset | chunkData[endLong] << endOffset);
            }
            value &= individualValueMask;

            chunk->tiles[tileNumber] = value;
        }


        m_WorldData.m_Chunks.insert({ {chunkX, chunkY}, chunk });
    }
}

DataBuffer KeepAlivePacket::Serialize(bool packetID) const {
    DataBuffer data;

    WritePacketID(data, packetID);
    data << m_AliveID;
    return data;
}

void KeepAlivePacket::Deserialize(DataBuffer& data) {
    data >> m_AliveID;
}

DataBuffer UpdateMoneyPacket::Serialize(bool packetID) const {
    DataBuffer data;

    WritePacketID(data, packetID);
    data << m_NewAmount;
    return data;
}

void UpdateMoneyPacket::Deserialize(DataBuffer& data) {
    data >> m_NewAmount;
}

DataBuffer UpdateExpPacket::Serialize(bool packetID) const {
    DataBuffer data;

    WritePacketID(data, packetID);
    data << m_NewAmount;
    return data;
}

void UpdateExpPacket::Deserialize(DataBuffer& data) {
    data >> m_NewAmount;
}

DataBuffer UpdateLobbyTimePacket::Serialize(bool packetID) const {
    DataBuffer data;

    WritePacketID(data, packetID);
    data << m_RemainingTime;
    return data;
}

void UpdateLobbyTimePacket::Deserialize(DataBuffer& data) {
    data >> m_RemainingTime;
}

DataBuffer UpdateGameStatePacket::Serialize(bool packetID) const {
    DataBuffer data;

    WritePacketID(data, packetID);
    data << m_GameState;
    return data;
}

void UpdateGameStatePacket::Deserialize(DataBuffer& data) {
    data >> m_GameState;
}

DataBuffer PlayerListPacket::Serialize(bool packetID) const {
    DataBuffer data;

    WritePacketID(data, packetID);
    data << static_cast<std::uint8_t>(m_Players.size());
    for (auto pair : m_Players) {
        data << pair.first << pair.second.name << pair.second.team;
    }
    return data;
}

void PlayerListPacket::Deserialize(DataBuffer& data) {
    std::uint8_t playerCount;
    data >> playerCount;

    for (int i = 0; i < playerCount; i++) {
        std::uint8_t playerID;
        PlayerInfo playerInfo;
        data >> playerID >> playerInfo.name >> playerInfo.team;
        m_Players.insert({ playerID, playerInfo });
    }
}

DataBuffer PlayerJoinPacket::Serialize(bool packetID) const {
    DataBuffer data;

    WritePacketID(data, packetID);
    data << m_PlayerID << m_PlayerName;
    return data;
}

void PlayerJoinPacket::Deserialize(DataBuffer& data) {
    data >> m_PlayerID >> m_PlayerName;
}

DataBuffer PlayerLeavePacket::Serialize(bool packetID) const {
    DataBuffer data;

    WritePacketID(data, packetID);
    data << m_PlayerID;
    return data;
}

void PlayerLeavePacket::Deserialize(DataBuffer& data) {
    data >> m_PlayerID;
}

DataBuffer ConnexionInfoPacket::Serialize(bool packetID) const {
    DataBuffer data;

    WritePacketID(data, packetID);
    data << m_ConnectionID;
    return data;
}

void ConnexionInfoPacket::Deserialize(DataBuffer& data) {
    data >> m_ConnectionID;
}

DataBuffer SelectTeamPacket::Serialize(bool packetID) const {
    DataBuffer data;

    WritePacketID(data, packetID);
    data << m_SelectedTeam;
    return data;
}

void SelectTeamPacket::Deserialize(DataBuffer& data) {
    data >> m_SelectedTeam;
}

DataBuffer UpdatePlayerTeamPacket::Serialize(bool packetID) const {
    DataBuffer data;

    WritePacketID(data, packetID);
    data << m_PlayerID << m_SelectedTeam;
    return data;
}

void UpdatePlayerTeamPacket::Deserialize(DataBuffer& data) {
    data >> m_PlayerID >> m_SelectedTeam;
}

DataBuffer DisconnectPacket::Serialize(bool packetID) const {
    DataBuffer data;

    WritePacketID(data, packetID);
    data << m_Reason;
    return data;
}

void DisconnectPacket::Deserialize(DataBuffer& data) {
    data >> m_Reason;
}

DataBuffer ServerTpsPacket::Serialize(bool packetID) const {
    DataBuffer data;

    WritePacketID(data, packetID);
    data << m_TPS << m_PacketSendTime;
    return data;
}

void ServerTpsPacket::Deserialize(DataBuffer& data) {
    data >> m_TPS >> m_PacketSendTime;
}

DataBuffer SpawnMobPacket::Serialize(bool packetID) const {
    DataBuffer data;

    WritePacketID(data, packetID);
    data << m_MobID << m_MobType << m_MobLevel << m_MobDirection
        << m_Sender << m_MobX << m_MobY;
    return data;
}

void SpawnMobPacket::Deserialize(DataBuffer& data) {
    data >> m_MobID >> m_MobType >> m_MobLevel >> m_MobDirection
        >> m_Sender >> m_MobX >> m_MobY;
}

DataBuffer PlaceTowerPacket::Serialize(bool packetID) const {
    DataBuffer data;

    WritePacketID(data, packetID);
    data << m_TowerX << m_TowerY << m_TowerType;
    return data;
}

void PlaceTowerPacket::Deserialize(DataBuffer& data) {
    data >> m_TowerX >> m_TowerY >> m_TowerType;
}

DataBuffer WorldAddTowerPacket::Serialize(bool packetID) const {
    DataBuffer data;

    WritePacketID(data, packetID);
    data << m_TowerID << m_TowerX << m_TowerY << m_TowerType << m_Builder;
    return data;
}

void WorldAddTowerPacket::Deserialize(DataBuffer& data) {
    data >> m_TowerID >> m_TowerX >> m_TowerY >> m_TowerType >> m_Builder;
}

DataBuffer RemoveTowerPacket::Serialize(bool packetID) const {
    DataBuffer data;

    WritePacketID(data, packetID);
    data << m_TowerID;
    return data;
}

void RemoveTowerPacket::Deserialize(DataBuffer& data) {
    data >> m_TowerID;
}

DataBuffer SendMobsPacket::Serialize(bool packetID) const {
    DataBuffer data;

    WritePacketID(data, packetID);
    data << static_cast<std::uint8_t>(m_MobSends.size());
    for (const MobSend& mobSend : m_MobSends) {
        data << mobSend;
    }
    return data;
}

void SendMobsPacket::Deserialize(DataBuffer& data) {
    std::uint8_t mobSendCount;
    data >> mobSendCount;
    protocol::MobSend mobSend;
    for (int i = 0; i < mobSendCount; i++) {
        data >> mobSend;
        m_MobSends.push_back(mobSend);
    }
}

DataBuffer UpgradeTowerPacket::Serialize(bool packetID) const {
    DataBuffer data;

    WritePacketID(data, packetID);
    data << m_TowerID << m_TowerLevel;
    return data;
}

void UpgradeTowerPacket::Deserialize(DataBuffer& data) {
    data >> m_TowerID >> m_TowerLevel;
}

DataBuffer UpdateCastleLifePacket::Serialize(bool packetID) const {
    DataBuffer data;

    WritePacketID(data, packetID);
    data << m_CastleLife << m_Team;
    return data;
}

void UpdateCastleLifePacket::Deserialize(DataBuffer& data) {
    data >> m_CastleLife >> m_Team;
}

DataBuffer UpdateMobStatesPacket::Serialize(bool packetID) const {
    DataBuffer data;

    WritePacketID(data, packetID);
    data << static_cast<std::uint64_t>(m_MobStates.size());
    for (auto mobState : m_MobStates) {
        data << mobState;
    }
    return data;
}

void UpdateMobStatesPacket::Deserialize(DataBuffer& data) {
    std::uint64_t mobCount;
    data >> mobCount;
    m_MobStates.resize(mobCount);
    for (std::uint64_t mobIndex = 0; mobIndex < mobCount; mobIndex++) {
        data >> m_MobStates[mobIndex];
    }
}

REGISTER_DISPATCH_CLASS(PlayerLoginPacket);
REGISTER_DISPATCH_CLASS(WorldBeginDataPacket);
REGISTER_DISPATCH_CLASS(WorldDataPacket);
REGISTER_DISPATCH_CLASS(KeepAlivePacket);
REGISTER_DISPATCH_CLASS(UpdateExpPacket);
REGISTER_DISPATCH_CLASS(UpdateMoneyPacket);
REGISTER_DISPATCH_CLASS(UpdateLobbyTimePacket);
REGISTER_DISPATCH_CLASS(UpdateGameStatePacket);
REGISTER_DISPATCH_CLASS(PlayerListPacket);
REGISTER_DISPATCH_CLASS(PlayerJoinPacket);
REGISTER_DISPATCH_CLASS(PlayerLeavePacket);
REGISTER_DISPATCH_CLASS(ConnexionInfoPacket);
REGISTER_DISPATCH_CLASS(SelectTeamPacket);
REGISTER_DISPATCH_CLASS(UpdatePlayerTeamPacket);
REGISTER_DISPATCH_CLASS(DisconnectPacket);
REGISTER_DISPATCH_CLASS(ServerTpsPacket);
REGISTER_DISPATCH_CLASS(SpawnMobPacket);
REGISTER_DISPATCH_CLASS(PlaceTowerPacket);
REGISTER_DISPATCH_CLASS(WorldAddTowerPacket);
REGISTER_DISPATCH_CLASS(RemoveTowerPacket);
REGISTER_DISPATCH_CLASS(SendMobsPacket);
REGISTER_DISPATCH_CLASS(UpgradeTowerPacket);
REGISTER_DISPATCH_CLASS(UpdateCastleLifePacket);
REGISTER_DISPATCH_CLASS(UpdateMobStatesPacket);

} // namespace protocol
} // namespace td