#include <Machine/MOilRefinery/MOilRefinery.hpp>
#include <MachineComponent/AttributionComponent/AttributionComponent.hpp>
#include <Milestone/FossilFuelsMilestone/FossilFuelsMilestone.hpp>
#include <Serialize/Serialize.hpp>
#include <Item/IEnergyContainingItem.hpp>
#include <Item/IFluidContainingItem.hpp>
#include <World/LocalWorld/LocalWorld.hpp>
#include <MachineComponent/ElectricComponent/ElectricComponent.hpp>
#include <MachineComponent/FluidComponent/FluidComponent.hpp>

ItemStack& MOilRefinery::oilInputSlot()
{
    return stacks[0];
}

ItemStack& MOilRefinery::petrolOutputSlot()
{
    return stacks[1];
}

ItemStack& MOilRefinery::rechargeSlot()
{
    return stacks[2];
}

void MOilRefinery::refine()
{
    ElectricComponent &electricComp = getComponent<ElectricComponent>();
    FluidComponent &fluidComp = getComponent<FluidComponent>();

    // Make sure we have oil.
    if (fluidComp.getFluidAmount("main:oil") < 1)
    {
        return;
    }

    // Make sure we have capacity for petrol.
    if (fluidComp.getFluidAmount("main:petrol") > fluidCapacity-petrolPerOil)
    {
        return;
    }

    // Try to consume energy, and refine if successful.
    if (!electricComp.tryConsume(powerUsage))
    {
        return;
    }

    fluidComp.drainFluid("main:oil", 1);
    fluidComp.addFluid("main:petrol", petrolPerOil);
}

Side MOilRefinery::getInputSide()
{
    return Side(*m_world.getBlockMeta(pos));
}

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

MOilRefinery::MOilRefinery(LocalWorld &world, glm::ivec3 pos) :
    Machine(world, pos),
    stacks(3),
    isSwitchedOnPriv(false)
{
    attach<AttributionComponent>();
    attach<ElectricComponent>();
    FluidComponent &fluidComp = attach<FluidComponent>();

    for (const Side &side : Side::all)
    {
        fluidComp.setDefaultFluidOnSide(side, "main:petrol");
    }
}

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

int MOilRefinery::getOilAmount()
{
    return getComponent<FluidComponent>().getFluidAmount("main:oil");
}

int MOilRefinery::getPetrolAmount()
{
    return getComponent<FluidComponent>().getFluidAmount("main:petrol");
}

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

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

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

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

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

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

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

bool MOilRefinery::isAllowedStackInSlot(int slot, ItemStack stack)
{
    return true;
}

void MOilRefinery::update()
{
    ElectricComponent &electricComp = getComponent<ElectricComponent>();
    FluidComponent &fluidComp = getComponent<FluidComponent>();

    electricComp.rechargeFrom(rechargeSlot(), energyCapacity);

    fluidComp.refillFrom(oilInputSlot());

    if (fluidComp.getFluidAmount("main:oil") > 0)
    {
        getComponent<AttributionComponent>().grantAchievementFor(
            std::string(Machine::Action::Placed),
            FossilFuelsMilestone::ID, FossilFuelsMilestone::LoadOil
        );
    }

    // If we are switched on, recharge from input and do a cycle of refining.
    if (isSwitchedOnPriv)
    {
        electricComp.rechargeFrom(Side::Bottom, energyCapacity);
        refine();
    }

    if (fluidComp.getFluidAmount("main:petrol") > 0)
    {
        getComponent<AttributionComponent>().grantAchievementFor(
            std::string(Machine::Action::Placed),
            FossilFuelsMilestone::ID, FossilFuelsMilestone::RefineOil
        );
    }

    fluidComp.drainInto(petrolOutputSlot(), "main:petrol");
}

bool MOilRefinery::canConnectFluidPipe(Side side)
{
    return side.axis() == getInputSide().axis();
}

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

    if (fluidName != "main:oil")
    {
        return 0;
    }

    return fluidCapacity - getOilAmount();
}

int MOilRefinery::getMaxFluidToRelease(std::optional<Side> side, std::string fluidName)
{
    if (side.has_value() && *side != getOutputSide())
    {
        return 0;
    }

    if (fluidName != "main:petrol")
    {
        return 0;
    }

    return INT_MAX;
}

bool MOilRefinery::canConnectElectricWire(Side side)
{
    // Energy can be connected through the bottom.
    return side == Side::Bottom;
}

void MOilRefinery::saveMainConfig(MachineConfig &outConfig)
{
    outConfig.blockName = "main:oilRefinery";
    outConfig.label = "Oil Refinery";

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

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