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

#ifndef QQUICK3DPROFILER_P_H
#define QQUICK3DPROFILER_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 <QtCore/private/qabstractanimation_p.h>
#include <QtQuick/private/qtquickglobal_p.h>
#include <QtQuick3DUtils/private/qtquick3dutilsglobal_p.h>


#if QT_CONFIG(qml_debug)
#include <QtQml/private/qqmlprofilerdefinitions_p.h>
#endif

#include <QtCore/qurl.h>
#include <QtCore/qsize.h>
#include <QtCore/qmutex.h>
#include <QtCore/qthreadstorage.h>

QT_BEGIN_NAMESPACE

#if !QT_CONFIG(qml_debug)

#define Q_QUICK3D_PROFILE_IF_ENABLED(feature, Code)

struct QQuick3DProfiler {

};

#define Q_QUICK3D_PROFILING_ENABLED false
#define Q_QUICK3D_PROFILE_REGISTER_D(obj)
#define Q_QUICK3D_PROFILE_REGISTER(obj)
#define Q_QUICK3D_PROFILE_ID
#define Q_QUICK3D_PROFILE_GET_ID
#define Q_QUICK3D_PROFILE_ASSIGN_ID_SG(obj, bgnode)
#define Q_QUICK3D_PROFILE_ASSIGN_ID(bgnode, obj)

#else

#define Q_QUICK3D_PROFILE_IF_ENABLED(feature, Code)\
    if (QQuick3DProfiler::featuresEnabled & (1 << feature)) {\
        Code;\
    } else\
        (void)0

#define Q_QUICK3D_PROFILING_ENABLED (QQuick3DProfiler::featuresEnabled > 0)
#define Q_QUICK3D_PROFILE_REGISTER_D(obj) d->profilingId = QQuick3DProfiler::registerObject(obj)
#define Q_QUICK3D_PROFILE_REGISTER(obj) profilingId = QQuick3DProfiler::registerObject(obj)
#define Q_QUICK3D_PROFILE_ID int profilingId = -1;
#define Q_QUICK3D_PROFILE_GET_ID(Object) \
    QQuick3DObjectPrivate::get(Object)->profilingId
#define Q_QUICK3D_PROFILE_ASSIGN_ID_SG(obj, bgnode) \
    (bgnode)->profilingId = Q_QUICK3D_PROFILE_GET_ID(obj);
#define Q_QUICK3D_PROFILE_ASSIGN_ID(bgnode, obj) \
    (obj)->profilingId = (bgnode)->profilingId;


struct Q_QUICK3DUTILS_EXPORT QQuick3DProfilerData
{
    static constexpr int s_numSupportedIds = 2;
    QQuick3DProfilerData() {}

    QQuick3DProfilerData(qint64 time, int messageType, int detailType, qint64 d1, qint64 d2)
        : time(time), messageType(messageType), detailType(detailType), subdata1(d1), subdata2(d2) {}
    QQuick3DProfilerData(qint64 time, int messageType, int detailType, qint64 d1, qint64 d2, const QList<int> &ids);

    qint64 time = 0;
    qint32 messageType = 0;
    qint32 detailType = 0;
    qint64 subdata1 = 0;
    qint64 subdata2 = 0;
    qint32 ids[s_numSupportedIds] = {0}; // keep this even sized
};

Q_DECLARE_TYPEINFO(QQuick3DProfilerData, Q_RELOCATABLE_TYPE);

class QQuick3DProfilerSceneGraphData : public QQmlProfilerDefinitions {
private:
    static const uint s_numSceneGraphTimings = 2;
    static const uint s_numNestedTimings = 5;
    struct Timings {
        uint nesting = 0;
        qint64 values[s_numNestedTimings][s_numSceneGraphTimings + 1];
    };
    template<uint size>
    struct TimingData
    {
        Timings timings[size];
    };
    QThreadStorage<TimingData<MaximumQuick3DFrameType>> eventTimings;
public:
    template<int type, bool inc>
    qint64 *timings()
    {
        Timings *timings;
        qint64 *t;
        timings = &eventTimings.localData().timings[type];
        if (inc) {
            Q_ASSERT(timings->nesting < s_numNestedTimings);
            t = timings->values[timings->nesting];
            timings->nesting++;
        } else {
            timings->nesting--;
            t = timings->values[timings->nesting];
        }
        return t;
    }
};

class Q_QUICK3DUTILS_EXPORT QQuick3DProfiler : public QObject, public QQmlProfilerDefinitions {
    Q_OBJECT
public:

    enum Quick3DStage {
        Quick3DStageBegin,
        Quick3DStageEnd
    };

