/*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 "ServerEvent.hpp"

#include "Network/Serialization/GameMetadata.hpp" ///< GameMetadataTools
#include "Tools/ByteTools.hpp"
#include "Tools/Log.hpp"
#include "Tools/Format.hpp"
#include "Tools/I18n.hpp" ///< _ macro for i18n
#include <cassert>

//////////////////
///WelcomeEvent///
//////////////////
WelcomeEvent::WelcomeEvent(const Utf8::utf8_string &message) :
m_message(message)
{

}

bool WelcomeEvent::readFromBuffer(std::list<std::uint8_t> &bytes)
{
    return (ByteTools::readUtf8(bytes, m_message) &&
            m_message.length() <= WelcomeEvent::MaxWelcomeLength);
}

bool WelcomeEvent::writeToBuffer(std::list<std::uint8_t> &dst) const
{
    return (m_message.length() <= WelcomeEvent::MaxWelcomeLength &&
            ByteTools::writeUtf8(m_message, dst));
}

std::string WelcomeEvent::getLog() const
{
    return m_message.get();
}

////////////////
///RulesEvent///
////////////////
RulesEvent::RulesEvent(const FightRules &rules) : m_rules(rules)
{

}

bool RulesEvent::readFromBuffer(std::list<std::uint8_t> &bytes)
{
    return GameMetadataTools::readRules(bytes, m_rules);
}

void RulesEvent::writeToBuffer(std::list<std::uint8_t> &dst) const
{
    GameMetadataTools::writeRules(m_rules, dst);
}

std::string RulesEvent::getLog() const
{
    if (m_rules.getMode() == FightRules::Mode::Versus)
        return _f(ServerEventRulesVersus,
                  std::make_pair("player_count", +m_rules.getPlayerCount()),
                  std::make_pair("rounds_to_win", +m_rules.getRoundsToWin()));

    return _f(ServerEventRulesDefault,
              std::make_pair("player_count", +m_rules.getPlayerCount()),
              std::make_pair("rounds_to_win", +m_rules.getRoundsToWin()));
}

////////////////////////
///PeerConnectedEvent///
////////////////////////
PeerConnectedEvent::PeerConnectedEvent(const Utf8::utf8_string &username) :
m_username(username)
{

}

bool PeerConnectedEvent::readFromBuffer(std::list<std::uint8_t> &bytes)
{
    return ByteTools::readUtf8(bytes, m_username);
}

bool PeerConnectedEvent::writeToBuffer(std::list<std::uint8_t> &dst) const
{
    return ByteTools::writeUtf8(m_username, dst);
}

std::string PeerConnectedEvent::getLog() const
{
    return _f(ServerEventPeerJoined, std::make_pair("peer", m_username.get()));
}

///////////////////////////
///PeerDisconnectedEvent///
///////////////////////////
PeerDisconnectedEvent::PeerDisconnectedEvent(const Utf8::utf8_string &username) :
m_username(username)
{

}

bool PeerDisconnectedEvent::readFromBuffer(std::list<std::uint8_t> &bytes)
{
    return ByteTools::readUtf8(bytes, m_username);
}

bool PeerDisconnectedEvent::writeToBuffer(std::list<std::uint8_t> &dst) const
{
    return ByteTools::writeUtf8(m_username, dst);
}

std::string PeerDisconnectedEvent::getLog() const
{
    return _f(ServerEventPeerLeft, std::make_pair("peer", m_username.get()));
}

///////////////////////
///PeerInactiveEvent///
///////////////////////
PeerInactiveEvent::PeerInactiveEvent(const Utf8::utf8_string &username) :
m_username(username)
{

}

bool PeerInactiveEvent::readFromBuffer(std::list<std::uint8_t> &bytes)
{
    return ByteTools::readUtf8(bytes, m_username);
}

bool PeerInactiveEvent::writeToBuffer(std::list<std::uint8_t> &dst) const
{
    return ByteTools::writeUtf8(m_username, dst);
}

std::string PeerInactiveEvent::getLog() const
{
    return _f(ServerEventPeerKickedForInactivity,
              std::make_pair("peer", m_username.get()));
}

//////////////////////
///MatchmakingEvent///
//////////////////////
MatchmakingEvent::MatchmakingEvent() :
m_strategy(MatchmakingStrategy::Type::Stack)
{

}

MatchmakingEvent::MatchmakingEvent(const MatchmakingStrategy::Type &strategy) :
m_strategy(strategy)
{

}

bool MatchmakingEvent::readFromBuffer(std::list<std::uint8_t> &bytes)
{
    return MatchmakingStrategy::readStrategy(bytes, m_strategy);
}

bool MatchmakingEvent::writeToBuffer(std::list<std::uint8_t> &dst) const
{
    MatchmakingStrategy::writeStrategy(m_strategy, dst);
    return true;
}

std::string MatchmakingEvent::getLog() const
{
    if (m_strategy == MatchmakingStrategy::Type::Stack)
        return _(ServerEventMatchmakingStack);
    else if (m_strategy == MatchmakingStrategy::Type::Random)
        return _(ServerEventMatchmakingRandom);
    else if (m_strategy == MatchmakingStrategy::Type::Challenger)
        return _(ServerEventMatchmakingChallenger);

    Log::err(Format::format("No log for matchmaking strategy: {}",
                            MatchmakingStrategy::getValue(m_strategy)));
    return std::string();
}

////////////////////////
///GameScheduledEvent///
////////////////////////
std::string GameScheduledEvent::getLog() const
{
    return _(ServerEventGameScheduled);
}

/////////////////
///ServerEvent///
/////////////////
ServerEvent::ServerEvent() : m_eventType(ServerEvent::Type::NoEvent) {}

ServerEvent::ServerEvent(const WelcomeEvent &welcome) :
m_eventType(ServerEvent::Type::Welcome),
m_welcome(new WelcomeEvent(welcome))
{

}

ServerEvent::ServerEvent(const RulesEvent &rules) :
m_eventType(ServerEvent::Type::Rules),
m_rules(new RulesEvent(rules))
{

}

ServerEvent::ServerEvent(const PeerConnectedEvent &peerConnected) :
m_eventType(ServerEvent::Type::PeerConnected),
m_peerConnected(new PeerConnectedEvent(peerConnected))
{

}

ServerEvent::ServerEvent(const PeerDisconnectedEvent &peerDisconnected) :
m_eventType(ServerEvent::Type::PeerDisconnected),
m_peerDisconnected(new PeerDisconnectedEvent(peerDisconnected))
{

}

ServerEvent::ServerEvent(const PeerInactiveEvent &peerInactive) :
m_eventType(ServerEvent::Type::PeerInactive),
m_peerInactive(new PeerInactiveEvent(peerInactive))
{

}

ServerEvent::ServerEvent(const MatchmakingEvent &matchmaking) :
m_eventType(ServerEvent::Type::Matchmaking),
m_matchmaking(new MatchmakingEvent(matchmaking))
{

}

ServerEvent::ServerEvent(const GameScheduledEvent &gameScheduled) :
m_eventType(ServerEvent::Type::GameScheduled),
m_gameScheduled(new GameScheduledEvent(gameScheduled))
{

}

ServerEvent::~ServerEvent()
{
    if (m_eventType == ServerEvent::Type::Welcome)
        delete m_welcome;
    else if (m_eventType == ServerEvent::Type::Rules)
        delete m_rules;
    else if (m_eventType == ServerEvent::Type::PeerConnected)
        delete m_peerConnected;
    else if (m_eventType == ServerEvent::Type::PeerDisconnected)
        delete m_peerDisconnected;
    else if (m_eventType == ServerEvent::Type::PeerInactive)
        delete m_peerInactive;
    else if (m_eventType == ServerEvent::Type::Matchmaking)
        delete m_matchmaking;
    else if (m_eventType == ServerEvent::Type::GameScheduled)
        delete m_gameScheduled;
}

bool ServerEvent::readFromBuffer(std::list<std::uint8_t> &bytes)
{
    //Can only read onto unitialized server events
    assert(m_eventType == ServerEvent::Type::NoEvent);

    if (bytes.empty() || bytes.front() > ServerEvent::TypeLimit ||
        bytes.front() == static_cast<std::uint8_t>(ServerEvent::Type::NoEvent))
    {
        Log::err("Invalid or unknown server event received");
        return false;
    }

    ServerEvent::Type eventType = static_cast<ServerEvent::Type>(bytes.front());
    bytes.pop_front();

    if (eventType == ServerEvent::Type::Welcome)
    {
        WelcomeEvent welcomeEvent;
        if (welcomeEvent.readFromBuffer(bytes))
        {
            m_eventType = eventType;
            m_welcome = new WelcomeEvent(welcomeEvent);
            return true;
        }

        return false;
    }
    else if (eventType == ServerEvent::Type::Rules)
    {
        RulesEvent rulesEvent;
        if (rulesEvent.readFromBuffer(bytes))
        {
            m_eventType = eventType;
            m_rules = new RulesEvent(rulesEvent);
            return true;
        }

        return false;
    }
    else if (eventType == ServerEvent::Type::PeerConnected)
    {
        PeerConnectedEvent connectedEvent;
        if (connectedEvent.readFromBuffer(bytes))
        {
            m_eventType = eventType;
            m_peerConnected = new PeerConnectedEvent(connectedEvent);
            return true;
        }

        return false;
    }
    else if (eventType == ServerEvent::Type::PeerDisconnected)
    {
        PeerDisconnectedEvent disconnectedEvent;
        if (disconnectedEvent.readFromBuffer(bytes))
        {
            m_eventType = eventType;
            m_peerDisconnected = new PeerDisconnectedEvent(disconnectedEvent);
            return true;
        }

        return false;
    }
    else if (eventType == ServerEvent::Type::PeerInactive)
    {
        PeerInactiveEvent inactiveEvent;
        if (inactiveEvent.readFromBuffer(bytes))
        {
            m_eventType = eventType;
            m_peerInactive = new PeerInactiveEvent(inactiveEvent);
            return true;
        }

        return false;
    }
    else if (eventType == ServerEvent::Type::Matchmaking)
    {
        MatchmakingEvent matchmakingEvent;
        if (matchmakingEvent.readFromBuffer(bytes))
        {
            m_eventType = eventType;
            m_matchmaking = new MatchmakingEvent(matchmakingEvent);
            return true;
        }

        return false;
    }
    else if (eventType == ServerEvent::Type::GameScheduled)
    {
        m_eventType = eventType;
        m_gameScheduled = new GameScheduledEvent();
        return true;
    }

    return false;
}

bool ServerEvent::writeToBuffer(std::list<std::uint8_t> &dst) const
{
    if (m_eventType == ServerEvent::Type::NoEvent)
        return false;

    dst.push_back(static_cast<std::uint8_t>(m_eventType));

    if (m_eventType == ServerEvent::Type::Welcome)
        return m_welcome->writeToBuffer(dst);
    else if (m_eventType == ServerEvent::Type::Rules)
    {
        m_rules->writeToBuffer(dst);
        return true;
    }
    else if (m_eventType == ServerEvent::Type::PeerConnected)
        return m_peerConnected->writeToBuffer(dst);
    else if (m_eventType == ServerEvent::Type::PeerDisconnected)
        return m_peerDisconnected->writeToBuffer(dst);
    else if (m_eventType == ServerEvent::Type::PeerInactive)
        return m_peerInactive->writeToBuffer(dst);
    else if (m_eventType == ServerEvent::Type::Matchmaking)
        return m_matchmaking->writeToBuffer(dst);
    else if (m_eventType == ServerEvent::Type::GameScheduled)
        return true;

    return false;
}

std::string ServerEvent::getLog() const
{
    if (m_eventType == ServerEvent::Type::Welcome)
        return m_welcome->getLog();
    else if (m_eventType == ServerEvent::Type::Rules)
        return m_rules->getLog();
    else if (m_eventType == ServerEvent::Type::PeerConnected)
        return m_peerConnected->getLog();
    else if (m_eventType == ServerEvent::Type::PeerDisconnected)
        return m_peerDisconnected->getLog();
    else if (m_eventType == ServerEvent::Type::PeerInactive)
        return m_peerInactive->getLog();
    else if (m_eventType == ServerEvent::Type::Matchmaking)
        return m_matchmaking->getLog();
    else if (m_eventType == ServerEvent::Type::GameScheduled)
        return m_gameScheduled->getLog();

    Log::err("Unknown server event");
    return std::string();
}
