#include "Chunk.hpp"

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

using namespace world::server;

Chunk::Chunk(const chunk_pos& pos, const std::unique_ptr<world::generator::Abstract>& rnd): world::Chunk() {
    rnd->generate(pos, voxels);
}
#include <iostream>
Chunk::Chunk(std::istream& str, bool rle): world::Chunk(str, rle) { }
Chunk::~Chunk() { }

world::Voxel Chunk::write(std::ostream& str, bool rle) const {
    Voxel::material_t majMat = UINT16_MAX;
    uint16_t majCounter = 1;
    size_t visibleDensity = 0;
    const auto doMaj = [&](const Voxel& current) {
        if (current.material() == majMat) {
            majCounter++;
        } else {
            majCounter--;
        }
        if (majCounter == 0) {
            majMat = current.material();
            majCounter = 1;
        }
        visibleDensity += current.density() * current.is_visible();
    };
    if (rle) {
        auto it = voxels.begin();
        uint16_t counter = 1;
        Voxel current = *it;
        while(true) {
            ++it;
            doMaj(current);
            const auto end = (it == voxels.end());
            if(end || current.value != it->value) {
                str.write(reinterpret_cast<char *>(&counter), sizeof(counter));
                str.write(reinterpret_cast<char *>(&current), sizeof(current));
                if(end)
                    break;

                current = *it;
                counter = 1;
            } else {
                counter++;
            }
        }
    } else {
        for(auto current: voxels) {
            doMaj(current);
            str.write(reinterpret_cast<char *>(&current), sizeof(current));
        }
    }
    if (majCounter-1 == INT16_MAX) { // Perfect majority
        return Voxel(voxels.front().material(), voxels.front().density(), true);
    }
    return Voxel(majMat, visibleDensity / CHUNK_SIZE);
}

void Chunk::set(uint16_t idx, const world::Voxel& val) {
    modified = modified || (voxels[idx].value != val.value);
    voxels[idx] = val;
}
void Chunk::setAt(const chunk_voxel_pos& pos, const world::Voxel& val) {
    set(glm::toIdx(pos), val);
}
std::optional<world::Item> Chunk::replace(chunk_voxel_idx idx, const world::Voxel& val, float) {
    const auto res = voxels[idx];
    set(idx, val);
    return {world::Item{res.density(), res.material()}};
}
