/*
 * +================================================================================+
 *      __  ___  ______________  __  __  ______________   ____
 *     / / / / |/ /  _/_  __/\ \/ / / / / /_  __/  _/ /  / __/
 *    / /_/ /    // /  / /    \  / / /_/ / / / _/ // /___\ \
 *    \____/_/|_/___/ /_/     /_/  \____/ /_/ /___/____/___/
 *
 * +================================================================================+
 *
 *    "THE BEER-WARE LICENSE"
 *    -----------------------
 *
 *    As long as you retain this notice you can do whatever you want with this
 *    stuff. However it is expressly forbidden to sell or commercially distribute
 *    Unity Utils or parts of it apart from your games.
 *    If you meet any of the authors some day, and you think this stuff is
 *    worth it, you can buy them a beer in return.
 *
 *    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *    THE SOFTWARE.
 *
 * +================================================================================+
 *
 *    Authors:
 *    David Krummenacher (dave@timbookedtwo.com)
 *
 */

using System.Collections.Generic;
using UnityEngine;
using Utils.Behaviours;

namespace Utils.Helpers {

	/// <summary>
	/// Audio object that lets you easily play audio.
	/// </summary>
	public class AudioObject : MonoBehaviour {

		/// <summary>
		/// Reference to the audio source.
		/// </summary>
		/// <value>The audio source.</value>
		public AudioSource Source { get; private set; }
		/// <summary>
		/// Indicates wheter the audio started or not.
		/// </summary>
		private bool audioStarted = false;
		/// <summary>
		/// The prefix that is added in the name.
		/// </summary>
		private static string prefix = "AudioObject ";

		#region Creation methods

		/// <summary>
		/// Creates an audio object.
		/// </summary>
		/// <param name="clip">The audio clip.</param>
		public static AudioObject Create(AudioClip clip) {
			return Create(clip, Vector3.zero);
		}

		/// <summary>
		/// Creates an audio object at a given position.
		/// </summary>
		/// <param name="clip">The audio clip.</param>
		/// <param name="position">The position.</param>
		public static AudioObject Create(AudioClip clip, Vector3 position) {
			GameObject go = CreateAudioGameObject(clip);
			go.transform.position = position;
			return go.GetComponent<AudioObject>();
		}

		/// <summary>
		/// Creates an audio object and parents it to a given transform.
		/// </summary>
		/// <param name="clip">The audio clip.</param>
		/// <param name="parent">The parent.</param>
		public static AudioObject Create(AudioClip clip, Transform parent) {
			return Create(clip, parent, Vector3.zero);
		}

		/// <summary>
		/// Creates an audio object and parents it to a given transform with a given offset.
		/// </summary>
		/// <param name="clip">The audio clip.</param>
		/// <param name="parent">The parent.</param>
		/// <param name="offset">The offset to the parent.</param>
		public static AudioObject Create(AudioClip clip, Transform parent, Vector3 offset) {
			GameObject go = CreateAudioGameObject(clip);
			go.transform.parent = parent;
			go.transform.position = parent.position + offset;
			return go.GetComponent<AudioObject>();
		}

		/// <summary>
		/// Creates a random audio object.
		/// </summary>
		/// <param name="clip">The audio clip.</param>
		public static AudioObject Create(AudioClip[] clips) {
			if (clips.Length > 0) return Create(clips[Random.Range(0, clips.Length)]);
			return null;
		}

		/// <summary>
		/// Creates a random audio object at a given position.
		/// </summary>
		/// <param name="clip">The audio clip.</param>
		/// <param name="position">The position.</param>
		public static AudioObject Create(AudioClip[] clips, Vector3 position) {
			if (clips.Length > 0) return Create(clips[Random.Range(0, clips.Length)], position);
			return null;
		}

		/// <summary>
		/// Creates a random audio object and parents it to a given transform.
		/// </summary>
		/// <param name="clip">The audio clip.</param>
		/// <param name="parent">The parent.</param>
		public static AudioObject Create(AudioClip[] clips, Transform parent) {
			if (clips.Length > 0) return Create(clips[Random.Range(0, clips.Length)], parent);
			return null;
		}

		/// <summary>
		/// Creates an audio object and parents it to a given transform with a given offset.
		/// </summary>
		/// <param name="clip">The audio clip.</param>
		/// <param name="parent">The parent.</param>
		/// <param name="offset">The offset to the parent.</param>
		public static AudioObject Create(AudioClip[] clips, Transform parent, Vector3 offset) {
			if (clips.Length > 0) return Create(clips[Random.Range(0, clips.Length)], parent, offset);
			return null;
		}

