/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "audio_errors.h"
#import "format_convert_util.h"
#import "audio_manager_impl.h"

#define MIN_VOLUME 0
#define MAX_VOLUME 100

#pragma mark - AudioManagerImpl implementation
@implementation AudioManagerImpl
{
    bool communicationDeviceActive_;
    AVAudioSessionCategory savedCategory_;
    AVAudioSessionCategoryOptions savedCategoryOptions_;
    std::vector<AudioRendererImpl *> rendererList_;
    std::vector<AudioCapturerImpl *> capturerList_;
    std::shared_ptr<OHOS::AudioStandard::VolumeKeyEventCallback> volumeCallback_;
    std::shared_ptr<OHOS::AudioStandard::AudioRendererStateChangeCallback> rendererChangeCallback_;
    std::shared_ptr<OHOS::AudioStandard::AudioCapturerStateChangeCallback> capturerChangeCallback_;
    bool isRouteChangeNotification_;
    std::shared_ptr<OHOS::AudioStandard::AudioPreferredOutputDeviceChangeCallback> outputDeviceCallback_;
    std::shared_ptr<OHOS::AudioStandard::AudioPreferredInputDeviceChangeCallback> inputDeviceCallback_;
}

static AudioManagerImpl *sharedInstance = nil;

+ (AudioManagerImpl *)sharedInstance {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[AudioManagerImpl alloc] init];
    });
    return sharedInstance;
}

- (const OHOS::AudioStandard::AudioScene)getAudioScene
{
    AVAudioSession *audioSession = [AVAudioSession sharedInstance];
    NSLog(@"AVAudioSession mode = %@", audioSession.mode);
    if ([audioSession.mode isEqualToString:AVAudioSessionModeVoiceChat]) {
        return OHOS::AudioStandard::AUDIO_SCENE_PHONE_CHAT;
    } else {
        return OHOS::AudioStandard::AUDIO_SCENE_DEFAULT;
    }
}

- (int32_t)registerVolumeKeyEventCallback:
    (const std::shared_ptr<OHOS::AudioStandard::VolumeKeyEventCallback> &)callback
{
    volumeCallback_ = callback;
    AVAudioSession *session = [AVAudioSession sharedInstance];
    [session setActive:YES error:nil];
    [session addObserver:self forKeyPath:@"outputVolume" options:NSKeyValueObservingOptionNew context:nil];
    return OHOS::AudioStandard::SUCCESS;
}

- (void)observeValueForKeyPath:
    (NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if (volumeCallback_ && [keyPath isEqual:@"outputVolume"]) {
        NSLog(@"observeValueForKeyPath outputVolume: %@", change);
        id newValue = change[NSKeyValueChangeNewKey];
        if ([newValue isKindOfClass:[NSNumber class]]) {
            float floatValue = [newValue floatValue];
            OHOS::AudioStandard::VolumeEvent volumeEvent;
            volumeEvent.volume = floatValue * MAX_VOLUME;
            volumeCallback_->OnVolumeKeyEvent(volumeEvent);
        } else {
            NSLog(@"Observe value is not NSNumber.");
        }
    }
}

- (std::vector<std::shared_ptr<OHOS::AudioStandard::AudioDeviceDescriptor>>)getDevices:
    (OHOS::AudioStandard::DeviceFlag)deviceFlag
{
    std::vector<std::shared_ptr<OHOS::AudioStandard::AudioDeviceDescriptor>> descriptors = {};
    if (deviceFlag == OHOS::AudioStandard::INPUT_DEVICES_FLAG) {
        AVAudioSession *audioSession = [AVAudioSession sharedInstance];
        NSLog(@"AVAudioSession availableInputs count = %lu",[audioSession.availableInputs count]);
        for (AVAudioSessionPortDescription *portDescription in audioSession.availableInputs) {
            std::shared_ptr<OHOS::AudioStandard::AudioDeviceDescriptor> descriptor =
                [self getDeviceInfo:portDescription role:OHOS::AudioStandard::DeviceRole::INPUT_DEVICE];
            descriptor->GetDeviceStreamInfo().samplingRate.clear();
            descriptors.push_back(descriptor);
        }
    }
    return descriptors;
}

