#include <Machine/MBeltMotor/MBeltMotor.hpp>
#include <MachineComponent/AttributionComponent/AttributionComponent.hpp>
#include <MachineComponent/ElectricComponent/ElectricComponent.hpp>
#include <Milestone/IndustrialRevolutionMilestone/IndustrialRevolutionMilestone.hpp>
#include <World/LocalWorld/LocalWorld.hpp>
#include <Block/SpriteBlock/BeltMotor/BeltMotor.hpp>
#include <Block/SpriteBlock/ConveyorBelt/ConveyorBelt.hpp>
#include <MachineComponent/BeltComponent/BeltComponent.hpp>
#include <MachineComponent/BeltMotorComponent/BeltMotorComponent.hpp>
#include <Belt/Belt.hpp>

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

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

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

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

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

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

    BeltMotorComponent &motor = getComponent<BeltMotorComponent>();
    bool advanced = motor.tryAdvance(mainBelt);
    if (advanced)
    {
        newMeta |= BeltMotor::metaActive;
        getComponent<AttributionComponent>().grantAchievementFor(
            std::string(Machine::Action::Placed),
            IndustrialRevolutionMilestone::ID, IndustrialRevolutionMilestone::PowerBelt
        );
    }

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

void MBeltMotor::onItemStackEntering(Side side, ItemStack &stack)
{
    uint8_t meta = *m_world.getBlockMeta(pos);
    BeltComponent &beltComp = getComponent<BeltComponent>();
    Side outputSide = BeltMotor::getMetaOrientation(meta);
    Belt &belt = *beltComp.getOutputBelt(outputSide);

    int voxelOffset;
    if (side == outputSide)
    {
        voxelOffset = 7;
    }
    else if (side == outputSide.opposite())
    {
        voxelOffset = 0;
    }
    else
    {
        voxelOffset = 2;
    }

    belt.tryInsertAt(voxelOffset, stack);
}