// Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include "fcurve_p.h"
#include <private/bezierevaluator_p.h>

#include <QtCore/qjsonarray.h>
#include <QtCore/qjsonobject.h>
#include <QtCore/QLatin1String>

QT_BEGIN_NAMESPACE

namespace Qt3DAnimation {
namespace Animation {

FCurve::FCurve()
    : m_rangeFinder(&m_localTimes)
{
}

float FCurve::evaluateAtTime(float localTime) const
{
    return evaluateAtTime(localTime, lowerKeyframeBound(localTime));
}


float FCurve::evaluateAtTime(float localTime, int lowerBound) const
{
    // TODO: Implement extrapolation beyond first/last keyframes
    if (localTime < m_localTimes.first()) {
        return m_keyframes.first().value;
    } else if (localTime > m_localTimes.last()) {
        return m_keyframes.last().value;
    } else {
        // Find keyframes that sandwich the requested localTime
        if (lowerBound < 0) // only one keyframe
            return m_keyframes.first().value;

        const float t0 = m_localTimes[lowerBound];
        const float t1 = m_localTimes[lowerBound + 1];
        const Keyframe &keyframe0(m_keyframes[lowerBound]);
        const Keyframe &keyframe1(m_keyframes[lowerBound + 1]);

        switch (keyframe0.interpolation) {
        case QKeyFrame::ConstantInterpolation:
            return keyframe0.value;
        case QKeyFrame::LinearInterpolation:
            if (localTime >= t0 && localTime <= t1 && t1 > t0) {
                float t = (localTime - t0) / (t1 - t0);
                return (1 - t) * keyframe0.value + t * keyframe1.value;
            }
            break;
        case QKeyFrame::BezierInterpolation:
        {
            BezierEvaluator evaluator(t0, keyframe0, t1, keyframe1);
            return evaluator.valueForTime(localTime);
        }
        default:
            qWarning("Unknown interpolation type %d", keyframe0.interpolation);
            break;
        }
    }

    return m_keyframes.first().value;
}

float FCurve::evaluateAtTimeAsSlerp(float localTime, int lowerBound, float halfTheta, float sinHalfTheta, float reverseQ1) const
{
    // TODO: Implement extrapolation beyond first/last keyframes
    if (localTime < m_localTimes.first())
        return m_keyframes.first().value;

    if (localTime > m_localTimes.last())
        return m_keyframes.last().value;
    // Find keyframes that sandwich the requested localTime
    if (lowerBound < 0) // only one keyframe
        return m_keyframes.first().value;

    const float t0 = m_localTimes[lowerBound];
    const float t1 = m_localTimes[lowerBound + 1];
    const Keyframe &keyframe0(m_keyframes[lowerBound]);
    const Keyframe &keyframe1(m_keyframes[lowerBound + 1]);

    switch (keyframe0.interpolation) {
    case QKeyFrame::ConstantInterpolation:
        return keyframe0.value;
    case QKeyFrame::LinearInterpolation:
        if (localTime >= t0 && localTime <= t1 && t1 > t0) {
            const auto t = (localTime - t0) / (t1 - t0);

            const auto A = std::sin((1.0f-t) * halfTheta) / sinHalfTheta;
            const auto B = std::sin(t * halfTheta) / sinHalfTheta;
            return A * keyframe0.value + reverseQ1 * B * keyframe1.value;
        }
        break;
    case QKeyFrame::BezierInterpolation:
        // TODO implement a proper slerp bezier interpolation
        BezierEvaluator evaluator(t0, keyframe0, t1, keyframe1);
        return evaluator.valueForTime(localTime);
    }

    return m_keyframes.first().value;
}

int FCurve::lowerKeyframeBound(float localTime) const
{
    if (localTime < m_localTimes.first())
        return 0;
    if (localTime > m_localTimes.last())
        return 0;
    return m_rangeFinder.findLowerBound(localTime);
}

float FCurve::startTime() const
{
    if (!m_localTimes.isEmpty())
        return m_localTimes.first();
    return 0.0f;
}

float FCurve::endTime() const
{
    if (!m_localTimes.isEmpty())
        return m_localTimes.last();
    return 0.0f;
}

void FCurve::appendKeyframe(float localTime, const Keyframe &keyframe)
{
    m_localTimes.append(localTime);
    m_keyframes.append(keyframe);
}

void FCurve::read(const QJsonObject &json)
{
    clearKeyframes();

    const QJsonArray keyframeArray = json[QLatin1String("keyFrames")].toArray();
    const qsizetype keyframeCount = keyframeArray.size();

    for (int i = 0; i < keyframeCount; ++i) {
        const QJsonObject keyframeData = keyframeArray.at(i).toObject();

        // Extract the keyframe local time and value
        const QJsonArray keyframeCoords = keyframeData[QLatin1String("coords")].toArray();
        float localTime = keyframeCoords.at(0).toDouble();

        Keyframe keyframe;
        keyframe.value = keyframeCoords.at(1).toDouble();

        if (keyframeData.contains(QLatin1String("leftHandle"))) {
            keyframe.interpolation = QKeyFrame::BezierInterpolation;

            const QJsonArray leftHandle = keyframeData[QLatin1String("leftHandle")].toArray();
            keyframe.leftControlPoint[0] = leftHandle.at(0).toDouble();
            keyframe.leftControlPoint[1] = leftHandle.at(1).toDouble();

            const QJsonArray rightHandle = keyframeData[QLatin1String("rightHandle")].toArray();
            keyframe.rightControlPoint[0] = rightHandle.at(0).toDouble();
            keyframe.rightControlPoint[1] = rightHandle.at(1).toDouble();
        } else {
            keyframe.interpolation = QKeyFrame::LinearInterpolation;
        }

        appendKeyframe(localTime, keyframe);
    }

    // TODO: Ensure beziers have no loops or cusps by scaling the control points
    // back so they do not interset.
}

void FCurve::setFromQChannelComponent(const QChannelComponent &qcc)
{
    clearKeyframes();

    for (const auto &frontendKeyFrame : qcc) {
        // Extract the keyframe local time and value
        const float localTime = frontendKeyFrame.coordinates()[0];

        Keyframe keyFrame;
        keyFrame.interpolation = frontendKeyFrame.interpolationType();
        keyFrame.value = frontendKeyFrame.coordinates()[1];
        keyFrame.leftControlPoint = frontendKeyFrame.leftControlPoint();
        keyFrame.rightControlPoint = frontendKeyFrame.rightControlPoint();
        appendKeyframe(localTime, keyFrame);
    }

    // TODO: Ensure beziers have no loops or cusps by scaling the control points
    // back so they do not interset.
}

void ChannelComponent::read(const QJsonObject &json)
{
    name = json[QLatin1String("channelComponentName")].toString();
    fcurve.read(json);
}

void ChannelComponent::setFromQChannelComponent(const QChannelComponent &qcc)
{
    name = qcc.name();
    fcurve.setFromQChannelComponent(qcc);
}

void Channel::read(const QJsonObject &json)
{
    name = json[QLatin1String("channelName")].toString();
    const auto jointIndexValue = json[QLatin1String("jointIndex")];
    if (!jointIndexValue.isUndefined())
        jointIndex = jointIndexValue.toInt();
    const QJsonArray channelComponentsArray = json[QLatin1String("channelComponents")].toArray();
    const qsizetype channelCount = channelComponentsArray.size();
    channelComponents.resize(channelCount);

    for (qsizetype i = 0; i < channelCount; ++i) {
        const QJsonObject channel = channelComponentsArray.at(i).toObject();
        channelComponents[i].read(channel);
    }
}

void Channel::setFromQChannel(const QChannel &qch)
{
    name = qch.name();
    jointIndex = qch.jointIndex();
    channelComponents.resize(qch.channelComponentCount());
    qsizetype i = 0;
    for (const auto &frontendChannelComponent : qch)
        channelComponents[i++].setFromQChannelComponent(frontendChannelComponent);
}

} // namespace Animation
} // namespace Qt3DAnimation

QT_END_NAMESPACE
