/*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 "GameMetadata.hpp"
#include "Fight/FightRules.hpp"
#include "Fight/FightState.hpp"

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

namespace
{
    bool writeRoundResult(const RoundResult &result,
                          std::list<std::uint8_t> &dst)
    {
        if (!result.drawGame && result.winnerIx > 255)
            return false;

        dst.push_back(result.drawGame ? 0 : 255);
        if (!result.drawGame)
            dst.push_back(static_cast<std::uint8_t>(result.winnerIx));

        return true;
    }

    bool readRoundResult(std::list<std::uint8_t> &bytes, RoundResult &dst)
    {
        if (bytes.empty())
            return false;

        dst.drawGame = (bytes.front() == 0);
        bytes.pop_front();

        if (!dst.drawGame)
        {
            if (bytes.empty())
                return false;

            dst.winnerIx = bytes.front();
            bytes.pop_front();
        }

        return true;
    }
}

namespace GameMetadataTools
{
    void writeRules(const FightRules &rules, std::list<std::uint8_t> &dst)
    {
        dst.push_back(static_cast<std::uint8_t>(rules.getMode()));
        dst.push_back(rules.getRoundsToWin());
        dst.push_back(rules.getPlayerCount());
    }

    bool readRules(std::list<std::uint8_t> &bytes, FightRules &dst)
    {
        if (bytes.size() < 3 ||
            bytes.front() > static_cast<std::uint8_t>(FightRules::Mode::Versus))
        {
            return false;
        }

        dst.setMode(static_cast<FightRules::Mode>(bytes.front()));
        bytes.pop_front();

        dst.setRoundsToWin(bytes.front());
        bytes.pop_front();

        dst.setPlayerCount(bytes.front());
        bytes.pop_front();

        return true;
    }

    bool writeFightState(const FightState &state, std::list<std::uint8_t> &dst)
    {
        dst.push_back(static_cast<std::uint8_t>(state.getPhase()));
        ByteTools::writeUnsigned<unsigned int, 2>(state.getRoundId(), dst);

        if (!writeRoundResult(state.getLastRoundResult(), dst))
            return false;

        const std::vector<std::uint8_t> &wins = state.getWinsPerPlayer();
        if (wins.size() > 255)
        {
            Log::err("Cannot write fight state: too many players");
            return false;
        }

        dst.push_back(static_cast<std::uint8_t>(wins.size()));
        dst.insert(dst.end(), wins.begin(), wins.end());
        return true;
    }

    bool readFightState(std::list<std::uint8_t> &bytes, FightState &dst)
    {
        if (bytes.size() < 2)
            return false;

        std::uint8_t phase = bytes.front();
        if (phase > static_cast<std::uint8_t>(FightState::Phase::Finished))
            return false;
        bytes.pop_front();

        unsigned int roundId;
        RoundResult lastResult;
        if (!ByteTools::readUnsigned<unsigned int, 2>(bytes, roundId) ||
            !readRoundResult(bytes, lastResult))
            return false;

        std::size_t expectedSize = bytes.front();
        bytes.pop_front();

        if (bytes.size() != expectedSize)
            return false;
        else if (!lastResult.drawGame && lastResult.winnerIx >= expectedSize)
        {
            Log::err("Cannot read fight state: Last winner is not known");
            return false;
        }

        dst = FightState(static_cast<FightState::Phase>(phase),
                         roundId,
                         lastResult,
                         std::vector<std::uint8_t>(bytes.begin(), bytes.end()));
        return true;
    }
}

GameMetadata::GameMetadata() : GameMetadata("dojo", FightRules(), {}, {})
{

}

GameMetadata::GameMetadata(const std::string &arena,
                           const FightRules &rules,
                           const std::vector<PeerData> &players,
                           const std::vector<PeerData> &spectators) :
m_arena(arena), m_rules(rules), m_serverLobby(true),
m_players(players), m_spectators(spectators)
{

}

void GameMetadata::setPeerRole(const PeerRole &role)
{
    m_peerRole = role;
}

void GameMetadata::setFightState(const FightState &state)
{
    if (state.getWinsPerPlayer().size() == m_players.size())
    {
        m_serverLobby = false;
        m_state = state;
    }
}

bool GameMetadata::readFromBuffer(std::list<std::uint8_t> &bytes)
{
    if (!ByteTools::readList<PeerData>(bytes, m_players) ||
        !ByteTools::readList<PeerData>(bytes, m_spectators) ||
        !m_peerRole.readFromBuffer(bytes) ||
        !ByteTools::readString(bytes, m_arena) ||
        !GameMetadataTools::readRules(bytes, m_rules))
        return false;

    if (bytes.empty())
    {
        m_serverLobby = true;
        return true;
    }
    else if (!GameMetadataTools::readFightState(bytes, m_state) ||
             m_state.getWinsPerPlayer().size() != m_players.size())
    {
        Log::err("Could not read fight state or unmatched players number");
        m_serverLobby = true;
        return false;
    }

    m_serverLobby = false;
    return true;
}

bool GameMetadata::writeToBuffer(std::list<std::uint8_t> &dst) const
{
    if (!ByteTools::writeList<PeerData>(m_players, dst) ||
        !ByteTools::writeList<PeerData>(m_spectators, dst) ||
        !m_peerRole.writeToBuffer(dst) ||
        !ByteTools::writeString(m_arena, dst))
        return false;

    GameMetadataTools::writeRules(m_rules, dst);
    return (m_serverLobby || GameMetadataTools::writeFightState(m_state, dst));
}

const std::vector<PeerData> &GameMetadata::getPlayers() const
{
    return m_players;
}

const std::vector<PeerData> &GameMetadata::getSpectators() const
{
    return m_spectators;
}

const PeerRole &GameMetadata::getPeerRole() const
{
    return m_peerRole;
}

const std::string &GameMetadata::getArena() const
{
    return m_arena;
}

const FightRules &GameMetadata::getFightRules() const
{
    return m_rules;
}

const FightState &GameMetadata::getFightState() const
{
    return m_state;
}

bool GameMetadata::inServerLobby() const
{
    return m_serverLobby;
}
