// 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 "qplatformaudiodevices_p.h"

#include <QtMultimedia/private/qaudiosystem_p.h>
#include <QtMultimedia/qaudiodevice.h>
#include <QtMultimedia/qmediadevices.h>
#include <QtCore/private/qminimalflatset_p.h>
#include <QtCore/qdebug.h>
#include <QtCore/qvarlengtharray.h>

#if defined(Q_OS_ANDROID)
#  include <QtMultimedia/private/qandroidaudiodevices_p.h>
#endif
#if QT_CONFIG(ohaudio)
#  include <QtMultimedia/private/qohosaudiodevices_p.h>
#endif
#if defined(Q_OS_DARWIN)
#  include <QtMultimedia/private/qcoreaudiodevices_p.h>
#endif
#if defined(Q_OS_WINDOWS)
#  include <QtMultimedia/private/qwindowsaudiodevices_p.h>
#endif
#if QT_CONFIG(alsa)
#  include <QtMultimedia/private/qalsaaudiodevices_p.h>
#endif
#if QT_CONFIG(pulseaudio)
#  include <QtMultimedia/private/qpulseaudiodevices_p.h>
#  include <QtMultimedia/private/qpulsehelpers_p.h>
#endif
#if QT_CONFIG(pipewire)
#  include <QtMultimedia/private/qpipewire_audiodevices_p.h>
#endif
#if defined(Q_OS_QNX) && defined(QT_MM_QNX_QSA)
#  include <QtMultimedia/private/qqnxaudiodevices_p.h>
#endif
#if defined(Q_OS_WASM)
#  include <QtMultimedia/private/qwasmmediadevices_p.h>
#endif

QT_BEGIN_NAMESPACE

using namespace Qt::Literals;

std::unique_ptr<QPlatformAudioDevices> QPlatformAudioDevices::create()
{
#ifdef Q_OS_DARWIN
    return std::make_unique<QCoreAudioDevices>();
#endif
#if defined(Q_OS_WINDOWS)
    return std::make_unique<QWindowsAudioDevices>();
#endif
#if defined(Q_OS_ANDROID)
    return std::make_unique<QAndroidAudioDevices>();
#endif
#if QT_CONFIG(ohaudio)
    return std::make_unique<QOhosAudioDevices>();
#endif
#if QT_CONFIG(pipewire)
    QByteArray requestedBackend = qgetenv("QT_AUDIO_BACKEND");
    const bool pipewireRequested = requestedBackend == "pipewire"_ba;

#  if QT_CONFIG(pulseaudio)
    using namespace QPulseAudioInternal;
    // Check if PulseAudio is actually the vanilla PulseAudio server, not PipeWire's PulseAudio server.
    // Some funny still distros ship PipeWire and PulseAudio side by side. In that case, we should not
    // use the PipeWire backend, as it might not report any audio devices.
    auto skipPipewire = [&] {
        if (pipewireRequested)
            return false;
        return requestedBackend.isNull()
                && pulseaudioDetectServerType() == PulseaudioServerType::Pulseaudio;
    };
#  else
    auto skipPipewire = [] {
        return false;
    };
#  endif

    const bool considerPipewire = requestedBackend.isNull() || pipewireRequested;

    if (considerPipewire && QtPipeWire::QAudioDevices::isSupported()) {
        if (!skipPipewire())
            return std::make_unique<QtPipeWire::QAudioDevices>();
    }

    if (pipewireRequested)
        qDebug() << "PipeWire audio backend requested. not available. Using default backend";

#endif
#if QT_CONFIG(pulseaudio)
    return std::make_unique<QPulseAudioDevices>();
#endif
#if QT_CONFIG(alsa)
    return std::make_unique<QAlsaAudioDevices>();
#endif
#if defined(Q_OS_QNX) && defined(QT_MM_QNX_QSA)
    return std::make_unique<QQnxAudioDevices>();
#endif
#if defined(Q_OS_WASM)
    return std::make_unique<QWasmAudioDevices>();
#endif
    return std::make_unique<QPlatformAudioDevices>();
}

QPlatformAudioDevices::QPlatformAudioDevices()
{
    qRegisterMetaType<PrivateTag>(); // for the case of queued connections
}

QPlatformAudioDevices::~QPlatformAudioDevices() = default;

QList<QAudioDevice> QPlatformAudioDevices::audioInputs() const
{
    return m_audioInputs.ensure([this]() {
        return findAudioInputs();
    });
}

QList<QAudioDevice> QPlatformAudioDevices::audioOutputs() const
{
    return m_audioOutputs.ensure([this]() {
        return findAudioOutputs();
    });
}

void QPlatformAudioDevices::onAudioInputsChanged()
{
    m_audioInputs.reset();
    emit audioInputsChanged(PrivateTag{});
}

void QPlatformAudioDevices::onAudioOutputsChanged()
{
    m_audioOutputs.reset();
    emit audioOutputsChanged(PrivateTag{});
}

void QPlatformAudioDevices::updateAudioInputsCache()
{
    if (m_audioInputs.update(findAudioInputs()))
        emit audioInputsChanged(PrivateTag{});
}

void QPlatformAudioDevices::updateAudioOutputsCache()
{
    if (m_audioOutputs.update(findAudioOutputs()))
        emit audioOutputsChanged(PrivateTag{});
}

QPlatformAudioSource *QPlatformAudioDevices::createAudioSource(const QAudioDevice &,
                                                               const QAudioFormat &, QObject *)
{
    return nullptr;
}
QPlatformAudioSink *QPlatformAudioDevices::createAudioSink(const QAudioDevice &,
                                                           const QAudioFormat &, QObject *)
{
    return nullptr;
}

QPlatformAudioSource *QPlatformAudioDevices::audioInputDevice(QAudioFormat format,
                                                              const QAudioDevice &deviceInfo,
                                                              QObject *parent)
{
    QAudioDevice device = deviceInfo;
    if (device.isNull())
        device = QMediaDevices::defaultAudioInput();

    if (device.isNull())
        return nullptr;

    if (format == QAudioFormat{})
        format = device.preferredFormat();

    return createAudioSource(device, format, parent);
}

QPlatformAudioSink *QPlatformAudioDevices::audioOutputDevice(QAudioFormat format,
                                                             const QAudioDevice &deviceInfo,
                                                             QObject *parent)
{
    QAudioDevice device = deviceInfo;
    if (device.isNull())
        device = QMediaDevices::defaultAudioOutput();

    if (device.isNull())
        return nullptr;

    if (format == QAudioFormat{})
        format = device.preferredFormat();

    return createAudioSink(device, format, parent);
}

bool QPlatformAudioDevices::hasCallbackApi() const
{
    return false;
}

QT_END_NAMESPACE

#include "moc_qplatformaudiodevices_p.cpp"
