#include <ChunkHandle/ChunkHandle.hpp>
#include <MachineComponent/ChunkMonitorComponent/ChunkMonitorComponent.hpp>
#include <Serialize/Serialize.hpp>
#include <Machine/Machine.hpp>
#include <World/LocalWorld/LocalWorld.hpp>

std::string ChunkMonitorComponent::tagName = "main:chunkMonitorComponent";

ChunkMonitorComponent::ChunkMonitorComponent(Machine &machine) :
    MachineComponent(machine)
{}

bool ChunkMonitorComponent::isMonitoring(Chunk &chunk)
{
    for (const ChunkHandle &monitored : monitoredChunks)
    {
        if (monitored->base == chunk.base)
        {
            return true;
        }
    }

    return false;
}

void ChunkMonitorComponent::monitor(ChunkHandle chunk)
{
    if (isMonitoring(*chunk))
    {
        return;
    }

    if (!chunk->isReady())
    {
        crash("ChunkMonitorComponent::monitor called on non-ready chunk");
    }

    chunk->monitorPositions.push_back(machine.pos);
    monitoredChunks.push_back(chunk);

    machine.getWorld().markNeedsSaving(chunk);
}

void ChunkMonitorComponent::addCanBreakRule(CanBreakCallback callback)
{
    canBreakRules.push_back(callback);
}

bool ChunkMonitorComponent::canBreak(glm::ivec3 pos)
{
    for (const CanBreakCallback &callback : canBreakRules)
    {
        if (!callback(pos))
        {
            return false;
        }
    }

    return true;
}

void ChunkMonitorComponent::addBlockChangedCallback(BlockChangedCallback callback)
{
    blockChangedCallbacks.push_back(callback);
}

void ChunkMonitorComponent::onBlockChanged(glm::ivec3 pos, Block &oldBlock, uint8_t oldMeta, Block &newBlock, uint8_t newMeta)
{
    for (const BlockChangedCallback &callback : blockChangedCallbacks)
    {
        callback(pos, oldBlock, oldMeta, newBlock, newMeta);
    }
}

void ChunkMonitorComponent::serializeState(std::vector<uint8_t> &outBytes)
{
    std::vector<glm::ivec3> monitoredChunkBases;
    for (const ChunkHandle &chunk : monitoredChunks)
    {
        monitoredChunkBases.push_back(chunk->base);
    }

    serializeInto(outBytes, monitoredChunkBases);
}

void ChunkMonitorComponent::deserializeState(const uint8_t *bytes, size_t size)
{
    std::vector<glm::ivec3> monitoredChunkBases;
    deserialize(bytes, size, monitoredChunkBases);

    for (const glm::ivec3 &base : monitoredChunkBases)
    {
        monitoredChunks.push_back(machine.getWorld().requestChunk(base));
    }
}

bool ChunkMonitorComponent::isReady()
{
    for (const ChunkHandle &chunk : monitoredChunks)
    {
        if (!chunk->isReady())
        {
            return false;
        }
    }

    return true;
}

void ChunkMonitorComponent::onRemoving()
{
    for (ChunkHandle &chunk : monitoredChunks)
    {
        if (!chunk->isReady())
        {
            crash("Trying to un-monitor a chunk which isn't ready");
        }

        auto it = std::find(chunk->monitorPositions.begin(), chunk->monitorPositions.end(), machine.pos);
        if (it == chunk->monitorPositions.end())
        {
            crash("ChunkMonitorComponent::onRemoving: a chunk is marked as monitored, but the chunk does not contain "
                    "this machine in its monitor list");
        }

        chunk->monitorPositions.erase(it);

        machine.getWorld().markNeedsSaving(chunk);
    }
}