// Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include "qcoreaudiodevice_p.h"

#include <QtCore/private/qcore_mac_p.h>

#include <QtMultimedia/private/qcoreaudioutils_p.h>
#include <QtMultimedia/private/qaudioformat_p.h>
#ifdef Q_OS_MACOS
#include <QtMultimedia/private/qmacosaudiodatautils_p.h>
#endif

#include <optional>

QT_BEGIN_NAMESPACE

namespace {

[[nodiscard]] QAudioFormat qDefaultPreferredFormat(
    QAudioDevice::Mode mode,
    QAudioFormat::ChannelConfig channelConfig)
{
    QAudioFormat format;
    format.setSampleRate(44100);
    format.setSampleFormat(QAudioFormat::Int16);
    format.setChannelCount(mode == QAudioDevice::Input ? 1 : 2);
    format.setChannelConfig(channelConfig);
    return format;
}

[[nodiscard]] QAudioFormat::ChannelConfig qGetDefaultChannelLayout(QAudioDevice::Mode mode)
{
    return (mode == QAudioDevice::Input) ? QAudioFormat::ChannelConfigMono : QAudioFormat::ChannelConfigStereo;
}

[[nodiscard]] QString qGetDefaultDescription(const QByteArray &id)
{
    return QString::fromUtf8(id);
}

#ifdef Q_OS_MACOS

[[nodiscard]] std::optional<QAudioFormat> qGetPreferredFormatForCoreAudioDevice(
    QAudioDevice::Mode mode,
    AudioDeviceID deviceId)
{
    using namespace QCoreAudioUtils;

    const auto audioDevicePropertyStreamsAddress =
            makePropertyAddress(kAudioDevicePropertyStreams, mode);
    const auto audioDevicePhysicalFormatPropertyAddress =
            makePropertyAddress(kAudioStreamPropertyPhysicalFormat, mode);

    auto streamIDs =
            getAudioPropertyList<AudioStreamID>(deviceId, audioDevicePropertyStreamsAddress);
    if (!streamIDs || streamIDs->empty())
        return std::nullopt;

    for (auto streamID : *streamIDs) {
        auto streamDescription = getAudioProperty<AudioStreamBasicDescription>(
                streamID, audioDevicePhysicalFormatPropertyAddress);
        if (!streamDescription)
            continue;

        QAudioFormat fmt = QCoreAudioUtils::toPreferredQAudioFormat(*streamDescription);
        if (fmt.isValid())
            return fmt;
    }
    return std::nullopt;
}

[[nodiscard]] std::optional<QAudioFormat::ChannelConfig> qGetChannelLayoutForCoreAudioDevice(
    QAudioDevice::Mode mode,
    AudioDeviceID deviceId)
{
    using namespace QCoreAudioUtils;

    const auto propertyAddress =
        makePropertyAddress(kAudioDevicePropertyPreferredChannelLayout, mode);

    if (auto layout = getAudioPropertyWithFlexibleArrayMember<AudioChannelLayout>(deviceId, propertyAddress))
        return QCoreAudioUtils::fromAudioChannelLayout(layout.get());

    return std::nullopt;
}

[[nodiscard]] std::optional<QString> qGetDescriptionForCoreAudioDevice(
    QAudioDevice::Mode mode,
    AudioDeviceID deviceId)
{
    using namespace QCoreAudioUtils;

    const auto propertyAddress = makePropertyAddress(kAudioObjectPropertyName, mode);
    if (auto name = getAudioProperty<QCFString>(deviceId, propertyAddress))
        return name;

    return std::nullopt;
}

[[nodiscard]] std::optional<int> qSupportedNumberOfChannels(
        QAudioDevice::Mode mode,
        AudioDeviceID deviceId)
{
    using namespace QCoreAudioUtils;

    const auto audioDevicePropertyStreamsAddress =
            makePropertyAddress(kAudioDevicePropertyStreams, mode);

    auto streamIDs = getAudioPropertyList<AudioStreamID>(deviceId, audioDevicePropertyStreamsAddress);
    if (!streamIDs)
        return std::nullopt;

    const auto propVirtualFormat = makePropertyAddress(kAudioStreamPropertyVirtualFormat, mode);

    int ret{};

    for (auto streamID : *streamIDs) {
        auto streamDescription = getAudioProperty<AudioStreamBasicDescription>(streamID, propVirtualFormat);
        if (!streamDescription)
            continue;
        ret += streamDescription->mChannelsPerFrame;
    }

    return ret;
}

QAudioDevicePrivate::AudioDeviceFormat probeCoreAudioDeviceFormat(AudioDeviceID id,
                                                                  QAudioDevice::Mode mode)
{
    QAudioDevicePrivate::AudioDeviceFormat format;

    const std::optional<QAudioFormat::ChannelConfig> channelConfigOpt =
            qGetChannelLayoutForCoreAudioDevice(mode, id);
    if (channelConfigOpt.has_value())
        format.channelConfiguration = channelConfigOpt.value();
    else
        format.channelConfiguration = qGetDefaultChannelLayout(mode);

    const std::optional<QAudioFormat> preferredFormatOpt =
            qGetPreferredFormatForCoreAudioDevice(mode, id);
    if (preferredFormatOpt.has_value())
        format.preferredFormat = preferredFormatOpt.value();
    else
        format.preferredFormat = qDefaultPreferredFormat(mode, format.channelConfiguration);

    format.minimumSampleRate = QtMultimediaPrivate::allSupportedSampleRates.front();
    format.maximumSampleRate = QtMultimediaPrivate::allSupportedSampleRates.back();
    format.minimumChannelCount = 1;
    format.maximumChannelCount = qSupportedNumberOfChannels(mode, id).value_or(16);

    format.supportedSampleFormats = qAllSupportedSampleFormats();

    return format;
}

#else

QAudioDevicePrivate::AudioDeviceFormat createDefaultCoreAudioDeviceFormat(QAudioDevice::Mode mode)
{
    QAudioDevicePrivate::AudioDeviceFormat format;

    format.channelConfiguration = qGetDefaultChannelLayout(mode);
    format.preferredFormat = qDefaultPreferredFormat(mode, format.channelConfiguration);

    format.minimumSampleRate = 1;
    format.maximumSampleRate = 96000;
    format.minimumChannelCount = 1;
    format.maximumChannelCount = 16;
    format.supportedSampleFormats = qAllSupportedSampleFormats();

    return format;
}

#endif

} // namespace

#ifdef Q_OS_MACOS

static QString getDescription(AudioDeviceID id, const QByteArray &device, QAudioDevice::Mode mode)
{
    if (auto optionalDescription = qGetDescriptionForCoreAudioDevice(mode, id))
        return *optionalDescription;
    return qGetDefaultDescription(device);
}

QCoreAudioDeviceInfo::QCoreAudioDeviceInfo(AudioDeviceID id, const QByteArray &device,
                                           QAudioDevice::Mode mode)
    : QAudioDevicePrivate(device, mode, getDescription(id, device, mode), false,
                          probeCoreAudioDeviceFormat(id, mode))
{
}

#else

QCoreAudioDeviceInfo::QCoreAudioDeviceInfo(const QByteArray &device, QAudioDevice::Mode mode)
    : QAudioDevicePrivate(device, mode, qGetDefaultDescription(device), false,
                          createDefaultCoreAudioDeviceFormat(mode))
{
}

#endif

QT_END_NAMESPACE
