#include <Item/StatefulItem/BlockItem/BlockItem.hpp>
#include <Machine/MBlockBreaker/MBlockBreaker.hpp>
#include <MachineComponent/ElectricComponent/ElectricComponent.hpp>
#include <Serialize/Base.hpp>
#include <World/LocalWorld/LocalWorld.hpp>
#include <Block/SpriteBlock/BlockBreaker/BlockBreaker.hpp>
#include <Block/SpriteBlock/ConveyorBelt/ConveyorBelt.hpp>
#include <MachineComponent/BeltComponent/BeltComponent.hpp>
#include <MachineComponent/BeltMotorComponent/BeltMotorComponent.hpp>
#include <Belt/Belt.hpp>

Side MBlockBreaker::getBreakingSide()
{
    return Side(BlockBreaker::getMetaOrientation(*m_world.getBlockMeta(pos))).opposite();
}

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

void MBlockBreaker::serializeState(std::vector<uint8_t> &outBytes)
{
    serializeInto(outBytes, itemQueue);
}

void MBlockBreaker::deserializeState(const uint8_t *data, size_t size)
{
    deserialize(data, size, itemQueue);
}

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

void MBlockBreaker::onItemStackEntering(Side side, ItemStack &stack)
{
    // NOP
}

void MBlockBreaker::update()
{
    uint8_t oldMeta = *m_world.getBlockMeta(pos);
    uint8_t newMeta = oldMeta & 3;

    ElectricComponent &electricComp = getComponent<ElectricComponent>();
    electricComp.rechargeFrom(Side::Bottom, energyCapacity);

    Side breakingSide = getBreakingSide();
    glm::ivec3 breakingPos = pos + breakingSide.out();

    BeltComponent &beltComp = getComponent<BeltComponent>();
    std::shared_ptr<Belt> mainBelt = beltComp.getOutputBelt(BlockBreaker::getMetaOrientation(oldMeta));

    if (itemQueue.empty() && m_world.canBreak(breakingPos))
    {
        Block *block = m_world.getBlockAt(breakingPos);
        if (block != nullptr && block->name != "main:air" && electricComp.getEnergy() >= energyPerBlock)
        {
            electricComp.drainEnergy(energyPerBlock);
            std::vector<ItemStack> stacks;
            block->getHarvestedStacks(m_world, breakingPos, stacks);
            m_world.playSound(block->getBreakingSound(), breakingPos);
            m_world.setBlockAt(breakingPos, 0);

            for (const auto &stack : stacks)
            {
                itemQueue.push_back(stack);
            }
        }
    }

    if (!itemQueue.empty())
    {
        mainBelt->tryInsertAt(0, itemQueue.front());
        if (itemQueue.front().isEmpty())
        {
            itemQueue.pop_front();
        }
    }

    BeltMotorComponent &motor = getComponent<BeltMotorComponent>();
    if (motor.tryAdvance(mainBelt))
    {
        newMeta |= BlockBreaker::metaActive;
    }

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