// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

//*********************************************************
//
// This sample code is a modified version of an official UWP sample which can be found on:
// https://github.com/Microsoft/Windows-universal-samples/blob/master/Samples/SpeechRecognitionAndSynthesis/cs/AudioCapturePermissions.cs
//
//*********************************************************

#if ENABLE_WINMD_SUPPORT
using System;
using System.Threading.Tasks;
using Windows.Media.Capture;
#endif

namespace HoloToolkit.Unity
{
    public enum MicrophoneStatus
    {
        MicrophoneReady,
        MicrophoneUseNotAuthorized,
        MicrophoneNotPresent,
        MicrophoneMuted,
        MicrophoneUseFailed,
        MediaComponentsMissing,
    }

    /// <summary>
    /// Provides a working status information of the default microphone on the system.
    /// Class is using UWP APIs. Everything UWP related is wrapped around ENABLE_WINMD_SUPPORT
    /// preprocessor definition. The definition is enabled in Unity in the following 2 cases:
    ///     - IL2CPP backend and .NET 4.6
    ///     - .NET backend and Net Core compilation override
    /// For full details see unity documentation page
    /// https://docs.unity3d.com/Manual/IL2CPP-WindowsRuntimeSupport.html
    /// </summary>
    public class MicrophoneHelper
    {
#if ENABLE_WINMD_SUPPORT

        /// <summary>
        /// Error code returned if there is no audio device in the system
        /// </summary>
        /// <remarks>
        /// If no recording device is attached, attempting to get access to audio capture devices will throw 
        /// a System.Exception object, with HResult = ‭0xC00DABE0‬ set.
        /// </remarks>
        private static int NoCaptureDevicesHResult = -1072845856;

        /// <summary>
        /// On desktop/tablet systems, users are prompted to give permission to use capture devices on a 
        /// per-app basis. Along with declaring the microphone DeviceCapability in the package manifest,
        /// this method tests the privacy setting for microphone access for this application.
        /// Note that this only checks the Settings->Privacy->Microphone setting, it does not handle
        /// the Cortana/Dictation privacy check, however (Under Settings->Privacy->Speech, Inking and Typing).
        /// 
        /// Developers should ideally perform a check like this every time their app gains focus, in order to 
        /// check if the user has changed the setting while the app was suspended or not in focus.
        /// </summary>
        /// <returns>
        /// Returns a MicrophoneStatus value indicating the specific issue if microphone cannot be used.
        /// If everything went well return value is MicrophoneStatus.MicrophoneReady
        /// </returns>
        public async static Task<MicrophoneStatus> GetMicrophoneStatus()
        {
            // Request access to the microphone only, to limit the number of capabilities we need
            // to request in the package manifest.
            MediaCaptureInitializationSettings settings = new MediaCaptureInitializationSettings();
            settings.StreamingCaptureMode = StreamingCaptureMode.Audio;
            settings.MediaCategory = MediaCategory.Speech;
            MediaCapture capture = new MediaCapture();
            try
            {
                // If this is the first time we run the app, this will show the
                // microphone permissions dialog, where the user is asked if they
                // allow the app to use the microphone. If the dialog is dismissed, or not
                // accepted, the app will not ask for permissions again. The user will need
                // to go to Settings->Privacy->Microphone Settings to allow the app to
                // use the microphone
                await capture.InitializeAsync(settings);
            }
            catch (TypeLoadException)
            {
                // On SKUs without media player (eg, the N SKUs), we may not have access to the Windows.Media.Capture
                // namespace unless the media player pack is installed.
                return MicrophoneStatus.MediaComponentsMissing;
            }
            catch (UnauthorizedAccessException)
            {
                // The user has turned off access to the microphone. If this occurs, we should show an error, or disable
                // functionality within the app to ensure that further exceptions aren't generated when 
                // recognition is attempted.
                return MicrophoneStatus.MicrophoneUseNotAuthorized;
            }
            catch (Exception exception)
            {
                // In mixed reality this would be the case when your HMD uses headset jack, your host machine does not have connected microphone
                //       (i.e. desktop with no integrated mic, nothing plugged into mic/headset jack or you disabled all recording endpoints)
                // If the HMD has built in microphones, they would be disabled when HMD is not worn and will be enabled when you put the device on.
                if (exception.HResult == NoCaptureDevicesHResult)
                {
                    return MicrophoneStatus.MicrophoneNotPresent;
                }
                else
                {
                    return MicrophoneStatus.MicrophoneUseFailed;
                }
            }

            // if microphone is muted, speech recognition will not hear anything.
            // return appropriate value to indicate that.
            if(capture.AudioDeviceController.Muted)
            {
                return MicrophoneStatus.MicrophoneMuted;
            }

            return MicrophoneStatus.MicrophoneReady;
        }

        /// <summary>
        /// This function provides an easier query when one is only interested to know if the microphone
        /// can be used or not, without dealing with the specifics of why it may not be working
        /// </summary>
        /// <returns>
        /// Returns true if the microphone is accessible and ready for use.
        /// </returns>
        public static async Task<bool> IsMicrophoneReady()
        {
            var status = await GetMicrophoneStatus();
            return status == MicrophoneStatus.MicrophoneReady;
        }
#else
        /// <summary>
        /// Stub function imitating the original GetMicrophoneStatus
        /// </summary>
        /// <returns>
        /// Always returns microphone ready.
        /// </returns>
        public static MicrophoneStatus GetMicrophoneStatus()
        {
            return MicrophoneStatus.MicrophoneReady;
        }

        /// <summary>
        /// Stub function imitating the original IsMicrophoneReady
        /// </summary>
        public static bool IsMicrophoneReady()
        {
            var status = GetMicrophoneStatus();
            return status == MicrophoneStatus.MicrophoneReady;
        }
#endif
    }
}
