// Copyright (C) 2016 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
// Qt-Security score:significant

#include "qv4profileradapter.h"
#include "qqmlprofilerservice.h"

QT_BEGIN_NAMESPACE

QV4ProfilerAdapter::QV4ProfilerAdapter(QQmlProfilerService *service, QV4::ExecutionEngine *engine) :
    m_functionCallPos(0), m_memoryPos(0)
{
    setService(service);
    engine->setProfiler(new QV4::Profiling::Profiler(engine));
    connect(this, &QQmlAbstractProfilerAdapter::profilingEnabled,
            this, &QV4ProfilerAdapter::forwardEnabled);
    connect(this, &QQmlAbstractProfilerAdapter::profilingEnabledWhileWaiting,
            this, &QV4ProfilerAdapter::forwardEnabledWhileWaiting, Qt::DirectConnection);
    connect(this, &QV4ProfilerAdapter::v4ProfilingEnabled,
            engine->profiler(), &QV4::Profiling::Profiler::startProfiling);
    connect(this, &QV4ProfilerAdapter::v4ProfilingEnabledWhileWaiting,
            engine->profiler(), &QV4::Profiling::Profiler::startProfiling, Qt::DirectConnection);
    connect(this, &QQmlAbstractProfilerAdapter::profilingDisabled,
            engine->profiler(), &QV4::Profiling::Profiler::stopProfiling);
    connect(this, &QQmlAbstractProfilerAdapter::profilingDisabledWhileWaiting,
            engine->profiler(), &QV4::Profiling::Profiler::stopProfiling,
            Qt::DirectConnection);
    connect(this, &QQmlAbstractProfilerAdapter::dataRequested,
            engine->profiler(), &QV4::Profiling::Profiler::reportData);
    connect(this, &QQmlAbstractProfilerAdapter::referenceTimeKnown,
            engine->profiler(), &QV4::Profiling::Profiler::setTimer);
    connect(engine->profiler(), &QV4::Profiling::Profiler::dataReady,
            this, &QV4ProfilerAdapter::receiveData);
}

qint64 QV4ProfilerAdapter::appendMemoryEvents(qint64 until, QList<QByteArray> &messages,
                                              QQmlDebugPacket &d)
{
    // Make it const, so that we cannot accidentally detach it.
    const QList<QV4::Profiling::MemoryAllocationProperties> &memoryData = m_memoryData;

    while (memoryData.size() > m_memoryPos && memoryData[m_memoryPos].timestamp <= until) {
        const QV4::Profiling::MemoryAllocationProperties &props = memoryData[m_memoryPos];
        d << props.timestamp << int(MemoryAllocation) << int(props.type) << props.size;
        ++m_memoryPos;
        messages.append(d.squeezedData());
        d.clear();
    }
    return memoryData.size() == m_memoryPos ? -1 : memoryData[m_memoryPos].timestamp;
}

qint64 QV4ProfilerAdapter::finalizeMessages(qint64 until, QList<QByteArray> &messages,
                                            qint64 callNext, QQmlDebugPacket &d)
{
    qint64 memoryNext = -1;

    if (callNext == -1) {
        m_functionLocations.clear();
        m_functionCallData.clear();
        m_functionCallPos = 0;
        memoryNext = appendMemoryEvents(until, messages, d);
    } else {
        memoryNext = appendMemoryEvents(qMin(callNext, until), messages, d);
    }

    if (memoryNext == -1) {
        m_memoryData.clear();
        m_memoryPos = 0;
        return callNext;
    }

    return callNext == -1 ? memoryNext : qMin(callNext, memoryNext);
}

qint64 QV4ProfilerAdapter::sendMessages(qint64 until, QList<QByteArray> &messages)
{
    QQmlDebugPacket d;

    // Make it const, so that we cannot accidentally detach it.
    const QList<QV4::Profiling::FunctionCallProperties> &functionCallData = m_functionCallData;

    while (true) {
        while (!m_stack.isEmpty() &&
               (m_functionCallPos == functionCallData.size() ||
                m_stack.top() <= functionCallData[m_functionCallPos].start)) {
            if (m_stack.top() > until || messages.size() > s_numMessagesPerBatch)
                return finalizeMessages(until, messages, m_stack.top(), d);

            appendMemoryEvents(m_stack.top(), messages, d);
            d << m_stack.pop() << int(RangeEnd) << int(Javascript);
            messages.append(d.squeezedData());
            d.clear();
        }
        while (m_functionCallPos != functionCallData.size() &&
               (m_stack.empty() || functionCallData[m_functionCallPos].start < m_stack.top())) {
            const QV4::Profiling::FunctionCallProperties &props =
                    functionCallData[m_functionCallPos];
            if (props.start > until || messages.size() > s_numMessagesPerBatch)
                return finalizeMessages(until, messages, props.start, d);

            appendMemoryEvents(props.start, messages, d);
            auto location = m_functionLocations.constFind(props.id);

            d << props.start << int(RangeStart) << int(Javascript) << static_cast<qint64>(props.id);
            if (location != m_functionLocations.cend()) {
                messages.push_back(d.squeezedData());
                d.clear();
                d << props.start << int(RangeLocation) << int(Javascript) << location->file << location->line
                  << location->column << static_cast<qint64>(props.id);
                messages.push_back(d.squeezedData());
                d.clear();
                d << props.start << int(RangeData) << int(Javascript) << location->name
                  << static_cast<qint64>(props.id);
                m_functionLocations.erase(location);
            }
            messages.push_back(d.squeezedData());
            d.clear();
            m_stack.push(props.end);
            ++m_functionCallPos;
        }
        if (m_stack.empty() && m_functionCallPos == functionCallData.size())
            return finalizeMessages(until, messages, -1, d);
    }
}

void QV4ProfilerAdapter::receiveData(
        const QV4::Profiling::FunctionLocationHash &locations,
        const QList<QV4::Profiling::FunctionCallProperties> &functionCallData,
        const QList<QV4::Profiling::MemoryAllocationProperties> &memoryData)
{
    // In rare cases it could be that another flush or stop event is processed while data from
    // the previous one is still pending. In that case we just append the data.
    if (m_functionLocations.isEmpty())
        m_functionLocations = locations;
    else
        m_functionLocations.insert(locations);

    if (m_functionCallData.isEmpty())
        m_functionCallData = functionCallData;
    else
        m_functionCallData.append(functionCallData);

    if (m_memoryData.isEmpty())
        m_memoryData = memoryData;
    else
        m_memoryData.append(memoryData);

    service->dataReady(this);
}

quint64 QV4ProfilerAdapter::translateFeatures(quint64 qmlFeatures)
{
    quint64 v4Features = 0;
    const quint64 one = 1;
    if (qmlFeatures & (one << ProfileJavaScript))
        v4Features |= (one << QV4::Profiling::FeatureFunctionCall);
    if (qmlFeatures & (one << ProfileMemory))
        v4Features |= (one << QV4::Profiling::FeatureMemoryAllocation);
    return v4Features;
}

void QV4ProfilerAdapter::forwardEnabled(quint64 features)
{
    emit v4ProfilingEnabled(translateFeatures(features));
}

void QV4ProfilerAdapter::forwardEnabledWhileWaiting(quint64 features)
{
    emit v4ProfilingEnabledWhileWaiting(translateFeatures(features));
}

QT_END_NAMESPACE

#include "moc_qv4profileradapter.cpp"
