#include "EdittableChunk.hpp"

#include <algorithm>
#include <Tracy.hpp>
#include "../../core/data/math.hpp"

using namespace world::client;

EdittableChunk::EdittableChunk(): world::Chunk() { }
EdittableChunk::~EdittableChunk() { }

std::optional<Faces> EdittableChunk::update(float deltaTime, bool animate) {
    ZoneScopedN("Chunk");
    for(auto it = edits.begin(); it != edits.end();) {
        it->second.delay -= deltaTime;
        if(it->second.delay <= 0 && animate) {
            invalidate(it->first);
            it = edits.erase(it);
        } else {
            ++it;
        }
    }

    if(upToDate) {
        return {};
    } else {
        upToDate = true;
        return {toUpdate};
    }
}

void EdittableChunk::invalidate(uint16_t idx) {
    invalidate(
        ((!getNeighborIdx(idx, Face::Up).has_value()) & Faces::Up) |
        ((!getNeighborIdx(idx, Face::Down).has_value()) & Faces::Down) |
        ((!getNeighborIdx(idx, Face::Left).has_value()) & Faces::Left) |
        ((!getNeighborIdx(idx, Face::Right).has_value()) & Faces::Right) |
        ((!getNeighborIdx(idx, Face::Forward).has_value()) & Faces::Forward) |
        ((!getNeighborIdx(idx, Face::Backward).has_value()) & Faces::Backward));
}
void EdittableChunk::apply(const Edit& edit) {
    const auto prev = voxels[edit.idx];
    if(prev.value != edit.value.value) {
        voxels[edit.idx] = edit.value;
        edits.erase(edit.idx);
        if(edit.delay > 0) {
            edits.emplace(edit.idx, EditBody{prev, edit.delay});
        } else {
            invalidate(edit.idx);
        }
    }
}

std::optional<chunk_voxel_idx> EdittableChunk::getNeighborIdx(chunk_voxel_idx idx, Face dir) {
    switch (dir) {
    case Face::Forward:
        if (idx % glm::IDX_LENGTH >= glm::IDX_LENGTH - 1)
            return {};
        return idx + 1;

    case Face::Backward:
        if (idx % glm::IDX_LENGTH <= 0)
            return {};
        return idx - 1;

    case Face::Up:
        if ((idx / glm::IDX_LENGTH) % glm::IDX_LENGTH >= glm::IDX_LENGTH - 1)
            return {};
        return idx + glm::IDX_LENGTH;

    case Face::Down:
        if ((idx / glm::IDX_LENGTH) % glm::IDX_LENGTH <= 0)
            return {};
        return idx - glm::IDX_LENGTH;

    case Face::Right:
        if (idx / glm::IDX_LENGTH2 >= glm::IDX_LENGTH - 1)
            return {};
        return idx + glm::IDX_LENGTH2;

    case Face::Left:
        if (idx / glm::IDX_LENGTH2 <= 0)
            return {};
        return idx - glm::IDX_LENGTH2;

    default:
        return {};
    }
}
