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

public enum SoundType{
    BGM,
    SE,
    Voice,
}
public class SoundManager : SingletonMonoBehaviour<SoundManager> {

    private string bgmFilePath = "";
    private AudioSource bgmSource = null;
    private float uniqueBGMVolume;
    public float UniqueBGMVolume {
        get{ return uniqueBGMVolume; }
    }
    private List<PlayingAudio> seSourceList = new List<PlayingAudio>();
    private Dictionary<string, PlayingAudio> seLoopSourceDic = new Dictionary<string, PlayingAudio>();
    private List<PlayingAudio> voiceSourceList = new List<PlayingAudio>();

    public AudioMixerGroup defaultBGMAudioMixer;
    public AudioMixerGroup defaultSEAudioMixer;
    public AudioMixerGroup defaultVoiceAudioMixer;
    public float bgmVolume = 1.0f;
    public float seVolume = 1.0f;
    public float voiceVolume = 1.0f;
    public float pauseTime = 0.0f;
    public float GetVolume(SoundType soundType){
        switch(soundType){
        case SoundType.BGM:
            return bgmVolume;
        case SoundType.Voice:
            return voiceVolume;
        default:
            return seVolume;
        }
    }

    [SerializeField]
    private bool enabledBGM = true;
    [SerializeField]
    private bool enabledSE = true;
    [SerializeField]
    private bool enabledVoice = true;
    public void EnabledSound(SoundType soundType, bool enabled){
        switch(soundType){
        case SoundType.BGM:
            enabledBGM = enabled;
            break;
        case SoundType.Voice:
            enabledVoice = enabled;
            break;
        default:
            enabledSE = enabled;
            break;
        }
    }
    public bool IsEnabled(SoundType soundType){
        switch(soundType){
        case SoundType.BGM:
            return enabledBGM;
        case SoundType.Voice:
            return enabledVoice;
        default:
            return enabledSE;
        }
    }

    
    void Awake(){
        if(CheckInstance()){
            return;
        }
#if !OLD_STORAGE
        enabledBGM = UsayaStorageManager.LoadOrDefault<bool>(UsayaStorageFilename.Settings_Data, "EnabledBGM", true);
        enabledSE = UsayaStorageManager.LoadOrDefault<bool>(UsayaStorageFilename.Settings_Data, "EnabledSE", true);
        enabledVoice = UsayaStorageManager.LoadOrDefault<bool>(UsayaStorageFilename.Settings_Data, "EnabledVoice", true);

        bgmVolume = UsayaStorageManager.LoadOrDefault<float>(UsayaStorageFilename.Settings_Data, "VolumeBGM", bgmVolume);
        seVolume = UsayaStorageManager.LoadOrDefault<float>(UsayaStorageFilename.Settings_Data, "VolumeSE", seVolume);
        voiceVolume = UsayaStorageManager.LoadOrDefault<float>(UsayaStorageFilename.Settings_Data, "VolumeVoice", voiceVolume);
#else
        if(UsayaStorageManager.Exists(UsayaStorageFilename.Settings_Data, "EnabledBGM")){
            enabledBGM = UsayaStorageManager.Load<bool>(UsayaStorageFilename.Settings_Data, "EnabledBGM");
        }else{
            try{
                if(StorageManager.Exists(StorageFilename.SettingsData, "EnabledBGM")){
                    enabledBGM = StorageManager.Load<bool>(StorageFilename.SettingsData, "EnabledBGM");
                }else{
                    enabledBGM = DataManager.LoadOrDefault<bool>("EnabledBGM", true);
                }
            }catch{
                enabledBGM = DataManager.LoadOrDefault<bool>("EnabledBGM", true);
            }
        }
        if(UsayaStorageManager.Exists(UsayaStorageFilename.Settings_Data, "EnabledSE")){
            enabledSE = UsayaStorageManager.Load<bool>(UsayaStorageFilename.Settings_Data, "EnabledSE");
        }else{
            try{
                if(StorageManager.Exists(StorageFilename.SettingsData, "EnabledSE")){
                    enabledSE = StorageManager.Load<bool>(StorageFilename.SettingsData, "EnabledSE");
                }else{
                    enabledSE = DataManager.LoadOrDefault<bool>("EnabledSE", true);
                }
            }catch{
                enabledSE = DataManager.LoadOrDefault<bool>("EnabledSE", true);
            }
        }
        if(UsayaStorageManager.Exists(UsayaStorageFilename.Settings_Data, "EnabledVoice")){
            enabledVoice = UsayaStorageManager.Load<bool>(UsayaStorageFilename.Settings_Data, "EnabledVoice");
        }else{
            try{
                if(StorageManager.Exists(StorageFilename.SettingsData, "EnabledVoice")){
                    enabledVoice = StorageManager.Load<bool>(StorageFilename.SettingsData, "EnabledVoice");
                }else{
                    enabledVoice = DataManager.LoadOrDefault<bool>("EnabledVoice", true);
                }
            }catch{
                enabledVoice = DataManager.LoadOrDefault<bool>("EnabledVoice", true);
            }
        }

        if(UsayaStorageManager.Exists(UsayaStorageFilename.Settings_Data, "VolumeBGM")){
            bgmVolume = UsayaStorageManager.Load<float>(UsayaStorageFilename.Settings_Data, "VolumeBGM");
        }else{
            try{
                if(StorageManager.Exists(StorageFilename.SettingsData, "VolumeBGM")){
                    bgmVolume = StorageManager.Load<float>(StorageFilename.SettingsData, "VolumeBGM");
                }else{
                    bgmVolume = DataManager.LoadOrDefault<float>("VolumeBGM", bgmVolume);
                }
            }catch{
                bgmVolume = DataManager.LoadOrDefault<float>("VolumeBGM", bgmVolume);
            }
        }
        if(UsayaStorageManager.Exists(UsayaStorageFilename.Settings_Data, "VolumeSE")){
            seVolume = UsayaStorageManager.Load<float>(UsayaStorageFilename.Settings_Data, "VolumeSE");
        }else{
            try{
                if(StorageManager.Exists(StorageFilename.SettingsData, "VolumeSE")){
                    seVolume = StorageManager.Load<float>(StorageFilename.SettingsData, "VolumeSE");
                }else{
                    seVolume = DataManager.LoadOrDefault<float>("VolumeSE", seVolume);
                }
            }catch{
                seVolume = DataManager.LoadOrDefault<float>("VolumeSE", seVolume);
            }
        }
        if(UsayaStorageManager.Exists(UsayaStorageFilename.Settings_Data, "VolumeVoice")){
            voiceVolume = UsayaStorageManager.Load<float>(UsayaStorageFilename.Settings_Data, "VolumeVoice");
        }else{
            try{
                if(StorageManager.Exists(StorageFilename.SettingsData, "VolumeVoice")){
                    voiceVolume = StorageManager.Load<float>(StorageFilename.SettingsData, "VolumeVoice");
                }else{
                    voiceVolume = DataManager.LoadOrDefault<float>("VolumeVoice", voiceVolume);
                }
            }catch{
                voiceVolume = DataManager.LoadOrDefault<float>("VolumeVoice", voiceVolume);
            }
        }
#endif
    }
    public void SaveEnabledBGM(bool enabled){
        UsayaStorageManager.Save(UsayaStorageFilename.Settings_Data, "EnabledBGM", enabled);
        enabledBGM = enabled;
        if(bgmSource != null){
            bgmSource.mute = !enabledBGM;
        }
    }
    public void SaveEnabledSE(bool enabled){
        UsayaStorageManager.Save(UsayaStorageFilename.Settings_Data, "EnabledSE", enabled);
        enabledSE = enabled;
        seSourceList.ForEach(se => se.ChangeMute(!enabledSE));
    }

