// 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 "qcoreaudiosessionmanager_p.h"
#import <AVFoundation/AVAudioSession.h>
#import <Foundation/Foundation.h>

QT_BEGIN_NAMESPACE

@interface CoreAudioSessionObserver : NSObject
{
    CoreAudioSessionManager *m_sessionManager;
    AVAudioSession *m_audioSession;
}

@property (readonly, getter=sessionManager) CoreAudioSessionManager *m_sessionManager;
@property (readonly, getter=audioSession) AVAudioSession *m_audioSession;

-(CoreAudioSessionObserver *)initWithAudioSessionManager:(CoreAudioSessionManager *)sessionManager;

-(BOOL)activateAudio;
-(BOOL)deactivateAudio;

//Notification handlers
-(void)audioSessionInterruption:(NSNotification *)notification;
-(void)audioSessionRouteChange:(NSNotification *)notification;
-(void)audioSessionMediaServicesWereReset:(NSNotification *)notification;

@end //interface CoreAudioSessionObserver

@implementation CoreAudioSessionObserver

@synthesize m_sessionManager, m_audioSession;

-(CoreAudioSessionObserver *)initWithAudioSessionManager:(CoreAudioSessionManager *)sessionManager
{
        if (!(self = [super init]))
            return nil;

        self->m_sessionManager = sessionManager;
        self->m_audioSession = [AVAudioSession sharedInstance];

        //Set up observers
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(audioSessionInterruption:)
                                                     name:AVAudioSessionInterruptionNotification
                                                   object:self->m_audioSession];
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(audioSessionMediaServicesWereReset:)
                                                     name:AVAudioSessionMediaServicesWereResetNotification
                                                   object:self->m_audioSession];
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(audioSessionRouteChange:)
                                                     name:AVAudioSessionRouteChangeNotification
                                                   object:self->m_audioSession];

        return self;
}

-(void)dealloc
{
#ifdef QT_DEBUG_COREAUDIO
    qDebug() << Q_FUNC_INFO;
#endif

    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:AVAudioSessionInterruptionNotification
                                                  object:self->m_audioSession];
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:AVAudioSessionMediaServicesWereResetNotification
                                                  object:self->m_audioSession];
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:AVAudioSessionRouteChangeNotification
                                                  object:self->m_audioSession];

    [super dealloc];
}

-(BOOL)activateAudio
{
    NSError *error = nil;
    BOOL success = [self->m_audioSession setActive:YES error:&error];
    if (![self->m_audioSession setActive:YES error:&error]) {
#ifdef QT_DEBUG_COREAUDIO
        qDebug("audio session activation failed: %s", [[error localizedDescription] UTF8String]);
    } else {
        qDebug("audio session activated");
#endif
    }

    return success;
}

-(BOOL)deactivateAudio
{
    NSError *error = nil;
    BOOL success = [m_audioSession setActive:NO error:&error];
#ifdef QT_DEBUG_COREAUDIO
    if (!success) {
        qDebug("%s", [[error localizedDescription] UTF8String]);
    }
#endif
    return success;
}

-(void)audioSessionInterruption:(NSNotification *)notification
{
    NSNumber *type = [[notification userInfo] valueForKey:AVAudioSessionInterruptionTypeKey];
    if ([type intValue] == AVAudioSessionInterruptionTypeBegan) {
#ifdef QT_DEBUG_COREAUDIO
        qDebug("audioSession Interuption begain");
#endif
    } else if ([type intValue] == AVAudioSessionInterruptionTypeEnded) {
#ifdef QT_DEBUG_COREAUDIO
        qDebug("audioSession Interuption ended");
#endif
        NSNumber *option = [[notification userInfo] valueForKey:AVAudioSessionInterruptionOptionKey];
        if ([option intValue] == AVAudioSessionInterruptionOptionShouldResume) {
#ifdef QT_DEBUG_COREAUDIO
            qDebug("audioSession is active and immediately ready to be used.");
#endif
        } else {
            [self activateAudio];
        }
    }
}

-(void)audioSessionMediaServicesWereReset:(NSNotification *)notification
{
    Q_UNUSED(notification);
#ifdef QT_DEBUG_COREAUDIO
    qDebug("audioSession Media Services were reset");
#endif
    //Reactivate audio when this occurs
    [self activateAudio];
}

