#include "LocalUniverse.hpp"

#include "../../core/data/math.hpp"
#include "../../core/data/mem.hpp"
#include "../contouring/Abstract.hpp"
#include "../Window.hpp"
#include "../../core/utils/logger.hpp"
#include "../../core/world/Area.hpp"

using namespace world::client;

LocalUniverse::LocalUniverse(server_handle *const handle, const std::string& contour): Universe(contour), handle(handle) {
    assert(handle != nullptr);
    for (auto i = 0; i < 500 && !handle->running; i++) {
        LOG_D("Waiting local server startup");
        Window::wait();
    }
    handle->onUpdate = std::function([&](const area_<chunk_pos> &pos, const chunk_pos &offset, const world::ChunkContainer &data, geometry::Faces neighbors) {
        contouring->onUpdate(pos, this->last_chunk - offset, data, neighbors);
    });
}
LocalUniverse::~LocalUniverse() {
    handle->running = false;
}

void LocalUniverse::update(voxel_pos pos, float) {
    if (handle->teleport.has_value() && onTeleport) {
        pos = handle->teleport.value();
        onTeleport(pos);
        handle->teleport = std::nullopt;
    }
    if (handle->message.has_value() && onMessage) {
        onMessage(handle->message.value());
        handle->message = std::nullopt;
    }
    if (handle->entityChange) {
        handle->entities->iter([&](entity_id eId, const Entity &entity) {
            if (entity.instances.size() == 0)
                return;

            if (auto area = std::get_if<Entity::area_t>(&entity.shape)) {
                contouring->onEntityLoad(eId.index, glm::lvec3(1) + glm::divide(entity.size), *area);
            } else {
                auto model = std::get_if<Entity::model_t>(&entity.shape);
                assert(model);
                data::vec_istream idata(*model);
                std::istream iss(&idata);
                contouring->onEntityLoad(eId.index, iss);
            }
        });
        handle->entityChange = false;
    }

    const auto cur_chunk = glm::divide(pos);
    const auto chunkChange = cur_chunk != last_chunk;
    last_chunk = cur_chunk;

    if(chunkChange) {
        for(const auto& area: *handle->areas) {
            const chunk_pos diff = glm::divide(pos - area.second->getOffset().as_voxel());
            for(const auto& chunk: area.second->getChunks()) {
                contouring->onNotify(std::make_pair(area.first, chunk.first), diff, area.second->getChunks());
            }
        }
    }

    contouring->update(pos, *handle->areas);
}

void LocalUniverse::emit(const world::action::packet &packet) {
    handle->emit(packet);
}
Universe::ray_result LocalUniverse::raycast(const geometry::Ray& ray) const {
    return handle->raycast(ray);
}
bool LocalUniverse::isAreaFree(const area_<voxel_pos> &pos, const geometry::Shape shape, const uint16_t radius) const {
    if (const auto it = handle->areas->find(pos.first); it != handle->areas->end()) {
        const auto center = pos.second + it->second->getOffset().as_voxel();
        return !handle->entities->contains([&](entity_id, const Entity &entity) {
            return entity.instances.contains([&](entity_id, const Entity::Instance &inst) {
                return geometry::InShape(shape, center, radius, inst.pos.as_voxel(), entity.size);
            });
        });
    } else
        return false;
}

void LocalUniverse::getEntitiesModels(const std::function<void(size_t, const std::vector<glm::mat4>&)>& call,
    const std::optional<geometry::Frustum>& frustum, const glm::llvec3& offset, int density)
{
    std::vector<glm::mat4> mats;
    handle->entities->iter([&](entity_id eId, const Entity &entity) {
        entity.instances.iter([&](entity_id iId, const Entity::Instance &inst) {
            if (eId.index == PLAYER_ENTITY_ID.index && iId.index == PLAYER_ENTITY_ID.index)
                return;

            glm::mat4 tmp;
            if (contouring::Abstract::CullEntity(tmp, entity.size, entity.scale, inst.pos, frustum, offset, density))
                mats.push_back(tmp);
        });
        if(!mats.empty()) {
            call(eId.index, mats);
            mats.resize(0);
        }
    });
}