#include <Controller/Controller.hpp>
#include <ControllerChannel/ControllerChannel.hpp>
#include <ControllerRegistry/ControllerRegistry.hpp>
#include <Entity/PhysicsEntity/CameraEntity/Player/Player.hpp>
#include <Protocol/Protocol.hpp>
#include <World/World.hpp>

std::shared_ptr<Messenger> ControllerChannel::getPeer()
{
    return player.world->getPeerMessenger(player);
}

ControllerChannel::ControllerChannel(Player &player, uint8_t channelId) : channelId(channelId), player(player)
{
    player.ctrlChannelTable[channelId] = this;
}

Controller& ControllerChannel::attach(const std::string &id, uint16_t stamp)
{
    currentController = controllerRegistry.createController(*this, id);
    if (currentController == nullptr)
    {
        crash(formatString("Attempted to attach an invalid controller ID: $+", id));
    }

    currentStamp = stamp;

    if (player.client != nullptr)
    {
        player.client->sendSerial(Protocol::MessageType::AttachController, channelId, stamp, id);
    }

    return *currentController;
}

void ControllerChannel::detach()
{
    currentController = nullptr;

    if (player.client != nullptr)
    {
        player.client->sendSerial(Protocol::MessageType::DetachController, channelId);
    }
}

void ControllerChannel::update()
{
    if (currentController != nullptr)
    {
        currentController->update();
    }
}

void ControllerChannel::remoteCall(uint8_t opcode, uint64_t promiseId, const NestedPayload &payload)
{
    getPeer()->sendSerial(Protocol::MessageType::RemoteCall, channelId, opcode, currentStamp, promiseId, payload);
}

void ControllerChannel::remoteReturn(uint64_t promiseId, const NestedPayload &payload)
{
    getPeer()->sendSerial(Protocol::MessageType::RemoteReturn, channelId, currentStamp, promiseId, payload);
}

void ControllerChannel::dispatch(uint8_t opcode, uint16_t stamp, uint64_t promiseId, const NestedPayload &payload)
{
    if (currentController == nullptr)
    {
        return;
    }

    if (currentStamp != stamp)
    {
        return;
    }

    currentController->dispatch(promiseId, opcode, payload.buffer.data(), payload.buffer.size());
}

void ControllerChannel::resolve(uint16_t stamp, uint64_t promiseId, const NestedPayload &payload)
{
    if (currentController == nullptr)
    {
        return;
    }

    if (currentStamp != stamp)
    {
        return;
    }

    currentController->resolve(promiseId, payload.buffer.data(), payload.buffer.size());
}

bool ControllerChannel::hasPeer()
{
    return getPeer() != nullptr;
}

uint16_t ControllerChannel::getStamp()
{
    return currentStamp;
}

void ControllerChannel::onClosedByClient(uint16_t stamp)
{
    if (currentStamp != stamp)
    {
        return;
    }

    currentController = nullptr;
}