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

#include "qquick3dparticlesystemlogging_p.h"
#include <float.h> // FLT_MAX

QT_BEGIN_NAMESPACE

/*!
    \qmltype ParticleSystem3DLogging
    \inherits QtObject
    \inqmlmodule QtQuick3D.Particles3D
    \brief Provides information of the particle system.
    \since 6.2

    The \c ParticleSystem3DLogging type provides information about particle system statistics.
    This element cannot be created directly, but can be retrieved from a \l ParticleSystem3D.
*/

QQuick3DParticleSystemLogging::QQuick3DParticleSystemLogging(QObject *parent)
    : QObject(parent)
{
}

/*!
    \qmlproperty int ParticleSystem3DLogging::loggingInterval

    This property defines in milliseconds how often the logging data is updated.
    Longer update time increases the accuracy of \l {ParticleSystem3DLogging::time}{time} and
    \l {ParticleSystem3DLogging::timeAverage}{timeAverage}, while shorter update times keep
    the data more up to date.

    The default value is \c 1000.
*/

int QQuick3DParticleSystemLogging::loggingInterval() const
{
    return m_loggingInterval;
}

void QQuick3DParticleSystemLogging::setLoggingInterval(int interval)
{
    if (m_loggingInterval == interval)
        return;

    m_loggingInterval = interval;
    Q_EMIT loggingIntervalChanged();
}

/*!
    \qmlproperty int ParticleSystem3DLogging::updates
    \readonly

    This property holds the amount of particle system updates since the last logging.
    When \a loggingInterval is 1000 (default), this can be considered to match the fps.
*/
int QQuick3DParticleSystemLogging::updates() const
{
    return m_updates;
}

/*!
    \qmlproperty int ParticleSystem3DLogging::particlesMax
    \readonly

    This property holds the maximum amount of particles in this system.
    Maximum amount is the sum of system particles \l {Particle3D::maxAmount}{maxAmount} properties.
*/
int QQuick3DParticleSystemLogging::particlesMax() const
{
    return m_particlesMax;
}

/*!
    \qmlproperty int ParticleSystem3DLogging::particlesUsed
    \readonly

    This property holds the amount of particles currently in use in this system.
    This value should be close to \l particlesMax at some point of particle system
    animation. If it is much smaller, consider decreasing \l {Particle3D::maxAmount}{maxAmount} values.
    If it reaches \l particlesMax, particles are used effectively but it can also mean that
    particles are reused before they reach the end of their \l {ParticleEmitter3D::lifeSpan}{lifeSpan}.
    In this case, consider increasing the \l {Particle3D::maxAmount}{maxAmount} values.
*/
int QQuick3DParticleSystemLogging::particlesUsed() const
{
    return m_particlesUsed;
}

/*!
    \qmlproperty real ParticleSystem3DLogging::time
    \readonly

    This property holds the time in milliseconds used for emitting and animating particles
    in each frame.
*/
float QQuick3DParticleSystemLogging::time() const
{
    return m_time;
}

/*!
    \qmlproperty real ParticleSystem3DLogging::timeAverage
    \readonly

    This property holds the average time in milliseconds used for emitting and animating
    particles in each frame. Average is calculated from the middle 50% of the past
    max 100 logging updates. So when \l loggingInterval is 1000, this represents an
    average \l time in past 100 seconds. This can be used for measuring the performance
    of current particle system.
*/
float QQuick3DParticleSystemLogging::timeAverage() const
{
    return m_timeAverage;
}

/*!
    \qmlproperty real ParticleSystem3DLogging::timeDeviation
    \since 6.3
    \readonly

    This property holds the deviation of the average times in milliseconds.
    The value is the difference between maximum and minimum values of middle 50%
    of the results, also called interquartile range (IQR).
    Bigger deviation means that the times fluctuate more so \l timeAverage
    can be considered to be less accurate.
*/
float QQuick3DParticleSystemLogging::timeDeviation() const
{
    return m_timeDeviation;
}

void QQuick3DParticleSystemLogging::updateTimes(qint64 time)
{
    m_time = float(time / 1000000.0) / m_updates;

    m_totalTimesList.append(m_time);

    // Keep max amount of times stored and remove the oldest values
    const int MAX_TIMES = 100;
    if (m_totalTimesList.size() > MAX_TIMES)
        m_totalTimesList.removeFirst();

    auto sortedTimes = m_totalTimesList;
    std::sort(sortedTimes.begin(), sortedTimes.end());

    // Calculate average from stored times.
    // Only take into account the middle 50% of the values.
    // This gives us interquartile range (IQR) and the average time among IQR.
    if (sortedTimes.size() > 5) {
        // Skip 25%, count 50%, so maxItem at 75%
        const int skipAmount = roundf(0.25f * float(sortedTimes.size()));
        const int maxItem = sortedTimes.size() - skipAmount;
        int countAmount = 0;
        double totalTime = 0.0;
        float maxTime = 0.0f;
        float minTime = FLT_MAX;
        for (int i = skipAmount; i < maxItem; i++) {
            const float time = sortedTimes.at(i);
            totalTime += time;
            minTime = std::min(minTime, time);
            maxTime = std::max(maxTime, time);
            countAmount++;
        }
        m_timeAverage = float(totalTime / countAmount);
        m_timeDeviation = maxTime - minTime;
        Q_EMIT timeAverageChanged();
        Q_EMIT timeDeviationChanged();
    }
    Q_EMIT timeChanged();
}

void QQuick3DParticleSystemLogging::resetData()
{
    m_updates = 0;
    m_particlesMax = 0;
    m_particlesUsed = 0;
    m_time = 0.0f;
    m_timeAverage = 0.0f;
    m_timeDeviation = 0.0f;
    m_totalTimesList.clear();
    Q_EMIT updatesChanged();
    Q_EMIT particlesMaxChanged();
    Q_EMIT particlesUsedChanged();
    Q_EMIT timeChanged();
    Q_EMIT timeAverageChanged();
    Q_EMIT timeDeviationChanged();
}

QT_END_NAMESPACE