		/// <summary>
		/// Creates a random audio object.
		/// </summary>
		/// <param name="clip">The audio clip.</param>
		public static AudioObject Create(List<AudioClip> clips) {
			if (clips.Count > 0) return Create(clips[Random.Range(0, clips.Count)]);
			return null;
		}

		/// <summary>
		/// Creates a random audio object at a given position.
		/// </summary>
		/// <param name="clip">The audio clip.</param>
		/// <param name="position">The position.</param>
		public static AudioObject Create(List<AudioClip> clips, Vector3 position) {
			if (clips.Count > 0) return Create(clips[Random.Range(0, clips.Count)], position);
			return null;
		}

		/// <summary>
		/// Creates a random audio object and parents it to a given transform.
		/// </summary>
		/// <param name="clip">The audio clip.</param>
		/// <param name="parent">The parent.</param>
		public static AudioObject Create(List<AudioClip> clips, Transform parent) {
			if (clips.Count > 0) return Create(clips[Random.Range(0, clips.Count)], parent);
			return null;
		}

		/// <summary>
		/// Creates an audio object and parents it to a given transform with a given offset.
		/// </summary>
		/// <param name="clip">The audio clip.</param>
		/// <param name="parent">The parent.</param>
		/// <param name="offset">The offset to the parent.</param>
		public static AudioObject Create(List<AudioClip> clips, Transform parent, Vector3 offset) {
			if (clips.Count > 0) return Create(clips[Random.Range(0, clips.Count)], parent, offset);
			return null;
		}

		#endregion

		#region Static convenience methods

		/// <summary>
		/// Plays an audio object.
		/// </summary>
		/// <param name="clip">The audio clip.</param>
		public static void Play(AudioClip clip) {
			Create(clip).Play();
		}

		/// <summary>
		/// Plays an audio object at a given position.
		/// </summary>
		/// <param name="clip">The audio clip.</param>
		/// <param name="position">The position.</param>
		public static void Play(AudioClip clip, Vector3 position) {
			Create(clip, position).Play();
		}

		/// <summary>
		/// Plays an audio object that is parented to a given transform.
		/// </summary>
		/// <param name="clip">The audio clip.</param>
		/// <param name="parent">The parent.</param>
		public static void Play(AudioClip clip, Transform parent) {
			Create(clip, parent).Play();
		}

		/// <summary>
		/// Plays an audio object that is parented to a given transform with a given offset.
		/// </summary>
		/// <param name="clip">The audio clip.</param>
		/// <param name="parent">The parent.</param>
		/// <param name="offset">The offset to the parent.</param>
		public static void Play(AudioClip clip, Transform parent, Vector3 offset) {
			Create(clip, parent, offset).Play();
		}

		/// <summary>
		/// Plays a random audio object.
		/// </summary>
		/// <param name="clips">The audio clips.</param>
		public static void Play(AudioClip[] clips) {
			AudioObject ao = Create(clips);
			if (ao != null) ao.Play();
		}

		/// <summary>
		/// Plays a random audio object at a given position.
		/// </summary>
		/// <param name="clips">The audio clips.</param>
		/// <param name="position">The position.</param>
		public static void Play(AudioClip[] clips, Vector3 position) {
			AudioObject ao = Create(clips, position);
			if (ao != null) ao.Play();
		}

		/// <summary>
		/// Plays a random audio object that is parented to a given transform.
		/// </summary>
		/// <param name="clips">The audio clips.</param>
		/// <param name="parent">The parent.</param>
		public static void Play(AudioClip[] clips, Transform parent) {
			AudioObject ao = Create(clips, parent);
			if (ao != null) ao.Play();
		}

		/// <summary>
		/// Plays an audio object that is parented to a given transform with a given offset.
		/// </summary>
		/// <param name="clips">The audio clips.</param>
		/// <param name="parent">The parent.</param>
		/// <param name="offset">The offset to the parent.</param>
		public static void Play(AudioClip[] clips, Transform parent, Vector3 offset) {
			AudioObject ao = Create(clips, parent, offset);
			if (ao != null) ao.Play();
		}

		/// <summary>
		/// Plays a random audio object.
		/// </summary>
		/// <param name="clips">The audio clips.</param>
		public static void Play(List<AudioClip> clips) {
			AudioObject ao = Create(clips);
			if (ao != null) ao.Play();
		}

		/// <summary>
		/// Plays a random audio object at a given position.
		/// </summary>
		/// <param name="clips">The audio clips.</param>
		/// <param name="position">The position.</param>
		public static void Play(List<AudioClip> clips, Vector3 position) {
			AudioObject ao = Create(clips, position);
			if (ao != null) ao.Play();
		}

