// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default

#include "qquickgeocoordinateanimation_p.h"
#include "qquickgeocoordinateanimation_p_p.h"
#include <QtQuick/private/qquickanimation_p_p.h>
#include <QtPositioning/private/qdoublevector2d_p.h>
#include <QtPositioning/private/qwebmercator_p.h>
#include <QtPositioning/private/qgeocoordinate_p.h>

QT_BEGIN_NAMESPACE

/*!
    \qmltype CoordinateAnimation
    \inherits PropertyAnimation
    \inqmlmodule QtPositioning
    \since 5.3

    \brief A PropertyAnimation for geo coordinate properties.

    A specialized \l{PropertyAnimation} that defines an animation
    between two \l{geoCoordinate}{coordinates}.

    By default, a \l{geoCoordinate::latitude}{latitude} of the
    \l geoCoordinate is animated in the direction of shortest
    (geodesic) distance between those coordinates. Since CoordinateAnimation uses Mercator
    map projection, the \l{geoCoordinate::latitude}{latitude} animation
    is always between -90 and 90 degrees.
    The \l{geoCoordinate::longitude}{longitude} animation path is not
    limited and can go over 180 degrees in both west and east directions.

    The \l{direction} property can be set to specify the direction in which the
    \l{geoCoordinate::longitude}{longitude} animation should occur.

    \sa {Animation and Transitions in Qt Quick}
*/

QVariant q_coordinateInterpolator(const QGeoCoordinate &from, const QGeoCoordinate &to, qreal progress)
{
    if (from == to) {
        if (progress < 0.5) {
            return QVariant::fromValue(from);
        } else {
            return QVariant::fromValue(to);
        }
    }

    QGeoCoordinate result = QWebMercator::coordinateInterpolation(from, to, progress);

    return QVariant::fromValue(result);
}

QVariant q_coordinateShortestInterpolator(const QGeoCoordinate &from, const QGeoCoordinate &to, qreal progress)
{
    const QGeoMercatorCoordinatePrivate* fromMercator =
            static_cast<const QGeoMercatorCoordinatePrivate*>(QGeoCoordinatePrivate::get(&from));
    const QGeoMercatorCoordinatePrivate* toMercator =
            static_cast<const QGeoMercatorCoordinatePrivate*>(QGeoCoordinatePrivate::get(&to));

    double toX = toMercator->m_mercatorX;
    double toY = toMercator->m_mercatorY;
    double fromX = fromMercator->m_mercatorX;
    double fromY = fromMercator->m_mercatorY;
    double x;
    if (0.5 < qAbs(toX - fromX)) {
        // handle dateline crossing
        double ex = toX;
        double sx = fromX;
        if (ex < sx)
            sx -= 1.0;
        else if (sx < ex)
            ex -= 1.0;

        x = sx + (ex - sx) * progress;

        if (x < 0.0)
            x += 1.0;

    } else {
        x = fromX + (toX - fromX) * progress;
    }

    double y = fromY + (toY - fromY) * progress;

    QGeoCoordinate result = QWebMercator::mercatorToCoord(QDoubleVector2D(x, y));
    result.setAltitude(from.altitude() + (to.altitude() - from.altitude()) * progress);
    return QVariant::fromValue(result);
}

QVariant q_coordinateEastInterpolator(const QGeoCoordinate &from, const QGeoCoordinate &to, qreal progress)
{
    const QGeoMercatorCoordinatePrivate* fromMercator =
            static_cast<const QGeoMercatorCoordinatePrivate*>(QGeoCoordinatePrivate::get(&from));
    const QGeoMercatorCoordinatePrivate* toMercator =
            static_cast<const QGeoMercatorCoordinatePrivate*>(QGeoCoordinatePrivate::get(&to));

    double toX = toMercator->m_mercatorX;
    double toY = toMercator->m_mercatorY;
    double fromX = fromMercator->m_mercatorX;
    double fromY = fromMercator->m_mercatorY;
    double diff = toX - fromX;

    while (diff < 0.0) {
        toX += 1.0;
        diff += 1.0;
    }

    double x = fromX + (toX - fromX) * progress;
    double y = fromY + (toY - fromY) * progress;

    while (x > 1.0)
        x -= 1.0;

    QGeoCoordinate result = QWebMercator::mercatorToCoord(QDoubleVector2D(x, y));
    result.setAltitude(from.altitude() + (to.altitude() - from.altitude()) * progress);

    return QVariant::fromValue(result);
}

QVariant q_coordinateWestInterpolator(const QGeoCoordinate &from, const QGeoCoordinate &to, qreal progress)
{
    const QGeoMercatorCoordinatePrivate* fromMercator =
            static_cast<const QGeoMercatorCoordinatePrivate*>(QGeoCoordinatePrivate::get(&from));
    const QGeoMercatorCoordinatePrivate* toMercator =
            static_cast<const QGeoMercatorCoordinatePrivate*>(QGeoCoordinatePrivate::get(&to));

    double toX = toMercator->m_mercatorX;
    double toY = toMercator->m_mercatorY;
    double fromX = fromMercator->m_mercatorX;
    double fromY = fromMercator->m_mercatorY;
    double diff = toX - fromX;

    while (diff > 0.0) {
        toX -= 1.0;
        diff -= 1.0;
    }

    double x = fromX + (toX - fromX) * progress;
    double y = fromY + (toY - fromY) * progress;

    while (x < 0.0)
        x += 1.0;

    QGeoCoordinate result = QWebMercator::mercatorToCoord(QDoubleVector2D(x, y));
    result.setAltitude(from.altitude() + (to.altitude() - from.altitude()) * progress);

    return QVariant::fromValue(result);
}

