/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (info@qt.nokia.com)
**
**
** GNU Lesser General Public License Usage
**
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this file.
** Please review the following information to ensure the GNU Lesser General
** Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**************************************************************************/

#include "anchorcontroller.h"

#include "formeditoritem.h"
#include "layeritem.h"
#include "formeditorscene.h"
#include "anchorhandleitem.h"
#include <QtDebug>
#include <cmath>
namespace QmlDesigner {

AnchorControllerData::AnchorControllerData(LayerItem *layerItem, FormEditorItem *formEditorItem)
    : layerItem(layerItem),
    formEditorItem(formEditorItem),
    topItem(0),
    leftItem(0),
    rightItem(0),
    bottomItem(0)
{
}

AnchorControllerData::AnchorControllerData(const AnchorControllerData &other)
    : layerItem(other.layerItem),
    formEditorItem(other.formEditorItem),
    topItem(other.topItem),
    leftItem(other.leftItem),
    rightItem(other.rightItem),
    bottomItem(other.bottomItem)
{
}

AnchorControllerData::~AnchorControllerData()
{
    if (layerItem) {
        layerItem->scene()->removeItem(topItem);
        layerItem->scene()->removeItem(leftItem);
        layerItem->scene()->removeItem(rightItem);
        layerItem->scene()->removeItem(bottomItem);
    }
}


AnchorController::AnchorController()
   : m_data(new AnchorControllerData(0, 0))
{

}

AnchorController::AnchorController(const QSharedPointer<AnchorControllerData> &data)
    : m_data(data)
{

}

AnchorController::AnchorController(LayerItem *layerItem, FormEditorItem *formEditorItem)
    : m_data(new AnchorControllerData(layerItem, formEditorItem))
{
    m_data->topItem = new AnchorHandleItem(layerItem, *this);
    m_data->topItem->setZValue(400);
    m_data->topItem->setToolTip(m_data->topItem->toolTipString());

    m_data->leftItem = new AnchorHandleItem(layerItem, *this);
    m_data->leftItem->setZValue(400);
    m_data->leftItem->setToolTip(m_data->leftItem->toolTipString());

    m_data->rightItem = new AnchorHandleItem(layerItem, *this);
    m_data->rightItem->setZValue(400);
    m_data->rightItem->setToolTip(m_data->rightItem->toolTipString());

    m_data->bottomItem = new AnchorHandleItem(layerItem, *this);
    m_data->bottomItem->setZValue(400);
    m_data->bottomItem->setToolTip(m_data->bottomItem->toolTipString());

    m_data->sceneTransform = formEditorItem->sceneTransform();

    updatePosition();
}


bool AnchorController::isValid() const
{
    return m_data->formEditorItem != 0;
}

void AnchorController::show()
{
    m_data->topItem->show();
    m_data->leftItem->show();
    m_data->rightItem->show();
    m_data->bottomItem->show();
}



void AnchorController::hide()
{
    m_data->topItem->hide();
    m_data->leftItem->hide();
    m_data->rightItem->hide();
    m_data->bottomItem->hide();
}


static QPointF topCenter(const QRectF &rect)
{
    return QPointF(rect.center().x(), rect.top());
}

static QPointF leftCenter(const QRectF &rect)
{
    return QPointF(rect.left(), rect.center().y());
}

static QPointF rightCenter(const QRectF &rect)
{
    return QPointF(rect.right(), rect.center().y());
}

static QPointF bottomCenter(const QRectF &rect)
{
    return QPointF(rect.center().x(), rect.bottom());
}

static QPainterPath curveToPath(const QPointF &firstPoint,
                                const QPointF &secondPoint,
                                const QPointF &thirdPoint,
                                const QPointF &fourthPoint)
{
    QPainterPath path;
    path.moveTo(firstPoint);
    path.cubicTo(secondPoint, thirdPoint, fourthPoint);

    return path;
}

static QPointF anchorPoint(const QRectF &boundingRect, AnchorLine::Type anchorLine,  double baseOffset, double innerOffset = 0.0)
{
    switch(anchorLine) {
        case AnchorLine::Top : return topCenter(boundingRect) + QPointF(baseOffset, innerOffset);
        case AnchorLine::Bottom : return bottomCenter(boundingRect) - QPointF(baseOffset, innerOffset);
        case AnchorLine::Left : return leftCenter(boundingRect) + QPointF(innerOffset, baseOffset);
        case AnchorLine::Right : return rightCenter(boundingRect) - QPointF(innerOffset, baseOffset);
        default: return QPointF();
    }

    return QPointF();
}


static QPainterPath createArrowPath(QPointF arrowCenter, double arrowDegrees)
{
    QRectF arrowRect(0.0, 0.0, 16., 16.);
    arrowRect.moveCenter(arrowCenter);
    QPainterPath arrowPath;


    arrowPath.moveTo(arrowCenter);

    arrowPath.arcTo(arrowRect, arrowDegrees + 180 - 20, 40.);

    return arrowPath;
}

AnchorHandlePathData AnchorController::createPainterPathForAnchor(const QRectF &boundingRect,
                                                    AnchorLine::Type anchorLine,
                                                    const QPointF &targetPoint) const
{


    QPointF firstPointInLayerSpace(m_data->sceneTransform.map(anchorPoint(boundingRect, anchorLine, 0.0, 5.0)));

    QPointF topLeftBoundingBoxInLayerSpace(m_data->sceneTransform.map(boundingRect.topLeft()));
    QPointF bottomLeftBoundingBoxInLayerSpace(m_data->sceneTransform.map(boundingRect.bottomLeft()));
    QPointF topRightBoundingBoxInLayerSpace(m_data->sceneTransform.map(boundingRect.topRight()));
    QPointF bottomRightBoundingBoxInLayerSpace(m_data->sceneTransform.map(boundingRect.bottomRight()));

    AnchorLine::Type secondAnchorLine(AnchorLine::Invalid);

    QPointF secondPointInLayerSpace(targetPoint);
    if (targetPoint.isNull()) {
        AnchorLine targetAnchorLine(m_data->formEditorItem->qmlItemNode().anchors().instanceAnchor(anchorLine));
        secondAnchorLine = targetAnchorLine.type();
        FormEditorItem *targetItem = m_data->formEditorItem->scene()->itemForQmlItemNode(targetAnchorLine.qmlItemNode());;
        bool secondItemIsParent = m_data->formEditorItem->parentItem() == targetItem;

        if (secondItemIsParent)
            secondPointInLayerSpace = (targetItem->mapToItem(m_data->layerItem.data(),
                                                             anchorPoint(targetItem->qmlItemNode().instanceBoundingRect(), targetAnchorLine.type(), 0.0)));
        else

            secondPointInLayerSpace = (targetItem->mapToItem(m_data->layerItem.data(),
                                                             anchorPoint(targetItem->qmlItemNode().instanceBoundingRect(), targetAnchorLine.type(), 0.0)));
    }

    QPointF firstControlPointInLayerSpace = (3. * firstPointInLayerSpace + 3 * secondPointInLayerSpace) / 6.;
    QPointF secondControlPointInLayerSpace = (3 * firstPointInLayerSpace + 3. * secondPointInLayerSpace) / 6.;

    bool showAnchorLine(true);
    switch (anchorLine) {
        case AnchorLine::Top :
        case AnchorLine::Bottom :
            firstControlPointInLayerSpace.rx() = firstPointInLayerSpace.x();
            if (qAbs(secondPointInLayerSpace.y() - firstPointInLayerSpace.y()) < 18.0)
                showAnchorLine = false;
            if (qAbs(secondControlPointInLayerSpace.y() - secondPointInLayerSpace.y()) < 20.0 &&
               qAbs(secondControlPointInLayerSpace.x() - secondPointInLayerSpace.x()) > 20.0) {
                firstControlPointInLayerSpace.ry() = firstPointInLayerSpace.y() + ((firstControlPointInLayerSpace.y() - firstPointInLayerSpace.y() > 0) ? 20 : -20);
            }
            break;
        case AnchorLine::Left :
        case AnchorLine::Right :
            firstControlPointInLayerSpace.ry() = firstPointInLayerSpace.y();
            if (qAbs(secondPointInLayerSpace.x() - firstPointInLayerSpace.x()) < 18.0)
                showAnchorLine = false;
            if (qAbs(secondControlPointInLayerSpace.x() - secondPointInLayerSpace.x()) < 20.0 &&
               qAbs(secondControlPointInLayerSpace.y() - secondPointInLayerSpace.y()) > 20.0) {
                firstControlPointInLayerSpace.rx() = firstPointInLayerSpace.x() + ((firstControlPointInLayerSpace.x() - firstPointInLayerSpace.x() > 0) ? 20 : -20);
            }
            break;
        default: break;
    }

    switch(secondAnchorLine) {
        case AnchorLine::Top :
        case AnchorLine::Bottom :
            secondControlPointInLayerSpace.rx() = secondPointInLayerSpace.x();
            if (qAbs(secondControlPointInLayerSpace.y() - secondPointInLayerSpace.y()) < 20.0 &&
               qAbs(secondControlPointInLayerSpace.x() - secondPointInLayerSpace.x()) > 20.0) {
                secondControlPointInLayerSpace.ry() = secondPointInLayerSpace.y() + ((secondControlPointInLayerSpace.y() - secondPointInLayerSpace.y() < 0) ? 20 : -20);
            }
            break;
        case AnchorLine::Left :
        case AnchorLine::Right :
            secondControlPointInLayerSpace.ry() = secondPointInLayerSpace.y();
            if (qAbs(secondControlPointInLayerSpace.x() - secondPointInLayerSpace.x()) < 20.0 &&
               qAbs(secondControlPointInLayerSpace.y() - secondPointInLayerSpace.y()) > 20.0) {
                secondControlPointInLayerSpace.rx() = secondPointInLayerSpace.x() + ((secondControlPointInLayerSpace.x() - secondPointInLayerSpace.x() < 0) ? 20 : -20);
            }
            break;
        default: break;
    }

    QPainterPath anchorLinePath;
    anchorLinePath.setFillRule(Qt::WindingFill);

    QRectF baseRect(0.0, 0.0, 5., 5.);
    baseRect.moveCenter(firstPointInLayerSpace);
    QPainterPath basePath;
    basePath.addRoundedRect(baseRect, 6., 6.);
    anchorLinePath = anchorLinePath.united(basePath);

    QRectF baseLineRect;
    switch (anchorLine) {
        case AnchorLine::Left : {
                baseLineRect = QRectF(topLeftBoundingBoxInLayerSpace, bottomLeftBoundingBoxInLayerSpace);
                baseLineRect.setWidth(3);
            }
            break;
        case AnchorLine::Top : {
                baseLineRect = QRectF(topLeftBoundingBoxInLayerSpace, topRightBoundingBoxInLayerSpace);
                baseLineRect.setHeight(3);
            }
            break;
        case AnchorLine::Right : {
                baseLineRect = QRectF(topRightBoundingBoxInLayerSpace, bottomRightBoundingBoxInLayerSpace);
                baseLineRect.adjust(-3, 0, 0, 0);
            }
            break;
        case AnchorLine::Bottom : {
                baseLineRect = QRectF(bottomLeftBoundingBoxInLayerSpace, bottomRightBoundingBoxInLayerSpace);
                baseLineRect.adjust(0, -3, 0, 0);
            }
            break;
        default: break;
    }

    if (!baseLineRect.isEmpty()) {

        QPainterPath baseLinePath;
        baseLinePath.addRoundedRect(baseLineRect, 1., 1.);
        anchorLinePath = anchorLinePath.united(baseLinePath);
    }

    QPainterPath arrowPath;
    arrowPath.setFillRule(Qt::WindingFill);



    if (showAnchorLine) {
        QPainterPath curvePath(curveToPath(firstPointInLayerSpace,
                                           firstControlPointInLayerSpace,
                                           secondControlPointInLayerSpace,
                                           secondPointInLayerSpace));

        double arrowDegrees = curvePath.angleAtPercent(curvePath.percentAtLength(curvePath.length() - 2.5));



        QPainterPathStroker arrowPathStroker;
        arrowPathStroker.setWidth(2.0);
        arrowPathStroker.setCapStyle(Qt::RoundCap);

        arrowPath = arrowPath.united(arrowPathStroker.createStroke(curvePath));





        QRectF arrowCutRect(0.0, 0.0, 8., 8.);
        arrowCutRect.moveCenter(secondPointInLayerSpace);
        QPainterPath arrowCutPath;
        arrowCutPath.addRect(arrowCutRect);
        arrowPath = arrowPath.subtracted(arrowCutPath);

        arrowPath = arrowPath.united(createArrowPath(secondPointInLayerSpace, arrowDegrees));
    }

    AnchorHandlePathData pathData;
    pathData.arrowPath = arrowPath;
    pathData.sourceAnchorLinePath = anchorLinePath;
    pathData.beginArrowPoint = firstPointInLayerSpace;
    pathData.endArrowPoint = secondPointInLayerSpace;

    pathData.targetAnchorLinePath = createTargetAnchorLinePath(anchorLine);
    pathData.targetNamePath = createTargetNamePathPath(anchorLine);

    return pathData;
}

QPainterPath AnchorController::createTargetNamePathPath(AnchorLine::Type anchorLine) const
{
    QPainterPath path;
    QmlAnchors anchors(formEditorItem()->qmlItemNode().anchors());
    if (anchors.instanceHasAnchor(anchorLine)) {
        AnchorLine targetAnchorLine(anchors.instanceAnchor(anchorLine));

        FormEditorItem *targetItem = formEditorItem()->scene()->itemForQmlItemNode(targetAnchorLine.qmlItemNode());
        QRectF boundingRect(targetItem->qmlItemNode().instanceBoundingRect());

        QTransform sceneTransform(targetItem->qmlItemNode().instanceSceneTransform());

        QPointF centerBoundingBoxInLayerSpace(sceneTransform.map(boundingRect.center()));

        QFont font;
        font.setPixelSize(24);
        QString nameString(QString("%1 (%2)").arg(targetAnchorLine.qmlItemNode().simplifiedTypeName()).arg(targetAnchorLine.qmlItemNode().id()));
        path.addText(0., -4., font, nameString);
        //path.translate(centerBoundingBoxInLayerSpace - path.qmlItemNode().instanceBoundingRect().center());

    }

    return path;
}

QPainterPath AnchorController::createTargetAnchorLinePath(AnchorLine::Type anchorLine) const
{
    QPainterPath path;
    QmlAnchors anchors(formEditorItem()->qmlItemNode().anchors());
    if (anchors.instanceHasAnchor(anchorLine)) {
        AnchorLine targetAnchorLine(anchors.instanceAnchor(anchorLine));

        FormEditorItem *targetItem = formEditorItem()->scene()->itemForQmlItemNode(targetAnchorLine.qmlItemNode());
        QRectF boundingRect(targetItem->qmlItemNode().instanceBoundingRect());

        QTransform sceneTransform(targetItem->qmlItemNode().instanceSceneTransform());

        QPointF topLeftBoundingBoxInLayerSpace(sceneTransform.map(boundingRect.topLeft()));
        QPointF bottomLeftBoundingBoxInLayerSpace(sceneTransform.map(boundingRect.bottomLeft()));
        QPointF topRightBoundingBoxInLayerSpace(sceneTransform.map(boundingRect.topRight()));
        QPointF bottomRightBoundingBoxInLayerSpace(sceneTransform.map(boundingRect.bottomRight()));


        switch(targetAnchorLine.type()) {
            case AnchorLine::Top : {
                    path.moveTo(topLeftBoundingBoxInLayerSpace);
                    path.lineTo(topRightBoundingBoxInLayerSpace);
                }
                break;
            case AnchorLine::Bottom : {
                    path.moveTo(bottomLeftBoundingBoxInLayerSpace);
                    path.lineTo(bottomRightBoundingBoxInLayerSpace);
                }
                break;
            case AnchorLine::Left : {
                    path.moveTo(topLeftBoundingBoxInLayerSpace);
                    path.lineTo(bottomLeftBoundingBoxInLayerSpace);
                }
                break;
            case AnchorLine::Right : {
                    path.moveTo(topRightBoundingBoxInLayerSpace);
                    path.lineTo(bottomRightBoundingBoxInLayerSpace);
                }
                break;
            default: break;
        }

        QPainterPathStroker pathStroker;
        pathStroker.setWidth(20.0);
        pathStroker.setCapStyle(Qt::RoundCap);
        path = pathStroker.createStroke(path);


    }

    return path;
}

void AnchorController::updatePosition()
{
    QRectF boundingRect = m_data->formEditorItem->qmlItemNode().instanceBoundingRect();
    QPointF beginPoint;
    QPointF endPoint;
    QmlAnchors anchors(m_data->formEditorItem->qmlItemNode().anchors());
    m_data->sceneTransform = m_data->formEditorItem->sceneTransform();

    if (anchors.instanceHasAnchor(AnchorLine::Top))
        m_data->topItem->setHandlePath(createPainterPathForAnchor(boundingRect, AnchorLine::Top));
    else
        m_data->topItem->setHandlePath(AnchorHandlePathData());

    if (anchors.instanceHasAnchor(AnchorLine::Bottom))
        m_data->bottomItem->setHandlePath(createPainterPathForAnchor(boundingRect, AnchorLine::Bottom));
    else
        m_data->bottomItem->setHandlePath(AnchorHandlePathData());

    if (anchors.instanceHasAnchor(AnchorLine::Right))
        m_data->rightItem->setHandlePath(createPainterPathForAnchor(boundingRect, AnchorLine::Right));
    else
        m_data->rightItem->setHandlePath(AnchorHandlePathData());

    if (anchors.instanceHasAnchor(AnchorLine::Left))
        m_data->leftItem->setHandlePath(createPainterPathForAnchor(boundingRect, AnchorLine::Left));
    else
        m_data->leftItem->setHandlePath(AnchorHandlePathData());
}


FormEditorItem* AnchorController::formEditorItem() const
{
    return m_data->formEditorItem;
}

QWeakPointer<AnchorControllerData> AnchorController::weakPointer() const
{
    return m_data;
}


bool AnchorController::isTopHandle(const AnchorHandleItem *handle) const
{
    return handle == m_data->topItem;
}

bool AnchorController::isLeftHandle(const AnchorHandleItem *handle) const
{
    return handle == m_data->leftItem;
}

bool AnchorController::isRightHandle(const AnchorHandleItem *handle) const
{
    return handle == m_data->rightItem;
}

bool AnchorController::isBottomHandle(const AnchorHandleItem *handle) const
{
    return handle == m_data->bottomItem;
}

void AnchorController::updateTargetPoint(AnchorLine::Type anchorLine, const QPointF &targetPoint)
{
    QRectF boundingRect = m_data->formEditorItem->qmlItemNode().instanceBoundingRect();

    switch(anchorLine) {
        case AnchorLine::Top :
            m_data->topItem->setHandlePath(createPainterPathForAnchor(boundingRect, anchorLine, targetPoint)); break;
        case AnchorLine::Bottom :
            m_data->bottomItem->setHandlePath(createPainterPathForAnchor(boundingRect, anchorLine, targetPoint)); break;
        case AnchorLine::Left :
            m_data->leftItem->setHandlePath(createPainterPathForAnchor(boundingRect, anchorLine, targetPoint)); break;
        case AnchorLine::Right :
            m_data->rightItem->setHandlePath(createPainterPathForAnchor(boundingRect, anchorLine, targetPoint)); break;
        default: break;
    }
}

void AnchorController::highlight(AnchorLine::Type anchorLine)
{
    switch(anchorLine) {
        case AnchorLine::Top :
            m_data->topItem->setHighlighted(true); break;
        case AnchorLine::Bottom :
            m_data->bottomItem->setHighlighted(true); break;
        case AnchorLine::Left :
            m_data->leftItem->setHighlighted(true); break;
        case AnchorLine::Right :
            m_data->rightItem->setHighlighted(true); break;
        default: break;
    }
}

void AnchorController::clearHighlight()
{
    m_data->topItem->setHighlighted(false);
    m_data->leftItem->setHighlighted(false);
    m_data->rightItem->setHighlighted(false);
    m_data->bottomItem->setHighlighted(false);
}

} // namespace QmlDesigner
