using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Sdl;
using StarEngine.Core;

namespace StarEngine.Sdl
{
    internal sealed class Audio : IAudio, IDisposable
    {
        public static Audio Create()
        {
            if (Instance != null)
                throw new InvalidOperationException("Audio オブジェクトは同時に二つ生成できません。");   
            return Instance = new Audio();
        }

        private static Audio Instance;

        private Audio()
        {
            if (SDL.Mix_OpenAudio(SDL.MIX_DEFAULT_FREQUENCY,
                                  SDL.MIX_DEFAULT_FORMAT, 2, 1024) < 0)
                throw new StarEngineException(SDL.Mix_GetError());
            SDL.Mix_AllocateChannels(8);
            this.BgmVolume = 255;
        }

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        ~Audio()
        {
            this.Dispose(false);
        }

        private void Dispose(bool disposing)
        {
            if (!this.isDisposed)
            {
                this.IsDisposed = true;
                foreach (IntPtr sdlBgm in this.BgmCache.Values)
                    SDL.Mix_FreeMusic(sdlBgm);
                foreach (IntPtr sdlSE in this.SECache.Values)
                    SDL.Mix_FreeChunk(sdlSE);
                SDL.Mix_CloseAudio();
            }
        }

        public bool IsDisposed
        {
            get { return this.isDisposed; }
            private set { this.isDisposed = value; }
        }
        private bool isDisposed = false;

        private Dictionary<string, IntPtr> BgmCache = new Dictionary<string, IntPtr>();

        public void PlayBgm(string path, int fadeInTime, byte volume, bool isLoop)
        {
            if (!this.BgmCache.ContainsKey(path))
            {
                if (!File.Exists(path))
                    throw new FileNotFoundException("file not found", path);

                IntPtr sdlPointer = SDL.Mix_LoadMUS(path);

                if (sdlPointer == IntPtr.Zero)
                    throw new StarEngineException(SDL.Mix_GetError());

                this.BgmCache[path] = sdlPointer;
            }

            this.BgmVolume = volume;
            if (fadeInTime == 0)
                SDL.Mix_PlayMusic(this.BgmCache[path], isLoop ? -1 : 0);
            else
                SDL.Mix_FadeInMusic(this.BgmCache[path], isLoop ? -1 : 0, fadeInTime);
        }

        public void StopBgm(int fadeOutTime)
        {
            if (fadeOutTime == 0)
                SDL.Mix_HaltMusic();
            else
                SDL.Mix_FadeOutMusic(fadeOutTime);
        }

        public bool IsPlayingBgm
        {
            get { return SDL.Mix_PlayingMusic() != 0; }
        }

        public byte BgmVolume
        {
            get { return this.bgmVolume; }
            set
            {
                int sdlVolume = (int)((double)value / byte.MaxValue * SDL.MIX_MAX_VOLUME);
                SDL.Mix_VolumeMusic(sdlVolume);
                this.bgmVolume = value;
            }
        }
        private byte bgmVolume;

        private Dictionary<string, IntPtr> SECache = new Dictionary<string, IntPtr>();

        public void PlaySE(string path, int fadeInTime, byte volume, byte panning)
        {
            if (!this.SECache.ContainsKey(path))
            {
                if (!File.Exists(path))
                    throw new FileNotFoundException("file not found", path);

                IntPtr sdlPointer = SDL.Mix_LoadWAV(path);
                if (sdlPointer == IntPtr.Zero)
                    throw new StarEngineException(SDL.Mix_GetError());
                this.SECache[path] = sdlPointer;
            }

            int channel;
            if (fadeInTime == 0)
                channel = SDL.Mix_PlayChannel(-1, this.SECache[path], 0);
            else
                channel = SDL.Mix_FadeInChannel(-1, this.SECache[path], 0, fadeInTime);
            if (channel == -1)
                return;

            int sdlVolume = (int)((double)volume / byte.MaxValue * SDL.MIX_MAX_VOLUME);
            SDL.Mix_Volume(channel, sdlVolume);
            if (panning < 127)
                SDL.Mix_SetPanning(channel, 255, (byte)(panning * 2));
            else if (128 < panning)
                SDL.Mix_SetPanning(channel, (byte)(254 - (panning - 128) * 2), 255);
            else
                SDL.Mix_SetPanning(channel, 255, 255);
        }

        public void StopAllSEs(int fadeOutTime)
        {
            if (fadeOutTime == 0)
                SDL.Mix_HaltChannel(-1);
            else
                SDL.Mix_FadeOutChannel(-1, fadeOutTime);
        }
    }
}
