#include <Item/IEnergyContainingItem.hpp>
#include <Machine/MUraniumRefinery/MUraniumRefinery.hpp>
#include <MachineComponent/BeltComponent/BeltComponent.hpp>
#include <MachineComponent/ElectricComponent/ElectricComponent.hpp>
#include <MachineComponent/FluidComponent/FluidComponent.hpp>
#include <MachineComponent/MultiBlockComponent/MultiBlockComponent.hpp>
#include <Serialize/Serialize.hpp>
#include <World/LocalWorld/LocalWorld.hpp>
#include <Block/Block.hpp>
#include <Block/SpriteBlock/MultiBlock/UraniumRefinery/UraniumRefinery.hpp>
#include <Block/SpriteBlock/Centrifuge/Centrifuge.hpp>

ItemStack& MUraniumRefinery::inputSlot()
{
    return stacks[0];
}

ItemStack& MUraniumRefinery::rechargeSlot()
{
    return stacks[1];
}

bool MUraniumRefinery::cycle()
{
    auto &electricComp = getComponent<ElectricComponent>();
    auto &multiBlockComp = getComponent<MultiBlockComponent>();
    auto &fluidComp = getComponent<FluidComponent>();

    electricComp.rechargeFrom(rechargeSlot(), energyCapacity);
    electricComp.rechargeFrom(multiBlockComp.getEnergyInput(), energyCapacity);

    if (inputSlot().isEmpty())
    {
        progress = 0;
        return false;
    }

    if (inputSlot().getItem()->name != "main:uraniumDust")
    {
        progress = 0;
        return false;
    }

    if (fluidComp.getFluidAmount("main:refu") > refuCapacity - refuPerDust)
    {
        return false;
    }

    if (!electricComp.tryConsume(powerUsage))
    {
        return false;
    }

    if (++progress == completedProgress)
    {
        progress = 0;
        inputSlot().extractOne();
        fluidComp.addFluid("main:refu", refuPerDust);
    }

    return true;
}

MUraniumRefinery::MUraniumRefinery(LocalWorld &world, glm::ivec3 pos) :
    Machine(world, pos),
    stacks(2)
{
    attach<BeltComponent>();
    attach<MultiBlockComponent>();
    attach<ElectricComponent>();
    
    auto &fluidComp = attach<FluidComponent>();
    for (const Side &side : Side::all)
    {
        fluidComp.setDefaultFluidOnSide(side, "main:refu");
    }
}

int MUraniumRefinery::getEnergy()
{
    return getComponent<ElectricComponent>().getEnergy();
}

int MUraniumRefinery::getRefuAmount()
{
    return getComponent<FluidComponent>().getFluidAmount("main:refu");
}

int MUraniumRefinery::getProgress()
{
    return progress;
}

void MUraniumRefinery::serializeState(std::vector<uint8_t> &outBytes)
{
    serializeInto(outBytes, stacks, progress);
}

void MUraniumRefinery::deserializeState(const uint8_t *data, size_t size)
{
    deserialize(data, size, stacks, progress);
}

int MUraniumRefinery::getNumSlots()
{
    return (int) stacks.size();
}

ItemStack MUraniumRefinery::getStackInSlot(int slot)
{
    return stacks[slot];
}

void MUraniumRefinery::setStackInSlot(int slot, ItemStack stack)
{
    stacks[slot] = stack;
}

bool MUraniumRefinery::isAllowedStackInSlot(int slot, ItemStack stack)
{
    if (stack.isEmpty())
    {
        return true;
    }

    if (slot == 0)
    {
        return stack.getItem()->name == "main:uraniumDust";
    }

    if (slot == 1)
    {
        return stack.getItemAs<IEnergyContainingItem>() != nullptr;
    }

    return true;
}

void MUraniumRefinery::onItemStackEntering(Side side, ItemStack &stack)
{
    insertStack(stack);
}

ItemStack MUraniumRefinery::tryExtractItem(Side side, ItemFilter &filter, uint32_t modeCode)
{
    for (size_t i = 0; i < stacks.size(); i++)
    {
        ItemStack &stack = stacks[i];
        if (!stack.isEmpty() && filter.matches(stack))
        {
            return stack.extractOne();
        }
    }

    return ItemStack();
}

bool MUraniumRefinery::canConnectExtractor(Side side)
{
    return true;
}

void MUraniumRefinery::update()
{
    uint8_t oldMeta = *m_world.getBlockMeta(pos);
    uint8_t newMeta = oldMeta & ~UraniumRefinery::metaActive;
    uint8_t centrifugeMeta = 0;

    int orientation = oldMeta & 3;
    MultiBlock &refineryBlock = static_cast<MultiBlock&>(*m_world.getBlockAt(pos));
    glm::ivec3 intoVector = refineryBlock.structure.getIntoVector(orientation);

    if (cycle())
    {
        newMeta |= UraniumRefinery::metaActive;
        centrifugeMeta |= Centrifuge::metaActive;
    }

    if (oldMeta != newMeta)
    {
        m_world.setBlockMeta(pos, newMeta);

        for (int v = 2; v <= 4; v++)
        {
            m_world.setBlockMeta(pos + intoVector * v, centrifugeMeta);
        }
    }
}

bool MUraniumRefinery::canConnectElectricWire(Side side)
{
    return true;
}

bool MUraniumRefinery::canConnectFluidPipe(Side side)
{
    return true;
}

int MUraniumRefinery::getMaxFluidToRelease(std::optional<Side> side, std::string fluidName)
{
    if (fluidName != "main:refu")
    {
        return 0;
    }

    return INT_MAX;
}