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

#include "qlottiefreeformshape_p.h"

#include <QJsonObject>
#include <QString>

#include "qlottietrimpath_p.h"

QT_BEGIN_NAMESPACE

using namespace Qt::Literals::StringLiterals;

QLottieFreeFormShape::QLottieFreeFormShape(const QLottieFreeFormShape &other)
    : QLottieShape(other)
{
    m_vertexList = other.m_vertexList;
    m_closedShape = other.m_closedShape;
    m_vertexMap = other.m_vertexMap;
}

QLottieFreeFormShape::QLottieFreeFormShape(QLottieBase *parent)
{
    setParent(parent);
}

QLottieBase *QLottieFreeFormShape::clone() const
{
    return new QLottieFreeFormShape(*this);
}

int QLottieFreeFormShape::parse(const QJsonObject &definition)
{
    QLottieBase::parse(definition);
    if (m_hidden)
        return 0;

    qCDebug(lcLottieQtLottieParser) << "QLottieFreeFormShape::parse():" << m_name;

    m_direction = definition.value("d"_L1).toVariant().toInt();

    if (!checkRequiredKeys(definition, "Shape"_L1, { "ks"_L1 }, m_name))
        return -1;
    QJsonObject vertexObj = definition.value("ks"_L1).toObject();
    if (!checkRequiredKeys(vertexObj, "Shape"_L1, { "k"_L1 }, m_name))
        return -1;

    if (vertexObj.value("a"_L1).toInt())
        parseShapeKeyframes(vertexObj);
    else
        buildShape(vertexObj.value("k"_L1).toObject());

    return 0;
}

void QLottieFreeFormShape::updateProperties(int frame)
{
    if (m_vertexMap.size()) {
        QJsonObject keyframe = m_vertexMap.value(frame);
        // If this frame is a keyframe, so values must be updated
        if (!keyframe.isEmpty())
            buildShape(keyframe.value("s"_L1).toArray().at(0).toObject());
    } else {
        for (int i =0; i < m_vertexList.size(); i++) {
            VertexInfo vi = m_vertexList.at(i);
            vi.pos.update(frame);
            vi.ci.update(frame);
            vi.co.update(frame);
            m_vertexList.replace(i, vi);
        }
        buildShape(frame);
    }
}

void QLottieFreeFormShape::render(QLottieRenderer &renderer) const
{
    renderer.render(*this);
}

bool QLottieFreeFormShape::acceptsTrim() const
{
    return true;
}

bool QLottieFreeFormShape::isAnimated() const
{
    return !m_vertexList.isEmpty();
}

void QLottieFreeFormShape::parseShapeKeyframes(QJsonObject &keyframes)
{
    QJsonArray vertexKeyframes = keyframes.value("k"_L1).toArray();
    for (int i = 0; i < vertexKeyframes.count(); i++) {
        QJsonObject keyframe = vertexKeyframes.at(i).toObject();
        if (keyframe.value("h"_L1).toInt())
            m_vertexMap.insert(keyframe.value("t"_L1).toVariant().toInt(), keyframe);
        else
            parseEasedVertices(keyframe, keyframe.value("t"_L1).toVariant().toInt());
    }
    if (m_vertexInfos.size())
        finalizeVertices();
}

void QLottieFreeFormShape::addCurve(QPointF start, QPointF ctlOffset1, QPointF ctlOffset2, QPointF end)
{
    QPointF c1 = start + ctlOffset1;
    QPointF c2 = end + ctlOffset2;

    if (isAnimated() && start == c1 && c1 == c2 && c2 == end) {
        // QPP::cubicTo() skips null curves, so work around since all paths should have all elements
        end += QPointF(0.001, 0.001);
    }

    m_path.cubicTo(c1, c2, end);
}

