#include <Controller/Controller.hpp>
#include <Display/Display.hpp>
#include <Entity/PhysicsEntity/CameraEntity/Player/Player.hpp>
#include <Item/Item.hpp>
#include <Display/WidgetDisplay/WorldDisplay/WorldDisplay.hpp>

Controller::Controller(ControllerChannel &channel) :
    nextPromiseId(1),
    channel(channel),
    player(&channel.player),
    world(*player->world),
    requestClose(this, RunsOn::Client,
        [](uint32_t) -> uint32_t
        {
            static_cast<WorldDisplay&>(*Display::getCurrent()).shouldCloseMenu = true;
            return 0;
        }
    ),
    saveConfig(this, RunsOn::Server, [this](uint32_t) -> MachineConfig
    {
        MachineConfig config;
        boundMachine->saveConfig(config);
        return config;
    }),
    loadConfig(this, RunsOn::Server, [this](MachineConfig config) -> uint32_t
    {
        boundMachine->loadConfig(config, false);
        return 0;
    })
{}

Player* Controller::getPlayer()
{
    return player;
}

void Controller::update()
{
    if (boundMachine == nullptr)
    {
        return;
    }

    if (boundMachine->isDetached())
    {
        requestClose(0);
        return;
    }

    double distanceFromMachine = glm::length(glm::vec3(player->pos - wfvec3(boundMachine->pos)));
    if (distanceFromMachine > Player::handRange)
    {
        requestClose(0);
        return;
    }
}

void Controller::dispatch(uint64_t promiseId, uint8_t opcode, const uint8_t *payload, size_t size)
{
    if (opcode >= commands.size())
    {
        return;
    }

    commands[opcode](promiseId, payload, size);
}

void Controller::resolve(uint64_t promiseId, const uint8_t *payload, size_t size)
{
    auto it = promises.find(promiseId);
    
    if (it != promises.end())
    {
        it->second(payload, size);
        promises.erase(it);
    }
}

Controller::RunsOn Controller::getRunsOnPolicy()
{
    return world.isLocal() ? RunsOn::Server : RunsOn::Client;
}