// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0

#include "anchorindicatorgraphicsitem.h"
#include "formeditortracing.h"

#include <QPainter>

#include <QGraphicsScene>
#include <QGraphicsView>

const int AngleDegree = 16;

namespace QmlDesigner {

using FormEditorTracing::category;

AnchorIndicatorGraphicsItem::AnchorIndicatorGraphicsItem(QGraphicsItem *parent) :
    QGraphicsObject(parent)
{
    NanotraceHR::Tracer tracer{"anchor indicator graphics item constructor", category()};

    setZValue(-3);
}

int startAngleForAnchorLine(const AnchorLineType &anchorLineType)
{
    switch (anchorLineType) {
    case AnchorLineTop:
        return 0;
    case AnchorLineBottom:
        return 180 * AngleDegree;
    case AnchorLineLeft:
        return 90 * AngleDegree;
    case AnchorLineRight:
        return 270 * AngleDegree;
    default:
        return 0;
    }
}

void AnchorIndicatorGraphicsItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /* option */, QWidget * /* widget */)
{
    NanotraceHR::Tracer tracer{"anchor indicator graphics item paint", category()};

    painter->save();

    QPen linePen(QColor(0, 0, 0, 150));
    linePen.setCosmetic(true);
    linePen.setDashPattern({3., 2.});

    painter->setPen(linePen);

    painter->drawLine(m_startPoint, m_firstControlPoint);
    painter->drawLine(m_firstControlPoint, m_secondControlPoint);
    painter->drawLine(m_secondControlPoint, m_endPoint);

    linePen.setColor(QColor(255, 255, 255, 150));
    linePen.setDashPattern({2., 3.});
    linePen.setDashOffset(2.);

    painter->setPen(linePen);

    painter->drawLine(m_startPoint, m_firstControlPoint);
    painter->drawLine(m_firstControlPoint, m_secondControlPoint);
    painter->drawLine(m_secondControlPoint, m_endPoint);

    qreal zoomFactor = 1;
    if (QGraphicsView* view = scene()->views().at(0))
        zoomFactor = view->transform().m11();
    qreal bumpSize = 8 / zoomFactor;
    QRectF bumpRectangle(0., 0., bumpSize, bumpSize);

    QPen greenPen(Qt::green, 2);
    greenPen.setCosmetic(true);
    painter->setPen(greenPen);
    painter->drawLine(m_sourceAnchorLineFirstPoint, m_sourceAnchorLineSecondPoint);

    bumpRectangle.moveTo(m_startPoint.x() - bumpSize / 2, m_startPoint.y() - bumpSize / 2);
    painter->setBrush(painter->pen().color());
    painter->setRenderHint(QPainter::Antialiasing, true);
    painter->drawChord(bumpRectangle, startAngleForAnchorLine(m_sourceAnchorLineType), 180 * AngleDegree);
    painter->setRenderHint(QPainter::Antialiasing, false);

    QPen bluePen(Qt::blue, 2);
    bluePen.setCosmetic(true);
    painter->setPen(bluePen);
    painter->drawLine(m_targetAnchorLineFirstPoint, m_targetAnchorLineSecondPoint);

    bumpRectangle.moveTo(m_endPoint.x() - bumpSize / 2, m_endPoint.y() - bumpSize / 2);
    painter->setBrush(painter->pen().color());
    painter->setRenderHint(QPainter::Antialiasing, true);
    painter->drawChord(bumpRectangle, startAngleForAnchorLine(m_targetAnchorLineType), 180 * AngleDegree);
    painter->setRenderHint(QPainter::Antialiasing, false);

    painter->restore();
}

QRectF AnchorIndicatorGraphicsItem::boundingRect() const
{
    NanotraceHR::Tracer tracer{"anchor indicator graphics item boundingRect", category()};

    return m_boundingRect;
}

static QPointF createParentAnchorPoint(const QmlItemNode &parentQmlItemNode, AnchorLineType anchorLineType, const QmlItemNode &childQmlItemNode)
{
    QRectF parentBoundingRect = parentQmlItemNode.instanceSceneTransform().mapRect(parentQmlItemNode.instanceBoundingRect());
    QRectF childBoundingRect = childQmlItemNode.instanceSceneTransform().mapRect(childQmlItemNode.instanceBoundingRect());

    QPointF anchorPoint;

    switch (anchorLineType) {
    case AnchorLineTop:
        anchorPoint = QPointF(childBoundingRect.center().x(), parentBoundingRect.top());
        break;
    case AnchorLineBottom:
        anchorPoint = QPointF(childBoundingRect.center().x(), parentBoundingRect.bottom());
        break;
    case AnchorLineLeft:
        anchorPoint = QPointF(parentBoundingRect.left(), childBoundingRect.center().y());
        break;
    case AnchorLineRight:
        anchorPoint = QPointF(parentBoundingRect.right(), childBoundingRect.center().y());
        break;
    default:
        break;
    }

    return anchorPoint;
}