		/// <summary>
		/// Plays a random audio object that is parented to a given transform.
		/// </summary>
		/// <param name="clips">The audio clips.</param>
		/// <param name="parent">The parent.</param>
		public static void Play(List<AudioClip> clips, Transform parent) {
			AudioObject ao = Create(clips, parent);
			if (ao != null) ao.Play();
		}

		/// <summary>
		/// Plays an audio object that is parented to a given transform with a given offset.
		/// </summary>
		/// <param name="clips">The audio clips.</param>
		/// <param name="parent">The parent.</param>
		/// <param name="offset">The offset to the parent.</param>
		public static void Play(List<AudioClip> clips, Transform parent, Vector3 offset) {
			AudioObject ao = Create(clips, parent, offset);
			if (ao != null) ao.Play();
		}

		/// <summary>
		/// Plays an audio object with a delay.
		/// </summary>
		/// <param name="clip">The audio clip.</param>
		/// <param name="delay">The delay</param>
		public static void PlayDelayed(AudioClip clip, float delay) {
			Create(clip).PlayDelayed(delay);
		}

		/// <summary>
		/// Plays an audio object at a given position with a delay.
		/// </summary>
		/// <param name="clip">The audio clip.</param>
		/// <param name="delay">The delay</param>
		/// <param name="position">The position.</param>
		public static void PlayDelayed(AudioClip clip, float delay, Vector3 position) {
			Create(clip, position).PlayDelayed(delay);
		}

		/// <summary>
		/// Plays an audio object that is parented to a given transform with a delay.
		/// </summary>
		/// <param name="clip">The audio clip.</param>
		/// <param name="delay">The delay</param>
		/// <param name="parent">The parent.</param>
		public static void PlayDelayed(AudioClip clip, float delay, Transform parent) {
			Create(clip, parent).PlayDelayed(delay);
		}

		/// <summary>
		/// Plays an audio object that is parented to a given transform with a given offset with a delay.
		/// </summary>
		/// <param name="clip">The audio clip.</param>
		/// <param name="delay">The delay</param>
		/// <param name="parent">The parent.</param>
		/// <param name="offset">The offset to the parent.</param>
		public static void PlayDelayed(AudioClip clip, float delay, Transform parent, Vector3 offset) {
			Create(clip, parent, offset).PlayDelayed(delay);
		}

		/// <summary>
		/// Plays a random audio object with a delay.
		/// </summary>
		/// <param name="clips">The audio clips.</param>
		/// <param name="delay">The delay</param>
		public static void PlayDelayed(AudioClip[] clips, float delay) {
			AudioObject ao = Create(clips);
			if (ao != null) ao.PlayDelayed(delay);
		}

		/// <summary>
		/// Plays a random audio object at a given position with a delay.
		/// </summary>
		/// <param name="clips">The audio clips.</param>
		/// <param name="delay">The delay</param>
		/// <param name="position">The position.</param>
		public static void PlayDelayed(AudioClip[] clips, float delay, Vector3 position) {
			AudioObject ao = Create(clips, position);
			if (ao != null) ao.PlayDelayed(delay);
		}

		/// <summary>
		/// Plays a random audio object that is parented to a given transform with a delay.
		/// </summary>
		/// <param name="clips">The audio clips.</param>
		/// <param name="delay">The delay</param>
		/// <param name="parent">The parent.</param>
		public static void PlayDelayed(AudioClip[] clips, float delay, Transform parent) {
			AudioObject ao = Create(clips, parent);
			if (ao != null) ao.PlayDelayed(delay);
		}

		/// <summary>
		/// Plays a random audio object that is parented to a given transform with a given offset with a delay.
		/// </summary>
		/// <param name="clips">The audio clips.</param>
		/// <param name="delay">The delay</param>
		/// <param name="parent">The parent.</param>
		/// <param name="offset">The offset to the parent.</param>
		public static void PlayDelayed(AudioClip[] clips, float delay, Transform parent, Vector3 offset) {
			AudioObject ao = Create(clips, parent, offset);
			if (ao != null) ao.PlayDelayed(delay);
		}

		/// <summary>
		/// Plays a random audio object with a delay.
		/// </summary>
		/// <param name="clips">The audio clips.</param>
		/// <param name="delay">The delay</param>
		public static void PlayDelayed(List<AudioClip> clips, float delay) {
			AudioObject ao = Create(clips);
			if (ao != null) ao.PlayDelayed(delay);
		}

		/// <summary>
		/// Plays a random audio object at a given position with a delay.
		/// </summary>
		/// <param name="clips">The audio clips.</param>
		/// <param name="delay">The delay</param>
		/// <param name="position">The position.</param>
		public static void PlayDelayed(List<AudioClip> clips, float delay, Vector3 position) {
			AudioObject ao = Create(clips, position);
			if (ao != null) ao.PlayDelayed(delay);
		}