    public void SaveVolumeBGM(){
        SaveVolumeBGM(bgmVolume);
    }
    public void SaveVolumeBGM(float volume){
        UsayaStorageManager.Save(UsayaStorageFilename.Settings_Data, "VolumeBGM", volume);
        ChangeVolumeBGM(volume);
    }
    public void ChangeVolumeBGM(float volume){
        bgmVolume = volume;
        if(bgmSource != null){
            bgmSource.volume = uniqueBGMVolume * volume;
            bgmSource.mute = bgmSource.volume <= 0.0f;
        }
    }
    public void ChangeVolumeUniqueBGM(float volume){
        uniqueBGMVolume = volume;
        if(bgmSource != null){
            bgmSource.volume = uniqueBGMVolume * bgmVolume;
        }
    }
    public void SaveVolumeSE(){
        SaveVolumeSE(seVolume);
    }
    public void SaveVolumeSE(float volume){
        UsayaStorageManager.Save(UsayaStorageFilename.Settings_Data, "VolumeSE", volume);
        ChangeVolumeSE(volume);
    }
    public void ChangeVolumeSE(float volume){
        seVolume = volume;
        seSourceList.ForEach(se => se.ChangeVolume(volume));
        foreach(KeyValuePair<string, PlayingAudio> pair in seLoopSourceDic){
            pair.Value.ChangeVolume(volume);
        }
    }
    public void SaveVolumeVoice(){
        SaveVolumeVoice(voiceVolume);
    }
    public void SaveVolumeVoice(float volume){
        UsayaStorageManager.Save(UsayaStorageFilename.Settings_Data, "VolumeVoice", volume);
        ChangeVolumeVoice(volume);
    }
    public void ChangeVolumeVoice(float volume){
        voiceVolume = volume;
        voiceSourceList.ForEach(voice => voice.ChangeVolume(volume));
    }

