// Copyright (C) 2017 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 QQMLPROFILEREVENT_P_H
#define QQMLPROFILEREVENT_P_H

#include "qqmlprofilerclientdefinitions_p.h"

#include <QtCore/qstring.h>
#include <QtCore/qbytearray.h>
#include <QtCore/qvarlengtharray.h>
#include <QtCore/qmetatype.h>

#include <initializer_list>
#include <limits>
#include <type_traits>

//
//  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.
//

QT_BEGIN_NAMESPACE

struct QQmlProfilerEvent {
    QQmlProfilerEvent() :
        m_timestamp(-1), m_typeIndex(-1), m_dataType(Inline8Bit), m_dataLength(0)
    {}

    template<typename Number>
    QQmlProfilerEvent(qint64 timestamp, int typeIndex, std::initializer_list<Number> list)
        : m_timestamp(timestamp), m_typeIndex(typeIndex)
    {
        assignNumbers<std::initializer_list<Number>, Number>(list);
    }

    QQmlProfilerEvent(qint64 timestamp, int typeIndex, const QString &data)
        : m_timestamp(timestamp), m_typeIndex(typeIndex)
    {
        assignNumbers<QByteArray, qint8>(data.toUtf8());
    }

    template<typename Number>
    QQmlProfilerEvent(qint64 timestamp, int typeIndex, const QVector<Number> &data)
        : m_timestamp(timestamp), m_typeIndex(typeIndex)
    {
        assignNumbers<QVector<Number>, Number>(data);
    }

    QQmlProfilerEvent(const QQmlProfilerEvent &other)
        : m_timestamp(other.m_timestamp), m_typeIndex(other.m_typeIndex),
          m_dataType(other.m_dataType), m_dataLength(other.m_dataLength)
    {
        assignData(other);
    }

    QQmlProfilerEvent(QQmlProfilerEvent &&other)
    {
        memcpy(static_cast<void *>(this), static_cast<const void *>(&other), sizeof(QQmlProfilerEvent));
        other.m_dataType = Inline8Bit; // prevent dtor from deleting the pointer
    }

    QQmlProfilerEvent &operator=(const QQmlProfilerEvent &other)
    {
        if (this != &other) {
            clearPointer();
            m_timestamp = other.m_timestamp;
            m_typeIndex = other.m_typeIndex;
            m_dataType = other.m_dataType;
            m_dataLength = other.m_dataLength;
            assignData(other);
        }
        return *this;
    }

    QQmlProfilerEvent &operator=(QQmlProfilerEvent &&other)
    {
        if (this != &other) {
            memcpy(static_cast<void *>(this), static_cast<const void *>(&other), sizeof(QQmlProfilerEvent));
            other.m_dataType = Inline8Bit;
        }
        return *this;
    }

    ~QQmlProfilerEvent()
    {
        clearPointer();
    }

    qint64 timestamp() const { return m_timestamp; }
    void setTimestamp(qint64 timestamp) { m_timestamp = timestamp; }

    int typeIndex() const { return m_typeIndex; }
    void setTypeIndex(int typeIndex) { m_typeIndex = typeIndex; }

    template<typename Number>
    Number number(int i) const
    {
        // Trailing zeroes can be omitted, for example for SceneGraph events
        if (i >= m_dataLength)
            return 0;
        switch (m_dataType) {
        case Inline8Bit:
            return m_data.internal8bit[i];
QT_WARNING_PUSH
QT_WARNING_DISABLE_GCC("-Warray-bounds") // Mingw 5.3 gcc doesn't get the type/length logic.
        case Inline16Bit:
            return m_data.internal16bit[i];
        case Inline32Bit:
            return m_data.internal32bit[i];
        case Inline64Bit:
            return m_data.internal64bit[i];
QT_WARNING_POP
        case External8Bit:
            return static_cast<const qint8 *>(m_data.external)[i];
        case External16Bit:
            return static_cast<const qint16 *>(m_data.external)[i];
        case External32Bit:
            return static_cast<const qint32 *>(m_data.external)[i];
        case External64Bit:
            return static_cast<const qint64 *>(m_data.external)[i];
        default:
            return 0;
        }
    }

    template<typename Number>
    void setNumber(int i, Number number)
    {
        QVarLengthArray<Number> nums = numbers<QVarLengthArray<Number>, Number>();
        int prevSize = nums.size();
        if (i >= prevSize) {
            nums.resize(i + 1);
            // Fill with zeroes. We don't want to accidentally prevent squeezing.
            while (prevSize < i)
                nums[prevSize++] = 0;
        }
        nums[i] = number;
        setNumbers<QVarLengthArray<Number>, Number>(nums);
    }

    template<typename Container, typename Number>
    void setNumbers(const Container &numbers)
    {
        clearPointer();
        assignNumbers<Container, Number>(numbers);
    }

    template<typename Number>
    void setNumbers(std::initializer_list<Number> numbers)
    {
        setNumbers<std::initializer_list<Number>, Number>(numbers);
    }