- (std::shared_ptr<OHOS::AudioStandard::AudioDeviceDescriptor>)getDeviceInfo:
    (AVAudioSessionPortDescription *)portDescription role:(OHOS::AudioStandard::DeviceRole)deviceRole
{
    AVAudioSession *audioSession = [AVAudioSession sharedInstance];
    NSLog(@"portDescription = %@",portDescription);
    std::shared_ptr<OHOS::AudioStandard::AudioDeviceDescriptor> descriptor =
        std::make_shared<OHOS::AudioStandard::AudioDeviceDescriptor>();
    descriptor->deviceRole_ = deviceRole;
    ConvertDeviceTypeToOh(portDescription.portType, descriptor->deviceType_);
    descriptor->deviceName_ = std::string([portDescription.portName UTF8String]);
    descriptor->displayName_ = std::string([portDescription.UID UTF8String]);
    NSLog(@"portType = %@, portName = %@, UID = %@", portDescription.portType, portDescription.portName,
        portDescription.UID);
    NSLog(@"channels = %lu",[portDescription.channels count]);
    for (AVAudioSessionChannelDescription *channelDescription in portDescription.channels) {
        descriptor->channelMasks_ |= channelDescription.channelLabel;
        NSLog(@"channelName = %@, channelNumber = %lu, owningPortUID = %@, channelLabel = %u",
            channelDescription.channelName,channelDescription.channelNumber,
            channelDescription.owningPortUID, channelDescription.channelLabel);
    }

    descriptor->GetDeviceStreamInfo().samplingRate.insert(
        static_cast<OHOS::AudioStandard::AudioSamplingRate>(audioSession.sampleRate));
    NSLog(@"sampleRate = %f", audioSession.sampleRate);
    return descriptor;
}

- (int32_t)setDeviceActive:(OHOS::AudioStandard::DeviceType)deviceType active:(bool)flag
{
    NSLog(@"setDeviceActive deviceType = %d, flag = %d",deviceType, flag);
    if (deviceType == OHOS::AudioStandard::DEVICE_TYPE_SPEAKER) {
        AVAudioSession *audioSession = [AVAudioSession sharedInstance];
        if (!communicationDeviceActive_) {
            savedCategory_ = audioSession.category;
            savedCategoryOptions_ = audioSession.categoryOptions;
            NSLog(@"Saved category = %@ categoryOptions = %lu",audioSession.category,audioSession.categoryOptions);
        }
        if (flag) {
            [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord
                withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker
                error:nil];
            [audioSession setActive:YES error:nil];
            communicationDeviceActive_ = true;
        } else {
            [audioSession setCategory:savedCategory_ withOptions:savedCategoryOptions_ error:nil];
            [audioSession setActive:YES error:nil];
            communicationDeviceActive_ = false;
        }
        return OHOS::AudioStandard::SUCCESS;
    } else {
        NSLog(@"deviceType = %d is not supported",deviceType);
        return OHOS::AudioStandard::ERR_NOT_SUPPORTED;
    }
}

- (bool)isDeviceActive:(OHOS::AudioStandard::DeviceType)deviceType
{
    if (deviceType == OHOS::AudioStandard::DEVICE_TYPE_SPEAKER) {
        AVAudioSession *audioSession = [AVAudioSession sharedInstance];
        AVAudioSessionRouteDescription *routeDescription = audioSession.currentRoute;
        for (AVAudioSessionPortDescription *portDescription in routeDescription.outputs) {
            if ([portDescription.portType isEqualToString:AVAudioSessionPortBuiltInSpeaker]) {
                return true;
            }
        }
    }
    return false;
}

- (void)updateCategory:(AVAudioSessionCategory)category options:(AVAudioSessionCategoryOptions)categoryOptions
{
    savedCategory_ = category;
    savedCategoryOptions_ = categoryOptions;
}

- (OHOS::AudioStandard::InterruptMode)getAllInterruptMode
{
    for (auto &renderer : rendererList_) {
        if (!renderer) {
            continue;
        }
        if ([renderer getInterruptMode] == OHOS::AudioStandard::INDEPENDENT_MODE) {
            return OHOS::AudioStandard::INDEPENDENT_MODE;
        }
    }
    return OHOS::AudioStandard::SHARE_MODE;
}

- (void)addRenderer:(AudioRendererImpl *)audioRenderer
{
    rendererList_.push_back(audioRenderer);
    [self updateRendererChangeInfos];
}

