using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Threading.Tasks;
using UnityEngine.UIElements;
using UnityEditor.Experimental.GraphView;
using static Unity.VisualScripting.Member;

public class AudioManager : MonoBehaviour
{

    #region Instance Setup

    /* 
     This Manager is set to work without needing any scene set-up, so it Instantiates itself 
    as soon as any object requests to play any sound. If an Instance has been preloaded on the
    scene, that step is skipped so that ti works as a regular Singleton-based manager
     
     */

    public static AudioManager _Instance { get; private set; }

    private void Awake()
    {
        if (_Instance != null && _Instance != this)
        {
            Destroy(this);
        }
        else
        {
            _Instance = this;
        }

        //preloaded audiosources (if any)
        _usableAudioSources.AddRange(_Instance.GetComponentsInChildren<AudioSource>());

        _usableAudioSources.ForEach((s) => { _sourceCount++; s.name = "Audio Source #" + _sourceCount; s.playOnAwake = false; s.gameObject.SetActive(false); });
    }

    private static void TryCreateInstance()
    {
        if (_Instance == null)
        {
            _Instance = new GameObject("AudioManager Instance", typeof(AudioManager)).GetComponent<AudioManager>();
        }
    }
    #endregion

    #region AudioSourceManagment

    private int _sourceCount;
    private List<AudioSource> _usableAudioSources = new();
    private List<AudioCue> _loopingCues = new();

    #endregion

    #region Static Methods

    /// <summary>
    /// Plays the cue.
    /// </summary>
    /// <param name="cue"> The AudioCue to play</param>
    /// <param name="volumeMult"> Optional Volume Multiplier</param>
    /// <param name="pitchMult"> Optional Pitch Multiplier</param>

    public static void PlayCue(AudioCue cue, float volumeMult = 1f, float pitchMult = 1f)
    {
        TryCreateInstance();

        int clipIndex = 0;
        if (_Instance.TryPrepareClip(ref cue, out AudioSource src, volumeMult, pitchMult, ref clipIndex))
        {
            _Instance.StartCoroutine(_Instance.PlayAudio(cue, src, false, true, clipIndex));
        }
    }

    /// <summary>
    /// Plays the cue from the specified world position.
    /// </summary>
    /// <param name="cue"> The AudioCue to play</param>
    /// <param name="position"> The point in global coordinates to play this cue from</param>
    /// <param name="volumeMult"> Optional Volume Multiplier</param>
    /// <param name="pitchMult"> Optional Pitch Multiplier</param>
    public static void PlayCue(AudioCue cue, Vector3 position, float volumeMult = 1f, float pitchMult = 1f)
    {
        TryCreateInstance();

        int clipIndex = 0;
        if (_Instance.TryPrepareClip(ref cue, out AudioSource src, volumeMult, pitchMult, ref clipIndex))
        {
            src.transform.position = position;

            _Instance.StartCoroutine(_Instance.PlayAudio(cue, src, false, true, clipIndex));
        }
    }

    /// <summary>
    /// Plays the cue, attaching it to the specified Transform.
    /// </summary>
    /// <param name="cue"> The AudioCue to play</param>
    /// <param name="attachTo"> The Transform this cue will be attached to</param>
    /// <param name="volumeMult"> Optional Volume Multiplier</param>
    /// <param name="pitchMult"> Optional Pitch Multiplier</param>
    public static void PlayCue(AudioCue cue, Transform attachTo, float volumeMult = 1f, float pitchMult = 1f)
    {
        TryCreateInstance();

        int clipIndex = 0;
        if (_Instance.TryPrepareClip(ref cue, out AudioSource src, volumeMult, pitchMult, ref clipIndex))
        {
            src.transform.parent = attachTo;
            src.transform.localPosition = Vector3.zero;

            _Instance.StartCoroutine(_Instance.PlayAudio(cue, src, false, true, clipIndex));
        }
    }

    /// <summary>
    /// Stops the current cue if it is playing on a loop
    /// </summary>
    /// <param name="cue"> The AudioCue to stop</param>
    public static void StopLoopingCue(AudioCue cue)
    {
        if(cue._Loop && _Instance._loopingCues.Contains(cue) && cue._recurrentSource != null)
        {
            cue._recurrentSource.Stop();
            cue._recurrentSource.gameObject.SetActive(false);

            if (!cue._IsRecurrent)
            {
                cue._recurrentSource.transform.parent = _Instance.transform;
                cue._recurrentSource.transform.localPosition = Vector3.zero;

                _Instance._usableAudioSources.Add(cue._recurrentSource);

                cue._recurrentSource = null;
            }

            _Instance._loopingCues.Remove(cue);
        }
    }

    #endregion

    #region Methods


