#include "QuantumCircuitNode.h"

#include "QuantumCircuit.h"
#include "QuantumCircuitElement.h"
#include "QuantumWire.h"

#include "QuantumCircuitElementNode.h"
#include "QuantumTerminalNode.h"
#include "QuantumWireNode.h"
#include "QuantumGateNode.h"

#include "LatexMathExpression.h"
#include "EquationNode.h"

QuantumCircuitNode::QuantumCircuitNode(const Transform& t)
    : AbstractVisualNode(t)
{

}

void QuantumCircuitNode::setContent(QuantumCircuit* circuit)
{
    mContent = circuit;
    mContentDirty = true;
}

void QuantumCircuitNode::update(SceneInfo* sceneInfo)
{
    if (mContentDirty)
    {
        createOrUpdateGeometry(sceneInfo);
        mContentDirty = false;
    }
}

void QuantumCircuitNode::buildWireConnections()
{
    mWireInputConnections.clear();
    mWireOutputConnections.clear();

    for (auto terminal : mContent->getInputTerminals())
    {
        mWireOutputConnections[terminal->getConnection()] = terminal;
    }

    for (auto gate : mContent->getLogicGates())
    {
        for (std::size_t idx = 0; idx < gate->getNumInputs(); idx++)
        {
            mWireInputConnections[gate->getInput(idx)] = gate;
        }

        for (std::size_t idx = 0; idx < gate->getNumOutputs(); idx++)
        {
            mWireOutputConnections[gate->getOutput(idx)] = gate;
        }
    }

    for (auto terminal : mContent->getOutputTerminals())
    {
        mWireInputConnections[terminal->getConnection()] = terminal;
    }
}

void QuantumCircuitNode::createOrUpdateGeometry(SceneInfo*)
{
    buildWireConnections();

    double wire_vertical_spacing = 50;
    double terminal_left_margin = 10;

    double terminal_y = 10;
    for (auto terminal : mContent->getInputTerminals())
    {
        Point loc{ terminal_left_margin, terminal_y };
        auto terminal_node = std::make_unique<QuantumTerminalNode>(Transform(loc));

        terminal_node->setContent(terminal);

        addChild(terminal_node.get());
        mNodesForContent[terminal] = terminal_node.get();

        mInputTerminalNodes.push_back(std::move(terminal_node));
        terminal_y += wire_vertical_spacing;
    }

    terminal_y = 10;
    for (auto terminal : mContent->getOutputTerminals())
    {
        Point loc{ 150.0, terminal_y };
        auto terminal_node = std::make_unique<QuantumTerminalNode>(Transform(loc));

        terminal_node->setContent(terminal);

        addChild(terminal_node.get());
        mNodesForContent[terminal] = terminal_node.get();

        mOutputTerminalNodes.push_back(std::move(terminal_node));
        terminal_y += wire_vertical_spacing;
    }

    double gate_y = 0;
    for (auto gate : mContent->getLogicGates())
    {
        Point loc{ 75.0, gate_y };
        auto gate_node = std::make_unique<QuantumGateNode>(Transform(loc));
        gate_node->setContent(gate);

        addChild(gate_node.get());
        mNodesForContent[gate] = gate_node.get();

        mGateNodes.push_back(std::move(gate_node));
        gate_y += wire_vertical_spacing;
    }

    for (auto wire : mContent->getQuantumWires())
    {
        auto start_node = mNodesForContent[mWireOutputConnections[wire]];
        auto end_node = mNodesForContent[mWireInputConnections[wire]];

        auto wire_node = std::make_unique<QuantumWireNode>(Transform());
        auto start_loc = start_node->getConnectionLocation(wire);
        wire_node->setInputLocation(start_loc);

        auto end_loc = end_node->getConnectionLocation(wire);
        auto straight_end_loc = Point(end_loc.getX(), start_loc.getY());
        wire_node->setOutputLocation(straight_end_loc);

        addChild(wire_node.get());

        mWireNodes.push_back(std::move(wire_node));
    }
}