- (void)removeRenderer:(AudioRendererImpl *)audioRenderer
{
    auto it = std::find(rendererList_.begin(), rendererList_.end(), audioRenderer);
    if (it != rendererList_.end()) {
        rendererList_.erase(it);
        [self updateRendererChangeInfos];
    }
    if ([self getAllInterruptMode] == OHOS::AudioStandard::SHARE_MODE &&
        !capturerList_.size() && !communicationDeviceActive_) {
        AVAudioSession *audioSession = [AVAudioSession sharedInstance];
        [audioSession setCategory:AVAudioSessionCategoryAmbient error:nil];
        [audioSession setActive:YES error:nil];
        [self updateCategory:audioSession.category options:audioSession.categoryOptions];
    }
}

- (void)updateRendererChangeInfos
{
    if (rendererChangeCallback_) {
        std::vector<std::shared_ptr<OHOS::AudioStandard::AudioRendererChangeInfo>> audioRendererChangeInfos;
        [self getCurrentRendererChangeInfos:audioRendererChangeInfos];
        rendererChangeCallback_->OnRendererStateChange(audioRendererChangeInfos);
    }
}

- (void)addCapturer:(AudioCapturerImpl *)audioCapturer
{
    capturerList_.push_back(audioCapturer);
    [self updateCapturerChangeInfos];
}

- (void)removeCapturer:(AudioCapturerImpl *)audioCapturer
{
    auto it = std::find(capturerList_.begin(), capturerList_.end(), audioCapturer);
    if (it != capturerList_.end()) {
        capturerList_.erase(it);
        [self updateCapturerChangeInfos];
    }
    if ([self getAllInterruptMode] == OHOS::AudioStandard::SHARE_MODE &&
        !capturerList_.size() && !communicationDeviceActive_) {
        AVAudioSession *audioSession = [AVAudioSession sharedInstance];
        [audioSession setCategory:AVAudioSessionCategoryAmbient error:nil];
        [audioSession setActive:YES error:nil];
        [self updateCategory:audioSession.category options:audioSession.categoryOptions];
    }
}

- (void)updateCapturerChangeInfos
{
    if (capturerChangeCallback_) {
        std::vector<std::shared_ptr<OHOS::AudioStandard::AudioCapturerChangeInfo>> audioCapturerChangeInfos;
        [self getCurrentCapturerChangeInfos:audioCapturerChangeInfos];
        capturerChangeCallback_->OnCapturerStateChange(audioCapturerChangeInfos);
    }
}

- (int32_t)getCurrentRendererChangeInfos:
    (std::vector<std::shared_ptr<OHOS::AudioStandard::AudioRendererChangeInfo>> &)audioRendererChangeInfos
{
    NSLog(@"rendererList_ size = %lu", rendererList_.size());
    for (auto &renderer : rendererList_) {
        if (!renderer) {
            continue;
        }
        auto changeInfo = std::make_shared<OHOS::AudioStandard::AudioRendererChangeInfo>();
        uint32_t sessionId;
        int32_t ret = [renderer getAudioStreamId:sessionId];
        if (!ret) {
            changeInfo->sessionId = sessionId;
        }
        OHOS::AudioStandard::AudioRendererInfo rendererInfo;
        [renderer getRendererInfo:rendererInfo];
        changeInfo->rendererInfo = rendererInfo;

        OHOS::AudioStandard::AudioDeviceDescriptor deviceInfo;
        [renderer getCurrentOutputDevices:deviceInfo];
        changeInfo->outputDeviceInfo = deviceInfo;
        audioRendererChangeInfos.push_back(std::move(changeInfo));
    }
    return OHOS::AudioStandard::SUCCESS;
}

- (int32_t)getCurrentCapturerChangeInfos:
    (std::vector<std::shared_ptr<OHOS::AudioStandard::AudioCapturerChangeInfo>> &)audioCapturerChangeInfos
{
    NSLog(@"capturerList_ size = %lu", capturerList_.size());
    for (auto &capturer : capturerList_) {
        if (!capturer) {
            continue;
        }
        auto changeInfo = std::make_shared<OHOS::AudioStandard::AudioCapturerChangeInfo>();
        int32_t ret = [capturer getCurrentCapturerChangeInfo: *changeInfo];
        if (!ret) {
            audioCapturerChangeInfos.push_back(std::move(changeInfo));
        }
    }
    return OHOS::AudioStandard::SUCCESS;
}

