#include <BlockRegistry/BlockRegistry.hpp>
#include <Item/StatefulItem/BlockItem/BlockItem.hpp>
#include <Machine/MFluidDispenser/MFluidDispenser.hpp>
#include <MachineComponent/ElectricComponent/ElectricComponent.hpp>
#include <MachineComponent/FluidComponent/FluidComponent.hpp>
#include <World/LocalWorld/LocalWorld.hpp>
#include <Block/SpriteBlock/BlockPlacer/BlockPlacer.hpp>
#include <Block/SpriteBlock/ConveyorBelt/ConveyorBelt.hpp>
#include <MachineComponent/BeltComponent/BeltComponent.hpp>
#include <Belt/Belt.hpp>
#include <Block/FluidBlock/FluidBlock.hpp>

Side MFluidDispenser::getOutputSide()
{
    return Side(*m_world.getBlockMeta(pos));
}

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

MFluidDispenser::MFluidDispenser(LocalWorld &world, glm::ivec3 pos) : Machine(world, pos)
{
    attach<FluidComponent>();
}

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

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

void MFluidDispenser::update()
{
    FluidComponent &fluidComp = getComponent<FluidComponent>();

    if (storedFluid.has_value() && fluidComp.getFluidAmount(*storedFluid) == 0)
    {
        storedFluid = std::nullopt;
    }

    if (!storedFluid.has_value())
    {
        return;
    }

    glm::ivec3 spillPos = pos + getOutputSide().out();
    Block *currentBlock = m_world.getBlockAt(spillPos);
    std::string fluidBlockName = *storedFluid + "Block";

    if (!currentBlock->canReplaceWithPlacedBlock())
    {
        return;
    }

    int existingAmount = 0;
    if (currentBlock->name == fluidBlockName)
    {
        existingAmount = *m_world.getBlockMeta(spillPos) + 1;
    }

    int drained = fluidComp.drainFluid(*storedFluid, 256 - existingAmount);
    if (drained == 0)
    {
        return;
    }

    m_world.setBlockAt(spillPos, fluidBlockName, existingAmount + drained - 1);
    m_world.sendSignal(spillPos, FluidBlock::sigSink, 1);
}

bool MFluidDispenser::canConnectFluidPipe(Side side)
{
    return side == getInputSide();
}

int MFluidDispenser::getMaxFluidToAccept(std::optional<Side> side, std::string fluidName)
{
    if (side.has_value() && side != getInputSide())
    {
        return 0;
    }

    FluidComponent &fluidComp = getComponent<FluidComponent>();

    if (storedFluid.has_value() && fluidComp.getFluidAmount(*storedFluid) == 0)
    {
        storedFluid = std::nullopt;
    }

    if (storedFluid.has_value() && *storedFluid != fluidName)
    {
        return 0;
    }

    std::string blockName = fluidName + "Block";
    if (!blockRegistry.isValidBlock(blockName))
    {
        return 0;
    }

    storedFluid = fluidName;
    return capacity - fluidComp.getFluidAmount(fluidName);
}