-(void)audioSessionRouteChange:(NSNotification *)notification
{
    NSNumber *reason = [[notification userInfo] valueForKey:AVAudioSessionRouteChangeReasonKey];
    NSUInteger reasonEnum = [reason intValue];

    if (reasonEnum == AVAudioSessionRouteChangeReasonUnknown) {
#ifdef QT_DEBUG_COREAUDIO
        qDebug("audioSession route changed. reason: unknown");
#endif
    } else if (reasonEnum == AVAudioSessionRouteChangeReasonNewDeviceAvailable) {
#ifdef QT_DEBUG_COREAUDIO
        qDebug("audioSession route changed. reason: new device available");
#endif
    } else if (reasonEnum == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) {
#ifdef QT_DEBUG_COREAUDIO
        qDebug("audioSession route changed. reason: old device unavailable");
#endif
    } else if (reasonEnum == AVAudioSessionRouteChangeReasonCategoryChange) {
#ifdef QT_DEBUG_COREAUDIO
        qDebug("audioSession route changed. reason: category changed");
#endif
    } else if (reasonEnum == AVAudioSessionRouteChangeReasonOverride) {
#ifdef QT_DEBUG_COREAUDIO
        qDebug("audioSession route changed. reason: override");
#endif
    } else if (reasonEnum == AVAudioSessionRouteChangeReasonWakeFromSleep) {
#ifdef QT_DEBUG_COREAUDIO
        qDebug("audioSession route changed. reason: woken from sleep");
#endif
    } else if (reasonEnum == AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory) {
#ifdef QT_DEBUG_COREAUDIO
        qDebug("audioSession route changed. reason: no suitable route for category");
#endif
    }

    m_sessionManager->devicesAvailableChanged();
}

@end //implementation CoreAudioSessionObserver

CoreAudioSessionManager::CoreAudioSessionManager() :
    QObject(0)
{
    m_sessionObserver = [[CoreAudioSessionObserver alloc] initWithAudioSessionManager:this];
}

CoreAudioSessionManager::~CoreAudioSessionManager()
{
#ifdef QT_DEBUG_COREAUDIO
    qDebug() << Q_FUNC_INFO;
#endif
    [m_sessionObserver release];
}


CoreAudioSessionManager &CoreAudioSessionManager::instance()
{
    static CoreAudioSessionManager instance;
    return instance;
}

bool CoreAudioSessionManager::setActive(bool active)
{
    if (active) {
        return [m_sessionObserver activateAudio];
    } else {
        return [m_sessionObserver deactivateAudio];
    }
}

bool CoreAudioSessionManager::setCategory(CoreAudioSessionManager::AudioSessionCategorys category, CoreAudioSessionManager::AudioSessionCategoryOptions options)
{
    NSString *targetCategory = nil;

    switch (category) {
    case CoreAudioSessionManager::Ambient:
        targetCategory = AVAudioSessionCategoryAmbient;
        break;
    case CoreAudioSessionManager::SoloAmbient:
        targetCategory = AVAudioSessionCategorySoloAmbient;
        break;
    case CoreAudioSessionManager::Playback:
        targetCategory = AVAudioSessionCategoryPlayback;
        break;
    case CoreAudioSessionManager::Record:
        targetCategory = AVAudioSessionCategoryRecord;
        break;
    case CoreAudioSessionManager::PlayAndRecord:
        targetCategory = AVAudioSessionCategoryPlayAndRecord;
        break;
    case CoreAudioSessionManager::AudioProcessing:
#ifndef Q_OS_TVOS
        targetCategory = AVAudioSessionCategoryAudioProcessing;
#endif
        break;
    case CoreAudioSessionManager::MultiRoute:
        targetCategory = AVAudioSessionCategoryMultiRoute;
        break;
    }

    if (targetCategory == nil)
        return false;

    return [[m_sessionObserver audioSession] setCategory:targetCategory
                                             withOptions:(AVAudioSessionCategoryOptions)options
                                                   error:nil];
}

bool CoreAudioSessionManager::setMode(CoreAudioSessionManager::AudioSessionModes mode)
{
    NSString *targetMode = nil;
    switch (mode) {
    case CoreAudioSessionManager::Default:
        targetMode = AVAudioSessionModeDefault;
        break;
    case CoreAudioSessionManager::VoiceChat:
        targetMode = AVAudioSessionModeVoiceChat;
        break;
    case CoreAudioSessionManager::GameChat:
        targetMode = AVAudioSessionModeGameChat;
        break;
    case CoreAudioSessionManager::VideoRecording:
        targetMode = AVAudioSessionModeVideoRecording;
        break;
    case CoreAudioSessionManager::Measurement:
        targetMode = AVAudioSessionModeMeasurement;
        break;
    case CoreAudioSessionManager::MoviePlayback:
        targetMode = AVAudioSessionModeMoviePlayback;
        break;
    }

    if (targetMode == nil)
        return false;

    return [[m_sessionObserver audioSession] setMode:targetMode error:nil];

}

CoreAudioSessionManager::AudioSessionCategorys CoreAudioSessionManager::category()
{
    NSString *category = [[m_sessionObserver audioSession] category];
    AudioSessionCategorys localCategory = Ambient;

    if (category == AVAudioSessionCategoryAmbient) {
        localCategory = Ambient;
    } else if (category == AVAudioSessionCategorySoloAmbient) {
        localCategory = SoloAmbient;
    } else if (category == AVAudioSessionCategoryPlayback) {
        localCategory = Playback;
    } else if (category == AVAudioSessionCategoryRecord) {
        localCategory = Record;
    } else if (category == AVAudioSessionCategoryPlayAndRecord) {
        localCategory = PlayAndRecord;
#ifndef Q_OS_TVOS
    } else if (category == AVAudioSessionCategoryAudioProcessing) {
        localCategory = AudioProcessing;
#endif
    } else if (category == AVAudioSessionCategoryMultiRoute) {
        localCategory = MultiRoute;
    }

    return localCategory;
}

