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

using System;
using UnityEngine;

namespace HoloToolkit.Unity
{
    /// <summary>
    /// Class that implements IAudioInfluencer to provide an occlusion effect.
    /// </summary>
    /// <remarks>
    /// Ensure that all sound emitting objects have an attached AudioEmitter. 
    /// Failing to do so will result in the desired effect not being applied to the sound.
    /// </remarks>
    public class AudioOccluder : MonoBehaviour, IAudioInfluencer
    {
        /// <summary>
        /// Frequency above which sound will not be heard after applying occlusion.
        /// Setting this value to 22000.0 effectively disables the effect.
        /// </summary>
        /// <remarks>
        /// Chaining occluders will result in the lowest of the cutoff frequencies being applied to the sound.
        /// The CutoffFrequency range is 0.0 - 22000.0 (0 - 22kHz), inclusive.
        /// The default value is 5000.0 (5kHz).
        /// </remarks>
        [Tooltip("Frequency above which sound will not be heard after applying occlusion.")]
        [Range(10.0f, 22000.0f)]
        [SerializeField]
        private float cutoffFrequency = 5000.0f;
        public float CutoffFrequency
        {
            get { return cutoffFrequency; }
            set
            {
                // set cutoffFrequency and enforce the specified range
                if (value < 10.0f)
                {
                    cutoffFrequency = 10.0f;
                }
                else if (value > 22000.0f)
                {
                    cutoffFrequency = 22000.0f;
                }
                else
                {
                    cutoffFrequency = value;
                }
            }
        }

        /// <summary>
        /// Percentage of the audio source volume that will be heard after applying occlusion.
        /// </summary>
        /// <remarks>
        /// VolumePassThrough is cumulative. It is applied to the current volume of the object at the time
        /// the effect is applied.
        /// The VolumePassThrough range is from 0.0 - 1.0 (0-100%), inclusive.
        /// The default value is 1.0.
        /// </remarks>
        [Tooltip("Percentage of the audio source volume that will be heard after applying occlusion.")]
        [Range(0.0f, 1.0f)]
        [SerializeField]
        private float volumePassThrough = 1.0f;
        public float VolumePassThrough
        {
            get { return volumePassThrough; }
            set
            {
                // set cutoffFrequency and enforce the specified range
                if (value < 10.0f)
                {
                    volumePassThrough = 10.0f;
                }
                else if (value > 22000.0f)
                {
                    volumePassThrough = 22000.0f;
                }
                else
                {
                    volumePassThrough = value;
                }
            }
        }

        // Update is not used, but is kept so that this component can be enabled/disabled.
        private void Update() { }

        /// <summary>
        /// Applies the audio effect.
        /// </summary>
        /// <param name="soundEmittingObject">The GameObject on which the effect is to be applied.</param>
        /// <param name="audioSource">The AudioSource that is emitting the sound.</param>
        /// <remarks>
        /// This method has been deprecated and should be removed in the next major release of the
        /// Mixed Reality Toolkit.
        /// </remarks>
        public void ApplyEffect(GameObject soundEmittingObject, AudioSource audioSource)
        {
            // This version of ApplyEffect has been deprecated.
            // Ignore the provided audioSource as the prefered method will retreive it from
            // the soundEmittingObject GameObject.
            ApplyEffect(soundEmittingObject);
        }

        /// <summary>
        /// Applies the audio effect.
        /// </summary>
        /// <param name="soundEmittingObject">The GameObject on which the effect is to be applied.</param>
        public void ApplyEffect(GameObject soundEmittingObject)
        {
            if (!isActiveAndEnabled)
            { return; }

            AudioSource audioSource = soundEmittingObject.GetComponent<AudioSource>();
            if (audioSource == null)
            {
                Debug.LogWarning("The specified emitter does not have an attached AudioSource component.");
                return;
            }

            // Audio occlusion is performed using a low pass filter.                
            AudioLowPassFilter lowPass = soundEmittingObject.GetComponent<AudioLowPassFilter>();
            if (lowPass == null)
            { 
                lowPass = soundEmittingObject.AddComponent<AudioLowPassFilter>();
            }
            lowPass.enabled = true;

            // In the real world, chaining multiple low-pass filters will result in the 
            // lowest of the cutoff frequencies being the highest pitches heard.
            lowPass.cutoffFrequency = Mathf.Min(lowPass.cutoffFrequency, CutoffFrequency);

            // Unlike the cutoff frequency, volume pass-through is cumulative.
            audioSource.volume *= VolumePassThrough;
        }

        /// <summary>
        /// Removes the previously applied audio effect.
        /// </summary>
        /// <param name="soundEmittingObject">The GameObject from which the effect is to be removed.</param>
        /// <param name="audioSource">The AudioSource that is emitting the sound.</param>
        /// <remarks>
        /// This method has been deprecated and should be removed in the next major release of the
        /// Mixed Reality Toolkit.
        /// </remarks>
        public void RemoveEffect(GameObject soundEmittingObject, AudioSource audioSource)
        {
            // This version of RemoveEffect has been deprecated.
            // The provided audioSource has always been ignored as it is not used.
            RemoveEffect(soundEmittingObject);
        }

        /// <summary>
        /// Removes the previously applied audio effect.
        /// </summary>
        /// <param name="soundEmittingObject">The GameObject from which the effect is to be removed.</param>
        public void RemoveEffect(GameObject soundEmittingObject)
        {
            // Audio occlusion is performed using a low pass filter.                
            AudioLowPassFilter lowPass = soundEmittingObject.GetComponent<AudioLowPassFilter>();
            if (lowPass == null) { return; }

            float neutralFrequency = AudioEmitter.NeutralHighFrequency;
            AudioEmitter influencerManager = soundEmittingObject.GetComponent<AudioEmitter>();
            if (influencerManager != null)
            {
                neutralFrequency = influencerManager.GetNativeLowPassCutoffFrequency();
            }

            lowPass.cutoffFrequency = neutralFrequency;
            lowPass.enabled = false;

            // Note: Volume attenuation is reset in the AudioInfluencerManager, attached to the sound emitting object.
        }
    }
}