#include <MachineComponent/ElectricComponent/ElectricComponent.hpp>
#include <Serialize/Serialize.hpp>
#include <Machine/Machine.hpp>
#include <WorldComponent/Wiring/ElectricWiring/ElectricWiring.hpp>
#include <Item/IEnergyContainingItem.hpp>
#include <WorldGridNode/WorldGridNode.hpp>

std::string ElectricComponent::tagName = "main:electricComponent";

ElectricComponent::ElectricComponent(Machine &machine) :
    MachineComponent(machine),
    electricMachine(dynamic_cast<IElectricMachine&>(machine))
{}

void ElectricComponent::rechargeFrom(Side inputSide, int maxEnergy)
{
    if ((sideBitmap & inputSide.mask()) == 0)
    {
        return;
    }

    if (energy >= maxEnergy)
    {
        return;
    }

    std::shared_ptr<ElectricLink> link = ElectricLink::get(machine, inputSide);
    energy += link->drainEnergy(maxEnergy - energy);
}

void ElectricComponent::rechargeFrom(ItemStack &stack, int maxEnergy)
{
    if (energy >= maxEnergy)
    {
        return;
    }

    IEnergyContainingItem* energyContainer = stack.getItemAs<IEnergyContainingItem>();
    if (energyContainer == nullptr)
    {
        return;
    }

    energy += energyContainer->pullEnergy(stack, maxEnergy - energy);
}

void ElectricComponent::rechargeFrom(std::shared_ptr<ElectricLink> link, int maxEnergy)
{
    if (energy >= maxEnergy)
    {
        return;
    }

    if (link == nullptr)
    {
        return;
    }

    energy += link->drainEnergy(maxEnergy - energy);
}

void ElectricComponent::rechargeBy(int amount, int maxEnergy)
{
    if (energy >= maxEnergy)
    {
        return;
    }

    energy = std::min(energy + amount, maxEnergy);
}

int ElectricComponent::getEnergy()
{
    return energy;
}

void ElectricComponent::addEnergy(int amount)
{
    energy += amount;
}

int ElectricComponent::drainEnergy(int amount)
{
    int amountToDrain = std::min(amount, energy);
    energy -= amountToDrain;

    if (amountToDrain != 0)
    {
        electricMachine.onEnergyDrained(amountToDrain);
    }

    return amountToDrain;
}

void ElectricComponent::drainEnergyInto(ItemStack &stack)
{
    if (energy == 0)
    {
        return;
    }

    IEnergyContainingItem* energyContainer = stack.getItemAs<IEnergyContainingItem>();
    if (energyContainer == nullptr)
    {
        return;
    }

    energy -= energyContainer->pushEnergy(stack, energy);
}

int ElectricComponent::drainEnergyFromSide(Side side, int amount)
{
    if (!electricMachine.canDrainEnergyFromSide(side))
    {
        return 0;
    }

    return drainEnergy(amount);
}

bool ElectricComponent::tryConsume(int amount)
{
    if (energy < amount)
    {
        return false;
    }

    energy -= amount;
    return true;
}

void ElectricComponent::serializeState(std::vector<uint8_t> &outBytes)
{
    serializeInto(outBytes, energy, sideBitmap);
}

void ElectricComponent::deserializeState(const uint8_t *bytes, size_t size)
{
    deserialize(bytes, size, energy, sideBitmap);
}

void ElectricComponent::onPreUpdate()
{
    std::shared_ptr<WorldGridNode> node = machine.getChunk().getNode();
    ElectricWiring &wiring = node->getComponent<ElectricWiring>();

    for (const Side &side : Side::all)
    {
        if (electricMachine.canConnectElectricWire(side))
        {
            if ((sideBitmap & side.mask()) == 0)
            {
                sideBitmap |= side.mask();
                wiring.onMachineSidePlaced(machine.getWorld(), node, machine.pos, side);
            }
        }
        else
        {
            if (sideBitmap & side.mask())
            {
                sideBitmap &= ~side.mask();
                wiring.onMachineSideRemoved(machine.getWorld(), node, machine.pos, side);
            }
        }
    }
}

void ElectricComponent::onRemoving()
{
    std::shared_ptr<WorldGridNode> node = machine.getChunk().getNode();
    ElectricWiring &wiring = node->getComponent<ElectricWiring>();

    for (const Side &side : Side::all)
    {
        if (sideBitmap & side.mask())
        {
            wiring.onMachineSideRemoved(machine.getWorld(), node, machine.pos, side);
        }
    }
}