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

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

    private static readonly string DIR = "SoundEffects";
    private const int LIMITED_CONCURRENT_SOUNDS_LIMIT = 3;
    private const float DEFAULT_VOLUME = 1.0f;
    private const int DEFAULT_PRIORITY = 0;

    private bool mInitialized = false;
    private bool mStopAllOnPlay;
    private Dictionary<string, List<AudioSource>> mAudioSources = new Dictionary<string, List<AudioSource>>();
    private List<string> mLimitedConcurrentSounds;
    private Dictionary<string, float> mVolumes = new Dictionary<string, float>();
    private Dictionary<string, int> mPriorities = new Dictionary<string, int>();

    private bool _enabled = true;
    public bool Enabled
    {
        get
        {
            return _enabled;
        }
        set
        {
            if (!value)
            {
                StopAll();
            }
            _enabled = value;
        }
    }

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

    public void Init(SoundEffectsPlayerInitData initData)
    {
        mStopAllOnPlay = initData.StopAllOnPlay;
        UnityEngine.Object[] objects = Resources.LoadAll(DIR);
        Debug.Assert(objects.Length != 0, "No files found on the " + DIR + " sounds directory.");
        AudioClip[] clips = objects.Select
            (   
                obj =>
                {
                    AudioClip clip = obj as AudioClip;
                    Debug.Assert(obj != null, "File that is not an audio clip found on the " + DIR + " sounds directory: " + obj.name);
                    return clip;
                }
            ).ToArray();
        clips.ForEach(clip =>
        {
            AudioSource audioSource = gameObject.AddComponent<AudioSource>();
            audioSource.clip = clip;
            mAudioSources.Add(clip.name, new List<AudioSource>(){ audioSource });
            mVolumes[clip.name] = DEFAULT_VOLUME;
            mPriorities[clip.name] = DEFAULT_PRIORITY;
        });

        float globalVolumeDivisor = 1.0f;
        Dictionary<string, float> changedVolumes = initData.GetChangedVolumesSoundsList();
        for(int i=0; i< changedVolumes.Count; i++)
        {
            if(changedVolumes.ElementAt(i).Value.CompareTo(globalVolumeDivisor) > 0)
            {
                Debug.LogWarning("Setting a sound volume higher than 1 in Unity will make the overall volume of the game lower. Ask the sound engineer to raise the volume of the audio file itself.");
                globalVolumeDivisor = changedVolumes.ElementAt(i).Value;
            }
        }
        changedVolumes.ForEach(changedVolume => mVolumes[changedVolume.Key] = changedVolume.Value);

        initData.GetChangedPrioritiesSoundsList().ForEach(changedPriority => mPriorities[changedPriority.Key] = changedPriority.Value);
        mLimitedConcurrentSounds = initData.GetLimitedConcurrentSounds();
        mLimitedConcurrentSounds.ForEach(limitedSounds =>
        {
            for (int i = 0; i < LIMITED_CONCURRENT_SOUNDS_LIMIT - 1; i++)
            {
                AudioSource audioSource = gameObject.AddComponent<AudioSource>();
                audioSource.clip = mAudioSources[limitedSounds][0].clip;
                mAudioSources[limitedSounds].Add(audioSource);
            }
        });
        mAudioSources.ForEach(kvp => kvp.Value.ForEach(source => source.volume = mVolumes[kvp.Key] / globalVolumeDivisor));
        Debug.Assert(mLimitedConcurrentSounds.TrueForAll(limitedSound => mAudioSources.ContainsKey(limitedSound)), "Limited sounds list contains unkown audio id(s)");
        mInitialized = true;
    }

    public void Play(string key, bool loop = false, bool randomizePitch = false, float minRandomizePitchValue = 0.9f, float maxRandomizePitchValue = 1.1f)
    {
        if (Enabled)
        {
            float pitch = 1.0f;
            if (randomizePitch)
            {
                pitch = UnityEngine.Random.Range(minRandomizePitchValue, maxRandomizePitchValue);
            }
            InternalPlay(key, loop, pitch);
        }
    }

    public void PlayRandomAudio(string[] possibleKeys, bool loop = false, bool randomizePitch = false, float minRandomizePitchValue = 0.9f, float maxRandomizePitchValue = 1.1f)
    {
        if (Enabled)
        {
            int random = UnityEngine.Random.Range(0, possibleKeys.Length);
            string key = possibleKeys[random];
            Play(key, loop, randomizePitch, minRandomizePitchValue, maxRandomizePitchValue);
        }
    }

    public void PlaySpecificPitch(string key, float pitch, bool loop = false)
    {
        if (Enabled)
        {
            InternalPlay(key, loop, pitch);
        }
    }

    private void InternalPlay(string key, bool loop, float pitch)
    {
        RunAssertions(key);
        mAudioSources[key].ForEach(source => source.loop = loop);
        mAudioSources[key].ForEach(source => source.pitch = pitch);
        if (mStopAllOnPlay)
        {
            bool shouldPlay = true;
            mAudioSources.ForEach(kvp => 
            {
                if (kvp.Value[0].isPlaying)
                {
                    if (mPriorities[kvp.Key] < mPriorities[key])
                    {
                        kvp.Value[0].Stop();
                    }
                    else
                    {
                        shouldPlay = false;
                    }
                }
            });
            if (shouldPlay)
            {
                mAudioSources[key][0].Play();
            }
        }
        else if (loop)
        {
            if (!mAudioSources[key][0].isPlaying)
            {
                mAudioSources[key][0].Play();
            }
        }
        else if (mLimitedConcurrentSounds.Contains(key))
        {
            bool tocou = false;
            float maxTime = 0.0f;
            int maxTimeIndex = 0;
            for (int i = 0; i < LIMITED_CONCURRENT_SOUNDS_LIMIT; i++)
            {
                if (mAudioSources[key][i].time > maxTime)
                {
                    maxTime = mAudioSources[key][i].time;
                    maxTimeIndex = i;
                }
                if (!mAudioSources[key][i].isPlaying)
                {
                    mAudioSources[key][i].Play();
                    tocou = true;
                    break;
                }
            }
            if (!tocou)
            {
                mAudioSources[key][maxTimeIndex].Play();
            }
        }
        else
        {
            mAudioSources[key][0].PlayOneShot(mAudioSources[key][0].clip);
        }
    }

    public void Stop(string key)
    {
        if (Enabled)
        {
            RunAssertions(key);
            mAudioSources[key][0].Stop();
        }
    }

    private void StopAll()
    {
        if (Enabled)
        {
            mAudioSources.ForEach(kvp => Stop(kvp.Key));
        }
    }

    private void RunAssertions(string key)
    {
        Debug.Assert(mAudioSources.ContainsKey(key), "Audio file name not found: " + key);
        Debug.Assert(mInitialized, "SoundEffectsPlayer must be initialized before use.");
    }

    #region VICTOR
    public void Mute()
    {
        _enabled = false;
    }
    public void Unmute()
    {
        _enabled = true;
    }
    #endregion
}