    public void ChangeVolume(SoundType soundType, float volume){
        switch(soundType){
        case SoundType.BGM:
            ChangeVolumeBGM(volume);
            break;
        case SoundType.Voice:
            ChangeVolumeVoice(volume);
            break;
        default:
            ChangeVolumeSE(volume);
            break;
        }
    }

    void LateUpdate(){
        for(int i = seSourceList.Count - 1; i >= 0; --i){
            if(!seSourceList[i].IsPlaying()){
                seSourceList[i].PlayCallBack();
                seSourceList.RemoveAt(i);
            }
        }
        for(int i = voiceSourceList.Count - 1; i >= 0; --i){
            if(!voiceSourceList[i].IsPlaying()){
                voiceSourceList[i].PlayCallBack();
                voiceSourceList.RemoveAt(i);
            }
        }
    }

	public void PauseBGM() {
        if(bgmSource) bgmSource.Pause();
	}
	public void UnPauseBGM() {
		if(bgmSource) bgmSource.UnPause();
	}
	public void PauseTimeBGM() {
        if(bgmSource){
            pauseTime = bgmSource.time;
            bgmSource.Pause();
        }
	}
	public void UnPauseTimeBGM() {
        if(bgmSource){
            bgmSource.UnPause();
            bgmSource.time = pauseTime;
        }
    }

    public void ReplayBGM(){
        if(!bgmSource) return ;
        bgmSource.Stop();
        bgmSource.Play();
    }

    public void StopBGM(){
        if(!bgmSource) return ;
        bgmFilePath = "";
        bgmSource.Stop();
        bgmSource.clip = null;
    }
    public void StopSE(string filePath){
        var index = seSourceList.FindIndex(playing => playing.Name == filePath);
        if (index == -1) return;
        seSourceList[index].StopDestroy();
        seSourceList.RemoveAt(index);
    }
    public void StopSE(){
        foreach(PlayingAudio playing in seSourceList){
            playing.Stop();
        }
        seSourceList.Clear();
    }
    public void StopSELoop(){
        foreach(KeyValuePair<string, PlayingAudio> pair in seLoopSourceDic){
            pair.Value.StopDestroy();
        }
        seLoopSourceDic.Clear();
    }
    public void StopSELoop(string filePath){
        if(!seLoopSourceDic.ContainsKey(filePath)) return ;
        PlayingAudio audioSource = seLoopSourceDic[filePath];
        audioSource.StopDestroy();
        seLoopSourceDic.Remove(filePath);
    }
    public void StopVoice(){
        foreach(PlayingAudio playing in voiceSourceList){
            playing.Stop();
        }
        voiceSourceList.Clear();
    }
    public void StopAll(){
        StopBGM();
        StopSE();
        StopSELoop();
        StopVoice();
    }
    public void StopAllFade(float interval){
        FadeOut(interval, StopAll);
    }

    public void ChangeFadeBGM(string filePath, float interval, float volume = 1.0f, bool loop = true){
        if(bgmFilePath.Contains(filePath)) return ;
        if(bgmFilePath.Length > 0){
            FadeOut(interval * 0.5f, () => {
                PlayBGM(filePath, volume);
                FadeIn(interval * 0.5f, ActionExtensions.EmptyAction);
            });
        }else{
            PlayBGM(filePath, volume, loop);
        }
    }
    public void PlayBGM(string filePath, float volume = 1.0f, bool loop = true){
        if(bgmFilePath.Contains(filePath)) return ;
        bgmFilePath = filePath;
        PlayBGM(LoadAudioClip(filePath, SoundType.BGM), volume, loop);
    }
    public void PlayBGM(AudioClip clip, float volume = 1.0f, bool loop = true){
        if(bgmSource == null){
            bgmSource = gameObject.AddComponent<AudioSource>();
        }
        if(bgmSource.clip == clip) return ;

        uniqueBGMVolume = volume;
        pauseTime = 0.0f;
        bgmSource.time = 0.0f;
        bgmSource.Stop();
        bgmSource.clip = clip;
        bgmSource.loop = loop;
        bgmSource.volume = uniqueBGMVolume * bgmVolume;
        bgmSource.mute = !enabledBGM;
        bgmSource.Play();

        bgmSource.outputAudioMixerGroup = defaultBGMAudioMixer;
    }

