// Copyright (C) 2022 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
#include "qqnxaudiorecorder_p.h"
#include "qqnxmediaeventthread_p.h"

#include <QtCore/qcoreapplication.h>

#include <private/qmediastoragelocation_p.h>

#include <mm/renderer.h>

#include <sys/stat.h>
#include <sys/strm.h>

static QByteArray buildDevicePath(const QByteArray &deviceId, const QMediaEncoderSettings &settings)
{
    QByteArray devicePath = QByteArrayLiteral("snd:/dev/snd/") + deviceId + QByteArrayLiteral("?");

    if (settings.audioSampleRate() > 0)
        devicePath += QByteArrayLiteral("frate=") + QByteArray::number(settings.audioSampleRate());

    if (settings.audioChannelCount() > 0)
        devicePath += QByteArrayLiteral("nchan=") + QByteArray::number(settings.audioChannelCount());

    return devicePath;
}

QT_BEGIN_NAMESPACE

QQnxAudioRecorder::QQnxAudioRecorder(QObject *parent)
    : QObject(parent)
{
    openConnection();
}

QQnxAudioRecorder::~QQnxAudioRecorder()
{
    stop();
    closeConnection();
}

void QQnxAudioRecorder::openConnection()
{
    static int idCounter = 0;

    m_connection = ConnectionUniquePtr { mmr_connect(nullptr) };

    if (!m_connection) {
        qWarning("QQnxAudioRecorder: Unable to connect to the multimedia renderer");
        return;
    }

    m_id = idCounter++;

    char contextName[256];

    std::snprintf(contextName, sizeof contextName, "QQnxAudioRecorder_%d_%llu",
            m_id, QCoreApplication::applicationPid());

    m_context = ContextUniquePtr { mmr_context_create(m_connection.get(),
            contextName, 0, S_IRWXU|S_IRWXG|S_IRWXO) };

    if (m_context) {
        startMonitoring();
    } else {
        qWarning("QQnxAudioRecorder: Unable to create context");
        closeConnection();
    }
}

void QQnxAudioRecorder::closeConnection()
{
    m_context.reset();
    m_context.reset();

    stopMonitoring();
}

void QQnxAudioRecorder::attach()
{
    if (isAttached())
        return;

    const QString container = m_encoderSettings.preferredSuffix();
    const QString location = QMediaStorageLocation::generateFileName(m_outputUrl.toLocalFile(),
            QStandardPaths::MusicLocation, container);

    m_audioId = mmr_output_attach(m_context.get(), qPrintable(location), "file");

    if (m_audioId == -1) {
        qWarning("QQnxAudioRecorder: mmr_output_attach() for file failed");
        return;
    }

    configureOutputBitRate();

    const QByteArray devicePath = buildDevicePath(m_inputDeviceId, m_encoderSettings);

    if (mmr_input_attach(m_context.get(), devicePath.constData(), "track") != 0) {
        qWarning("QQnxAudioRecorder: mmr_input_attach() failed");
        detach();
    } else {
        Q_EMIT actualLocationChanged(QUrl{location});
    }
}

void QQnxAudioRecorder::detach()
{
    if (!isAttached())
        return;

    mmr_input_detach(m_context.get());
    mmr_output_detach(m_context.get(), m_audioId);

    m_audioId = -1;
}

void QQnxAudioRecorder::configureOutputBitRate()
{
    const int bitRate = m_encoderSettings.audioBitRate();

    if (!isAttached() || bitRate <= 0)
        return;

    char buf[12];
    std::snprintf(buf, sizeof buf, "%d", bitRate);

    strm_dict_t *dict = strm_dict_new();
    dict = strm_dict_set(dict, "audio_bitrate", buf);

    if (mmr_output_parameters(m_context.get(), m_audioId, dict) != 0)
        qWarning("mmr_output_parameters: setting bitrate failed");
}

bool QQnxAudioRecorder::isAttached() const
{
    return m_context && m_audioId != -1;
}

