#include "NodeConnectionInteraction.hpp"

#include "ConnectionGraphicsObject.hpp"
#include "DataModelRegistry.hpp"
#include "FlowScene.hpp"
#include "NodeDataModel.hpp"
#include "NodeGraphicsObject.hpp"
using QtNodes::Connection;
using QtNodes::FlowScene;
using QtNodes::Node;
using QtNodes::NodeConnectionInteraction;
using QtNodes::NodeDataModel;
using QtNodes::PortIndex;
using QtNodes::PortType;
using QtNodes::TypeConverter;
NodeConnectionInteraction::NodeConnectionInteraction(Node &node, Connection &connection, FlowScene &scene)
    : _node(&node), _connection(&connection), _scene(&scene)
{
}
bool NodeConnectionInteraction::canConnect(PortIndex &portIndex, TypeConverter &converter) const
{
    // 1) Connection requires a port
    PortType requiredPort = connectionRequiredPort();
    if (requiredPort == PortType::None)
    {
        return false;
    }
    // 1.5) Forbid connecting the node to itself
    Node *node = _connection->getNode(oppositePort(requiredPort));
    if (node == _node)
        return false;
    // 2) connection point is on top of the node port
    QPointF connectionPoint = connectionEndScenePosition(requiredPort);
    portIndex = nodePortIndexUnderScenePoint(requiredPort, connectionPoint);
    if (portIndex == INVALID)
    {
        return false;
    }
    // 3) Node port is vacant
    // port should be empty
    if (!nodePortIsEmpty(requiredPort, portIndex))
        return false;
    // 4) Connection type equals node port type, or there is a registered type
    // conversion that can translate between the two
    auto connectionDataType = _connection->dataType(oppositePort(requiredPort));
    auto const &modelTarget = _node->nodeDataModel();
    auto candidateNodeDataType = modelTarget->dataType(requiredPort, portIndex);
    if (*connectionDataType != *candidateNodeDataType)
    {
        if (requiredPort == PortType::In)
        {
            converter = _scene->registry().getTypeConverter(connectionDataType->id(), candidateNodeDataType->id());
        }
        else if (requiredPort == PortType::Out)
        {
            converter = _scene->registry().getTypeConverter(candidateNodeDataType->id(), connectionDataType->id());
        }
        return (converter != nullptr);
    }
    return true;
}
bool NodeConnectionInteraction::tryConnect() const
{
    // 1) Check conditions from 'canConnect'
    PortIndex portIndex = INVALID;
    TypeConverter converter;
    if (!canConnect(portIndex, converter))
    {
        return false;
    }
    // 1.5) If the connection is possible but a type conversion is needed,
    //      assign a convertor to connection
    if (converter)
    {
        _connection->setTypeConverter(converter);
    }
    // 2) Assign node to required port in Connection
    PortType requiredPort = connectionRequiredPort();
    _node->nodeState().setConnection(requiredPort, portIndex, *_connection);
    // 3) Assign Connection to empty port in NodeState
    // The port is not longer required after this function
    _connection->setNodeToPort(*_node, requiredPort, portIndex);
    // 4) Adjust Connection geometry
    _node->nodeGraphicsObject().moveConnections();
    // 5) Poke model to intiate data transfer
    auto outNode = _connection->getNode(PortType::Out);
    if (outNode)
    {
        PortIndex outPortIndex = _connection->getPortIndex(PortType::Out);
        outNode->onDataUpdated(outPortIndex);
    }
    return true;
}
/// 1) Node and Connection should be already connected
/// 2) If so, clear Connection entry in the NodeState
/// 3) Set Connection end to 'requiring a port'
bool NodeConnectionInteraction::disconnect(PortType portToDisconnect) const
{
    PortIndex portIndex = _connection->getPortIndex(portToDisconnect);
    NodeState &state = _node->nodeState();
    // clear pointer to Connection in the NodeState
    state.getEntries(portToDisconnect)[portIndex].erase(_connection->id());
    // 4) Propagate invalid data to IN node
    _connection->propagateEmptyData();
    // clear Connection side
    _connection->clearNode(portToDisconnect);
    _connection->setRequiredPort(portToDisconnect);
    _connection->getConnectionGraphicsObject().grabMouse();
    return true;
}
// ------------------ util functions below
PortType NodeConnectionInteraction::connectionRequiredPort() const
{
    auto const &state = _connection->connectionState();
    return state.requiredPort();
}
QPointF NodeConnectionInteraction::connectionEndScenePosition(PortType portType) const
{
    auto &go = _connection->getConnectionGraphicsObject();
    ConnectionGeometry &geometry = _connection->connectionGeometry();
    QPointF endPoint = geometry.getEndPoint(portType);
    return go.mapToScene(endPoint);
}
QPointF NodeConnectionInteraction::nodePortScenePosition(PortType portType, PortIndex portIndex) const
{
    NodeGeometry const &geom = _node->nodeGeometry();
    QPointF p = geom.portScenePosition(portIndex, portType);
    NodeGraphicsObject &ngo = _node->nodeGraphicsObject();
    return ngo.sceneTransform().map(p);
}
PortIndex NodeConnectionInteraction::nodePortIndexUnderScenePoint(PortType portType, QPointF const &scenePoint) const
{
    NodeGeometry const &nodeGeom = _node->nodeGeometry();
    QTransform sceneTransform = _node->nodeGraphicsObject().sceneTransform();
    PortIndex portIndex = nodeGeom.checkHitScenePoint(portType, scenePoint, sceneTransform);
    return portIndex;
}
bool NodeConnectionInteraction::nodePortIsEmpty(PortType portType, PortIndex portIndex) const
{
    if (portType == PortType::None)
    {
        return false;
    }
    NodeState const &nodeState = _node->nodeState();
    auto const &entries = nodeState.getEntries(portType);
    if (entries[portIndex].empty())
        return true;
    if (portType == PortType::In)
        return _node->nodeDataModel()->portInConnectionPolicy(portIndex) == NodeDataModel::ConnectionPolicy::Many;
    return _node->nodeDataModel()->portOutConnectionPolicy(portIndex) == NodeDataModel::ConnectionPolicy::Many;
}