    public void PlaySE(string filePath, int maxCount = 0, float volume = 1.0f, float pitch = 1.0f){
        PlaySE(LoadAudioClip(filePath, SoundType.SE), maxCount, volume, pitch);
    }
    public void PlaySE(AudioClip clip, int maxCount = 0, float volume = 1.0f, float pitch = 1.0f){
        if(clip == null) return ;
        PlaySE(clip, ActionExtensions.EmptyAction, maxCount, volume, pitch);
    }
    public void PlaySE(string filePath, Action callBack, int maxCount = 0, float volume = 1.0f){
        PlaySE(LoadAudioClip(filePath, SoundType.SE), callBack, maxCount, volume);
    }
    public void PlaySE(AudioClip clip, Action callBack, int maxCount = 0, float volume = 1.0f, float pitch = 1.0f){
        if(clip == null || isPausing) return ;
        PlaySE(clip, maxCount, volume, pitch, callBack, seSourceList);
    }
    private void PlaySE(AudioClip clip, int maxCount, float volume, float pitch, Action callBack, List<PlayingAudio> list){
        string clipName = clip.name;
        var result = FindCountOverPlayingAudio(clipName, maxCount, list);
        if(result != null){
            result.audioSource.Stop();
            result.audioSource.Play();
        }else{
            AudioSource source = CreateAudioSource(clip, volume * seVolume, pitch, !enabledSE, defaultSEAudioMixer);
            list.Add(new PlayingAudio(clipName, source, volume, callBack));
        }
    }

    public AudioSource PlaySELoop(string filePath, float volume = 1.0f, float pitch = 1.0f){
        if(seLoopSourceDic.ContainsKey(filePath)) return seLoopSourceDic[filePath].audioSource;
        AudioClip clip = LoadAudioClip(filePath, SoundType.SE);
        AudioSource source = CreateAudioSource(clip, volume * seVolume, pitch, !enabledSE, defaultSEAudioMixer);
        source.loop = true;
        seLoopSourceDic.Add(filePath, new PlayingAudio(clip.name, source, volume, ActionExtensions.EmptyAction));
        return source;
    }

    public void PlayVoice(string filePath, int maxCount = 0, float volume = 1.0f, float pitch = 1.0f){
        PlayVoice(LoadAudioClip(filePath, SoundType.Voice), maxCount, volume, pitch);
    }
    public void PlayVoice(AudioClip clip, int maxCount = 0, float volume = 1.0f, float pitch = 1.0f){
        if(clip == null) return ;
        PlayVoice(clip, ActionExtensions.EmptyAction, maxCount, volume, pitch);
    }
    public void PlayVoice(string filePath, Action callBack, int maxCount = 0, float volume = 1.0f){
        PlayVoice(LoadAudioClip(filePath, SoundType.Voice), callBack, maxCount, volume);
    }
    public void PlayVoice(AudioClip clip, Action callBack, int maxCount = 0, float volume = 1.0f, float pitch = 1.0f){
        if(clip == null || isPausing){
            callBack();
        }else{
            PlayVoice(clip, maxCount, volume, pitch, callBack, voiceSourceList);
        }
    }
    private void PlayVoice(AudioClip clip, int maxCount, float volume, float pitch, Action callBack, List<PlayingAudio> list){
        string clipName = clip.name;
        var result = FindCountOverPlayingAudio(clipName, maxCount, list);
        if(result != null){
            result.audioSource.Stop();
            result.audioSource.Play();
        }else{
            AudioSource source = CreateAudioSource(clip, volume * voiceVolume, pitch, !enabledVoice, defaultVoiceAudioMixer);
            list.Add(new PlayingAudio(clipName, source, volume, callBack));
        }
    }
    private PlayingAudio FindCountOverPlayingAudio(string clipName, int maxCount, List<PlayingAudio> list){
        if(maxCount > 0){
            int counter = 0;
            foreach(var pa in list){
                if(pa.Name == clipName && ++counter >= maxCount){
                    foreach(var p in list){
                        if(p.Name == clipName){
                            return p;
                        }
                    }
                }
            }
        }
        return null;
    }
    private AudioSource CreateAudioSource(AudioClip clip, float volume, float pitch, bool mute, AudioMixerGroup audioMixerGroup){
        AudioSource source = gameObject.AddComponent<AudioSource>();
        source.Stop();
        source.clip = clip;
        source.volume = volume;
        source.pitch = pitch;
        source.mute = mute;
        source.Play();
        source.outputAudioMixerGroup = defaultVoiceAudioMixer;
        return source;
    }

