#include <Machine/MSubstation/MSubstation.hpp>
#include <MachineComponent/DataCableComponent/DataCableComponent.hpp>
#include <MachineComponent/MultiBlockComponent/MultiBlockComponent.hpp>
#include <Serialize/Base.hpp>
#include <Serialize/Serialize.hpp>
#include <Item/IEnergyContainingItem.hpp>
#include <World/LocalWorld/LocalWorld.hpp>
#include <MachineComponent/ElectricComponent/ElectricComponent.hpp>

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

ItemStack& MSubstation::outputSlot()
{
    return stacks[1];
}

bool MSubstation::isConditionTrue(const Condition &cond)
{
    int currentEnergy = getEnergy();

    switch (cond.op)
    {
    case Operator::EnergyLessThan: return currentEnergy < cond.energy;
    case Operator::EnergyMoreThan: return currentEnergy > cond.energy;
    default: return false;
    }
}

MSubstation::MSubstation(LocalWorld &world, glm::ivec3 pos) :
    Machine(world, pos),
    stacks(2)
{
    attach<ElectricComponent>();
    attach<MultiBlockComponent>();
    attach<DataCableComponent>();
}

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

void MSubstation::setCondition(DataLink::Channel channel, Operator op, int energy)
{
    Condition &cond = channelConditions[channel];
    cond.op = op;
    cond.energy = energy;

    channelUpdatedCounter++;
}

int MSubstation::getChannelUpdateCounter()
{
    return channelUpdatedCounter;
}

const std::unordered_map<DataLink::Channel, MSubstation::Condition>& MSubstation::getChannelConditions()
{
    return channelConditions;
}

void MSubstation::serializeState(std::vector<uint8_t> &outBytes)
{
    std::vector<uint8_t> statsBytes;
    serializeInto(statsBytes, stats);

    serializeInto(outBytes, stacks, channelConditions, statsBytes);
}

void MSubstation::deserializeState(const uint8_t *data, size_t size)
{
    std::vector<uint8_t> statsBytes;
    deserialize(data, size, stacks, channelConditions, statsBytes);

    deserialize(statsBytes.data(), statsBytes.size(), stats);
}

void MSubstation::saveMainConfig(MachineConfig &outConfig)
{
    outConfig.blockName = "main:substation";
    outConfig.label = "Substation";

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

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

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

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

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

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

void MSubstation::update()
{
    uint8_t oldMeta = *m_world.getBlockMeta(pos);
    uint8_t newMeta = oldMeta & 3;

    ElectricComponent& electricComp = getComponent<ElectricComponent>();
    MultiBlockComponent &multiBlockComp = getComponent<MultiBlockComponent>();

    int currentEnergyLevel = electricComp.getEnergy();
    if (lastEnergyLevel.has_value())
    {
        stats.powerOut = *lastEnergyLevel - currentEnergyLevel;
    }

    electricComp.rechargeFrom(inputSlot(), capacity);
    electricComp.rechargeFrom(multiBlockComp.getEnergyInput(), capacity);
    lastEnergyLevel = electricComp.getEnergy();
    stats.powerIn = electricComp.getEnergy() - currentEnergyLevel;
    electricComp.drainEnergyInto(outputSlot());

    int level = 3 * getEnergy() / MSubstation::capacity;
    newMeta |= (level << 2);

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

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

bool MSubstation::canDrainEnergyFromSide(Side side)
{
    return true;
}

Side MSubstation::getDataInputSide()
{
    return Side::Top;
}

DataLink::ChannelBitmap MSubstation::getActiveChannels()
{
    DataLink::ChannelBitmap activeChannels;

    for (const auto &[channel, cond] : channelConditions)
    {
        if (isConditionTrue(cond))
        {
            activeChannels += channel;
        }
    }

    return activeChannels;
}