#include "EdittableChunk.hpp"

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

using namespace world;

EdittableChunk::EdittableChunk(owner<ChunkEdits*> edits, std::istream& str, bool rle): world::Chunk(str, rle), edits(edits) { }
EdittableChunk::EdittableChunk(owner<ChunkEdits*> edits): world::Chunk(), edits(edits) { }
EdittableChunk::~EdittableChunk() { }

void EdittableChunk::setAt(const chunk_voxel_pos &pos, const Voxel& voxel) {
    return set(glm::toIdx(pos), voxel);
}

std::optional<Faces> ChunkEdits::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 ChunkEdits::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 ChunkEdits::add(const Edit& edit) {
    edits.erase(edit.idx);
    if(edit.delay > 0) {
        edits.emplace(edit.idx, EditBody{edit.value, edit.delay});
    } else {
        invalidate(edit.idx);
    }
}
void ChunkEdits::apply(const Edit& edit) {
    const auto prev = parent->get(edit.idx);
    if(prev.value != edit.value.value) {
        parent->set(edit.idx, edit.value);
        add(Edit{prev, edit.delay, edit.idx});
    }
}

std::optional<world::chunk_voxel_idx> ChunkEdits::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 {};
    }
}
