// Copyright (C) 2026 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 "qohosaudiosink_p.h"

#include <QtMultimedia/private/qaudiohelpers_p.h>

#include <QtCore/qloggingcategory.h>

QT_BEGIN_NAMESPACE

namespace QtOHAudio {

Q_STATIC_LOGGING_CATEGORY(qLcOhosAudioSink, "qt.multimedia.ohos.audiosink")

QOhosAudioSinkStream::QOhosAudioSinkStream(QAudioDevice device, const QAudioFormat &format,
                                           std::optional<qsizetype> ringbufferSize,
                                           QOhosAudioSink *parent, float volume,
                                           std::optional<QtMultimediaPrivate::NativePeriodFrames> hardwareBufferFrames,
                                           AudioEndpointRole role)
    : QtMultimediaPrivate::QPlatformAudioSinkStream(std::move(device), format, ringbufferSize,
                                                    hardwareBufferFrames, volume),
      m_parent(parent),
      m_role(role)
{
    QAudioFormat builderFormat = format;
    const QAudioFormat::SampleFormat preferredFormat =
            QtOHAudio::preferredCompatibleSampleFormat(format.sampleFormat());
    if (preferredFormat != format.sampleFormat()) {
        m_hostFormat = format;
        builderFormat.setSampleFormat(preferredFormat);
    }

    QtOHAudio::StreamBuilder builder(builderFormat, AUDIOSTREAM_TYPE_RENDERER);

    qCDebug(qLcOhosAudioSink) << "Creating sink for device id:" << m_audioDevice.id()
                              << "description:" << m_audioDevice.description();

    switch (m_role) {
    case QtMultimediaPrivate::AudioEndpointRole::SoundEffect:
        builder.params.outputUsage = AUDIOSTREAM_USAGE_GAME;
        break;
    case QtMultimediaPrivate::AudioEndpointRole::Accessibility:
        builder.params.outputUsage = AUDIOSTREAM_USAGE_ACCESSIBILITY;
        break;
    case QtMultimediaPrivate::AudioEndpointRole::MediaPlayback:
    case QtMultimediaPrivate::AudioEndpointRole::Other:
        builder.params.outputUsage = AUDIOSTREAM_USAGE_MUSIC;
        break;
    }

    builder.userData = this;
    builder.writeCallback = [](OH_AudioRenderer *, void *userData, void *audioData,
                               int32_t audioDataSize) -> OH_AudioData_Callback_Result {
        auto *stream = reinterpret_cast<QOhosAudioSinkStream *>(userData);
        Q_ASSERT(stream);
        QSpan<std::byte> audioSpan = stream->getHostSpan(audioData, audioDataSize);
        return stream->m_audioCallback ? stream->processCallback(audioSpan)
                                       : stream->processRingbuffer(audioSpan, audioDataSize);
    };

    builder.setupBuilder();
    m_stream = std::make_unique<QtOHAudio::Stream>(builder);
}

bool QOhosAudioSinkStream::open()
{
    if (!m_stream || !m_stream->isOpen()) {
        qCWarning(qLcOhosAudioSink) << "Stream creation failed";
        requestStop();
        return false;
    }
    return true;
}

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

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

QIODevice *QOhosAudioSinkStream::start()
{
    auto *writer = createRingbufferWriterDevice();
    return start(writer) ? writer : nullptr;
}

bool QOhosAudioSinkStream::start(AudioCallback cb)
{
    Q_ASSERT(thread()->isCurrentThread());
    m_audioCallback = std::move(cb);

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

void QOhosAudioSinkStream::suspend()
{
    Q_ASSERT(thread()->isCurrentThread());
    m_stream->pause();
}

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

void QOhosAudioSinkStream::stop(ShutdownPolicy policy)
{
    requestStop();
    disconnectQIODeviceConnections();

    switch (policy) {
    case ShutdownPolicy::DrainRingbuffer:
        stop();
        break;
    case ShutdownPolicy::DiscardRingbuffer:
        reset();
        break;
    default:
        Q_UNREACHABLE_RETURN();
    }
}

void QOhosAudioSinkStream::stop()
{
    if (isIdle() || m_audioCallback)
        return reset();

    stopIdleDetection();
    connectIdleHandler([this] {
        Q_ASSERT(thread()->isCurrentThread());
        if (!isIdle())
            return;
        m_stream->stop();
        m_self = nullptr;
    });

    m_parent = nullptr;
    m_self = shared_from_this();
}

void QOhosAudioSinkStream::reset()
{
    // Note: reset() can be invoked on the engine's destruction thread when the
    // owning QRtAudioEngine was moved across threads after stream construction
    // (the stream's idle-detection notifier does not follow the sink across
    // moveToThread). OH_AudioRenderer_Stop is documented thread-safe, so we
    // intentionally do not assert thread()->isCurrentThread() here.
    m_stream->stop();
}

void QOhosAudioSinkStream::updateStreamIdle(bool arg)
{
    if (m_parent)
        m_parent->updateStreamIdle(arg);
}

QSpan<std::byte>
QOhosAudioSinkStream::getHostSpan(void *audioData,
                                  int32_t numBytes) const noexcept QT_MM_NONBLOCKING
{
    return QSpan<std::byte>{ reinterpret_cast<std::byte *>(audioData),
                             static_cast<qsizetype>(numBytes) };
}

OH_AudioData_Callback_Result
QOhosAudioSinkStream::processRingbuffer(QSpan<std::byte> audioSpan,
                                        int32_t numBytes) noexcept QT_MM_NONBLOCKING
{
    const QAudioFormat &format = m_hostFormat.value_or(m_format);
    const int32_t numFrames = numBytes / format.bytesPerFrame();

    auto consumedFrames = m_hostFormat
            ? QPlatformAudioSinkStream::process(
                      audioSpan, numFrames,
                      QAudioHelperInternal::toNativeSampleFormat(m_hostFormat->sampleFormat()))
            : QPlatformAudioSinkStream::process(audioSpan, numFrames);

    if (consumedFrames != static_cast<uint64_t>(numFrames) && isStopRequested())
        return AUDIO_DATA_CALLBACK_RESULT_INVALID;

    return AUDIO_DATA_CALLBACK_RESULT_VALID;
}

OH_AudioData_Callback_Result
QOhosAudioSinkStream::processCallback(QSpan<std::byte> audioSpan) noexcept QT_MM_NONBLOCKING
{
    if (isStopRequested())
        return AUDIO_DATA_CALLBACK_RESULT_INVALID;

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

    return AUDIO_DATA_CALLBACK_RESULT_VALID;
}

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

QOhosAudioSink::~QOhosAudioSink() = default;

} // namespace QtOHAudio

QT_END_NAMESPACE
