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

#ifndef QLOTTIESPATIALPROPERTY_P_H
#define QLOTTIESPATIALPROPERTY_P_H

//
//  W A R N I N G
//  -------------
//
// This file is not part of the Qt API.  It exists purely as an
// implementation detail.  This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//

#include <QPointF>
#include <QPainterPath>

#include <QtLottie/private/qlottieproperty_p.h>

QT_BEGIN_NAMESPACE

class QLottieSpatialProperty : public QLottieProperty2D<QPointF>
{
public:
    virtual void construct(const QJsonObject &definition) override
    {
        qCDebug(lcLottieQtLottieParser) << "QLottieSpatialProperty::parse()";
        QLottieProperty2D<QPointF>::construct(definition);
    }

    virtual EasingSegment<QPointF> parseKeyframe(const QJsonObject keyframe,
                                                 bool fromExpression) override
    {
        m_subPaths.append(QPainterPath{});

        EasingSegment<QPointF> easing =
                QLottieProperty2D<QPointF>::parseKeyframe(keyframe, fromExpression);

        // No need to parse further incomplete keyframes (i.e. last keyframes)
        if (!easing.complete) {
            return easing;
        }

        qreal tix = 0, tiy = 0, tox = 0, toy = 0;
        if (fromExpression) {
            // If spatial property definition originates from
            // an expression (specifically Slider), it contains scalar
            // property. It must be expanded to both x and y coordinates
            QJsonArray iArr = keyframe.value("i"_L1).toArray();
            QJsonArray oArr = keyframe.value("o"_L1).toArray();

            if (iArr.count() && oArr.count()) {
                tix = iArr.at(0).toDouble();
                tiy = tix;
                tox = oArr.at(0).toDouble();
                toy = tox;
            }
        } else {
            QJsonArray tiArr = keyframe.value("ti"_L1).toArray();
            QJsonArray toArr = keyframe.value("to"_L1).toArray();

            if (tiArr.count() && toArr.count()) {
                tix = tiArr.at(0).toDouble();
                tiy = tiArr.at(1).toDouble();
                tox = toArr.at(0).toDouble();
                toy = toArr.at(1).toDouble();
            }
        }
        QPointF s(easing.startValue);
        QPointF e(easing.endValue);
        QPointF c1(tox, toy);
        QPointF c2(tix, tiy);

        moveTo(s);
        if (c1.isNull() && c2.isNull()) {
            lineTo(e);
        } else {
            c1 += s;
            c2 += e;
            cubicTo(c1, c2, e);
        }

        return easing;
    }

    virtual EasingSegment<QPointF> parseKeyframe(const QJsonObject keyframe,
                                                 const QJsonObject nextKeyframe,
                                                 bool fromExpression) override
    {
        m_subPaths.append(QPainterPath{});

        EasingSegment<QPointF> easing =
                QLottieProperty2D<QPointF>::parseKeyframe(keyframe, nextKeyframe, fromExpression);

        // No need to parse further incomplete keyframes (i.e. last keyframes)
        if (!easing.complete) {
            return easing;
        }

        qreal tix = 0, tiy = 0, tox = 0, toy = 0;
        if (fromExpression) {
            // If spatial property definition originates from
            // an expression (specifically Slider), it contains scalar
            // property. It must be expanded to both x and y coordinates
            QJsonArray iArr = keyframe.value("i"_L1).toArray();
            QJsonArray oArr = keyframe.value("o"_L1).toArray();

            if (iArr.count() && oArr.count()) {
                tix = iArr.at(0).toDouble();
                tiy = tix;
                tox = oArr.at(0).toDouble();
                toy = tox;
            }
        } else {
            QJsonArray tiArr = keyframe.value("ti"_L1).toArray();
            QJsonArray toArr = keyframe.value("to"_L1).toArray();

            if (tiArr.count() && toArr.count()) {
                tix = tiArr.at(0).toDouble();
                tiy = tiArr.at(1).toDouble();
                tox = toArr.at(0).toDouble();
                toy = toArr.at(1).toDouble();
            }
        }
        QPointF s(easing.startValue);
        QPointF e(easing.endValue);
        QPointF c1(tox, toy);
        QPointF c2(tix, tiy);

        moveTo(s);
        if (c1.isNull() && c2.isNull()) {
            lineTo(e);
        } else {
            c1 += s;
            c2 += e;
            cubicTo(c1, c2, e);
        }

        return easing;
    }

    virtual bool update(int frame) override
    {
        if (!m_animated)
            return false;

        int adjustedFrame = qBound(m_startFrame, frame, m_endFrame);
        int index = -1;
        if (const EasingSegment<QPointF> *easing = getEasingSegment(adjustedFrame, &index)) {
            if (easing->endFrame == easing->startFrame) { // Last, special keyframe
                m_value = easing->startValue;
            } else if (adjustedFrame == easing->startFrame) {
                m_value = easing->startValue;
            } else if (adjustedFrame == easing->endFrame) {
                m_value = easing->endValue;
            } else {
                const qreal progress = qreal(adjustedFrame - easing->startFrame)
                                       / (easing->endFrame - easing->startFrame);
                const qreal easedProgress = easing->valueForProgress(progress);
                const QPainterPath subPath = m_subPaths.value(index);
                if (subPath.elementCount() == 2) // I.e. a lineto; do simple linear calculation
                    m_value = easing->startValue + easedProgress * (easing->endValue - easing->startValue);
                else
                    m_value = subPath.pointAtPercent(easedProgress);
            }
        }
        return true;
    }

    bool hasCurves() const
    {
        for (qsizetype i = 0; i < m_subPaths.size(); ++i) {
            const QPainterPath path = m_subPaths.at(i);
            for (int j = 0; j < path.elementCount(); ++j)
                if (path.elementAt(j).isCurveTo())
                    return true;
        }

        return false;
    }

    const QList<QPainterPath> &subPaths() const
    {
        return m_subPaths;
    }

private:
    void moveTo(const QPointF &e)
    {
        m_subPaths.last().setCachingEnabled(true);
        m_subPaths.last().moveTo(e);
    }

    void lineTo(const QPointF &e)
    {
        m_subPaths.last().lineTo(e);
    }

    void cubicTo(const QPointF &c1, const QPointF &c2, const QPointF &e)
    {
        m_subPaths.last().cubicTo(c1, c2, e);
    }

    QList<QPainterPath> m_subPaths;
};

QT_END_NAMESPACE

#endif // QLOTTIESPATIALPROPERTY_P_H
