#include <MachineComponent/DataCableComponent/DataCableComponent.hpp>
#include <MachineComponent/MultiBlockComponent/MultiBlockComponent.hpp>
#include <Serialize/Serialize.hpp>
#include <Machine/Machine.hpp>
#include <WorldComponent/Wiring/DataWiring/DataWiring.hpp>
#include <Item/IEnergyContainingItem.hpp>

std::string DataCableComponent::tagName = "main:dataCableComponent";

DataCableComponent::DataCableComponent(Machine &machine) :
    MachineComponent(machine),
    dataMachine(dynamic_cast<IDataMachine&>(machine))
{}

DataLink::ChannelBitmap DataCableComponent::getOutputs()
{
    return outputs;
}

DataLink::ChannelBitmap DataCableComponent::getLastKnownInputs()
{
    return prevInputs;
}

bool DataCableComponent::syncInputRules(std::vector<InputRule> &inputRules)
{
    if (inputRules.size() != this->inputRules.size())
    {
        inputRules = this->inputRules;
        return true;
    }

    bool updatedAny = false;

    for (size_t i=0; i<inputRules.size(); i++)
    {
        InputRule &a = inputRules[i];
        InputRule &b = this->inputRules[i];

        if (a.channel != b.channel || a.destState != b.destState || a.verb != b.verb)
        {
            a = b;
            updatedAny = true;
        }
    }

    return updatedAny;
}

void DataCableComponent::addInputRule(InputRule rule)
{
    inputRules.push_back(rule);
}

void DataCableComponent::removeInputRule(uint32_t index)
{
    if (index >= inputRules.size())
    {
        return;
    }

    inputRules.erase(inputRules.begin() + index);
}

void DataCableComponent::setInputRuleChannel(uint32_t index, DataLink::Channel channel)
{
    if (index >= inputRules.size())
    {
        return;
    }

    inputRules[index].channel = channel;
}

void DataCableComponent::setInputRuleDestState(uint32_t index, bool destState)
{
    if (index >= inputRules.size())
    {
        return;
    }

    inputRules[index].destState = destState;
}

void DataCableComponent::setInputRuleVerb(uint32_t index, std::string verb)
{
    if (index >= inputRules.size())
    {
        return;
    }

    inputRules[index].verb = verb;
}

Side DataCableComponent::getInputSide()
{
    return dataMachine.getDataInputSide();
}

Side DataCableComponent::getOutputSide()
{
    return dataMachine.getDataOutputSide();
}

void DataCableComponent::serializeState(std::vector<uint8_t> &outBytes)
{
    uint8_t dataByte = (prevInputs.unpack() << 4) | outputs.unpack();
    serializeInto(outBytes, dataByte, sideBitmap, inputRules);
}

void DataCableComponent::deserializeState(const uint8_t *bytes, size_t size)
{
    uint8_t dataByte;
    deserialize(bytes, size, dataByte, sideBitmap, inputRules);

    prevInputs = (dataByte >> 4);
    outputs = (dataByte & 0xF);
}

void DataCableComponent::onPreUpdate()
{
    std::shared_ptr<WorldGridNode> node = machine.getChunk().getNode();
    DataWiring &wiring = node->getComponent<DataWiring>();
    Side dataInputSide = dataMachine.getDataInputSide();
    Side dataOutputSide = dataMachine.getDataOutputSide();

    for (const Side &side : Side::all)
    {
        if (side == dataInputSide || side == dataOutputSide)
        {
            if ((sideBitmap & side.mask()) == 0)
            {
                sideBitmap |= side.mask();
                wiring.onMachineSidePlaced(machine.getWorld(), node, machine.pos, side);
            }
        }
        else
        {
            if (sideBitmap & side.mask())
            {
                sideBitmap &= ~side.mask();
                wiring.onMachineSideRemoved(machine.getWorld(), node, machine.pos, side);
            }
        }
    }

    outputs = dataMachine.getActiveChannels();

    std::shared_ptr<DataLink> link = DataLink::get(machine, dataInputSide);

    DataLink::ChannelBitmap currentInputs = {};
    if (dataInputSide == dataOutputSide)
    {
        currentInputs = outputs;
    }

    link->combineStates(currentInputs);

    if (machine.hasComponent<MultiBlockComponent>())
    {
        link = machine.getComponent<MultiBlockComponent>().getDataLink();

        if (link != nullptr)
        {
            link->combineStates(currentInputs);
        }
    }

    for (const InputRule &rule : inputRules)
    {
        if (rule.destState && !prevInputs.contains(rule.channel) && currentInputs.contains(rule.channel))
        {
            dataMachine.onDataInputVerb(rule.verb);
        }
        else if (!rule.destState && prevInputs.contains(rule.channel) && !currentInputs.contains(rule.channel))
        {
            dataMachine.onDataInputVerb(rule.verb);
        }
    }

    prevInputs = currentInputs;
}

void DataCableComponent::onRemoving()
{
    std::shared_ptr<WorldGridNode> node = machine.getChunk().getNode();
    DataWiring &wiring = node->getComponent<DataWiring>();

    for (const Side &side : Side::all)
    {
        if (sideBitmap & side.mask())
        {
            wiring.onMachineSideRemoved(machine.getWorld(), node, machine.pos, side);
        }
    }
}

void DataCableComponent::saveConfig(std::vector<ItemStack> &consumedItems, std::vector<uint8_t> &data)
{
    serializeInto(data, inputRules);
}

void DataCableComponent::loadConfig(const std::vector<uint8_t> &data, bool consumed)
{
    deserialize(data.data(), data.size(), inputRules);
}