#include <Item/StatefulItem/BlockItem/BlockItem.hpp>
#include <Machine/MItemDeployer/MItemDeployer.hpp>
#include <MachineComponent/ElectricComponent/ElectricComponent.hpp>
#include <World/LocalWorld/LocalWorld.hpp>
#include <Block/SpriteBlock/ItemDeployer/ItemDeployer.hpp>
#include <Block/SpriteBlock/ConveyorBelt/ConveyorBelt.hpp>
#include <MachineComponent/BeltComponent/BeltComponent.hpp>
#include <Belt/Belt.hpp>

Side MItemDeployer::getOutputSide()
{
    return Side(*m_world.getBlockMeta(pos) & 3);
}

Side MItemDeployer::getInputSide()
{
    return getOutputSide().opposite();
}

Side MItemDeployer::getUsingSide()
{
    uint8_t meta = *m_world.getBlockMeta(pos);
    uint8_t side = meta & 2;
    side ^= 2;

    if (meta & ItemDeployer::metaUseSign)
    {
        side |= 1;
    }

    return Side(side);
}

MItemDeployer::MItemDeployer(LocalWorld &world, glm::ivec3 pos) : Machine(world, pos)
{
    attach<ElectricComponent>();
    attach<BeltComponent>();
}

void MItemDeployer::serializeState(std::vector<uint8_t> &outBytes)
{
    // NOP
}

void MItemDeployer::deserializeState(const uint8_t *data, size_t size)
{
    // NOP
}

bool MItemDeployer::canConnectElectricWire(Side side)
{
    return side == Side::Bottom;
}

void MItemDeployer::onItemStackEntering(Side side, ItemStack &stack)
{
    ElectricComponent &electricComp = getComponent<ElectricComponent>();
    electricComp.rechargeFrom(Side::Bottom, energyPerItem);

    if (!electricComp.tryConsume(energyPerItem))
    {
        return;
    }

    Side usingSide = getUsingSide();
    Log::debug(formatString("usingSide=$+", usingSide.name()));
    glm::ivec3 usePos = pos + usingSide.out();

    stack.getItem()->onUse(m_world, *this, stack, usePos, usingSide.opposite());
    
    if (stack.isEmpty())
    {
        return;
    }

    getComponent<BeltComponent>()
        .getOutputBelt(getOutputSide())
        ->tryInsertAt(0, stack);
}

Side MItemDeployer::getSideFacing2D()
{
    return getUsingSide().opposite();
}

void MItemDeployer::getBeltsBranchingFrom(Side queriedSide, std::vector<std::shared_ptr<Belt>> &belts)
{
    BeltComponent &beltComp = getComponent<BeltComponent>();

    Side inputSide = getInputSide();
    if (queriedSide != inputSide)
    {
        return;
    }

    belts.push_back(beltComp.getOutputBelt(inputSide.opposite()));
}

void MItemDeployer::update()
{
    uint8_t oldMeta = *m_world.getBlockMeta(pos);
    uint8_t newMeta = oldMeta & ~(3 << 2);

    std::shared_ptr<Belt> outputBelt = getComponent<BeltComponent>().getOutputBelt(getOutputSide());

    newMeta |= (outputBelt->beltOffset & 3) << 2;
    if (newMeta != oldMeta)
    {
        m_world.setBlockMeta(pos, newMeta);
    }
}

bool MItemDeployer::isUsingItemDirectly()
{
    return true;
}