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

ItemStack& MCombustionGenerator::petrolSlot()
{
    return stacks[0];
}

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

Side MCombustionGenerator::getOutputSide()
{
    return Side(*m_world.getBlockMeta(pos) & 3);
}

MCombustionGenerator::MCombustionGenerator(LocalWorld &world, glm::ivec3 pos) :
    Machine(world, pos),
    stacks(2),
    isSwitchedOnPriv(false)
{
    attach<ElectricComponent>();
    attach<FluidComponent>();
    attach<AttributionComponent>();
}

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

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

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

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

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

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

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

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

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

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

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

    fluidComp.refillFrom(petrolSlot());

    uint8_t oldMeta = *m_world.getBlockMeta(pos);
    uint8_t newMeta = oldMeta & 3;

    // If we are switched on and have fuel, generate some energy.
    if (isSwitchedOnPriv && getPetrolLevel() >= fuelUsage && getStoredEnergy() <= energyCapacity - powerOutput)
    {
        fluidComp.drainFluid("main:petrol", fuelUsage);
        electricComp.rechargeBy(powerOutput, energyCapacity);
        newMeta |= (1 << 2);

        getComponent<AttributionComponent>().grantAchievementFor(
            std::string(Machine::Action::Placed),
            FossilFuelsMilestone::ID, FossilFuelsMilestone::GenerateCombustionEnergy
        );
    }

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

    // If there is an energy container in the recharge slot, charge it.
    electricComp.drainEnergyInto(rechargeSlot());
}

bool MCombustionGenerator::canConnectFluidPipe(Side side)
{
    return side == Side::Top;
}

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

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

    return fluidCapacity - getPetrolLevel();
}

bool MCombustionGenerator::canConnectElectricWire(Side side)
{
    return side == getOutputSide();
}

bool MCombustionGenerator::canDrainEnergyFromSide(Side side)
{
    return side == getOutputSide();
}

void MCombustionGenerator::saveMainConfig(MachineConfig &outConfig)
{
    outConfig.blockName = "main:combustionGenerator";
    outConfig.label = "Combustion Generator";

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

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