- (int32_t)registerAudioRendererEventListener:
    (const std::shared_ptr<OHOS::AudioStandard::AudioRendererStateChangeCallback> &)callback
{
    rendererChangeCallback_ = callback;
    return OHOS::AudioStandard::SUCCESS;
}

- (int32_t)unregisterAudioRendererEventListener
{
    rendererChangeCallback_ = nil;
    return OHOS::AudioStandard::SUCCESS;
}

- (int32_t)registerAudioCapturerEventListener:
    (const std::shared_ptr<OHOS::AudioStandard::AudioCapturerStateChangeCallback> &)callback
{
    capturerChangeCallback_ = callback;
    return OHOS::AudioStandard::SUCCESS;
}

- (int32_t)unregisterAudioCapturerEventListener
{
    capturerChangeCallback_ = nil;
    return OHOS::AudioStandard::SUCCESS;
}

- (bool)isStreamActive:(OHOS::AudioStandard::AudioVolumeType)volumeType
{
    for (auto &renderer : rendererList_) {
        if (!renderer) {
            continue;
        }
        if ([renderer getStatus] != OHOS::AudioStandard::RENDERER_RUNNING) {
            continue;
        }
        OHOS::AudioStandard::AudioRendererInfo rendererInfo;
        [renderer getRendererInfo:rendererInfo];
        if (volumeType == [self getVolumeTypeFromStreamUsage:rendererInfo.streamUsage]) {
            return true;
        }
    }
    return false;
}

- (OHOS::AudioStandard::AudioVolumeType)getVolumeTypeFromStreamUsage:(OHOS::AudioStandard::StreamUsage)streamUsage
{
    switch (streamUsage) {
        case OHOS::AudioStandard::STREAM_USAGE_VOICE_COMMUNICATION:
        case OHOS::AudioStandard::STREAM_USAGE_VOICE_MESSAGE:
        case OHOS::AudioStandard::STREAM_USAGE_VIDEO_COMMUNICATION:
            return OHOS::AudioStandard::STREAM_VOICE_CALL;
        case OHOS::AudioStandard::STREAM_USAGE_RINGTONE:
        case OHOS::AudioStandard::STREAM_USAGE_NOTIFICATION:
            return OHOS::AudioStandard::STREAM_RING;
        case OHOS::AudioStandard::STREAM_USAGE_MUSIC:
        case OHOS::AudioStandard::STREAM_USAGE_MOVIE:
        case OHOS::AudioStandard::STREAM_USAGE_GAME:
        case OHOS::AudioStandard::STREAM_USAGE_AUDIOBOOK:
        case OHOS::AudioStandard::STREAM_USAGE_NAVIGATION:
            return OHOS::AudioStandard::STREAM_MUSIC;
        case OHOS::AudioStandard::STREAM_USAGE_VOICE_ASSISTANT:
            return OHOS::AudioStandard::STREAM_VOICE_ASSISTANT;
        case OHOS::AudioStandard::STREAM_USAGE_ALARM:
            return OHOS::AudioStandard::STREAM_ALARM;
        case OHOS::AudioStandard::STREAM_USAGE_ACCESSIBILITY:
            return OHOS::AudioStandard::STREAM_ACCESSIBILITY;
        default:
            return OHOS::AudioStandard::STREAM_MUSIC;
    }
}

- (int32_t)getPreferredOutputDeviceForRendererInfo:
    (std::vector<std::shared_ptr<OHOS::AudioStandard::AudioDeviceDescriptor>> &)desc
{
    AVAudioSession *audioSession = [AVAudioSession sharedInstance];
    AVAudioSessionRouteDescription *routeDescription = audioSession.currentRoute;
    for (AVAudioSessionPortDescription *portDescription in routeDescription.outputs) {
        desc.push_back([self getDeviceInfo:portDescription role:OHOS::AudioStandard::DeviceRole::OUTPUT_DEVICE]);
    }
    return OHOS::AudioStandard::SUCCESS;
}

