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

#include <QtCore/QPointF>
#include "QSplineSeries"
#include "private/qsplinecontrolanimation_p.h"
#include "private/qsplineseries_p.h"

/*!
    \qmltype SplineControlAnimation
    \inqmlmodule QtGraphs
    \ingroup graphs_qml_2D
    \inherits XYSeriesAnimation
    \brief An animation type which signifies the animation for spline control points.

    SplineControlAnimation is an animation type derived from QVariantAnimation which defines how spline control points
    are animated. It can make use of QVariantAnimation functionality and properties for its animations, such as \c duration
    and \c easing. These animations are housed inside a QParallelAnimationGroup and hence will run in parallel.
    This animation will not affect the main points of the SplineSeries, but only the two control handles
    on either side of the point. Each of the control points are linearly interpolated in succession.

    This example shows how to use both a SplineControlPointAnimation and a
    GraphPointAnimation to define animations for both the main series of points and the
    control points of a SplineSeries:

    \snippet doc_src_qmlgraphs.cpp 13

    \sa GraphTransition, GraphPointAnimation
*/

QSplineControlAnimation::QSplineControlAnimation(QObject *parent)
    : QXYSeriesAnimation(parent)
{
    setDuration(800);
    setEasingCurve(QEasingCurve::OutCubic);
}

QSplineControlAnimation::~QSplineControlAnimation() {}

QGraphAnimation::GraphAnimationType QSplineControlAnimation::animationType()
{
    return QGraphAnimation::GraphAnimationType::ControlPoint;
}

void QSplineControlAnimation::setAnimatingValue(const QVariant &start, const QVariant &end)
{
    setStartValue(start);
    setEndValue(end);
}

QVariant QSplineControlAnimation::interpolated(const QVariant &start,
                                               const QVariant &end,
                                               qreal progress) const
{
    auto startList = qvariant_cast<QList<QPointF>>(start);
    auto endList = qvariant_cast<QList<QPointF>>(end);
    auto interpolateList = QList<QPointF>();

    for (int i = 0; i < qMin(startList.size(), endList.size()); ++i) {
        interpolateList.push_back(
            {qreal(startList[i].x() * (1.0 - progress) + endList[i].x() * progress),
             qreal(startList[i].y() * (1.0 - progress) + endList[i].y() * progress)});
    }

    return QVariant::fromValue(interpolateList);
}

void QSplineControlAnimation::animate()
{
    // Hierarchy should look like GraphAnimation -> ParallelAnimationGroup -> GraphTransition -> SplineSeries
    auto series = qobject_cast<QSplineSeries *>(parent()->parent()->parent());

    if (!series || series->points().size() < 1) {
        qCDebug(lcAnimation, "Series is nullptr or series point list is empty");
        return;
    }

    auto pointList = series->points();
    auto &cPoints = series->d_func()->m_controlPoints;

    if (animating() == QGraphAnimation::AnimationState::Playing)
        end();

    setAnimating(QGraphAnimation::AnimationState::Playing);

    auto oldPoints = cPoints;

    series->d_func()->calculateSplinePoints();

    while (oldPoints.size() < cPoints.size()) {
        // Each point corresponds to a 2n - 1 control point pair other than the first
        // (Except when there are only 2 points)
        // 0 ---- 0
        // 1 ---- 1
        //   ---- 2
        // 2 ---- 3
        //   ---- 4 ...
        QPointF point = pointList[oldPoints.size() / 2];
        oldPoints.append(point);
    }

    auto varStart = QVariant::fromValue(oldPoints);
    auto varEnd = QVariant::fromValue(cPoints);

    setAnimatingValue(varStart, varEnd);
}

void QSplineControlAnimation::end()
{
    auto series = qobject_cast<QSplineSeries *>(parent()->parent()->parent());

    if (!series || animating() == QGraphAnimation::AnimationState::Stopped)
        return;

    setAnimating(QGraphAnimation::AnimationState::Stopped);
    stop();

    series->d_func()->calculateSplinePoints();

    emit series->update();
}

void QSplineControlAnimation::valueUpdated(const QVariant &value)
{
    auto series = qobject_cast<QSplineSeries *>(parent()->parent()->parent());

    if (!series)
        return;

    auto &cPoints = series->d_func()->m_controlPoints;
    auto points = qvariant_cast<QList<QPointF>>(value);

    for (int i = 0; i < qMin(points.size(), cPoints.size()); ++i)
        cPoints.replace(i, points[i]);

    emit series->update();
}