CoreAudioSessionManager::AudioSessionModes CoreAudioSessionManager::mode()
{
    NSString *mode = [[m_sessionObserver audioSession] mode];
    AudioSessionModes localMode = Default;

    if (mode == AVAudioSessionModeDefault) {
        localMode = Default;
    } else if (mode == AVAudioSessionModeVoiceChat) {
        localMode = VoiceChat;
    } else if (mode == AVAudioSessionModeGameChat) {
        localMode = GameChat;
    } else if (mode == AVAudioSessionModeVideoRecording) {
        localMode = VideoRecording;
    } else if (mode == AVAudioSessionModeMeasurement) {
        localMode = Measurement;
    } else if (mode == AVAudioSessionModeMoviePlayback) {
        localMode = MoviePlayback;
    }

    return localMode;
}

QList<QByteArray> CoreAudioSessionManager::inputDevices()
{
    //TODO: Add support for USB input devices
    //Right now the default behavior on iOS is to have only one input route
    //at a time.
    QList<QByteArray> inputDevices;
    inputDevices << "default";
    return inputDevices;
}

QList<QByteArray> CoreAudioSessionManager::outputDevices()
{
    //TODO: Add support for USB output devices
    //Right now the default behavior on iOS is to have only one output route
    //at a time.
    QList<QByteArray> outputDevices;
    outputDevices << "default";
    return outputDevices;
}

float CoreAudioSessionManager::currentIOBufferDuration()
{
    return [[m_sessionObserver audioSession] IOBufferDuration];
}

float CoreAudioSessionManager::preferredSampleRate()
{
    return [[m_sessionObserver audioSession] preferredSampleRate];
}

#ifdef QT_DEBUG_COREAUDIO
QDebug operator<<(QDebug dbg, CoreAudioSessionManager::AudioSessionCategorys category)
{
    QDebug output = dbg.nospace();
    switch (category) {
    case CoreAudioSessionManager::Ambient:
        output << "AudioSessionCategoryAmbient";
        break;
    case CoreAudioSessionManager::SoloAmbient:
        output << "AudioSessionCategorySoloAmbient";
        break;
    case CoreAudioSessionManager::Playback:
        output << "AudioSessionCategoryPlayback";
        break;
    case CoreAudioSessionManager::Record:
        output << "AudioSessionCategoryRecord";
        break;
    case CoreAudioSessionManager::PlayAndRecord:
        output << "AudioSessionCategoryPlayAndRecord";
        break;
    case CoreAudioSessionManager::AudioProcessing:
        output << "AudioSessionCategoryAudioProcessing";
        break;
    case CoreAudioSessionManager::MultiRoute:
        output << "AudioSessionCategoryMultiRoute";
        break;
    }
    return output;
}

QDebug operator<<(QDebug dbg, CoreAudioSessionManager::AudioSessionCategoryOptions option)
{
    QDebug output = dbg.nospace();
    switch (option) {
    case CoreAudioSessionManager::None:
        output << "AudioSessionCategoryOptionNone";
        break;
    case CoreAudioSessionManager::MixWithOthers:
        output << "AudioSessionCategoryOptionMixWithOthers";
        break;
    case CoreAudioSessionManager::DuckOthers:
        output << "AudioSessionCategoryOptionDuckOthers";
        break;
    case CoreAudioSessionManager::AllowBluetooth:
        output << "AudioSessionCategoryOptionAllowBluetooth";
        break;
    case CoreAudioSessionManager::DefaultToSpeaker:
        output << "AudioSessionCategoryOptionDefaultToSpeaker";
        break;
    }
    return output;
}

QDebug operator<<(QDebug dbg, CoreAudioSessionManager::AudioSessionModes mode)
{
    QDebug output = dbg.nospace();
    switch (mode) {
    case CoreAudioSessionManager::Default:
        output << "AudioSessionModeDefault";
        break;
    case CoreAudioSessionManager::VoiceChat:
        output << "AudioSessionModeVoiceChat";
        break;
    case CoreAudioSessionManager::GameChat:
        output << "AudioSessionModeGameChat";
        break;
    case CoreAudioSessionManager::VideoRecording:
        output << "AudioSessionModeVideoRecording";
        break;
    case CoreAudioSessionManager::Measurement:
        output << "AudioSessionModeMeasurement";
        break;
    case CoreAudioSessionManager::MoviePlayback:
        output << "AudioSessionModeMoviePlayback";
        break;
    }
    return output;
}
#endif

QT_END_NAMESPACE

#include "moc_qcoreaudiosessionmanager_p.cpp"