- (int32_t)getPreferredInputDeviceForCapturerInfo:
    (std::vector<std::shared_ptr<OHOS::AudioStandard::AudioDeviceDescriptor>> &)desc
{
    AVAudioSession *audioSession = [AVAudioSession sharedInstance];
    AVAudioSessionRouteDescription *routeDescription = audioSession.currentRoute;
    for (AVAudioSessionPortDescription *portDescription in routeDescription.inputs) {
        desc.push_back([self getDeviceInfo:portDescription role:OHOS::AudioStandard::DeviceRole::INPUT_DEVICE]);
    }
    return OHOS::AudioStandard::SUCCESS;
}

- (int32_t)setPreferredOutputDeviceChangeCallback:
    (const std::shared_ptr<OHOS::AudioStandard::AudioPreferredOutputDeviceChangeCallback> &)callback
{
    AVAudioSession *audioSession = [AVAudioSession sharedInstance];
    if (!isRouteChangeNotification_) {
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                selector:@selector(handleRouteChange:)
                                                name:AVAudioSessionRouteChangeNotification
                                                object:audioSession];
        isRouteChangeNotification_ = true;
    }
    outputDeviceCallback_ = callback;
    return OHOS::AudioStandard::SUCCESS;
}

- (int32_t)setPreferredInputDeviceChangeCallback:
    (const std::shared_ptr<OHOS::AudioStandard::AudioPreferredInputDeviceChangeCallback> &)callback
{
    AVAudioSession *audioSession = [AVAudioSession sharedInstance];
    if (!isRouteChangeNotification_) {
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                selector:@selector(handleRouteChange:)
                                                name:AVAudioSessionRouteChangeNotification
                                                object:audioSession];
        isRouteChangeNotification_ = true;
    }
    inputDeviceCallback_ = callback;
    return OHOS::AudioStandard::SUCCESS;
}

- (void)handleRouteChange:(NSNotification *)notification
{
    AVAudioSession *audioSession = [AVAudioSession sharedInstance];
    AVAudioSessionRouteDescription *routeDescription = audioSession.currentRoute;
    if (outputDeviceCallback_) {
        std::vector<std::shared_ptr<OHOS::AudioStandard::AudioDeviceDescriptor>> descriptors = {};
        for (AVAudioSessionPortDescription *portDescription in routeDescription.outputs) {
            descriptors.push_back(
                [self getDeviceInfo:portDescription role:OHOS::AudioStandard::DeviceRole::OUTPUT_DEVICE]);
        }
        outputDeviceCallback_->OnPreferredOutputDeviceUpdated(descriptors);
    }

    if (inputDeviceCallback_) {
        std::vector<std::shared_ptr<OHOS::AudioStandard::AudioDeviceDescriptor>> descriptors = {};
        for (AVAudioSessionPortDescription *portDescription in routeDescription.inputs) {
            descriptors.push_back(
                [self getDeviceInfo:portDescription role:OHOS::AudioStandard::DeviceRole::INPUT_DEVICE]);
        }
        inputDeviceCallback_->OnPreferredInputDeviceUpdated(descriptors);
    }
}

- (int32_t)unsetPreferredOutputDeviceChangeCallback
{
    outputDeviceCallback_ = nil;
    if (!inputDeviceCallback_) {
        AVAudioSession *audioSession = [AVAudioSession sharedInstance];
        [[NSNotificationCenter defaultCenter] removeObserver:self
                                                name:AVAudioSessionRouteChangeNotification
                                                object:audioSession];
        isRouteChangeNotification_ = false;
    }
    return OHOS::AudioStandard::SUCCESS;
}

- (int32_t)unsetPreferredInputDeviceChangeCallback
{
    inputDeviceCallback_ = nil;
    if (!outputDeviceCallback_) {
        AVAudioSession *audioSession = [AVAudioSession sharedInstance];
        [[NSNotificationCenter defaultCenter] removeObserver:self
                                                name:AVAudioSessionRouteChangeNotification
                                                object:audioSession];
        isRouteChangeNotification_ = false;
    }
    return OHOS::AudioStandard::SUCCESS;
}

- (int32_t)getVolume
{
    AVAudioSession *audioSession = [AVAudioSession sharedInstance];
    [audioSession setActive:YES error:nil];
    NSLog(@"AVAudioSession outputVolume = %f", audioSession.outputVolume);
    return audioSession.outputVolume * MAX_VOLUME;
}

- (int32_t)getMaxVolume
{
    return MAX_VOLUME;
}

- (int32_t)getMinVolume
{
    return MIN_VOLUME;
}
@end