#include <memory>

#include <DataLink/DataLink.hpp>
#include <World/LocalWorld/LocalWorld.hpp>
#include <Machine/Machine.hpp>
#include <WorldComponent/Wiring/DataWiring/DataWiring.hpp>
#include <MachineComponent/DataCableComponent/DataCableComponent.hpp>

std::shared_ptr<DataLink> DataLink::dummyLink = std::make_shared<DataLink>();

DataLink::DataLink(
    LocalWorld &world,
    std::shared_ptr<WorldGridNode> node,
    std::vector<Wiring::Port> &ports,
    size_t startingPort
) : pos(ports[startingPort].pos),
    side(ports[startingPort].side)
{
    Wiring::Port &ourPort = ports[startingPort];
    if (ourPort.connections.size() == 0)
    {
        std::shared_ptr<Machine> machine = world.getMachine(ourPort.pos);

        if (machine != nullptr)
        {
            endpoints.push_back({machine, ourPort.side});
        }
    }

    for (Wiring::Connection &connection : ourPort.connections)
    {
        Wiring::Port &targetPort = ports[connection.targetPort];
        std::shared_ptr<Machine> machine = world.getMachine(targetPort.machinePos);

        if (std::dynamic_pointer_cast<IDataMachine>(machine) != nullptr)
        {
            endpoints.push_back({machine, targetPort.side.opposite()});
        }
    }
}

std::shared_ptr<DataLink> DataLink::get(Machine &machine, Side side)
{
    LocalWorld &world = machine.getWorld();
    return world
        .getRootNode()
        ->getComponent<DataWiring>()
        .getLink(world, machine.pos + side.out(), side.opposite());
}

void DataLink::combineStates(ChannelBitmap &bitmap)
{
    for (Endpoint &endpoint : endpoints)
    {
        std::shared_ptr<Machine> machine = endpoint.machine.lock();
        if (machine == nullptr)
        {
            continue;
        }

        auto &dataComponent = machine->getComponent<DataCableComponent>();
        if (endpoint.side != dataComponent.getOutputSide())
        {
            if (dataComponent.getOutputSide() != dataComponent.getInputSide())
            {
                continue;
            }
        }

        bitmap |= dataComponent.getOutputs();
    }
}