using System;
using OpenTK.Audio.OpenAL;
using Microsoft.Xna.Framework;
using System.IO;

namespace Barotrauma.Sounds
{
    public abstract class Sound : IDisposable
    {
        protected bool disposed;
        public bool Disposed
        {
            get { return disposed; }
        }

        public SoundManager Owner
        {
            get;
            protected set;
        }

        public string Filename
        {
            get;
            protected set;
        }

        public bool Stream
        {
            get;
            protected set;
        }

        public bool StreamsReliably
        {
            get;
            protected set;
        }

        public virtual SoundManager.SourcePoolIndex SourcePoolIndex
        {
            get
            {
                return SoundManager.SourcePoolIndex.Default;
            }
        }

        private uint alBuffer;
        public uint ALBuffer
        {
            get { return !Stream ? alBuffer : 0; }
        }

        private uint alMuffledBuffer;
        public uint ALMuffledBuffer
        {
            get { return !Stream ? alMuffledBuffer : 0; }
        }

        public ALFormat ALFormat
        {
            get;
            protected set;
        }

        public int SampleRate
        {
            get;
            protected set;
        }

        /// <summary>
        /// How many instances of the same sound clip can be playing at the same time
        /// </summary>
        public int MaxSimultaneousInstances = 5;
        
        public float BaseGain;
        public float BaseNear;
        public float BaseFar;

        public Sound(SoundManager owner, string filename, bool stream, bool streamsReliably)
        {
            Owner = owner;
            Filename = Path.GetFullPath(filename);
            Stream = stream;
            StreamsReliably = streamsReliably;

            BaseGain = 1.0f;
            BaseNear = 100.0f;
            BaseFar = 200.0f;
            
            if (!stream)
            {
                AL.GenBuffer(out alBuffer);
                ALError alError = AL.GetError();
                if (alError != ALError.NoError)
                {
                    throw new Exception("Failed to create OpenAL buffer for non-streamed sound: " + AL.GetErrorString(alError));
                }

                if (!AL.IsBuffer(alBuffer))
                {
                    throw new Exception("Generated OpenAL buffer is invalid!");
                }

                AL.GenBuffer(out alMuffledBuffer);
                alError = AL.GetError();
                if (alError != ALError.NoError)
                {
                    throw new Exception("Failed to create OpenAL buffer for non-streamed sound: " + AL.GetErrorString(alError));
                }
                
                if (!AL.IsBuffer(alMuffledBuffer))
                {
                    throw new Exception("Generated OpenAL buffer is invalid!");
                }
            }
            else
            {
                alBuffer = 0;
            }
        }

        public override string ToString()
        {
            return GetType().ToString() + " (" + Filename + ")";
        }

        public virtual bool IsPlaying()
        {
            return Owner.IsPlaying(this);
        }

        public virtual SoundChannel Play(float gain, float range, Vector2 position, bool muffle = false)
        {
            return new SoundChannel(this, gain, new Vector3(position.X, position.Y, 0.0f), range * 0.4f, range, "default", muffle);
        }

        public virtual SoundChannel Play(Vector3? position, float gain, bool muffle = false)
        {
            return new SoundChannel(this, gain, position, BaseNear, BaseFar, "default", muffle);
        }

        public virtual SoundChannel Play(float gain)
        {
            return Play(null, gain);
        }

        public virtual SoundChannel Play()
        {
            return Play(BaseGain);
        }

        public virtual SoundChannel Play(float? gain, string category)
        {
            if (Owner.CountPlayingInstances(this) >= MaxSimultaneousInstances) { return null; }
            return new SoundChannel(this, gain ?? BaseGain, null, BaseNear, BaseFar, category);
        }

        static protected void CastBuffer(float[] inBuffer, short[] outBuffer, int length)
        {
            for (int i = 0; i < length; i++)
            {
                outBuffer[i] = FloatToShort(inBuffer[i]);
            }
        }

        static protected short FloatToShort(float fVal)
        {
            int temp = (int)(32767 * fVal);
            if (temp > short.MaxValue) temp = short.MaxValue;
            else if (temp < short.MinValue) temp = short.MinValue;
            return (short)temp;
        }
        static protected float ShortToFloat(short shortVal)
        {
            return shortVal / 32767f;
        }

        public abstract int FillStreamBuffer(int samplePos, short[] buffer);

        public virtual void Dispose()
        {
            if (disposed) { return; }

            Owner.KillChannels(this);
            if (alBuffer != 0)
            {
                if (!AL.IsBuffer(alBuffer))
                {
                    throw new Exception("Buffer to delete is invalid!");
                }
            
                AL.DeleteBuffer(ref alBuffer); alBuffer = 0;

                ALError alError = AL.GetError();
                if (alError != ALError.NoError)
                {
                    throw new Exception("Failed to delete OpenAL buffer for non-streamed sound: " + AL.GetErrorString(alError));
                }
            }
            if (alMuffledBuffer != 0)
            {
                if (!AL.IsBuffer(alMuffledBuffer))
                {
                    throw new Exception("Buffer to delete is invalid!");
                }

                AL.DeleteBuffer(ref alMuffledBuffer); alMuffledBuffer = 0;

                ALError alError = AL.GetError();
                if (alError != ALError.NoError)
                {
                    throw new Exception("Failed to delete OpenAL buffer for non-streamed sound: " + AL.GetErrorString(alError));
                }
            }

            Owner.RemoveSound(this);
            disposed = true;
        }
    }
}