void QLottieFreeFormShape::buildShape(const QJsonObject &shape)
{
    m_path.clear();

    bool needToClose = shape.value("c"_L1).toBool();
    QJsonArray bezierIn = shape.value("i"_L1).toArray();
    QJsonArray bezierOut = shape.value("o"_L1).toArray();
    QJsonArray vertices = shape.value("v"_L1).toArray();

    // If there are less than two vertices, cannot make a bezier curve
    if (vertices.count() < 2)
        return;

    QPointF s(vertices.at(0).toArray().at(0).toDouble(),
              vertices.at(0).toArray().at(1).toDouble());
    QPointF s0(s);

    m_path.moveTo(s);
    int i=0;

    while (i < vertices.count() - 1) {
        QPointF v = QPointF(vertices.at(i + 1).toArray().at(0).toDouble(),
                            vertices.at(i + 1).toArray().at(1).toDouble());
        QPointF c1 = QPointF(bezierOut.at(i).toArray().at(0).toDouble(),
                             bezierOut.at(i).toArray().at(1).toDouble());
        QPointF c2 = QPointF(bezierIn.at(i + 1).toArray().at(0).toDouble(),
                             bezierIn.at(i + 1).toArray().at(1).toDouble());
        addCurve(s, c1, c2, v);
        s = v;
        i++;
    }

    if (needToClose) {
        QPointF v = s0;
        QPointF c1 = QPointF(bezierOut.at(i).toArray().at(0).toDouble(),
                             bezierOut.at(i).toArray().at(1).toDouble());
        QPointF c2 = QPointF(bezierIn.at(0).toArray().at(0).toDouble(),
                             bezierIn.at(0).toArray().at(1).toDouble());
        addCurve(s, c1, c2, v);
    }

    m_path.setFillRule(Qt::WindingFill);

    if (hasReversedDirection())
        m_path = m_path.toReversed();
}

void QLottieFreeFormShape::buildShape(int frame)
{
    if (m_closedShape.size()) {
        m_path.clear();

        auto it = m_closedShape.constBegin();
        bool found = false;

        if (frame <= it.key())
            found = true;
        else {
            while (it != m_closedShape.constEnd()) {
                if (it.key() <= frame) {
                    found = true;
                    break;
                }
                ++it;
            }
        }

        bool needToClose = false;
        if (found)
            needToClose = (*it);

        // If there are less than two vertices, cannot make a bezier curve
        if (m_vertexList.size() < 2)
            return;

        QPointF s(m_vertexList.at(0).pos.value());
        QPointF s0(s);

        m_path.moveTo(s);
        int i = 0;

        while (i < m_vertexList.size() - 1) {
            QPointF v = m_vertexList.at(i + 1).pos.value();
            QPointF c1 = m_vertexList.at(i).co.value();
            QPointF c2 = m_vertexList.at(i + 1).ci.value();
            addCurve(s, c1, c2, v);
            s = v;
            i++;
        }

        if (needToClose) {
            QPointF v = s0;
            QPointF c1 = m_vertexList.at(i).co.value();
            QPointF c2 = m_vertexList.at(0).ci.value();
            addCurve(s, c1, c2, v);
        }

        m_path.setFillRule(Qt::WindingFill);

        if (hasReversedDirection())
            m_path = m_path.toReversed();
    }
}

