#region File Description
//-----------------------------------------------------------------------------
// AudioManager.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion

#region Using Statements
using System.Collections.Generic;
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Audio;
#endregion

namespace Audio3D
{
    /// <summary>
    /// Audio manager keeps track of what 3D sounds are playing, updating
    /// their settings as the camera and entities move around the world, and
    /// automatically disposing sound effect instances after they finish playing.
    /// </summary>
    public class AudioManager
    {
        #region Instance variables

        // List of all the sound effects that will be loaded into this manager.
        static string[] soundNames =
        {
            "barrelCollection",
            "choke",
            "fire",
            "footstep0",
            "footstep1",
            "summer",
        };

        // The listener describes the ear which is hearing 3D sounds.
        public AudioListener listener; 

        // The emitter describes an entity which is making a 3D sound.
        public AudioEmitter audioEmitter;

        // Store all the sound effects that are available to be played.
        public Dictionary<string, SoundEffect> soundEffects;

        // Keep track of all the 3D sounds that are currently playing.
        private List<ActiveSound> activeSounds;

        #endregion

        #region Constructors

        /// <summary>
        /// Create a new audio manager
        /// </summary>
        /// <param name="content"></param>
        /// <param name="distanceScale">How much sounds change volume as you move further away (2000f)</param>
        /// <param name="dopplerScale">How much sounds change pitch as you move past them (0.1f)</param>
        public AudioManager(ContentManager content, float distanceScale, float dopplerScale)
        {
            // Instanciate instance variables
            this.listener = new AudioListener();
            this.audioEmitter = new AudioEmitter();
            this.soundEffects = new Dictionary<string, SoundEffect>();
            this.activeSounds = new List<ActiveSound>();

            // Set the scale for 3D audio so it matches the scale of our game world.
            // DistanceScale controls how much sounds change volume as you move further away.
            // DopplerScale controls how much sounds change pitch as you move past them.
            SoundEffect.DistanceScale = distanceScale;
            SoundEffect.DopplerScale = dopplerScale;

            // Load all the sound effects.
            foreach (string soundName in soundNames)
            {
                soundEffects.Add(soundName, content.Load<SoundEffect>("Audio/" + soundName));
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Dispose the sound effect data.
        /// </summary>
        public void Dispose(bool disposing)
        {
            try
            {
                if (disposing)
                {
                    foreach (SoundEffect soundEffect in soundEffects.Values)
                    {
                        soundEffect.Dispose();
                    }

                    soundEffects.Clear();
                }
            }
            catch (Exception ex)
            {
                // Do nothing
                String exceptionMessage = ex.Message;
            }

        } // End of the dispose method

        
        /// <summary>
        /// Updates the state of the 3D audio system.
        /// </summary>
        public void Update(GameTime gameTime)
        {
            // Loop over all the currently playing 3D sounds.
            int index = 0;

            while (index < activeSounds.Count)
            {
                ActiveSound activeSound = activeSounds[index];

                if (activeSound.Instance.State == SoundState.Stopped)
                {
                    // If the sound has stopped playing, dispose it.
                    activeSound.Instance.Dispose();

                    // Remove it from the active list.
                    activeSounds.RemoveAt(index);

                }
                else
                {
                    // If the sound is still playing, update its 3D settings.
                    Apply3D(activeSound);

                    index++;
                }
            }

            // Start to remove sounds when it is to many sounds, we do not want to reach
            // the Audio Instance limit
            while (activeSounds.Count >= 280)
            {
                if (activeSounds[1] != null)
                {
                    activeSounds[1].Instance.Stop();
                    activeSounds[1].Instance.Dispose();
                }

                activeSounds.RemoveAt(1);
            }

        } // End of the Update method


        /// <summary>
        /// Triggers a new 3D sound.
        /// </summary>
        public SoundEffectInstance Play3DSound(string soundName, bool isLooped, IAudioEmitter emitter)
        {
            ActiveSound activeSound = new ActiveSound();

            // Fill in the instance and emitter fields.
            activeSound.Instance = soundEffects[soundName].CreateInstance();
            activeSound.Instance.IsLooped = isLooped;

            activeSound.Emitter = emitter;

            // Set the 3D position of this sound, and then play it.
            Apply3D(activeSound);

            activeSound.Instance.Play();

            // Remember that this sound is now active.
            activeSounds.Add(activeSound);

            return activeSound.Instance;
        }

        /// <summary>
        /// Updates the position and velocity settings of a 3D sound.
        /// </summary>
        private void Apply3D(ActiveSound activeSound)
        {
            audioEmitter.Position = activeSound.Emitter.Position;
            audioEmitter.Forward = activeSound.Emitter.Forward;
            audioEmitter.Up = activeSound.Emitter.Up;
            audioEmitter.Velocity = activeSound.Emitter.Velocity;

            activeSound.Instance.Apply3D(listener, audioEmitter);
        }

        #endregion

        #region Internal classes

        /// <summary>
        /// Internal helper class for keeping track of an active 3D sound,
        /// and remembering which emitter object it is attached to.
        /// </summary>
        private class ActiveSound
        {
            public SoundEffectInstance Instance;
            public IAudioEmitter Emitter;
        }

        #endregion

    } // End of class

} // End of namespace