    private bool TryPrepareClip(ref AudioCue cue, out AudioSource audioSource, float volMult, float pitchMult, ref int clipIndex)
    {
        //select clip
        switch (cue._ArrayBehaviour)
        {
            case AudioArrayBehaviour.SelectRandom:
            default:
                clipIndex = Random.Range(0, cue._Clips.Length);
                break;

            case AudioArrayBehaviour.PlayInOrder:

                //find first non-null clip in the array
                for (clipIndex = 0; clipIndex < cue._Clips.Length; clipIndex++)
                    if (cue._Clips[clipIndex] != null) break;
                break;
        }

        //abort on invalid clip
        if (cue._Clips[clipIndex] == null) { audioSource = null; return false; }

        //recurrent or looping Source
        if (cue._recurrentSource != null && cue._IsRecurrent) 
        {

            if (cue._Loop && !_loopingCues.Contains(cue)) _loopingCues.Add(cue);

            cue._recurrentSource.clip = cue._Clips[clipIndex];
            cue._recurrentSource.volume *= volMult;
            cue._recurrentSource.pitch *= pitchMult;
            audioSource = cue._recurrentSource; 
            return true; 
        }


        //Check AudioSource availability
        if (_usableAudioSources.Count < 1)
        {
            _sourceCount++;
            _usableAudioSources.Add(new GameObject("Audio Source #" + _Instance._sourceCount, typeof(AudioSource)).GetComponent<AudioSource>());
            _usableAudioSources[0].transform.parent = transform;
            _usableAudioSources[0].gameObject.SetActive(false);
            _usableAudioSources[0].playOnAwake = false;
        }

        //Parametrize
        _usableAudioSources[0].outputAudioMixerGroup = cue._Output;
        _usableAudioSources[0].loop = cue._Loop;
        _usableAudioSources[0].volume = cue._Volume;
        _usableAudioSources[0].pitch = cue._Pitch;
        _usableAudioSources[0].spatialBlend = cue._SpatialBlend;

        _usableAudioSources[0].bypassEffects = cue._AudioSourceProperties._BypassEffects;
        _usableAudioSources[0].bypassListenerEffects = cue._AudioSourceProperties._BypassListenerEffects;
        _usableAudioSources[0].bypassReverbZones = cue._AudioSourceProperties._BypassReverbZones;

        _usableAudioSources[0].priority = cue._AudioSourceProperties._Priority;
        _usableAudioSources[0].panStereo = cue._AudioSourceProperties._StereoPan;
        _usableAudioSources[0].reverbZoneMix = cue._AudioSourceProperties._ReverbZoneMix;
        _usableAudioSources[0].dopplerLevel = cue._AudioSourceProperties._DopplerLevel;
        _usableAudioSources[0].spread = cue._AudioSourceProperties._Spread;
        _usableAudioSources[0].rolloffMode = cue._AudioSourceProperties._VolumeRolloff;
        _usableAudioSources[0].minDistance = cue._AudioSourceProperties._MinDistance;
        _usableAudioSources[0].maxDistance = cue._AudioSourceProperties._MaxDistance;
        
        _usableAudioSources[0].clip = cue._Clips[clipIndex];
        _usableAudioSources[0].volume *= volMult;
        _usableAudioSources[0].pitch *= pitchMult;

        //first time use of recurrent or looping cue
        if (cue._IsRecurrent || cue._Loop)
        {
            if (cue._Loop && !_loopingCues.Contains(cue)) _loopingCues.Add(cue);

            cue._recurrentSource = _usableAudioSources[0];
            audioSource = cue._recurrentSource;
            return true;
        }

        //non-recurrent sources
        audioSource = _usableAudioSources[0];
        return true;
    }

    #endregion

    private IEnumerator PlayAudio(AudioCue cue, AudioSource source, bool restorePos, bool restoreParent, int clipIndex)
    {
        //prepare audio source
        _usableAudioSources.Remove(source);
        source.gameObject.SetActive(true);
        source.Play();

        //recurrent or loop behaviour
        if (cue._Loop) yield break;


        //wait for audio clip duration
        float t = 0f;
        while (source != null && t < source.clip.length) 
        {
            t += Time.unscaledDeltaTime;
            yield return null;
        }

        //Play Sequence behaviour
        if (cue._ArrayBehaviour == AudioArrayBehaviour.PlayInOrder)
        {
            //find the next non-null clip in the array
            for (clipIndex = clipIndex + 1; clipIndex < cue._Clips.Length; clipIndex++)
            {
                if (cue._Clips[clipIndex] != null)
                {
                    source.clip = cue._Clips[clipIndex];
                    yield return StartCoroutine(PlayAudio(cue, source, restorePos, restoreParent, clipIndex));
                    break;
                }
            }
        }

        //stop Playing
        source.Stop();
        source.gameObject.SetActive(false);

        if (cue._IsRecurrent) yield break;

        //Restore position and parent
        if (restorePos) source.transform.position = Vector3.zero;
        if (restoreParent)
        {
            source.transform.parent = transform;
            source.transform.localPosition = Vector3.zero;
        }

        //recurrentBehaviour
        _usableAudioSources.Add(source);
    }
}
