#include <FluidLink/FluidLink.hpp>
#include <Machine/MPump/MPump.hpp>
#include <Serialize/Serialize.hpp>
#include <Machine/IFluidMachine.hpp>
#include <World/LocalWorld/LocalWorld.hpp>
#include <MachineComponent/ElectricComponent/ElectricComponent.hpp>
#include <MachineComponent/FluidComponent/FluidComponent.hpp>

Side MPump::getInputSide()
{
    return Side((*m_world.getBlockMeta(pos) + 4) % 6);
}

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

Side MPump::getElectricInputSide()
{
    return getInputSide().isZ() ? Side::North : Side::Bottom;
}

bool MPump::pumpIn()
{
    std::shared_ptr<FluidLink> inputLink = FluidLink::get(*this, getInputSide());
    std::optional<std::string> defaultFluid = fluidFilter;

    if (!defaultFluid.has_value())
    {
        defaultFluid = inputLink->getDefaultFluid();
    }

    if (!defaultFluid.has_value())
    {
        return false;
    }

    std::string fluidName = *defaultFluid;

    if (this->bufferedFluidName == "")
    {
        this->bufferedFluidName = fluidName;
    }

    if (this->bufferedFluidName != fluidName)
    {
        return false;
    }

    if (bufferedAmount == pumpRate)
    {
        return false;
    }

    int delta = inputLink->pullFluid(fluidName, pumpRate - bufferedAmount);
    bufferedAmount += delta;
    return delta > 0;
}

bool MPump::pumpOut()
{
    std::shared_ptr<FluidLink> outputLink = FluidLink::get(*this, getOutputSide());

    if (bufferedFluidName.size() == 0 || bufferedAmount == 0)
    {
        return false;
    }

    int delta = outputLink->pushFluid(bufferedFluidName, bufferedAmount);
    bufferedAmount -= delta;
    return delta > 0;
}

MPump::MPump(LocalWorld &world, glm::ivec3 pos) :
    Machine(world, pos),
    bufferedFluidName(""),
    pumpRate(1000),
    bufferedAmount(0),
    isSwitchedOnPriv(false)
{
    attach<ElectricComponent>();
    attach<FluidComponent>();
}

bool MPump::pump()
{
    bool inOK = pumpIn();
    bool outOK = pumpOut();

    return inOK || outOK;
}

uint32_t MPump::getBufferedAmount()
{
    return bufferedAmount;
}

bool MPump::isSwitchedOn()
{
    return isSwitchedOnPriv;
}

void MPump::setSwitch(bool on)
{
    isSwitchedOnPriv = on;
}

void MPump::serializeState(std::vector<uint8_t> &outBytes)
{
    serializeInto(outBytes, bufferedFluidName, pumpRate, bufferedAmount, isSwitchedOnPriv, fluidFilter);
}

void MPump::deserializeState(const uint8_t *data, size_t size)
{
    deserialize(data, size, bufferedFluidName, pumpRate, bufferedAmount, isSwitchedOnPriv, fluidFilter);
}

void MPump::saveMainConfig(MachineConfig &outConfig)
{
    outConfig.blockName = "main:pump";
    outConfig.label = "Pump";

    serializeInto(outConfig.components[""], pumpRate, isSwitchedOnPriv, fluidFilter);
}

void MPump::loadMainConfig(const MachineConfig &config, bool consumed)
{
    const auto &data = config.components.at("");
    deserialize(data.data(), data.size(), pumpRate, isSwitchedOnPriv, fluidFilter);
}

void MPump::update()
{
    if (isSwitchedOnPriv)
    {
        ElectricComponent &electricComp = getComponent<ElectricComponent>();
        electricComp.rechargeFrom(getElectricInputSide(), powerConsumption);
        if (electricComp.tryConsume(powerConsumption))
        {
            if (!pump())
            {
                electricComp.addEnergy(powerConsumption);
            }
        }
    }

    if (bufferedAmount == 0)
    {
        bufferedFluidName.clear();
    }
}

bool MPump::canConnectFluidPipe(Side side)
{
    return side == getInputSide() || side == getOutputSide();
}

bool MPump::canConnectElectricWire(Side side)
{
    return side == getElectricInputSide();
}