#include "LocalUniverse.hpp"

#include "core/geometry/math.hpp"
#include "../contouring/Abstract.hpp"
#include "../render/Window.hpp"
#include "core/utils/logger.hpp"
#include "core/world/Elements.hpp"
#include "core/utils/io.hpp"
#include <random>

#undef LOG_PREFIX
#define LOG_PREFIX "Client: "

using namespace world::client;

LocalUniverse::LocalUniverse(const Universe::login& login, server_handle *const handle, const std::string& contour): Universe(contour), handle(handle) {
    assert(handle != nullptr);
    handle->playerName = login.name;
    for (auto i = 0; i < 500 && !handle->running; i++) {
        LOG_D("Waiting local server startup");
        Window::wait();
    }
    assert(handle->running);
}
LocalUniverse::~LocalUniverse() {
    handle->running = false;
}

void LocalUniverse::update(cell_pos pos, float deltaTime) {
    if (handle->teleport.has_value() && onTeleport) {
        const auto &rtf = handle->teleport.value();
        const auto abs = getAbsolute(rtf);
        pos = abs.position;
        onTeleport(rtf, abs);
        handle->teleport = std::nullopt;
    }
    if (onMessage) {
        handle->messages.extractor(onMessage);
    }

    const auto notifyTick = contouringNotifier.mustNotify(pos, deltaTime);

    const auto lock = getElements();
    const auto &elements = *lock;
    { // Update alive areas
        ZoneScopedN("World");
        auto rng = std::mt19937(std::rand());
        const auto contouringThreshold = UINT32_MAX / (1 + contouring->getQueueSize());
        for (auto& ref: elements.areas) {
            const area_id id = elements.withFlag(ref).val;
            const auto node = elements.findArea(id);
            assert(node);
            //MAYBE: if in load range
            const auto area_tf = node->absolute;

            const auto area = node->get();
            const auto& chunks = area->getChunks();
            //MAYBE: chunk area change
            for(const auto& chunk: chunks) {
                //MAYBE: compute absolute manually from chunk_pos(1) * pivot
                const cell_pos chunkPos = area_tf.computeChild(glm::multiply(chunk.first));
                const auto chunkDist = glm::length2(glm::divide(chunkPos - pos));
                //MAYBE: if in load range

                if(const auto neighbors = chunk.second->setEdits()->update(deltaTime, rng() < contouringThreshold)) {
                    contouring->onUpdate(area_chunk_pos{id, chunk.first}, chunkDist, chunks, neighbors.value());
                } else if (notifyTick) {
                    contouring->onNotify(area_chunk_pos{id, chunk.first}, chunkDist, chunks);
                }
            }
        }
    }
    if (notifyTick) {
        for(const auto& ref: elements.parts) {
            const part_id id = elements.withFlag(ref).val;
            const auto node = elements.findPart(id);
            if (node->get()->loaded()) {
                const auto partDist = glm::length2(glm::divide((cell_pos)node->absolute.position - pos));
                contouring->onNotify(id, partDist, node->get()->chunkSize(), node->get()->chunks, false);
            }
        }
        elements.models.iter([&] (const model_id& id, const Model& model) {
            if (!model.instances.empty()) {
                io::vec_istream idata(model.dt);
                std::istream iss(&idata);
                contouring->onLoad(id, iss);
            }
        });
    }

    contouring->update(pos, elements);
}

void LocalUniverse::emit(const world::action::packet &packet) {
    handle->emit(packet);
}
