#include <Serialize/Serialize.hpp>
#include <Machine/Machine.hpp>
#include <World/LocalWorld/LocalWorld.hpp>

Machine::Machine(LocalWorld &world, glm::ivec3 pos) :
    m_world(world),
    ourChunk(world.requestChunk(pos & glm::ivec3(~0xF))),
    pos(pos)
{
    stateFilePath = formatString("$+/mach_$+_$+_$+.dat", ourChunk->getNode()->getBaseDir(), pos.x, pos.y, pos.z);
}

bool Machine::isReady()
{
    for (const auto &[tag, comp] : components)
    {
        if (!comp->isReady())
        {
            return false;
        }
    }

    return ourChunk->isReady();
}

void Machine::update()
{
    // NOP
}

LocalWorld& Machine::getWorld()
{
    return m_world;
}

void Machine::serializeMachineFile(std::vector<uint8_t> &outBytes)
{
    std::unordered_map<std::string, std::vector<uint8_t>> compTable;
    serializeState(compTable[""]);

    for (auto &[tag, comp] : components)
    {
        comp->serializeState(compTable[tag]);
    }

    serializeInto(outBytes, compTable);
}

void Machine::deserializeMachineFile(const uint8_t *data, size_t size)
{
    std::unordered_map<std::string, std::vector<uint8_t>> compTable;
    deserialize(data, size, compTable);

    for (const auto &[tag, data] : compTable)
    {
        if (tag.size() == 0)
        {
            deserializeState(data.data(), data.size());
            continue;
        }

        if (components.count(tag) == 0)
        {
            continue;
        }

        components[tag]->deserializeState(data.data(), data.size());
    }
}

void Machine::onRemoving()
{
    for (auto &[tag, comp] : components)
    {
        comp->onRemoving();
    }

    detached = true;
}

void Machine::updateAll()
{
    for (auto &[tag, comp] : components)
    {
        comp->onPreUpdate();
    }

    update();

    for (auto &[tag, comp] : components)
    {
        comp->onPostUpdate();
    }
}

Chunk& Machine::getChunk()
{
    return *ourChunk;
}

bool Machine::isDetached()
{
    return detached;
}

void Machine::saveMainConfig(MachineConfig &config)
{
    // NOP
}

void Machine::loadMainConfig(const MachineConfig &config, bool consumed)
{
    // NOP
}

void Machine::saveConfig(MachineConfig &config)
{
    saveMainConfig(config);
    if (config.blockName.empty())
    {
        return;
    }

    for (auto &[tag, comp] : components)
    {
        comp->saveConfig(config.consumedItems, config.components[tag]);
    }
}

void Machine::loadConfig(MachineConfig &config, bool consumed)
{
    loadMainConfig(config, consumed);
    for (auto &[tag, comp] : components)
    {
        comp->loadConfig(config.components[tag], consumed);
    }
}