/*Copyright (C) 2010-2023 Duncan Deveaux

This file is part of Hikou no mizu.

Hikou no mizu is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Hikou no mizu is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Hikou no mizu.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "GameStateMessage.hpp"
#include "Network/Serialization/GameMetadata.hpp"
#include "Network/Serialization/PeerData.hpp" ///< PeerRole

#include "Tools/ByteTools.hpp"
#include "Tools/Log.hpp"

GameStateMessage::GameStateMessage() : m_metadata(nullptr) {}

GameStateMessage::GameStateMessage(
    const Timer::duration_uint64_us &clockTicks,
    const std::vector<PlayerStatus> &playerStatus,
    const std::vector<PhysicsObjectStatus> &platformStatus,
    const std::vector<DamageStatus> &damageStatus,
    const EtherStatus &etherStatus) :
Message(), m_ticks(clockTicks), m_metadata(nullptr), m_players(playerStatus),
m_platforms(platformStatus), m_damages(damageStatus), m_weaponEther(etherStatus)
{

}

GameStateMessage::~GameStateMessage()
{
    if (m_metadata != nullptr)
        delete m_metadata;
}

void GameStateMessage::setGameMetadata(const GameMetadata &metadata)
{
    if (m_metadata == nullptr)
        m_metadata = new GameMetadata(metadata);
}

void GameStateMessage::setMetadataPeerRole(const PeerRole &role)
{
    if (m_metadata != nullptr)
        m_metadata->setPeerRole(role);
}

bool GameStateMessage::fromBuffer(const Message::Buffer &buffer)
{
    std::list<std::uint8_t> bytes = buffer.bytes();

    std::uint64_t ticks = 0;
    if (!ByteTools::readUnsigned<std::uint64_t, 4>(bytes, ticks))
    {
        Log::err("Could not read game state (clock ticks)");
        return false;
    }
    else if (!ByteTools::readList<PlayerStatus>(bytes, m_players))
    {
        Log::err("Could not read game state (players)");
        m_players.clear();
        return false;
    }
    else if (!ByteTools::readList<PhysicsObjectStatus>(bytes, m_platforms))
    {
        Log::err("Could not read game state (platforms)");
        m_platforms.clear();
        return false;
    }
    else if (!ByteTools::readList<DamageStatus>(bytes, m_damages))
    {
        Log::err("Could not read game state (damages)");
        m_damages.clear();
        return false;
    }
    else if (!m_weaponEther.readFromBuffer(bytes))
    {
        Log::err("Could not read game state (weapons)");
        return false;
    }

    if (!bytes.empty()) //Game metadata
    {
        GameMetadata metadata;
        if (!metadata.readFromBuffer(bytes))
        {
            Log::err("Could not read game metadata");
            return false;
        }
        setGameMetadata(metadata);
    }

    m_ticks = Timer::duration_uint64_us(ticks); //Apply ticks
    return true;
}

Message::Buffer GameStateMessage::allocBuffer() const
{
    std::list<std::uint8_t> bytes;

    //Using only 4 bytes to store the server clock to save space
    //The ticks value will wrap back to 0 every 71 minutes,
    //causing a safe clock re-synchronization on the client side
    ByteTools::writeUnsigned<std::uint64_t, 4>(m_ticks.count(), bytes);

    if (!ByteTools::writeList<PlayerStatus>(m_players, bytes) ||
        !ByteTools::writeList<PhysicsObjectStatus>(m_platforms, bytes) ||
        !ByteTools::writeList<DamageStatus>(m_damages, bytes) ||
        !m_weaponEther.writeToBuffer(bytes) ||
        (m_metadata != nullptr && !m_metadata->writeToBuffer(bytes)))
    {
        return Message::Buffer();
    }

    return Message::Buffer::alloc(bytes);
}

const Timer::duration_uint64_us &GameStateMessage::getClockTicks() const
{
    return m_ticks;
}

const GameMetadata *GameStateMessage::getGameMetadata() const
{
    return m_metadata;
}

const std::vector<PlayerStatus> &GameStateMessage::getPlayerStatus() const
{
    return m_players;
}

const std::vector<PhysicsObjectStatus> &GameStateMessage::getPlatformStatus() const
{
    return m_platforms;
}

const std::vector<DamageStatus> &GameStateMessage::getDamageStatus() const
{
    return m_damages;
}

const EtherStatus &GameStateMessage::getEtherStatus() const
{
    return m_weaponEther;
}