QQuickGeoCoordinateAnimation::QQuickGeoCoordinateAnimation(QObject *parent)
    : QQuickPropertyAnimation(*(new QQuickGeoCoordinateAnimationPrivate), parent)

{
    Q_D(QQuickGeoCoordinateAnimation);
    d->interpolatorType = qMetaTypeId<QGeoCoordinate>();
    d->defaultToInterpolatorType = true;
    d->interpolator = QVariantAnimationPrivate::getInterpolator(d->interpolatorType);
}

QQuickGeoCoordinateAnimation::~QQuickGeoCoordinateAnimation()
{
}

/*!
    \qmlproperty coordinate CoordinateAnimation::from
    This property holds the coordinate where the animation should begin.
*/
QGeoCoordinate QQuickGeoCoordinateAnimation::from() const
{
    Q_D(const QQuickGeoCoordinateAnimation);
    return d->from.value<QGeoCoordinate>();
}

void QQuickGeoCoordinateAnimation::setFrom(const QGeoCoordinate &f)
{
    QGeoMercatorCoordinatePrivate *mercator = new QGeoMercatorCoordinatePrivate();
    QDoubleVector2D fromVector = QWebMercator::coordToMercator(f);
    mercator->lat = f.latitude();
    mercator->lng = f.longitude();
    mercator->alt = f.altitude();
    mercator->m_mercatorX = fromVector.x();
    mercator->m_mercatorY = fromVector.y();
    QGeoCoordinate from(*mercator);
    QQuickPropertyAnimation::setFrom(QVariant::fromValue(from));
}

/*!
    \qmlproperty coordinate CoordinateAnimation::to
    This property holds the coordinate where the animation should end.
*/
QGeoCoordinate QQuickGeoCoordinateAnimation::to() const
{
    Q_D(const QQuickGeoCoordinateAnimation);
    return d->to.value<QGeoCoordinate>();
}

void QQuickGeoCoordinateAnimation::setTo(const QGeoCoordinate &t)
{
    QGeoMercatorCoordinatePrivate *mercator = new QGeoMercatorCoordinatePrivate();
    QDoubleVector2D toVector = QWebMercator::coordToMercator(t);
    mercator->lat = t.latitude();
    mercator->lng = t.longitude();
    mercator->alt = t.altitude();
    mercator->m_mercatorX = toVector.x();
    mercator->m_mercatorY = toVector.y();
    QGeoCoordinate to(*mercator);
    QQuickPropertyAnimation::setTo(QVariant::fromValue(to));
}

/*!
    \qmlproperty enumeration CoordinateAnimation::direction

    This property holds the direction of the
    \l{geoCoordinate::longitude}{longitude} animation of the
    \l geoCoordinate.

    \value CoordinateAnimation.Shortest
        The longitude animation goes in the direction
        that produces the shortest animation path. This is the default.
    \value CoordinateAnimation.West
        The longitude animation always goes into western direction
        and may cross the date line.
    \value CoordinateAnimation.East
        The longitude animation always goes into eastern direction
        and may cross the date line.

    \since 5.5
*/


QQuickGeoCoordinateAnimation::Direction QQuickGeoCoordinateAnimation::direction() const
{
    Q_D(const QQuickGeoCoordinateAnimation);
    return d->m_direction.value();
}

void QQuickGeoCoordinateAnimation::setDirection(QQuickGeoCoordinateAnimation::Direction direction)
{
    Q_D( QQuickGeoCoordinateAnimation);
    d->m_direction.removeBindingUnlessInWrapper();
    if (d->m_direction.valueBypassingBindings() == direction)
        return;

    d->m_direction.setValueBypassingBindings(direction);
    switch (direction) {
    case West:
        d->interpolator = reinterpret_cast<QVariantAnimation::Interpolator>(reinterpret_cast<void *>(&q_coordinateWestInterpolator));
        break;
    case East:
        d->interpolator = reinterpret_cast<QVariantAnimation::Interpolator>(reinterpret_cast<void *>(&q_coordinateEastInterpolator));
        break;
    case Shortest:
    default:
        d->interpolator = reinterpret_cast<QVariantAnimation::Interpolator>(reinterpret_cast<void *>(&q_coordinateShortestInterpolator));
        break;
    }
    d->m_direction.notify();
}

QBindable<QQuickGeoCoordinateAnimation::Direction> QQuickGeoCoordinateAnimation::bindableDirection()
{
    Q_D(QQuickGeoCoordinateAnimation);
    return QBindable<Direction>(&d->m_direction);
}

QT_END_NAMESPACE

#include "moc_qquickgeocoordinateanimation_p.cpp"
