#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 (int)std::log2(number) + 1;
}

DataBuffer& operator<<(DataBuffer& buffer, game::TilePtr tile) {
    buffer << tile->getType();
    switch (tile->getType()) {
    case game::TileType::Tower: {
        const game::TowerTile* towerTile = (const game::TowerTile*)tile.get();
        buffer << towerTile->color_palette_ref << towerTile->team_owner;
        break;
    }
    case game::TileType::Walk: {
        const game::WalkableTile* walkTile = (const game::WalkableTile*)tile.get();
        buffer << walkTile->direction;
        break;
    }
    case game::TileType::Decoration: {
        const game::DecorationTile* decoTile = (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: {
        std::shared_ptr<game::TowerTile> tilePtr = std::make_shared<game::TowerTile>();
        buffer >> tilePtr->color_palette_ref >> tilePtr->team_owner;
        tile = tilePtr;
        break;
    }
    case game::TileType::Walk: {
        std::shared_ptr<game::WalkableTile> tilePtr = std::make_shared<game::WalkableTile>();
        buffer >> tilePtr->direction;
        tile = tilePtr;
        break;
    }
    case game::TileType::Decoration: {
        std::shared_ptr<game::DecorationTile> tilePtr = std::make_shared<game::DecorationTile>();
        buffer >> tilePtr->color_palette_ref;
        tile = tilePtr;
        break;
    }
    default:
        break;
    }
    return buffer;
}

DataBuffer PlayerLoginPacket::Serialize() const {
    DataBuffer data;
    data << getID() << m_PlayerName;
    return data;
}

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

DataBuffer WorldBeginDataPacket::SerializeCustom() const {
    DataBuffer data;
    const game::TowerTileColorPalette towerTilePalette = m_Header.m_TowerPlacePalette;
    const std::vector<game::Color>& decoTilePalette = m_Header.m_DecorationPalette;

    data << getID() << towerTilePalette << m_Header.m_WalkablePalette
        << (std::uint16_t)decoTilePalette.size();

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

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

    const game::Spawn& redSpawn = m_Header.m_RedSpawn, blueSpawn = m_Header.m_BlueSpawn;
    const game::TeamCastle& redCastle = m_Header.m_RedCastle, blueCastle = m_Header.m_BlueCastle;

    data << redSpawn << redCastle;
    data << blueSpawn << blueCastle;

    // tile palette
    data << m_Header.m_TilePalette.size();

    for (game::TilePtr tile : m_Header.m_TilePalette) {
        data << tile;
    }

    data << m_Header.m_SpawnColorPalette;

    return data;
}

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

    data << getID() << 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((std::uint8_t*)data.data() + bufferSize, decoTilePalette.data(), decoTilePalette.size() * sizeof(game::Color));

    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 << redCastle;
    data << blueSpawn << blueCastle;

    // tile palette
    data << 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((std::uint8_t*)m_Header.m_DecorationPalette.data(), data.data() + data.GetReadOffset(), decoPalletteSizeByte);

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

    data >> m_Header.m_RedSpawn >> m_Header.m_RedCastle;
    data >> m_Header.m_BlueSpawn >> m_Header.m_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::SerializeCustom() const {
    DataBuffer data;
    data << getID() << m_WorldData.m_Chunks.size();
    for (const auto& pair : m_WorldData.m_Chunks) {
        game::ChunkCoord coords = pair.first;
        game::ChunkPtr chunk = pair.second;

        data << coords.first << coords.second << (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((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::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((std::uint8_t*)data.data() + bufferSize, chunkData.data(), chunkData.size() * sizeof(ChunkPackedData::value_type));
    }
    return data;
}

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

        data << coords.first << coords.second << (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((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::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((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>();

        game::ChunkCoord::first_type chunkX, chunkY;
        data >> chunkX >> chunkY;

        std::uint64_t chunkPaletteSize;
        data >> chunkPaletteSize;

        game::ChunkPalette chunkPalette(chunkPaletteSize);

        memcpy((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::ChunkData::value_type individualValueMask = ((1 << bitsPerTile) - 1);

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

        memcpy((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::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() const {
    DataBuffer data;
    data << getID() << m_AliveID;
    return data;
}

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

DataBuffer UpdateMoneyPacket::Serialize() const {
    DataBuffer data;
    data << getID() << m_NewAmount;
    return data;
}

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

DataBuffer UpdateExpPacket::Serialize() const {
    DataBuffer data;
    data << getID() << m_NewAmount;
    return data;
}

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

DataBuffer UpdateLobbyTimePacket::Serialize() const {
    DataBuffer data;
    data << getID() << m_RemainingTime;
    return data;
}

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

DataBuffer UpdateGameStatePacket::Serialize() const {
    DataBuffer data;
    data << getID() << m_GameState;
    return data;
}

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

DataBuffer PlayerListPacket::Serialize() const {
    DataBuffer data;
    data << getID() << (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() const {
    DataBuffer data;
    data << getID() << m_PlayerID << m_PlayerName;
    return data;
}

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

DataBuffer PlayerLeavePacket::Serialize() const {
    DataBuffer data;
    data << getID() << m_PlayerID;
    return data;
}

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

DataBuffer ConnexionInfoPacket::Serialize() const {
    DataBuffer data;
    data << getID() << m_ConnectionID;
    return data;
}

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

DataBuffer SelectTeamPacket::Serialize() const {
    DataBuffer data;
    data << getID() << m_SelectedTeam;
    return data;
}

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

DataBuffer UpdatePlayerTeamPacket::Serialize() const {
    DataBuffer data;
    data << getID() << m_PlayerID << m_SelectedTeam;
    return data;
}

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

DataBuffer DisconnectPacket::Serialize() const {
    DataBuffer data;
    data << getID() << m_Reason;
    return data;
}

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

DataBuffer ServerTpsPacket::Serialize() const {
    DataBuffer data;
    data << getID() << m_TPS << m_PacketSendTime;
    return data;
}

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

DataBuffer SpawnMobPacket::Serialize() const {
    DataBuffer data;
    data << getID() << 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() const {
    DataBuffer data;
    data << getID() << m_TowerX << m_TowerY << m_TowerType;
    return data;
}

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

DataBuffer WorldAddTowerPacket::Serialize() const {
    DataBuffer data;
    data << getID() << 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 WorldRemoveTowerPacket::Serialize() const {
    DataBuffer data;
    data << getID() << m_TowerID;
    return data;
}

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

DataBuffer SendMobsPacket::Serialize() const {
    DataBuffer data;
    data << getID() << 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() const {
    DataBuffer data;
    data << getID() << m_TowerID << m_TowerLevel;
    return data;
}

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

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(WorldRemoveTowerPacket);
REGISTER_DISPATCH_CLASS(SendMobsPacket);
REGISTER_DISPATCH_CLASS(UpgradeTowerPacket);

} // namespace protocol
} // namespace td