// 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 "qmlanchors.h"
#include "bindingproperty.h"
#include "nodeabstractproperty.h"
#include "rewritertransaction.h"
#include "nodeinstanceview.h"

namespace QmlDesigner {

using NanotraceHR::keyValue;

using ModelTracing::category;

static PropertyName lineTypeToString(AnchorLineType lineType)
{
    switch (lineType) {
    case AnchorLineLeft:
        return QByteArrayLiteral("left");
    case AnchorLineTop:
        return QByteArrayLiteral("top");
    case AnchorLineRight:
        return QByteArrayLiteral("right");
    case AnchorLineBottom:
        return QByteArrayLiteral("bottom");
    case AnchorLineHorizontalCenter:
        return QByteArrayLiteral("horizontalCenter");
    case AnchorLineVerticalCenter:
        return QByteArrayLiteral("verticalCenter");
    case AnchorLineBaseline:
        return QByteArrayLiteral("baseline");
    case AnchorLineFill:
        return QByteArrayLiteral("fill");
    case AnchorLineCenter:
        return QByteArrayLiteral("centerIn");
    default:
        return {};
    }
}

static AnchorLineType propertyNameToLineType(PropertyNameView name)
{
    if (name == "left")
        return AnchorLineLeft;
    else if (name == "top")
        return AnchorLineTop;
    else if (name == "right")
        return AnchorLineRight;
    else if (name == "bottom")
        return AnchorLineBottom;
    else if (name == "horizontalCenter")
        return AnchorLineHorizontalCenter;
    else if (name == "verticalCenter")
        return AnchorLineVerticalCenter;
    else if (name == "baseline")
        return AnchorLineVerticalCenter;
    else if (name == "centerIn")
        return AnchorLineCenter;
    else if (name == "fill")
        return AnchorLineFill;

    return AnchorLineInvalid;
}

static PropertyNameView marginPropertyName(AnchorLineType lineType)
{
    switch (lineType) {
    case AnchorLineLeft:
        return {"anchors.leftMargin"};
    case AnchorLineTop:
        return {"anchors.topMargin"};
    case AnchorLineRight:
        return {"anchors.rightMargin"};
    case AnchorLineBottom:
        return {"anchors.bottomMargin"};
    case AnchorLineHorizontalCenter:
        return {"anchors.horizontalCenterOffset"};
    case AnchorLineVerticalCenter:
        return {"anchors.verticalCenterOffset"};
    default:
        return {};
    }
}

static PropertyNameView anchorPropertyName(AnchorLineType lineType)
{
    switch (lineType) {
    case AnchorLineLeft:
        return {"anchors.left"};
    case AnchorLineTop:
        return {"anchors.top"};
    case AnchorLineRight:
        return {"anchors.right"};
    case AnchorLineBottom:
        return {"anchors.bottom"};
    case AnchorLineHorizontalCenter:
        return {"anchors.horizontalCenter"};
    case AnchorLineVerticalCenter:
        return {"anchors.verticalCenter"};
    case AnchorLineBaseline:
        return {"anchors.baseline"};
    case AnchorLineFill:
        return {"anchors.fill"};
    case AnchorLineCenter:
        return {"anchors.centerIn"};
    default:
        return {};
    }
}

QmlAnchors::QmlAnchors(const QmlItemNode &fxItemNode) : m_qmlItemNode(fxItemNode)
{
}

QmlItemNode QmlAnchors::qmlItemNode() const
{
    return m_qmlItemNode;
}

bool QmlAnchors::modelHasAnchors(SL sl) const
{
    NanotraceHR::Tracer tracer{"qml anchors has anchors",
                               category(),
                               keyValue("model node", *this),
                               keyValue("caller location", sl)};

    return modelHasAnchor(AnchorLineLeft) || modelHasAnchor(AnchorLineRight)
           || modelHasAnchor(AnchorLineTop) || modelHasAnchor(AnchorLineBottom)
           || modelHasAnchor(AnchorLineHorizontalCenter) || modelHasAnchor(AnchorLineVerticalCenter)
           || modelHasAnchor(AnchorLineBaseline);
}

bool QmlAnchors::modelHasAnchor(AnchorLineType sourceAnchorLineType, SL sl) const
{
    NanotraceHR::Tracer tracer{"qml anchors has anchor",
                               category(),
                               keyValue("model node", *this),
                               keyValue("caller location", sl)};

    const PropertyNameView propertyName = anchorPropertyName(sourceAnchorLineType);

    if (sourceAnchorLineType & AnchorLineFill)
        return qmlItemNode().modelNode().hasBindingProperty(propertyName) || qmlItemNode().modelNode().hasBindingProperty("anchors.fill");

    if (sourceAnchorLineType & AnchorLineCenter)
        return qmlItemNode().modelNode().hasBindingProperty(propertyName) || qmlItemNode().modelNode().hasBindingProperty("anchors.centerIn");

    return qmlItemNode().modelNode().hasBindingProperty(anchorPropertyName(sourceAnchorLineType));
}

AnchorLine QmlAnchors::modelAnchor(AnchorLineType sourceAnchorLineType, SL sl) const
{
    NanotraceHR::Tracer tracer{"qml anchors get anchor",
                               category(),
                               keyValue("model node", *this),
                               keyValue("caller location", sl)};

    QPair<PropertyName, ModelNode> targetAnchorLinePair;
    if (sourceAnchorLineType & AnchorLineFill
        && qmlItemNode().modelNode().hasBindingProperty("anchors.fill")) {
        targetAnchorLinePair.second = qmlItemNode()
                                          .modelNode()
                                          .bindingProperty("anchors.fill")
                                          .resolveToModelNode();
        targetAnchorLinePair.first = lineTypeToString(sourceAnchorLineType);
    } else if (sourceAnchorLineType & AnchorLineCenter
               && qmlItemNode().modelNode().hasBindingProperty("anchors.centerIn")) {
        targetAnchorLinePair.second = qmlItemNode()
                                          .modelNode()
                                          .bindingProperty("anchors.centerIn")
                                          .resolveToModelNode();
        targetAnchorLinePair.first = lineTypeToString(sourceAnchorLineType);
    } else {
        AbstractProperty binding = qmlItemNode()
                                       .modelNode()
                                       .bindingProperty(anchorPropertyName(sourceAnchorLineType))
                                       .resolveToProperty();
        targetAnchorLinePair.first = binding.name().toByteArray();
        targetAnchorLinePair.second = binding.parentModelNode();
    }

 AnchorLineType targetAnchorLine = propertyNameToLineType(targetAnchorLinePair.first);

 if (targetAnchorLine == AnchorLineInvalid )
     return AnchorLine();


 return AnchorLine(QmlItemNode(targetAnchorLinePair.second), targetAnchorLine);
}

bool QmlAnchors::isValid(SL sl) const
{
    NanotraceHR::Tracer tracer{"qml anchors is valid",
                               category(),
                               keyValue("model node", *this),
                               keyValue("caller location", sl)};

    return m_qmlItemNode.isValid();
}

void QmlAnchors::setAnchor(AnchorLineType sourceAnchorLine,
                           const QmlItemNode &targetQmlItemNode,
                           AnchorLineType targetAnchorLine,
                           SL sl)
{
    NanotraceHR::Tracer tracer{"qml anchors set anchor",
                               category(),
                               keyValue("model node", *this),
                               keyValue("caller location", sl)};

    qmlItemNode().view()->executeInTransaction(
        "QmlAnchors::setAnchor", [this, sourceAnchorLine, targetQmlItemNode, targetAnchorLine]() {
            if (qmlItemNode().isInBaseState()) {
                if ((qmlItemNode().nodeInstance().hasAnchor("anchors.fill")
                     && (sourceAnchorLine & AnchorLineFill))
                    || ((qmlItemNode().nodeInstance().hasAnchor("anchors.centerIn")
                         && (sourceAnchorLine & AnchorLineCenter)))) {
                    removeAnchor(sourceAnchorLine);
                }

                const PropertyNameView propertyName = anchorPropertyName(sourceAnchorLine);
                ModelNode targetModelNode = targetQmlItemNode.modelNode();
                QString targetExpression = targetModelNode.validId();
                if (targetQmlItemNode.modelNode()
                    == qmlItemNode().modelNode().parentProperty().parentModelNode())
                    targetExpression = QLatin1String("parent");
                if (sourceAnchorLine != AnchorLineCenter && sourceAnchorLine != AnchorLineFill)
                    targetExpression = targetExpression + QLatin1Char('.')
                                       + QString::fromLatin1(lineTypeToString(targetAnchorLine));
                qmlItemNode().modelNode().bindingProperty(propertyName).setExpression(targetExpression);
            }
        });
}

static bool detectHorizontalCycle(const ModelNode &node, QList<ModelNode> knownNodeList)
{
    if (knownNodeList.contains(node))
        return true;

    knownNodeList.append(node);

    static const PropertyNameList validAnchorLines({"right", "left", "horizontalCenter"});
    static const PropertyNameList anchorNames({"anchors.right", "anchors.left", "anchors.horizontalCenter"});

    for (const PropertyName &anchorName : anchorNames) {
        if (node.hasBindingProperty(anchorName)) {
            AbstractProperty targetProperty = node.bindingProperty(anchorName).resolveToProperty();
            if (targetProperty.isValid()) {
                if (!validAnchorLines.contains(targetProperty.name()))
                    return true;

                if (detectHorizontalCycle(targetProperty.parentModelNode(), knownNodeList))
                    return true;
            }
        }

    }

    static const PropertyNameList anchorShortcutNames({"anchors.fill", "anchors.centerIn"});
    for (const PropertyName &anchorName : anchorShortcutNames) {
        if (node.hasBindingProperty(anchorName)) {
            ModelNode targetNode = node.bindingProperty(anchorName).resolveToModelNode();

            if (targetNode.isValid() && detectHorizontalCycle(targetNode, knownNodeList))
                return true;
        }
    }

    return false;
}

static bool detectVerticalCycle(const ModelNode &node, QList<ModelNode> knownNodeList)
{
    if (!node.isValid())
        return false;

    if (knownNodeList.contains(node))
        return true;

    knownNodeList.append(node);

    static const PropertyNameList validAnchorLines({"top", "bottom", "verticalCenter", "baseline"});
    static const PropertyNameList anchorNames({"anchors.top", "anchors.bottom", "anchors.verticalCenter", "anchors.baseline"});

    for (const PropertyName &anchorName : anchorNames) {
        if (node.hasBindingProperty(anchorName)) {
            AbstractProperty targetProperty = node.bindingProperty(anchorName).resolveToProperty();
            if (targetProperty.isValid()) {
                if (!validAnchorLines.contains(targetProperty.name()))
                    return true;

                if (detectVerticalCycle(targetProperty.parentModelNode(), knownNodeList))
                    return true;
            }
        }

    }

    static const PropertyNameList anchorShortcutNames({"anchors.fill", "anchors.centerIn"});
    for (const PropertyName &anchorName : anchorShortcutNames) {
        if (node.hasBindingProperty(anchorName)) {
            ModelNode targetNode = node.bindingProperty(anchorName).resolveToModelNode();

            if (targetNode.isValid() && detectVerticalCycle(targetNode, knownNodeList))
                return true;
        }
    }

    return false;
}

bool QmlAnchors::canAnchor(const QmlItemNode &targetModelNode, SL sl) const
{
    NanotraceHR::Tracer tracer{"qml anchors can anchor",
                               category(),
                               keyValue("model node", *this),
                               keyValue("caller location", sl)};

    if (!qmlItemNode().isInBaseState())
        return false;

    if (targetModelNode == qmlItemNode().instanceParent())
        return true;

    if (qmlItemNode().instanceParent() == targetModelNode.instanceParent())
        return true;

    return false;
}

AnchorLineType QmlAnchors::possibleAnchorLines(AnchorLineType sourceAnchorLineType,
                                               const QmlItemNode &targetQmlItemNode,
                                               SL sl) const
{
    NanotraceHR::Tracer tracer{"qml anchors possible anchor lines",
                               category(),
                               keyValue("model node", *this),
                               keyValue("caller location", sl)};

    if (!canAnchor(targetQmlItemNode))
        return AnchorLineInvalid;

    if (AnchorLine::isHorizontalAnchorLine(sourceAnchorLineType)) {
        if (!detectHorizontalCycle(targetQmlItemNode, {qmlItemNode().modelNode()}))
            return AnchorLineHorizontalMask;
    }

    if (AnchorLine::isVerticalAnchorLine(sourceAnchorLineType)) {
        if (!detectVerticalCycle(targetQmlItemNode, {qmlItemNode().modelNode()}))
            return AnchorLineVerticalMask;
    }

    return AnchorLineInvalid;
}

AnchorLine QmlAnchors::instanceAnchor(AnchorLineType sourceAnchorLine, SL sl) const
{
    NanotraceHR::Tracer tracer{"qml anchors get instance anchor",
                               category(),
                               keyValue("model node", *this),
                               keyValue("caller location", sl)};

    QPair<PropertyName, qint32> targetAnchorLinePair;
    if (qmlItemNode().nodeInstance().hasAnchor("anchors.fill") && (sourceAnchorLine & AnchorLineFill)) {
        targetAnchorLinePair = qmlItemNode().nodeInstance().anchor("anchors.fill");
        targetAnchorLinePair.first = lineTypeToString(sourceAnchorLine); // TODO: looks wrong
    } else if (qmlItemNode().nodeInstance().hasAnchor("anchors.centerIn") && (sourceAnchorLine & AnchorLineCenter)) {
        targetAnchorLinePair = qmlItemNode().nodeInstance().anchor("anchors.centerIn");
        targetAnchorLinePair.first = lineTypeToString(sourceAnchorLine);
    } else {
        targetAnchorLinePair = qmlItemNode().nodeInstance().anchor(anchorPropertyName(sourceAnchorLine));
    }

    AnchorLineType targetAnchorLine = propertyNameToLineType(targetAnchorLinePair.first);

    if (targetAnchorLine == AnchorLineInvalid )
        return AnchorLine();

    if (targetAnchorLinePair.second < 0) //there might be no node instance for the parent
        return AnchorLine();

    return AnchorLine(QmlItemNode(qmlItemNode().nodeForInstance(qmlItemNode().nodeInstanceView()->instanceForId(targetAnchorLinePair.second))), targetAnchorLine);
}

void QmlAnchors::removeAnchor(AnchorLineType sourceAnchorLine, SL sl)
{
    NanotraceHR::Tracer tracer{"qml anchors remove anchor",
                               category(),
                               keyValue("model node", *this),
                               keyValue("caller location", sl)};

    qmlItemNode().view()->executeInTransaction("QmlAnchors::removeAnchor", [this, sourceAnchorLine]() {
        if (qmlItemNode().isInBaseState()) {
            const PropertyNameView propertyName = anchorPropertyName(sourceAnchorLine);
            if (qmlItemNode().modelNode().hasProperty("anchors.fill")
                && (sourceAnchorLine & AnchorLineFill)) {
                qmlItemNode().modelNode().removeProperty("anchors.fill");
                qmlItemNode().modelNode().bindingProperty("anchors.top").setExpression(QLatin1String("parent.top"));
                qmlItemNode().modelNode().bindingProperty("anchors.left").setExpression(QLatin1String("parent.left"));
                qmlItemNode().modelNode().bindingProperty("anchors.bottom").setExpression(QLatin1String("parent.bottom"));
                qmlItemNode().modelNode().bindingProperty("anchors.right").setExpression(QLatin1String("parent.right"));

            } else if (qmlItemNode().modelNode().hasProperty("anchors.centerIn")
                       && (sourceAnchorLine & AnchorLineCenter)) {
                qmlItemNode().modelNode().removeProperty("anchors.centerIn");
                qmlItemNode().modelNode().bindingProperty("anchors.horizontalCenter").setExpression(QLatin1String("parent.horizontalCenter"));
                qmlItemNode().modelNode().bindingProperty("anchors.verticalCenter").setExpression(QLatin1String("parent.verticalCenter"));
            }

            qmlItemNode().modelNode().removeProperty(propertyName);
        }
    });
}

void QmlAnchors::removeAnchors(SL sl)
{
    NanotraceHR::Tracer tracer{"qml anchors remove anchors",
                               category(),
                               keyValue("model node", *this),
                               keyValue("caller location", sl)};

    qmlItemNode().view()->executeInTransaction("QmlAnchors::removeAnchors", [this]() {
        if (qmlItemNode().nodeInstance().hasAnchor("anchors.fill"))
            qmlItemNode().modelNode().removeProperty("anchors.fill");
        if (qmlItemNode().nodeInstance().hasAnchor("anchors.centerIn"))
            qmlItemNode().modelNode().removeProperty("anchors.centerIn");
        if (qmlItemNode().nodeInstance().hasAnchor("anchors.top"))
            qmlItemNode().modelNode().removeProperty("anchors.top");
        if (qmlItemNode().nodeInstance().hasAnchor("anchors.left"))
            qmlItemNode().modelNode().removeProperty("anchors.left");
        if (qmlItemNode().nodeInstance().hasAnchor("anchors.right"))
            qmlItemNode().modelNode().removeProperty("anchors.right");
        if (qmlItemNode().nodeInstance().hasAnchor("anchors.bottom"))
            qmlItemNode().modelNode().removeProperty("anchors.bottom");
        if (qmlItemNode().nodeInstance().hasAnchor("anchors.horizontalCenter"))
            qmlItemNode().modelNode().removeProperty("anchors.horizontalCenter");
        if (qmlItemNode().nodeInstance().hasAnchor("anchors.verticalCenter"))
            qmlItemNode().modelNode().removeProperty("anchors.verticalCenter");
        if (qmlItemNode().nodeInstance().hasAnchor("anchors.baseline"))
            qmlItemNode().modelNode().removeProperty("anchors.baseline");
    });
}

bool QmlAnchors::instanceHasAnchor(AnchorLineType sourceAnchorLine, SL sl) const
{
    NanotraceHR::Tracer tracer{"qml anchors instance has anchor",
                               category(),
                               keyValue("model node", *this),
                               keyValue("caller location", sl)};

    const PropertyNameView propertyName = anchorPropertyName(sourceAnchorLine);

    if (sourceAnchorLine & AnchorLineFill)
        return qmlItemNode().nodeInstance().hasAnchor(propertyName) || qmlItemNode().nodeInstance().hasAnchor("anchors.fill");

    if (sourceAnchorLine & AnchorLineCenter)
        return qmlItemNode().nodeInstance().hasAnchor(propertyName) || qmlItemNode().nodeInstance().hasAnchor("anchors.centerIn");


    return qmlItemNode().nodeInstance().hasAnchor(propertyName);
}

bool QmlAnchors::instanceHasAnchors(SL sl) const
{
    NanotraceHR::Tracer tracer{"qml anchors instance has anchors",
                               category(),
                               keyValue("model node", *this),
                               keyValue("caller location", sl)};

    return instanceHasAnchor(AnchorLineLeft) || instanceHasAnchor(AnchorLineRight)
           || instanceHasAnchor(AnchorLineTop) || instanceHasAnchor(AnchorLineBottom)
           || instanceHasAnchor(AnchorLineHorizontalCenter)
           || instanceHasAnchor(AnchorLineVerticalCenter) || instanceHasAnchor(AnchorLineBaseline);
}

static QRectF contentRect(const NodeInstance &nodeInstance)
{
    QRectF contentRect(nodeInstance.position(), nodeInstance.size());
    return nodeInstance.contentTransform().mapRect(contentRect);
}

double QmlAnchors::instanceLeftAnchorLine(SL sl) const
{
    NanotraceHR::Tracer tracer{"qml anchors instance left anchor line",
                               category(),
                               keyValue("model node", *this),
                               keyValue("caller location", sl)};

    return contentRect(qmlItemNode().nodeInstance()).x();
}

double QmlAnchors::instanceTopAnchorLine(SL sl) const
{
    NanotraceHR::Tracer tracer{"qml anchors instance top anchor line",
                               category(),
                               keyValue("model node", *this),
                               keyValue("caller location", sl)};

    return contentRect(qmlItemNode().nodeInstance()).y();
}

double QmlAnchors::instanceRightAnchorLine(SL sl) const
{
    NanotraceHR::Tracer tracer{"qml anchors instance right anchor line",
                               category(),
                               keyValue("model node", *this),
                               keyValue("caller location", sl)};

    return contentRect(qmlItemNode().nodeInstance()).x()
           + contentRect(qmlItemNode().nodeInstance()).width();
}

double QmlAnchors::instanceBottomAnchorLine(SL sl) const
{
    NanotraceHR::Tracer tracer{"qml anchors instance bottom anchor line",
                               category(),
                               keyValue("model node", *this),
                               keyValue("caller location", sl)};

    return contentRect(qmlItemNode().nodeInstance()).y()
           + contentRect(qmlItemNode().nodeInstance()).height();
}

double QmlAnchors::instanceHorizontalCenterAnchorLine(SL sl) const
{
    NanotraceHR::Tracer tracer{"qml anchors instance horizontal center anchor line",
                               category(),
                               keyValue("model node", *this),
                               keyValue("caller location", sl)};

    return (instanceLeftAnchorLine() + instanceRightAnchorLine()) / 2.0;
}

double QmlAnchors::instanceVerticalCenterAnchorLine(SL sl) const
{
    NanotraceHR::Tracer tracer{"qml anchors instance vertical center anchor line",
                               category(),
                               keyValue("model node", *this),
                               keyValue("caller location", sl)};

    return (instanceBottomAnchorLine() + instanceTopAnchorLine()) / 2.0;
}

double QmlAnchors::instanceAnchorLine(AnchorLineType anchorLine, SL sl) const
{
    NanotraceHR::Tracer tracer{"qml anchors instance anchor line",
                               category(),
                               keyValue("model node", *this),
                               keyValue("caller location", sl)};

    switch (anchorLine) {
    case AnchorLineLeft: return instanceLeftAnchorLine();
    case AnchorLineTop: return instanceTopAnchorLine();
    case AnchorLineBottom: return instanceBottomAnchorLine();
    case AnchorLineRight: return instanceRightAnchorLine();
    case AnchorLineHorizontalCenter: return instanceHorizontalCenterAnchorLine();
    case AnchorLineVerticalCenter: return instanceVerticalCenterAnchorLine();
    default: return 0.0;
    }
}

void QmlAnchors::setMargin(AnchorLineType sourceAnchorLineType, double margin, SL sl) const
{
    NanotraceHR::Tracer tracer{"qml anchors set margin",
                               category(),
                               keyValue("model node", *this),
                               keyValue("caller location", sl)};

    PropertyNameView propertyName = marginPropertyName(sourceAnchorLineType);
    qmlItemNode().setVariantProperty(propertyName, qRound(margin));
}

bool QmlAnchors::instanceHasMargin(AnchorLineType sourceAnchorLineType, SL sl) const
{
    NanotraceHR::Tracer tracer{"qml anchors instance has margin",
                               category(),
                               keyValue("model node", *this),
                               keyValue("caller location", sl)};

    return !qIsNull(instanceMargin(sourceAnchorLineType));
}

static bool checkForHorizontalCycleRecusive(const QmlAnchors &anchors, QList<QmlItemNode> &visitedItems)
{
    if (!anchors.isValid())
        return false;

    visitedItems.append(anchors.qmlItemNode());
    if (anchors.instanceHasAnchor(AnchorLineLeft)) {
        AnchorLine leftAnchorLine = anchors.instanceAnchor(AnchorLineLeft);
        if (visitedItems.contains(leftAnchorLine.qmlItemNode()) || checkForHorizontalCycleRecusive(leftAnchorLine.qmlItemNode().anchors(), visitedItems))
            return true;
    }

    if (anchors.instanceHasAnchor(AnchorLineRight)) {
        AnchorLine rightAnchorLine = anchors.instanceAnchor(AnchorLineRight);
        if (visitedItems.contains(rightAnchorLine.qmlItemNode()) || checkForHorizontalCycleRecusive(rightAnchorLine.qmlItemNode().anchors(), visitedItems))
            return true;
    }

    if (anchors.instanceHasAnchor(AnchorLineHorizontalCenter)) {
        AnchorLine horizontalCenterAnchorLine = anchors.instanceAnchor(AnchorLineHorizontalCenter);
        if (visitedItems.contains(horizontalCenterAnchorLine.qmlItemNode()) || checkForHorizontalCycleRecusive(horizontalCenterAnchorLine.qmlItemNode().anchors(), visitedItems))
            return true;
    }

    return false;
}

static bool checkForVerticalCycleRecusive(const QmlAnchors &anchors, QList<QmlItemNode> &visitedItems)
{
    if (!anchors.isValid())
        return false;

    visitedItems.append(anchors.qmlItemNode());

    if (anchors.instanceHasAnchor(AnchorLineTop)) {
        AnchorLine topAnchorLine = anchors.instanceAnchor(AnchorLineTop);
        if (visitedItems.contains(topAnchorLine.qmlItemNode()) || checkForVerticalCycleRecusive(topAnchorLine.qmlItemNode().anchors(), visitedItems))
            return true;
    }

    if (anchors.instanceHasAnchor(AnchorLineBottom)) {
        AnchorLine bottomAnchorLine = anchors.instanceAnchor(AnchorLineBottom);
        if (visitedItems.contains(bottomAnchorLine.qmlItemNode()) || checkForVerticalCycleRecusive(bottomAnchorLine.qmlItemNode().anchors(), visitedItems))
            return true;
    }

    if (anchors.instanceHasAnchor(AnchorLineVerticalCenter)) {
        AnchorLine verticalCenterAnchorLine = anchors.instanceAnchor(AnchorLineVerticalCenter);
        if (visitedItems.contains(verticalCenterAnchorLine.qmlItemNode()) || checkForVerticalCycleRecusive(verticalCenterAnchorLine.qmlItemNode().anchors(), visitedItems))
            return true;
    }

    return false;
}

bool QmlAnchors::checkForHorizontalCycle(const QmlItemNode &sourceItem, SL sl) const
{
    NanotraceHR::Tracer tracer{"qml anchors check for horizontal cycle",
                               category(),
                               keyValue("model node", *this),
                               keyValue("caller location", sl)};

    QList<QmlItemNode> visitedItems;
    visitedItems.append(sourceItem);

    return checkForHorizontalCycleRecusive(*this, visitedItems);
}

bool QmlAnchors::checkForVerticalCycle(const QmlItemNode &sourceItem, SL sl) const
{
    NanotraceHR::Tracer tracer{"qml anchors check for vertical cycle",
                               category(),
                               keyValue("model node", *this),
                               keyValue("caller location", sl)};

    QList<QmlItemNode> visitedItems;
    visitedItems.append(sourceItem);

    return checkForVerticalCycleRecusive(*this, visitedItems);
}

double QmlAnchors::instanceMargin(AnchorLineType sourceAnchorLineType, SL sl) const
{
    NanotraceHR::Tracer tracer{"qml anchors instance margin",
                               category(),
                               keyValue("model node", *this),
                               keyValue("caller location", sl)};

    return qmlItemNode().nodeInstance().property(marginPropertyName(sourceAnchorLineType)).toDouble();
}

void QmlAnchors::removeMargin(AnchorLineType sourceAnchorLineType, SL sl)
{
    NanotraceHR::Tracer tracer{"qml anchors remove margin",
                               category(),
                               keyValue("model node", *this),
                               keyValue("caller location", sl)};

    if (qmlItemNode().isInBaseState()) {
        PropertyNameView propertyName = marginPropertyName(sourceAnchorLineType);
        qmlItemNode().modelNode().removeProperty(propertyName);
    }
}

void QmlAnchors::removeMargins(SL sl)
{
    NanotraceHR::Tracer tracer{"qml anchors remove margins",
                               category(),
                               keyValue("model node", *this),
                               keyValue("caller location", sl)};

    qmlItemNode().view()->executeInTransaction("QmlAnchors::removeMargins", [this]() {
        removeMargin(AnchorLineLeft);
        removeMargin(AnchorLineRight);
        removeMargin(AnchorLineTop);
        removeMargin(AnchorLineBottom);
        removeMargin(AnchorLineHorizontalCenter);
        removeMargin(AnchorLineVerticalCenter);
    });
}

void QmlAnchors::fill(SL sl)
{
    NanotraceHR::Tracer tracer{"qml anchors fill",
                               category(),
                               keyValue("model node", *this),
                               keyValue("caller location", sl)};

    if (instanceHasAnchors())
        removeAnchors();

    qmlItemNode().modelNode().bindingProperty("anchors.fill").setExpression(QLatin1String("parent"));
}

void QmlAnchors::centerIn(SL sl)
{
    NanotraceHR::Tracer tracer{"qml anchors center in",
                               category(),
                               keyValue("model node", *this),
                               keyValue("caller location", sl)};

    if (instanceHasAnchors())
        removeAnchors();

    qmlItemNode().modelNode().bindingProperty("anchors.centerIn").setExpression(QLatin1String("parent"));
}

bool QmlAnchors::checkForCycle(AnchorLineType anchorLineTyp, const QmlItemNode &sourceItem, SL sl) const
{
    NanotraceHR::Tracer tracer{"qml anchors check for cycle",
                               category(),
                               keyValue("model node", *this),
                               keyValue("caller location", sl)};

    if (anchorLineTyp & AnchorLineHorizontalMask)
        return checkForHorizontalCycle(sourceItem);
    else
        return checkForVerticalCycle(sourceItem);
}

} //QmlDesigner
