/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtQml module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#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 QVector<QV4::Profiling::MemoryAllocationProperties> &memoryData = m_memoryData;

    while (memoryData.length() > 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.length() == 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 QVector<QV4::Profiling::FunctionCallProperties> &functionCallData = m_functionCallData;

    while (true) {
        while (!m_stack.isEmpty() &&
               (m_functionCallPos == functionCallData.length() ||
                m_stack.top() <= functionCallData[m_functionCallPos].start)) {
            if (m_stack.top() > until || messages.length() > 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.length() &&
               (m_stack.empty() || functionCallData[m_functionCallPos].start < m_stack.top())) {
            const QV4::Profiling::FunctionCallProperties &props =
                    functionCallData[m_functionCallPos];
            if (props.start > until || messages.length() > s_numMessagesPerBatch)
                return finalizeMessages(until, messages, props.start, d);

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

            d << props.start << int(RangeStart) << int(Javascript) << static_cast<qint64>(props.id);
            if (location != m_functionLocations.end()) {
                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.length())
            return finalizeMessages(until, messages, -1, d);
    }
}

void QV4ProfilerAdapter::receiveData(
        const QV4::Profiling::FunctionLocationHash &locations,
        const QVector<QV4::Profiling::FunctionCallProperties> &functionCallData,
        const QVector<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"