    template<int FrameType>
    static void recordSceneGraphTimestamp(uint position)
    {
        s_instance->m_sceneGraphData.timings<FrameType, true>()[position] = s_instance->timestamp();
    }
    template<int FrameType>
    static void reportQuick3DFrame(uint position, quint64 payload)
    {
        qint64 *timings = s_instance->m_sceneGraphData.timings<FrameType, false>();
        timings[position] = s_instance->timestamp();
        s_instance->processMessage(QQuick3DProfilerData(
                timings[position], Quick3DFrame, FrameType,
                timings[1] - timings[0],
                payload));
    }
    template<int FrameType>
    static void reportQuick3DFrame(uint position, quint64 payload, const QByteArray str)
    {
        qint64 *timings = s_instance->m_sceneGraphData.timings<FrameType, false>();
        timings[position] = s_instance->timestamp();
        int sid = registerString(str);
        QList<int> poids;
        poids << sid;
        s_instance->processMessage(QQuick3DProfilerData(
                timings[position], Quick3DFrame, FrameType,
                timings[1] - timings[0],
                payload,
                poids));
    }
    template<int FrameType>
    static void reportQuick3DFrame(uint position, quint64 payload, int poid)
    {
        qint64 *timings = s_instance->m_sceneGraphData.timings<FrameType, false>();
        timings[position] = s_instance->timestamp();
        QList<int> poids;
        poids << poid;
        s_instance->processMessage(QQuick3DProfilerData(
                timings[position], Quick3DFrame, FrameType,
                timings[1] - timings[0],
                payload,
                poids));
    }
    template<int FrameType>
    static void reportQuick3DFrame(uint position, quint64 payload, int poid, const QByteArray str)
    {
        qint64 *timings = s_instance->m_sceneGraphData.timings<FrameType, false>();
        timings[position] = s_instance->timestamp();
        int sid = registerString(str);
        QList<int> poids;
        poids << poid << sid;
        s_instance->processMessage(QQuick3DProfilerData(
                timings[position], Quick3DFrame, FrameType,
                timings[1] - timings[0],
                payload,
                poids));
    }
    template<int FrameType>
    static void reportQuick3DFrame(uint position, quint64 payload, const QList<int> &poids)
    {
        qint64 *timings = s_instance->m_sceneGraphData.timings<FrameType, false>();
        timings[position] = s_instance->timestamp();
        s_instance->processMessage(QQuick3DProfilerData(
                timings[position], Quick3DFrame, FrameType,
                timings[1] - timings[0],
                payload,
                poids));
    }


    qint64 timestamp() { return m_timer.nsecsElapsed(); }

    static quint64 featuresEnabled;

    static void initialize(QObject *parent);

    ~QQuick3DProfiler() override;

    static int registerObject(const QObject *object);
    static int registerString(const QByteArray &string);

signals:
    void dataReady(const QVector<QQuick3DProfilerData> &data, const QHash<int, QByteArray> &eventData);

protected:
    friend class QQuick3DProfilerAdapter;
    static QMutex s_eventDataMutex;
    static QHash<QByteArray, int> s_eventData;
    static QHash<int, QByteArray> s_eventDataRev;
    static QQuick3DProfiler *s_instance;
    QMutex m_dataMutex;
    QElapsedTimer m_timer;
    QVector<QQuick3DProfilerData> m_data;
    QQuick3DProfilerSceneGraphData m_sceneGraphData;

    QQuick3DProfiler(QObject *parent);

    void processMessage(const QQuick3DProfilerData &message)
    {
        QMutexLocker lock(&m_dataMutex);
        m_data.append(message);
    }

    void startProfilingImpl(quint64 features);
    void stopProfilingImpl();
    void reportDataImpl();
    void setTimer(const QElapsedTimer &t);
};

#endif // QT_CONFIG(qml_debug)

#define Q_QUICK3D_PROFILE(feature, Method) \
    Q_QUICK3D_PROFILE_IF_ENABLED(feature, QQuick3DProfiler::Method)

#define Q_QUICK3D_PROFILE_START(Type) \
    Q_QUICK3D_PROFILE_IF_ENABLED(QQuick3DProfiler::ProfileQuick3D, \
                               (QQuick3DProfiler::recordSceneGraphTimestamp<Type>(QQuick3DProfiler::Quick3DStageBegin)))

#define Q_QUICK3D_PROFILE_END(Type) \
    Q_QUICK3D_PROFILE_IF_ENABLED(QQuick3DProfiler::ProfileQuick3D, \
                               (QQuick3DProfiler::reportQuick3DFrame<Type>(QQuick3DProfiler::Quick3DStageEnd, 0, 0)))

#define Q_QUICK3D_PROFILE_END_WITH_PAYLOAD(Type, Payload) \
    Q_QUICK3D_PROFILE_IF_ENABLED(QQuick3DProfiler::ProfileQuick3D, \
                               (QQuick3DProfiler::reportQuick3DFrame<Type>(QQuick3DProfiler::Quick3DStageEnd, \
                                                                                  Payload)))

#define Q_QUICK3D_PROFILE_END_WITH_STRING(Type, Payload, Str) \
    Q_QUICK3D_PROFILE_IF_ENABLED(QQuick3DProfiler::ProfileQuick3D, \
                               (QQuick3DProfiler::reportQuick3DFrame<Type>(QQuick3DProfiler::Quick3DStageEnd, \
                                                                                  Payload, Str)))
#define Q_QUICK3D_PROFILE_END_WITH_ID(Type, Payload, POID) \
    Q_QUICK3D_PROFILE_IF_ENABLED(QQuick3DProfiler::ProfileQuick3D, \
                               (QQuick3DProfiler::reportQuick3DFrame<Type>(QQuick3DProfiler::Quick3DStageEnd, \
                                                                                  Payload, POID)))
#define Q_QUICK3D_PROFILE_END_WITH_IDS(Type, Payload, POIDs) \
    Q_QUICK3D_PROFILE_IF_ENABLED(QQuick3DProfiler::ProfileQuick3D, \
                               (QQuick3DProfiler::reportQuick3DFrame<Type>(QQuick3DProfiler::Quick3DStageEnd, \
                                                                                  Payload, POIDs)))
#define Q_QUICK3D_PROFILE_END_WITH_ALL(Type, Payload, POID, Str) \
    Q_QUICK3D_PROFILE_IF_ENABLED(QQuick3DProfiler::ProfileQuick3D, \
                               (QQuick3DProfiler::reportQuick3DFrame<Type>(QQuick3DProfiler::Quick3DStageEnd, \
                                                                                  Payload, POID, Str)))
QT_END_NAMESPACE

#endif
