#include <Graphics/Connection/Connection.hpp>

#include <QGraphicsScene>
#include <QPainter>

#include <Graphics/Connection/ConnectionBox.hpp>
#include <Graphics/Connection/InputConnectionBox.hpp>
#include <Graphics/Connection/OutputConnectionBox.hpp>
#include <Graphics/GraphicsBox.hpp>
#include <Utils.hpp>

#define SHORTEST_LINE 20.0

Connection::Connection(OutputConnectionBox* origin, InputConnectionBox* target)
    : QGraphicsItem(origin)
    , originConnectionBox(origin)
    , targetConnectionBox(target)
{
    this->setZValue(2);
    this->boundRect = Utils::pointsToRect({}, target->pos() - origin->pos());
    this->boundRect.adjust(-1, -1, 1, 1);
    origin->scene()->update();

    auto* originModel = origin->getParent()->getModelBox();
    auto* targetModel = target->getParent()->getModelBox();

    ModelBox::connect(originModel, targetModel, target->getSlot());

    origin->addConnection(this);
    target->setConnection(this);
}

QRectF Connection::boundingRect() const {
    return this->boundRect;
}

void Connection::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/) {
    painter->setPen(QPen(Qt::black,1));

    QPointF originPoint;
    QPointF targetPoint = this->targetConnectionBox->scenePos() - this->originConnectionBox->scenePos();

    if ((targetPoint.x() - originPoint.x()) > SHORTEST_LINE * 2)
        this->drawDirectConnection(painter, originPoint, targetPoint);
    else
        this->drawAroundConnection(painter, originPoint, targetPoint);
}

void Connection::drawDirectConnection(QPainter* painter, const QPointF& originPoint, const QPointF& targetPoint)
{
    QRectF rect = Utils::pointsToRect(originPoint, targetPoint);

    qreal midWidth = rect.left() + rect.width() / 2.0;

    painter->drawLine(originPoint,
                      { midWidth, originPoint.y() });
    painter->drawLine(QPointF { midWidth, originPoint.y() },
                      { midWidth, targetPoint.y() });
    painter->drawLine(QPointF { midWidth, targetPoint.y() },
                      { targetPoint.x(), targetPoint.y() });

    this->recalculateBoundingRect(originPoint, targetPoint);
}

void Connection::drawAroundConnection(QPainter* painter, const QPointF& originPoint, const QPointF& targetPoint)
{
    qreal midHeight;
    QPointF topLeft;
    QPointF bottomRight;

    if (originPoint.y() < targetPoint.y()) {
        midHeight = originPoint.y() + qAbs(originPoint.y() - targetPoint.y()) / 2.0;
        topLeft = QPointF(targetPoint.x() - SHORTEST_LINE, originPoint.y());
        bottomRight = QPointF(originPoint.x() + SHORTEST_LINE, targetPoint.y());
    }
    else {
        midHeight = targetPoint.y() + qAbs(originPoint.y() - targetPoint.y()) / 2.0;
        topLeft = QPointF(targetPoint.x() - SHORTEST_LINE, targetPoint.y());
        bottomRight = QPointF(originPoint.x() + SHORTEST_LINE, originPoint.y());
    }

    painter->drawLine(originPoint,
                      QPointF { bottomRight.x(), originPoint.y() });
    painter->drawLine(QPointF { bottomRight.x(), originPoint.y() },
                      QPointF { bottomRight.x(), midHeight });
    painter->drawLine(QPointF { bottomRight.x(), midHeight },
                      QPointF { topLeft.x(), midHeight });
    painter->drawLine(QPointF { topLeft.x(), midHeight },
                      QPointF { topLeft.x(), targetPoint.y() });
    painter->drawLine(QPointF { topLeft.x(), targetPoint.y() },
                      targetPoint);

    this->recalculateBoundingRect(topLeft, bottomRight);
}

void Connection::recalculateBoundingRect(const QPointF& a, const QPointF& b) {
    this->prepareGeometryChange();
    this->boundRect = Utils::pointsToRect(a, b);
    this->boundRect.adjust(-1, -1, 1, 1);
}

void Connection::destroy() {
    auto* originModel = originConnectionBox->getParent()->getModelBox();
    auto* targetModel = targetConnectionBox->getParent()->getModelBox();

    ModelBox::disconnect(originModel, targetModel, targetConnectionBox->getSlot());

    auto removed = this->originConnectionBox->connections.erase(this);
    Q_ASSERT(removed > 0); (void) removed;

    Q_ASSERT(this->targetConnectionBox->connection == this);
    this->targetConnectionBox->connection = nullptr;

    delete this;
}