void QLottieFreeFormShape::parseEasedVertices(const QJsonObject &keyframe, int startFrame)
{
    Q_UNUSED(startFrame);

    QJsonObject startValue = keyframe.value("s"_L1).toArray().at(0).toObject();
    QJsonObject endValue = keyframe.value("e"_L1).toArray().at(0).toObject();
    bool closedPathAtStart = keyframe.value("s"_L1).toArray().at(0).toObject().value("c"_L1).toBool();
    //bool closedPathAtEnd = keyframe.value("e"_L1).toArray().at(0).toObject().value("c"_L1).toBool();
    QJsonArray startVertices = startValue.value("v"_L1).toArray();
    QJsonArray startBezierIn = startValue.value("i"_L1).toArray();
    QJsonArray startBezierOut = startValue.value("o"_L1).toArray();
    QJsonArray endVertices = endValue.value("v"_L1).toArray();
    QJsonArray endBezierIn = endValue.value("i"_L1).toArray();
    QJsonArray endBezierOut = endValue.value("o"_L1).toArray();
    QJsonObject easingIn = keyframe.value("i"_L1).toObject();
    QJsonObject easingOut = keyframe.value("o"_L1).toObject();

    // if there are no vertices for this keyframe, they keyframe
    // is the last one, and it must be processed differently
    if (!startVertices.isEmpty()) {
        for (int i = 0; i < startVertices.count(); i++) {
            VertexBuildInfo *buildInfo = m_vertexInfos.value(i, nullptr);
            if (!buildInfo) {
                buildInfo = new VertexBuildInfo;
                m_vertexInfos.insert(i, buildInfo);
            }
            QJsonObject posKf = createKeyframe(startVertices.at(i).toArray(),
                                            endVertices.at(i).toArray(),
                                            startFrame, easingIn, easingOut);
            buildInfo->posKeyframes.push_back(posKf);

            QJsonObject ciKf = createKeyframe(startBezierIn.at(i).toArray(),
                                            endBezierIn.at(i).toArray(),
                                            startFrame, easingIn, easingOut);
            buildInfo->ciKeyframes.push_back(ciKf);

            QJsonObject coKf = createKeyframe(startBezierOut.at(i).toArray(),
                                            endBezierOut.at(i).toArray(),
                                            startFrame, easingIn, easingOut);
            buildInfo->coKeyframes.push_back(coKf);

            m_closedShape.insert(startFrame, closedPathAtStart);
        }
    } else {
        // Last keyframe

        int vertexCount = m_vertexInfos.size();
        for (int i = 0; i < vertexCount; i++) {
            VertexBuildInfo *buildInfo = m_vertexInfos.value(i, nullptr);
            if (!buildInfo) {
                buildInfo = new VertexBuildInfo;
                m_vertexInfos.insert(i, buildInfo);
            }
            QJsonObject posKf;
            posKf.insert("t"_L1, startFrame);
            buildInfo->posKeyframes.push_back(posKf);

            QJsonObject ciKf;
            ciKf.insert("t"_L1, startFrame);
            buildInfo->ciKeyframes.push_back(ciKf);

            QJsonObject coKf;
            coKf.insert("t"_L1, startFrame);
            buildInfo->coKeyframes.push_back(coKf);

            m_closedShape.insert(startFrame, false);
        }
    }
}

void QLottieFreeFormShape::finalizeVertices()
{

    for (int i = 0; i < m_vertexInfos.size(); i++) {
        QJsonObject posObj;
        posObj.insert("a"_L1, 1);
        posObj.insert("k"_L1, m_vertexInfos.value(i)->posKeyframes);

        QJsonObject ciObj;
        ciObj.insert("a"_L1, 1);
        ciObj.insert("k"_L1, m_vertexInfos.value(i)->ciKeyframes);

        QJsonObject coObj;
        coObj.insert("a"_L1, 1);
        coObj.insert("k"_L1, m_vertexInfos.value(i)->coKeyframes);

        VertexInfo vertexInfo;
        vertexInfo.pos.construct(posObj);
        vertexInfo.ci.construct(ciObj);
        vertexInfo.co.construct(coObj);
        m_vertexList.push_back(vertexInfo);
    }
    qDeleteAll(m_vertexInfos);
}

QJsonObject QLottieFreeFormShape::createKeyframe(QJsonArray startValue, QJsonArray endValue,
                                            int startFrame, QJsonObject easingIn,
                                            QJsonObject easingOut)
{
    QJsonObject keyframe;
    keyframe.insert("t"_L1, startFrame);
    keyframe.insert("s"_L1, startValue);
    keyframe.insert("e"_L1, endValue);
    keyframe.insert("i"_L1, easingIn);
    keyframe.insert("o"_L1, easingOut);
    return keyframe;
}

QT_END_NAMESPACE
