/*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 "ServerHost.hpp"
#include "Address.hpp"
#include "Network/Message/Message.hpp"

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

ServerHost::ServerHost() : m_enetServer(nullptr), m_callbackReceiver(nullptr)
{

}

ServerHost::~ServerHost()
{
    destroy();
}

void ServerHost::destroy()
{
    if (m_enetServer != nullptr)
    {
        enet_host_destroy(m_enetServer);
        m_enetServer = nullptr;
        m_callbackReceiver = nullptr;
    }
}

bool ServerHost::init(const Address &address, std::size_t peerLimit)
{
    if (m_enetServer != nullptr)
        destroy();

    m_enetServer = enet_host_create(&(address.m_enetAddr), peerLimit,
                                    ChannelInfo::Count, 0, 0);
    if (m_enetServer == nullptr)
        return false;

    //Enable packet compression
    if (enet_host_compress_with_range_coder(m_enetServer) < 0)
    {
        Log::err("Could not enable range coder compression");
        destroy();
        return false;
    }

    return true;
}

void ServerHost::setHostCallbackReceiver(ServerHostCallbackReceiver *receiver)
{
    m_callbackReceiver = receiver;
}

void ServerHost::listen()
{
    ENetEvent event;
    while (enet_host_service(m_enetServer, &event, 0) > 0)
    {
        if (event.type == ENET_EVENT_TYPE_CONNECT)
        {
            const std::uint16_t peerID = event.peer->incomingPeerID;
            m_peers[peerID] = event.peer;

            if (m_callbackReceiver != nullptr)
                m_callbackReceiver->peerConnectedCallback(peerID);
        }
        else if (event.type == ENET_EVENT_TYPE_RECEIVE)
        {
            receive(event.peer->incomingPeerID, *event.packet, event.channelID);
            enet_packet_destroy(event.packet);
        }
        else if (event.type == ENET_EVENT_TYPE_DISCONNECT)
        {
            const std::uint16_t peerID = event.peer->incomingPeerID;
            if (m_callbackReceiver != nullptr)
                m_callbackReceiver->peerDisconnectedCallback(peerID);

            m_peers.erase(peerID);
        }
    }
}

void ServerHost::send(const Message &message, std::uint16_t peerID,
                      Channel channel, bool forceReliable)
{
    if (m_peers.count(peerID) == 0)
    {
        Log::err(Format::format("Cannot send to unknown peer {}", peerID));
        return;
    }

    Message::Buffer buffer = message.allocBuffer();
    if (buffer.data != nullptr)
    {
        ENetPacket *packet = enet_packet_create(buffer.data, buffer.length,
            (forceReliable || ChannelInfo::isReliable(channel)) ?
                                      ENET_PACKET_FLAG_RELIABLE : 0);
        if (packet != nullptr)
            enet_peer_send(m_peers[peerID], static_cast<enet_uint8>(channel),
                           packet);

        buffer.destroy();
    }
}

void ServerHost::receive(std::uint16_t peerID, const ENetPacket &packet,
                         enet_uint8 channelID) const
{
    if (m_callbackReceiver != nullptr && ChannelInfo::isValid(channelID))
    {
        m_callbackReceiver->messageReceivedCallback(peerID, packet,
                                            static_cast<Channel>(channelID));
    }
}

void ServerHost::flush()
{
    enet_host_flush(m_enetServer);
}

void ServerHost::kick(std::uint16_t peerID, KickReason reason)
{
    if (m_peers.count(peerID) == 0)
    {
        Log::err(Format::format("Cannot kick unknown peer {}", peerID));
        return;
    }

    Log::info(Format::format("Peer> Kicked peer {}: {}",
                             peerID, KickInfo::describe(reason)));
    enet_peer_disconnect(m_peers[peerID], static_cast<enet_uint32>(reason));
}

std::uint32_t ServerHost::getPing(std::uint16_t peerID) const
{
    const std::map<std::uint16_t, ENetPeer*>::const_iterator it =
        m_peers.find(peerID);

    if (it == m_peers.end())
    {
        Log::err(Format::format("Cannot get ping from unknown peer {}",
                                peerID));
        return 0;
    }

    return it->second->roundTripTime;
}

Address ServerHost::getAddress(std::uint16_t peerID) const
{
    const std::map<std::uint16_t, ENetPeer*>::const_iterator it =
        m_peers.find(peerID);

    if (it == m_peers.end())
    {
        Log::err(Format::format("Cannot get address from unknown peer {}",
                                peerID));
        return Address();
    }

    return Address(it->second->address);
}
