using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class MusicPlayer : MonoBehaviour
{
    public static MusicPlayer Instance { get; private set; }
    static MusicPlayer() { Instance = (new GameObject("MusicPlayer")).AddComponent<MusicPlayer>(); }

    private bool _enabled = true;
    public bool Enabled
    {
        get
        {
            return _enabled;
        }
        set
        {
            if (!value)
            {
                Stop();
                if (mCurrentFadeCoroutine != null)
                {
                    mCurrentFadeCoroutine.Stop();
                }
            }
            _enabled = value;
        }
    }

    private const float DEFAULT_VOLUME = 1.0f;
    private const float DEFAULT_FADE_TIME = 1.0f;

    private bool mInitialized = false;
	private bool mIsFading = false;
    private List<string> mMusicIDQueue = new List<string>();
    private string mCurrentlyPlayingMusicID = null;
    private Coroutine mCurrentFadeCoroutine = null;
    private Dictionary<string, float> mMaxVolumes = new Dictionary<string, float>();
    private Dictionary<string, AudioSource> mAudioSources = new Dictionary<string, AudioSource>();

    private void Awake()
    {
        DontDestroyOnLoad(this);
    }

    public void Init(MusicPlayerInitData initData)
    {
        UnityEngine.Object[] objects = Resources.LoadAll("Music");
        AudioClip[] clips = objects.Select(obj => obj as AudioClip).ToArray();
        clips.ForEach(clip =>
        {
            AudioSource audioSource = gameObject.AddComponent<AudioSource>();
            audioSource.clip = clip;
            audioSource.playOnAwake = false;
            mAudioSources.Add(clip.name, audioSource);
            mMaxVolumes[clip.name] = DEFAULT_VOLUME;
        });

        Debug.Assert(initData.GetChangedVolumesSoundsList().TrueForAll(kvp => mAudioSources.ContainsKey(kvp.Key)), "Unknown music ID found on the Changed Volumes list of the Music Player Init Data.");

        initData.GetChangedVolumesSoundsList().ForEach(changedVolume => mMaxVolumes[changedVolume.Key] = changedVolume.Value);

        Debug.Assert(GameObject.FindObjectsOfType(GetType()).Length == 1, "There can be only 1 MusicPlayer in the game");
        mInitialized = true;
	}

    private void SetNewMusic(string newClip)
	{
        Debug.Assert(mAudioSources.ContainsKey(newClip), "Clip name not found on the audio sources: " + newClip);
        mCurrentlyPlayingMusicID = newClip;
	}

    public void PlayMusic(string newMusicID, bool loop = true)
	{
        RunAssertions();
        if (Enabled)
        {
            if (newMusicID != mCurrentlyPlayingMusicID)
            {
                if (mCurrentlyPlayingMusicID != null && mAudioSources[mCurrentlyPlayingMusicID] != null)
                {
                    Stop();
                }
                SetNewMusic(newMusicID);
                mAudioSources[mCurrentlyPlayingMusicID].loop = loop;
                mAudioSources[mCurrentlyPlayingMusicID].volume = mMaxVolumes[mCurrentlyPlayingMusicID];
                mAudioSources[mCurrentlyPlayingMusicID].Play();
            }
            else
            {
                mAudioSources[mCurrentlyPlayingMusicID].loop = loop;
            }
        }
	}

    public void FadeOutFadeIn(string newMusicID, bool loop = true, float outTimer = DEFAULT_FADE_TIME, float inTimer = DEFAULT_FADE_TIME)
	{
        RunAssertions();
        if (Enabled)
        {
            if ((!mIsFading) && (mMusicIDQueue.Count == 0))
            {
                if (newMusicID != mCurrentlyPlayingMusicID)
                {
                    mIsFading = true;
                    mCurrentFadeCoroutine = AsyncManager.Execute(this, InternalFadeOutFadeIn, newMusicID, loop, outTimer, inTimer);
                }
                else
                {
                    mAudioSources[mCurrentlyPlayingMusicID].loop = loop;
                }
            }
            else
            {
                mMusicIDQueue.Clear();
                mMusicIDQueue.Add(newMusicID);
            }
        }
	}

    private IEnumerator InternalFadeOutFadeIn(string newMusicID, bool loop, float outTimer, float inTimer)
	{
		float start = mAudioSources[mCurrentlyPlayingMusicID].volume;
		float end = 0.0f;
		float i = 0.0f;
		float step = 1.0f / outTimer;
		while (i <= 1.0f)
		{
			i += step * Time.deltaTime;
            mAudioSources[mCurrentlyPlayingMusicID].volume = Mathf.Lerp(start, end, i);
			yield return new WaitForSeconds(step * Time.deltaTime);
		}
		mAudioSources[mCurrentlyPlayingMusicID].Stop();
        mAudioSources[mCurrentlyPlayingMusicID].volume = start;

		SetNewMusic(newMusicID);
        mAudioSources[mCurrentlyPlayingMusicID].loop = loop;
		start = 0.0f;
		end = mMaxVolumes[mCurrentlyPlayingMusicID];
		i = 0.0f;
		step = 1.0f / inTimer;
        mAudioSources[mCurrentlyPlayingMusicID].volume = 0.0f;
        mAudioSources[mCurrentlyPlayingMusicID].Play();
		while (i <= 1.0f)
		{
			i += step * Time.deltaTime;
            mAudioSources[mCurrentlyPlayingMusicID].volume = Mathf.Lerp(start, end, i);
			yield return new WaitForSeconds(step * Time.deltaTime);
		}
        mAudioSources[mCurrentlyPlayingMusicID].volume = end;
		mIsFading = false;
		if (mMusicIDQueue.Count > 0)
		{
            string c = mMusicIDQueue[0];
			mMusicIDQueue.Clear();
			FadeOutFadeIn(c, true, outTimer, inTimer);
		}
	}

    private void Update()
    {
        if (!mIsFading)
        {
            if (mMusicIDQueue.Count > 0)
            {
                string c = mMusicIDQueue[0];
                mMusicIDQueue.Clear();
                FadeOutFadeIn(c, true);
            }
        }
    }

    public void Pause()
    {
        RunAssertions();
        if (mAudioSources[mCurrentlyPlayingMusicID] != null)
        {
            mAudioSources[mCurrentlyPlayingMusicID].Pause();
        }
        if (mCurrentFadeCoroutine != null)
        {
            // aqui deveria-se pausar a corotina. Porem, nao existe pause na Coroutine.
        }
    }

    public void Resume()
    {
        RunAssertions();
        if (mAudioSources[mCurrentlyPlayingMusicID] != null)
        {
            mAudioSources[mCurrentlyPlayingMusicID].Play();
        }
        if (mCurrentFadeCoroutine != null)
        {
            // aqui deveria-se dar um resume na corotina. Porem, nao existe resume na Coroutine.
        }
    }

    public void Stop()
    {
        RunAssertions();
        if (mAudioSources[mCurrentlyPlayingMusicID] != null)
        {
            mAudioSources[mCurrentlyPlayingMusicID].Stop();
        }
        mIsFading = false;
        mMusicIDQueue = new List<string>();
        mCurrentlyPlayingMusicID = null;
        if (mCurrentFadeCoroutine != null)
        {
            mCurrentFadeCoroutine.Stop();
        }
    }

    private void RunAssertions()
    {
        Debug.Assert(mInitialized, "You must first call Init before using the Music Player.");
    }

    #region VICTOR
    public bool Muted;

    public void SetVolume(float newVolume)
    {
        mAudioSources[mCurrentlyPlayingMusicID].volume = newVolume;
    }
    #endregion
}