static QPointF createAnchorPoint(const QmlItemNode &qmlItemNode, AnchorLineType anchorLineType)
{
    QRectF boundingRect = qmlItemNode.instanceBoundingRect();

    QPointF anchorPoint;

    switch (anchorLineType) {
    case AnchorLineTop:
        anchorPoint = QPointF(boundingRect.center().x(), boundingRect.top());
        break;
    case AnchorLineBottom:
        anchorPoint = QPointF(boundingRect.center().x(), boundingRect.bottom());
        break;
    case AnchorLineLeft:
        anchorPoint = QPointF(boundingRect.left(), boundingRect.center().y());
        break;
    case AnchorLineRight:
        anchorPoint = QPointF(boundingRect.right(), boundingRect.center().y());
        break;
    default:
        break;
    }

    return qmlItemNode.instanceSceneTransform().map(anchorPoint);
}

static QPointF createControlPoint(const QPointF &firstEditPoint, AnchorLineType anchorLineType, const QPointF &secondEditPoint)
{
    QPointF controlPoint = firstEditPoint;

    switch (anchorLineType) {
    case AnchorLineTop:
    case AnchorLineBottom:
        controlPoint.ry() += (secondEditPoint.y() - firstEditPoint.y()) / 2.0;
        break;
    case AnchorLineLeft:
    case AnchorLineRight:
        controlPoint.rx() += (secondEditPoint.x() - firstEditPoint.x()) / 2.0;
        break;
    default:
        break;
    }

    return controlPoint;
}

static void updateAnchorLinePoints(QPointF *firstPoint, QPointF *secondPoint, const AnchorLine &anchorLine)
{
    QRectF boundingRectangle = anchorLine.qmlItemNode().instanceBoundingRect();

    switch (anchorLine.type()) {
    case AnchorLineTop:
        *firstPoint = boundingRectangle.topLeft();
        *secondPoint = boundingRectangle.topRight();
        break;
    case AnchorLineBottom:
        *firstPoint = boundingRectangle.bottomLeft();
        *secondPoint = boundingRectangle.bottomRight();
        break;
    case AnchorLineLeft:
        *firstPoint = boundingRectangle.topLeft();
        *secondPoint = boundingRectangle.bottomLeft();
        break;
    case AnchorLineRight:
        *firstPoint = boundingRectangle.topRight();
        *secondPoint = boundingRectangle.bottomRight();
        break;
    default:
        break;
    }

    *firstPoint = anchorLine.qmlItemNode().instanceSceneTransform().map(*firstPoint);
    *secondPoint = anchorLine.qmlItemNode().instanceSceneTransform().map(*secondPoint);
}

void AnchorIndicatorGraphicsItem::updateAnchorIndicator(const AnchorLine &sourceAnchorLine,
                                                        const AnchorLine &targetAnchorLine)
{
    NanotraceHR::Tracer tracer{"anchor indicator graphics item update anchor indicator", category()};

    if (sourceAnchorLine.qmlItemNode().isValid() && targetAnchorLine.qmlItemNode().isValid()) {
        m_sourceAnchorLineType = sourceAnchorLine.type();
        m_targetAnchorLineType = targetAnchorLine.type();

        m_startPoint = createAnchorPoint(sourceAnchorLine.qmlItemNode(), sourceAnchorLine.type());

        if (targetAnchorLine.qmlItemNode() == sourceAnchorLine.qmlItemNode().instanceParentItem())
            m_endPoint = createParentAnchorPoint(targetAnchorLine.qmlItemNode(), targetAnchorLine.type(), sourceAnchorLine.qmlItemNode());
        else
            m_endPoint = createAnchorPoint(targetAnchorLine.qmlItemNode(), targetAnchorLine.type());

        m_firstControlPoint = createControlPoint(m_startPoint, sourceAnchorLine.type(), m_endPoint);
        m_secondControlPoint = createControlPoint(m_endPoint, targetAnchorLine.type(), m_startPoint);

        updateAnchorLinePoints(&m_sourceAnchorLineFirstPoint, &m_sourceAnchorLineSecondPoint, sourceAnchorLine);
        updateAnchorLinePoints(&m_targetAnchorLineFirstPoint, &m_targetAnchorLineSecondPoint, targetAnchorLine);

        updateBoundingRect();
        update();
    }
}

void AnchorIndicatorGraphicsItem::updateBoundingRect()
{
    NanotraceHR::Tracer tracer{"anchor indicator graphics item update bounding rect", category()};

    QPolygonF controlPolygon(QVector<QPointF>()
                             << m_startPoint << m_firstControlPoint << m_secondControlPoint << m_endPoint
                             << m_sourceAnchorLineFirstPoint << m_sourceAnchorLineSecondPoint
                             << m_targetAnchorLineFirstPoint << m_targetAnchorLineSecondPoint);

    m_boundingRect = controlPolygon.boundingRect().adjusted(-10., -10., 10., 10.);
}
AnchorLineType AnchorIndicatorGraphicsItem::sourceAnchorLineType() const
{
    NanotraceHR::Tracer tracer{"anchor indicator graphics item source anchor line type", category()};

    return m_sourceAnchorLineType;
}

void AnchorIndicatorGraphicsItem::setSourceAnchorLineType(const AnchorLineType &sourceAnchorLineType)
{
    NanotraceHR::Tracer tracer{"anchor indicator graphics item set source anchor line type",
                               category()};

    m_sourceAnchorLineType = sourceAnchorLineType;
}

} // namespace QmlDesigner