    public class PlayingAudio {
        private string name;
        public string Name {
            get{ return name; }
        }
        public AudioSource audioSource = null;
        private Action callBack = null;
        private float uniqueVolume;

        public PlayingAudio(string name, AudioSource audioSource, float uniqueVolume, Action callBack){
            this.name = name;
            this.audioSource = audioSource;
            this.uniqueVolume = uniqueVolume;
            this.callBack = callBack;
        }

        public bool IsPlaying(){
            return audioSource.isPlaying;
        }

        public void PlayCallBack(){
            callBack();
            Destroy(audioSource);
        }

        public void Stop(){
            audioSource.Stop();
        }
        public void StopDestroy(){
            audioSource.Stop();
            Destroy(audioSource);
        }

        public void ChangeVolume(float volume){
            audioSource.volume = volume * uniqueVolume;
        }
        public void ChangeMute(bool mute){
            audioSource.mute = mute;
        }
    }

    private Dictionary<string, AudioClip> audioClipDict = new Dictionary<string, AudioClip>();
    private AudioClip LoadAudioClip(string filePath, SoundType soundType){
        if(!filePath.Contains("/")){
            switch(soundType){
            case SoundType.BGM:     filePath = "Sounds/BGM/" + filePath; break;
            case SoundType.SE:      filePath = "Sounds/SE/" + filePath; break;
            case SoundType.Voice:   filePath = "Sounds/Voice/" + filePath; break;
            }
        }
        AudioClip result = null;
        if(!audioClipDict.TryGetValue(filePath, out result)){
            result = Resources.Load(filePath, typeof(AudioClip)) as AudioClip;
#if UNITY_EDITOR
            if(result == null){
                Debug.LogError("鳴らそうとしているファイル(" + filePath + ")が存在しません。");
                UnityEditor.EditorApplication.isPlaying = false;
            }
#endif
            audioClipDict.Add(filePath, result);
        }
        return result;
    }

    private void UpdateMute(){
        if(bgmSource != null){
            bgmSource.mute = !enabledBGM;
        }
        seSourceList.ForEach(se => se.ChangeMute(!enabledSE));
        voiceSourceList.ForEach(voice => voice.ChangeMute(!enabledVoice));
    }

    public void ChangeBGMAudioMixerGroup(AudioMixerGroup amg){
        bgmSource.outputAudioMixerGroup = amg;
    }
    public void ResetBGMAudioMixerGroup(){
        bgmSource.outputAudioMixerGroup = defaultBGMAudioMixer;
    }

    private bool isPause = false;
    void OnApplicationPause(bool pauseStatus){
        if(!bgmSource) return ;
        if(pauseStatus){
            isPause = true;
            bgmSource.volume = 0.0f;
            PauseBGM();
            isPausing = true;
        }else if(isPause){
            isPause = false;
            StartCoroutine(ReturnPause());
        }
    }

    private bool isPausing = false;
    private IEnumerator ReturnPause(){
        int i = 0;
        // 素通りだと起動時にFadeIn後のVolumeから開始される
		int waitFrameCounter = 20;
        while(i++ < waitFrameCounter){
            yield return null;
        }
        isPausing = false;
        UnPauseBGM();
        FadeIn(1.0f, ActionExtensions.EmptyAction);
    }
    
    private void FadeIn(float duration, Action callback){
        Fade(duration, lerp => Mathf.Lerp(0.0f, 1.0f, lerp * lerp), callback);
    }
    private void FadeOut(float duration, Action callback){
        Fade(duration, lerp => 1.0f - Mathf.Lerp(0.0f, 1.0f, lerp * lerp), callback);
    }
    private void Fade(float duration, Func<float, float> lerpToVolume, Action callback){
        this.CallLerpSmooth(duration, lerp => {
            float v = lerpToVolume(lerp);
            bgmSource.volume = v * uniqueBGMVolume * bgmVolume;
            float se = v * seVolume;
            foreach(PlayingAudio playing in seSourceList){
                playing.ChangeVolume(se);
            }
            foreach(KeyValuePair<string, PlayingAudio> pair in seLoopSourceDic){
                pair.Value.ChangeVolume(se);
            }
            float voice = v * voiceVolume;
            foreach(PlayingAudio playing in voiceSourceList){
                playing.ChangeVolume(voice);
            }
        }, callback);
    }
}