void QQnxAudioRecorder::setInputDeviceId(const QByteArray &id)
{
    m_inputDeviceId = id;
}

void QQnxAudioRecorder::setOutputUrl(const QUrl &url)
{
    m_outputUrl = url;
}

void QQnxAudioRecorder::setMediaEncoderSettings(const QMediaEncoderSettings &settings)
{
    m_encoderSettings = settings;
}

void QQnxAudioRecorder::record()
{
    if (!isAttached()) {
        attach();

        if (!isAttached())
            return;
    }

    if (mmr_play(m_context.get()) != 0)
        qWarning("QQnxAudioRecorder: mmr_play() failed");
}

void QQnxAudioRecorder::stop()
{
    if (!isAttached())
        return;

    mmr_stop(m_context.get());

    detach();
}

void QQnxAudioRecorder::startMonitoring()
{
    m_eventThread = std::make_unique<QQnxMediaEventThread>(m_context.get());

    connect(m_eventThread.get(), &QQnxMediaEventThread::eventPending,
            this, &QQnxAudioRecorder::readEvents);

    m_eventThread->setObjectName(QStringLiteral("MmrAudioEventThread-") + QString::number(m_id));
    m_eventThread->start();
}

void QQnxAudioRecorder::stopMonitoring()
{
    if (m_eventThread)
        m_eventThread.reset();
}

void QQnxAudioRecorder::readEvents()
{
    while (const mmr_event_t *event = mmr_event_get(m_context.get())) {
        if (event->type == MMR_EVENT_NONE)
            break;

        switch (event->type) {
        case MMR_EVENT_STATUS:
            handleMmEventStatus(event);
            break;
        case MMR_EVENT_STATE:
            handleMmEventState(event);
            break;
        case MMR_EVENT_ERROR:
            handleMmEventError(event);
            break;
        case MMR_EVENT_METADATA:
        case MMR_EVENT_NONE:
        case MMR_EVENT_OVERFLOW:
        case MMR_EVENT_WARNING:
        case MMR_EVENT_PLAYLIST:
        case MMR_EVENT_INPUT:
        case MMR_EVENT_OUTPUT:
        case MMR_EVENT_CTXTPAR:
        case MMR_EVENT_TRKPAR:
        case MMR_EVENT_OTHER:
            break;
        }
    }

    if (m_eventThread)
        m_eventThread->signalRead();
}

void QQnxAudioRecorder::handleMmEventStatus(const mmr_event_t *event)
{
    if (!event || event->type != MMR_EVENT_STATUS)
        return;

    if (!event->pos_str)
        return;

    const QByteArray valueBa(event->pos_str);

    bool ok;
    const qint64 duration = valueBa.toLongLong(&ok);

    if (!ok)
        qCritical("Could not parse duration from '%s'", valueBa.constData());
    else
        durationChanged(duration);
}

void QQnxAudioRecorder::handleMmEventState(const mmr_event_t *event)
{
    if (!event || event->type != MMR_EVENT_STATE)
        return;

    switch (event->state) {
    case MMR_STATE_DESTROYED:
    case MMR_STATE_IDLE:
    case MMR_STATE_STOPPED:
        Q_EMIT stateChanged(QMediaRecorder::StoppedState);
        break;
    case MMR_STATE_PLAYING:
        Q_EMIT stateChanged(QMediaRecorder::RecordingState);
        break;
    }
}

void QQnxAudioRecorder::handleMmEventError(const mmr_event_t *event)
{
    if (!event)
        return;

    // When playback is explicitly stopped using mmr_stop(), mm-renderer
    // generates a STATE event. When the end of media is reached, an ERROR
    // event is generated and the error code contained in the event information
    // is set to MMR_ERROR_NONE. When an error causes playback to stop,
    // the error code is set to something else.
    if (event->details.error.info.error_code == MMR_ERROR_NONE) {
        //TODO add error
        Q_EMIT stateChanged(QMediaRecorder::StoppedState);
        detach();
    }
}

QT_END_NAMESPACE

#include "moc_qqnxaudiorecorder_p.cpp"
