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

public abstract class AbstractSoundPlayer : MonoBehaviour 
{
    private const int LIMITED_CONCURRENT_SOUNDS_LIMIT = 3;
    private const float DEFAULT_VOLUME = 1.0f;

    private Dictionary<string, List<AudioSource>> mAudioSources = new Dictionary<string, List<AudioSource>>();
    private List<string> mLimitedConcurrentSounds;
    private Dictionary<string, float> mVolumes = new Dictionary<string, float>();

    protected abstract string GetDir();
    protected abstract string[] GetCheckingNames();
    protected abstract bool mStopAllOnPlay { get; }
    protected virtual List<string> GetLimitedConcurrentSounds() { return new List<string>(); }
    protected virtual Dictionary<string, float> GetChangedVolumesSoundsList() { return new Dictionary<string,float>() {}; }

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

    private void Awake()
    {
        DontDestroyOnLoad(this);
        UnityEngine.Object[] objects = Resources.LoadAll(GetDir());
        AudioClip[] clips = objects.Select(obj => obj as AudioClip).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;
        });
        GetChangedVolumesSoundsList().ForEach(changedVolume => mVolumes[changedVolume.Key] = changedVolume.Value);
        mLimitedConcurrentSounds = 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]));
        Util.AssertTrue(mLimitedConcurrentSounds.TrueForAll(limitedSound => mAudioSources.ContainsKey(limitedSound)), "Limited sounds list contains unkown audio id(s)");
        Util.AssertTrue(GetCheckingNames().TrueForAll(str => mAudioSources.ContainsKey(str)), "There is a value in the enum that does not have a corresponding audio source");
        Util.AssertTrue(mAudioSources.Keys.TrueForAll(str => GetCheckingNames().Contains(str)), "There is an audio source that does not have a corresponding value in the enum");
    }

    public void Play(string key, bool loop = false, bool randomizePitch = false, float minRandomizePitchValue = 0.9f, float maxRandomizePitchValue = 1.1f)
    {
        if (Enabled)
        {
            Util.AssertTrue(mAudioSources.ContainsKey(key), "Audio file name not found: " + key);
            mAudioSources[key].ForEach(source => source.loop = loop);
            float pitch = 1.0f;
            if (randomizePitch)
            {
                pitch = UnityEngine.Random.Range(minRandomizePitchValue, maxRandomizePitchValue);
            }
            mAudioSources[key].ForEach(source => source.pitch = pitch);
            if (mStopAllOnPlay)
            {
                mAudioSources.ForEach(kvp => { if (kvp.Value[0].isPlaying) kvp.Value[0].Stop(); });
                mAudioSources[key][0].Play();
            }
            else if (loop)
            {
                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 PlayRandom(string[] possibleKeys, bool loop = false, bool randomizePitch = false)
    {
        if (Enabled)
        {
            int random = UnityEngine.Random.Range(0, possibleKeys.Length);
            string key = possibleKeys[random];
            Play(key, loop, randomizePitch);
        }
    }

    public void Stop(string key)
    {
        if (Enabled)
        {
            Util.AssertTrue(mAudioSources.ContainsKey(key), "Audio file name not found: " + key);
            mAudioSources[key][0].Stop();
        }
    }

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