    template<typename Container, typename Number = qint64>
    Container numbers() const
    {
        Container container;
        for (int i = 0; i < m_dataLength; ++i)
            container.append(number<Number>(i));
        return container;
    }

    QString string() const
    {
        switch (m_dataType) {
        case External8Bit:
            return QString::fromUtf8(static_cast<const char *>(m_data.external), m_dataLength);
        case Inline8Bit:
            return QString::fromUtf8(m_data.internalChar, m_dataLength);
        default:
            Q_UNREACHABLE_RETURN(QString());
        }
    }

    void setString(const QString &data)
    {
        clearPointer();
        assignNumbers<QByteArray, char>(data.toUtf8());
    }

    Message rangeStage() const
    {
        Q_ASSERT(m_dataType == Inline8Bit);
        return static_cast<Message>(m_data.internal8bit[0]);
    }

    void setRangeStage(Message stage)
    {
        clearPointer();
        m_dataType = Inline8Bit;
        m_dataLength = 1;
        m_data.internal8bit[0] = stage;
    }

    bool isValid() const
    {
        return m_timestamp != -1;
    }

private:
    enum Type: quint16 {
        External = 1,
        Inline8Bit = 8,
        External8Bit = Inline8Bit | External,
        Inline16Bit = 16,
        External16Bit = Inline16Bit | External,
        Inline32Bit = 32,
        External32Bit = Inline32Bit | External,
        Inline64Bit = 64,
        External64Bit = Inline64Bit | External
    };

    qint64 m_timestamp;

    static const int s_internalDataLength = 8;
    union {
        void  *external;
        char   internalChar [s_internalDataLength];
        qint8  internal8bit [s_internalDataLength];
        qint16 internal16bit[s_internalDataLength / 2];
        qint32 internal32bit[s_internalDataLength / 4];
        qint64 internal64bit[s_internalDataLength / 8];
    } m_data;

    qint32 m_typeIndex;
    Type m_dataType;
    quint16 m_dataLength;

    void assignData(const QQmlProfilerEvent &other)
    {
        if (m_dataType & External) {
            uint length = m_dataLength * (other.m_dataType / 8);
            m_data.external = malloc(length);
            Q_CHECK_PTR(m_data.external);
            memcpy(m_data.external, other.m_data.external, length);
        } else {
            memcpy(&m_data, &other.m_data, sizeof(m_data));
        }
    }

    template<typename Big, typename Small>
    bool squeezable(Big source)
    {
        return static_cast<Small>(source) == source;
    }

    template<typename Container, typename Number>
    typename std::enable_if<(sizeof(Number) > 1), bool>::type
    squeeze(const Container &numbers)
    {
        typedef typename QIntegerForSize<sizeof(Number) / 2>::Signed Small;
        for (Number item : numbers) {
            if (!squeezable<Number, Small>(item))
                return false;
        }
        assignNumbers<Container, Small>(numbers);
        return true;
    }

    template<typename Container, typename Number>
    typename std::enable_if<(sizeof(Number) <= 1), bool>::type
    squeeze(const Container &)
    {
        return false;
    }

    template<typename Container, typename Number>
    void assignNumbers(const Container &numbers)
    {
        Number *data;
        m_dataLength = squeezable<size_t, quint16>(static_cast<size_t>(numbers.size())) ?
                    static_cast<quint16>(numbers.size()) : std::numeric_limits<quint16>::max();
        if (m_dataLength > sizeof(m_data) / sizeof(Number)) {
            if (squeeze<Container, Number>(numbers))
                return;
            m_dataType = static_cast<Type>((sizeof(Number) * 8) | External);
            m_data.external = malloc(m_dataLength * sizeof(Number));
            Q_CHECK_PTR(m_data.external);
            data = static_cast<Number *>(m_data.external);
        } else {
            m_dataType = static_cast<Type>(sizeof(Number) * 8);
            data = static_cast<Number *>(m_dataType & External ? m_data.external : &m_data);
        }
        quint16 i = 0;
        for (Number item : numbers) {
            if (i >= m_dataLength)
                break;
            data[i++] = item;
        }
    }

    void clearPointer()
    {
        if (m_dataType & External)
            free(m_data.external);
    }

    friend QDataStream &operator>>(QDataStream &stream, QQmlProfilerEvent &event);
    friend QDataStream &operator<<(QDataStream &stream, const QQmlProfilerEvent &event);
};

bool operator==(const QQmlProfilerEvent &event1, const QQmlProfilerEvent &event2);
bool operator!=(const QQmlProfilerEvent &event1, const QQmlProfilerEvent &event2);

QDataStream &operator>>(QDataStream &stream, QQmlProfilerEvent &event);
QDataStream &operator<<(QDataStream &stream, const QQmlProfilerEvent &event);

Q_DECLARE_TYPEINFO(QQmlProfilerEvent, Q_RELOCATABLE_TYPE);

QT_END_NAMESPACE

Q_DECLARE_METATYPE(QQmlProfilerEvent)

#endif // QQMLPROFILEREVENT_P_H