		/// <summary>
		/// Plays a random audio object that is parented to a given transform with a delay.
		/// </summary>
		/// <param name="clips">The audio clips.</param>
		/// <param name="delay">The delay</param>
		/// <param name="parent">The parent.</param>
		public static void PlayDelayed(List<AudioClip> clips, float delay, Transform parent) {
			AudioObject ao = Create(clips, parent);
			if (ao != null) ao.PlayDelayed(delay);
		}

		/// <summary>
		/// Plays a random audio object that is parented to a given transform with a given offset with a delay.
		/// </summary>
		/// <param name="clips">The audio clips.</param>
		/// <param name="delay">The delay</param>
		/// <param name="parent">The parent.</param>
		/// <param name="offset">The offset to the parent.</param>
		public static void PlayDelayed(List<AudioClip> clips, float delay, Transform parent, Vector3 offset) {
			AudioObject ao = Create(clips, parent, offset);
			if (ao != null) ao.PlayDelayed(delay);
		}

		#endregion

		/// <summary>
		/// Creates a game object and sets it up for further use in the audio object creation and play methods.
		/// </summary>
		/// <returns>The audio game object.</returns>
		/// <param name="clip">The audio clip.</param>
		private static GameObject CreateAudioGameObject(AudioClip clip) {
			GameObject go = new GameObject(prefix + clip.name);
			
			go.AddComponent<AudioSource>();
			go.AddComponent<AudioObject>().Source.clip = clip;
			
			return go;
		}

		public void Awake() {
			// Get audio source
			Source = gameObject.GetComponent<AudioSource>();
		}
		
		public void Update() {
			// Check if audio source has started playing
			if (Source.isPlaying && !audioStarted) audioStarted = true;

			// Destroy audio object when playing is done
			bool canDestroy = true;
			
			if (GetComponent<Pausable>() != null)
				canDestroy = !GetComponent<Pausable>().IsPaused;
			
			if (!Source.isPlaying && audioStarted && canDestroy)
				Destroy(gameObject);
		}

		/// <summary>
		/// Plays the audio object.
		/// </summary>
		/// <param name="spatialBlend">The spatial blend. Default is 1f for 3d sound.</param>
		/// <param name="dopplerLevel">The doppler level. Default is 0 to prevent unwanted pitch glitches.</param>
		public void Play(float spatialBlend = 1f, float dopplerLevel = 0f) {
			Source.spatialBlend = spatialBlend;
			Source.dopplerLevel = dopplerLevel;
			Source.Play();
		}

		/// <summary>
		/// Plays the audio object delayed.
		/// </summary>
		/// <param name="delay">The delay.</param>
		/// <param name="spatialBlend">The spatial blend. Default is 1f for 3d sound.</param>
		/// <param name="dopplerLevel">The doppler level. Default is 0 to prevent unwanted pitch glitches.</param>
		public void PlayDelayed(float delay, float spatialBlend = 1f, float dopplerLevel = 0f) {
			Source.spatialBlend = spatialBlend;
			Source.dopplerLevel = dopplerLevel;
			Source.PlayDelayed(delay);
		}

		/// <summary>
		/// Stops the audio object.
		/// </summary>
		public void Stop() {
			Source.Stop();
			Destroy(gameObject);
		}

		/// <summary>
		/// Randomizes the pitch.
		/// </summary>
		/// <returns>Reference of the audio object for further use (e.g. method chaining).</returns>
		/// <param name="subtract">The maximum amount to subtract from the given pitch.</param>
		/// <param name="add">The maximum amount to add to the given pitch.</param>
		public AudioObject RandomizePitch(float subtract = -0.2f, float add = 0.2f) {
			AudioHelper.RandomizePitch(Source, subtract, add);
			return this;
		}

		/// <summary>
		/// Randomizes the volume of the audio source.
		/// </summary>
		/// <returns>Reference of the audio object for further use (e.g. method chaining).</returns>
		/// <param name="subtract">The maximum amount to subtract from the given volume.</param>
		/// <param name="add">The maximum amount to add to the given volume.</param>
		public AudioObject RandomizeVolume(float subtract = -0.2f, float add = 0.2f) {
			AudioHelper.RandimizeVolume(Source, subtract, add);
			return this;
		}

		/// <summary>
		/// Makes the audio object pausable by adding a pausable component.
		/// </summary>
		/// <returns>Reference of the audio object for further use (e.g. method chaining).</returns>
		public AudioObject Pausable() {
			gameObject.AddComponent<Pausable>();
			return this;
		}
	}
}
