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

#include <QtMultimedia/private/qandroidaudiosource_p.h>

#include <QtCore/qcoreapplication.h>
#include <QtCore/qpermissions.h>

#include <QtMultimedia/private/qandroidaudiojnitypes_p.h>
#include <QtMultimedia/private/qandroidaudioutil_p.h>

QT_BEGIN_NAMESPACE

namespace QtAAudio {

Q_STATIC_LOGGING_CATEGORY(qLcAndroidAudioSource, "qt.multimedia.android.audiosource")

QAndroidAudioSourceStream::QAndroidAudioSourceStream(QAudioDevice device,
                                                     const QAudioFormat &format,
                                                     std::optional<int> ringbufferSize,
                                                     QAndroidAudioSource *parent, float volume,
                                                     std::optional<NativePeriodFrames> nativePeriodFrames)
    : QtMultimediaPrivate::QPlatformAudioSourceStream(std::move(device), format, ringbufferSize,
                                                      nativePeriodFrames, volume),
      m_parent(parent)
{
    QtAAudio::StreamBuilder builder(format);

    qCDebug(qLcAndroidAudioSource) << "Creating source for device id:" << m_audioDevice.id()
                                   << ", description:" << m_audioDevice.description();

    builder.deviceId = m_audioDevice.id().toInt();

    // Set buffer parameters
    builder.bufferCapacity = m_nativePeriodFrames
            ? qToUnderlying(*m_nativePeriodFrames)
            : 1024;

    // NOTE: AAudio doesn't support UINT8, so convert to INT16 if that's requested
    if (format.sampleFormat() == QAudioFormat::UInt8) {
        m_hostFormat = format;
        m_hostFormat->setSampleFormat(QAudioFormat::Int16);
    }

    // Set builder parameters for audio source
    builder.params.sharingMode = AAUDIO_SHARING_MODE_SHARED;
    builder.params.direction = AAUDIO_DIRECTION_INPUT;

    // TODO: Set input preset based on device

    builder.userData = this;
    builder.callback = [](AAudioStream *, void *userData, void *audioData,
                          int32_t numFrames) -> int {
        auto *stream = reinterpret_cast<QAndroidAudioSourceStream *>(userData);
        Q_ASSERT(stream);
        auto audioSpan = stream->getHostSpan(audioData, numFrames);
        return stream->m_audioCallback ? stream->processCallback(audioSpan)
                                       : stream->processRingbuffer(audioSpan, numFrames);
    };
    builder.errorCallback = [](AAudioStream *, void *userData, aaudio_result_t error) -> void {
        auto *stream = reinterpret_cast<QAndroidAudioSourceStream *>(userData);
        Q_ASSERT(stream);
        stream->handleError(error);
    };

    builder.setupBuilder();

    if (!QtJniTypes::QtAudioDeviceManager::callStaticMethod<jboolean>("prepareAudioInput",
                                                                      m_audioDevice.id().toInt()))
        qCWarning(qLcAndroidAudioSource) << "Preparation failed for device:" << m_audioDevice.id().toInt();

    m_stream = std::make_unique<QtAAudio::Stream>(builder);
    if (builder.format.sampleFormat() != format.sampleFormat()) {
        // Original sample format unsupported, so doing sample format conversion
        Q_ASSERT(builder.format.sampleFormat() == QAudioFormat::Float);
        m_hostFormat = builder.format;
    }
}

QAndroidAudioSourceStream::~QAndroidAudioSourceStream()
{
    QtJniTypes::QtAudioDeviceManager::callStaticMethod<void>("releaseAudioDevice",
                                                             m_audioDevice.id().toInt());
}

bool QAndroidAudioSourceStream::open()
{
    QMicrophonePermission permission;

    const bool permitted = qApp->checkPermission(permission) == Qt::PermissionStatus::Granted;
    if (!permitted) {
        qWarning("Missing microphone permission!");
        requestStop();
        return false;
    }

    if (!m_stream->isOpen()) {
        qCWarning(qLcAndroidAudioSource) << "Stream null";
        requestStop();
        return false;
    }

    if (!m_stream->areStreamParametersRespected())
        qCWarning(qLcAndroidAudioSource) << "Stream parameters not correct";

    return true;
}

bool QAndroidAudioSourceStream::start(QIODevice *device)
{
    Q_ASSERT(thread()->isCurrentThread());
    setQIODevice(device);
    createQIODeviceConnections(device);

    if (!m_stream->start()) {
        requestStop();
        return false;
    }

    return true;
}

QIODevice *QAndroidAudioSourceStream::start()
{
    auto *device = createRingbufferReaderDevice();
    return start(device) ? device : nullptr;
}

bool QAndroidAudioSourceStream::start(AudioCallback &&callback)
{
    Q_ASSERT(thread()->isCurrentThread());
    m_audioCallback = std::move(callback);

    if (!m_stream->start()) {
        requestStop();
        return false;
    }

    return true;
}

void QAndroidAudioSourceStream::suspend()
{
    Q_ASSERT(thread()->isCurrentThread());
    m_stream->stop();
}

void QAndroidAudioSourceStream::resume()
{
    Q_ASSERT(thread()->isCurrentThread());
    m_stream->start();
}

void QAndroidAudioSourceStream::stop(ShutdownPolicy policy)
{
    Q_ASSERT(thread()->isCurrentThread());
    requestStop();

    m_stream->stop();

    disconnectQIODeviceConnections();
    finalizeQIODevice(policy);

    if (policy == ShutdownPolicy::DiscardRingbuffer)
        emptyRingbuffer();
}

void QAndroidAudioSourceStream::updateStreamIdle(bool idle)
{
    if (m_parent)
        m_parent->updateStreamIdle(idle);
}

QSpan<const std::byte>
QAndroidAudioSourceStream::getHostSpan(void *audioData,
                                       int numFrames) const noexcept QT_MM_NONBLOCKING
{
    qsizetype byteAmount = m_hostFormat ? m_hostFormat->bytesForFrames(numFrames)
                                        : m_format.bytesForFrames(numFrames);
    return QSpan{ reinterpret_cast<const std::byte *>(audioData), byteAmount };
}

aaudio_data_callback_result_t
QAndroidAudioSourceStream::processRingbuffer(QSpan<const std::byte> audioSpan,
                                             int numFrames) noexcept QT_MM_NONBLOCKING
{
    auto framesWritten = m_hostFormat
            ? QPlatformAudioSourceStream::process(
                      audioSpan, numFrames,
                      QAudioHelperInternal::toNativeSampleFormat(m_hostFormat->sampleFormat()))
            : QPlatformAudioSourceStream::process(audioSpan, numFrames);

    if (framesWritten != static_cast<uint64_t>(numFrames) && isStopRequested())
        return AAUDIO_CALLBACK_RESULT_STOP;

    return AAUDIO_CALLBACK_RESULT_CONTINUE;
}

aaudio_data_callback_result_t
QAndroidAudioSourceStream::processCallback(QSpan<const std::byte> audioSpan) noexcept QT_MM_NONBLOCKING
{
    if (isStopRequested())
        return AAUDIO_CALLBACK_RESULT_STOP;

    if (m_hostFormat)
        QtMultimediaPrivate::runAudioCallback(*m_audioCallback, audioSpan, m_format, volume(),
                                              *m_hostFormat);
    else
        QtMultimediaPrivate::runAudioCallback(*m_audioCallback, audioSpan, m_format, volume());

    return AAUDIO_CALLBACK_RESULT_CONTINUE;
}

void QAndroidAudioSourceStream::handleError(aaudio_result_t)
{
    // Handle as IO error which closes the stream
    requestStop();
    invokeOnAppThread([this] {
        // clang-format off
        handleIOError(m_parent);
        // clang-format on
    });
}

QAndroidAudioSource::QAndroidAudioSource(QAudioDevice device, const QAudioFormat &format,
                                         QObject *parent)
    : BaseClass(std::move(device), format, parent)
{
}

QAndroidAudioSource::~QAndroidAudioSource()
    = default;

} // namespace QtAAudio

QT_END_NAMESPACE
