#include <Machine/MGrindingBowl/MGrindingBowl.hpp>
#include <Serialize/Serialize.hpp>
#include <FuelRuleRegistry/FuelRuleRegistry.hpp>
#include <World/LocalWorld/LocalWorld.hpp>
#include <Block/Block.hpp>
#include <SmeltingRuleRegistry/SmeltingRuleRegistry.hpp>

ItemStack& MGrindingBowl::inputSlot()
{
    return m_stacks[0];
}

ItemStack& MGrindingBowl::outputSlot()
{
    return m_stacks[1];
}

MGrindingBowl::MGrindingBowl(LocalWorld &world, glm::ivec3 pos) :
    Machine(world, pos),
    m_stacks(2),
    progress(0)
{}

uint32_t MGrindingBowl::getProgress()
{
    return progress;
}

void MGrindingBowl::grind()
{
    // If there is nothing in the input, do nothing.
    ItemStack &inputStack = inputSlot();
    if (inputStack.isEmpty())
    {
        return;
    }

    // If the input cannot be ground, do nothing.
    ItemStack output = inputStack.getItem()->getGrindingOutput(inputStack);
    if (output.isEmpty())
    {
        return;
    }

    // If the output slot is not empty and we can't merge, do nothing.
    ItemStack &outputStack = outputSlot();
    if (!outputStack.isEmpty() && !output.canMergeFrom(outputStack))
    {
        return;
    }

    // If there is not enough space in the output stack, do nothing.
    if (!outputStack.isEmpty() && outputStack.getStackSize() + output.getStackSize() > output.getMaxStackSize())
    {
        return;
    }

    // Otherwise, make progress.
    if (++progress == completionProgress)
    {
        // Grinding completed, reset progress.
        progress = 0;

        outputStack.leftMergeFrom(output);
        inputStack.extractOne();
    }
}

void MGrindingBowl::serializeState(std::vector<uint8_t> &outBytes)
{
    serializeInto(outBytes, m_stacks, progress);
}

void MGrindingBowl::deserializeState(const uint8_t *data, size_t size)
{
    deserialize(data, size, m_stacks, progress);
}

int MGrindingBowl::getNumSlots()
{
    return (int) m_stacks.size();
}

ItemStack MGrindingBowl::getStackInSlot(int slot)
{
    return m_stacks[slot];
}

void MGrindingBowl::setStackInSlot(int slot, ItemStack stack)
{
    m_stacks[slot] = stack;

    if (slot == 0)
    {
        // Input slot updated, cancel grinding progress.
        progress = 0;
    }
}

bool MGrindingBowl::isAllowedStackInSlot(int slot, ItemStack stack)
{
    if (stack.isEmpty())
    {
        // All slots can be emptied.
        return true;
    }

    // You can put whatever you want in the input slot, but nothing in the output.
    return slot == 0;
}