#include <MachineComponent/BeltMotorComponent/BeltMotorComponent.hpp>
#include <MachineComponent/BeltComponent/BeltComponent.hpp>
#include <MachineComponent/ElectricComponent/ElectricComponent.hpp>
#include <Machine/Machine.hpp>
#include <Serialize/Serialize.hpp>
#include <World/LocalWorld/LocalWorld.hpp>

std::string BeltMotorComponent::tagName = "main:beltMotorComponent";

void BeltMotorComponent::walkBranchingBelts(std::shared_ptr<Belt> belt, std::vector<std::shared_ptr<Belt>> &belts, int depth)
{
    if (depth == maxBranchingDepth)
    {
        return;
    }

    glm::ivec3 endPos = belt->getEndPos();
    std::shared_ptr<IBeltMachine> endMachine = std::dynamic_pointer_cast<IBeltMachine>(machine.m_world.getMachine(endPos));

    if (endMachine == nullptr)
    {
        return;
    }

    size_t startIndex = belts.size();
    endMachine->getBeltsBranchingFrom(belt->dir.opposite(), belts);
    size_t endIndex = belts.size();

    for (size_t i = startIndex; i < endIndex; i++)
    {
        walkBranchingBelts(belts[i], belts, depth + 1);
    }
}

BeltMotorComponent::BeltMotorComponent(Machine &machine) : MachineComponent(machine) {}

bool BeltMotorComponent::tryAdvance(std::shared_ptr<Belt> mainBelt)
{
    std::vector<std::shared_ptr<Belt>> belts;
    belts.reserve(32);
    belts.push_back(mainBelt);

    walkBranchingBelts(mainBelt, belts, 0);

    bool isActive = false;

    bool anyBeltHasItems = false;
    for (const std::shared_ptr<Belt> &belt : belts)
    {
        if (belt->hasItems())
        {
            anyBeltHasItems = true;
            break;
        }
    }

    if (anyBeltHasItems)
    {
        for (const std::shared_ptr<Belt> &belt : belts)
        {
            int powerConsumption = belt->getWeight() * powerPerWeight;
            bool pushed = false;

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

            if (electricComp.tryConsume(powerConsumption))
            {
                isActive = true;
                pushed = true;
                
                if ((movementTicks % ticksPerVoxel) == 0)
                {
                    belt->advance(machine.m_world);
                }
            }

            machine.m_world.onBeltUpdated(*belt, pushed);
        }
    }
    else
    {
        for (const std::shared_ptr<Belt> &belt : belts)
        {
            machine.m_world.onBeltUpdated(*belt, false);
        }
    }

    if (isActive)
    {
        movementTicks++;
    }

    return isActive;
}

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

void BeltMotorComponent::deserializeState(const uint8_t *bytes, size_t size)
{
    deserialize(bytes, size, movementTicks);
}