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

#include "Tools/Log.hpp"

ClientHost::ClientHost() :
m_enetClient(nullptr), m_serverPeer(nullptr),
m_messageReceiver(nullptr), m_connectionReceiver(nullptr)
{

}

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

void ClientHost::destroy()
{
    reset();

    if (m_enetClient != nullptr)
    {
        enet_host_destroy(m_enetClient);
        m_enetClient = nullptr;
    }
}

bool ClientHost::init()
{
    if (m_enetClient != nullptr)
        destroy();

    m_enetClient = enet_host_create(nullptr, 1, ChannelInfo::Count, 0, 0);

    if (m_enetClient == nullptr)
        return false;

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

    return true;
}

bool ClientHost::connect(const Address &address)
{
    //(Re-)Initialize server peer
    reset();
    m_serverPeer = enet_host_connect(m_enetClient, &(address.m_enetAddr),
                                     ChannelInfo::Count, 0);
    return (m_serverPeer != nullptr);
}

void ClientHost::setMessageReceiver(ClientHost::MessageReceiver *receiver)
{
    m_messageReceiver = receiver;
}

void ClientHost::setConnectionReceiver(ClientHost::ConnectionReceiver *receiver)
{
    m_connectionReceiver = receiver;
}

void ClientHost::disconnect(KickReason reason)
{
    if (m_serverPeer != nullptr)
        enet_peer_disconnect(m_serverPeer, 0);

    if (m_connectionReceiver != nullptr)
        m_connectionReceiver->serverDisconnectingCallback(reason);
}

void ClientHost::disconnectNow()
{
    if (m_serverPeer != nullptr)
    {
        enet_peer_disconnect_now(m_serverPeer, 0);
        m_serverPeer = nullptr;
    }
}

void ClientHost::reset()
{
    if (m_serverPeer != nullptr)
    {
        enet_peer_reset(m_serverPeer);
        m_serverPeer = nullptr;
    }
}

void ClientHost::reset(KickReason reason)
{
    reset();
    if (m_connectionReceiver != nullptr)
        m_connectionReceiver->serverDisconnectedCallback(reason);
}

void ClientHost::listen()
{
    ENetEvent event;
    while (enet_host_service(m_enetClient, &event, 0) > 0)
    {
        if (event.type == ENET_EVENT_TYPE_CONNECT)
        {
            if (m_connectionReceiver != nullptr)
                m_connectionReceiver->serverConnectedCallback();
        }
        else if (event.type == ENET_EVENT_TYPE_RECEIVE)
        {
            receive((*event.packet), event.channelID);
            enet_packet_destroy(event.packet);
        }
        else if (event.type == ENET_EVENT_TYPE_DISCONNECT &&
                 event.peer == m_serverPeer)
        {
            reset(KickInfo::getReason(event.data));
        }
    }
}

void ClientHost::send(const Message &message, Channel channel) const
{
    if (m_serverPeer == nullptr)
        return;

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

        buffer.destroy();
    }
}

void ClientHost::receive(const ENetPacket &packet, enet_uint8 channelID) const
{
    if (m_messageReceiver != nullptr && ChannelInfo::isValid(channelID))
    {
        m_messageReceiver->messageReceivedCallback(packet,
                                            static_cast<Channel>(channelID));
    }
}
