using System;
using System.Threading;
using System.Collections.Generic;
using System.Xml.Linq;
using OpenAL;
using Microsoft.Xna.Framework;
using System.Linq;
using Barotrauma.IO;

namespace Barotrauma.Sounds
{
    class SoundManager : IDisposable
    {
        public const int SOURCE_COUNT = 32;

        public bool Disabled
        {
            get;
            private set;
        }
        
        private IntPtr alcDevice;
        private IntPtr alcContext;
        
        public enum SourcePoolIndex
        {
            Default = 0,
            Voice = 1
        }
        private readonly SoundSourcePool[] sourcePools;
        
        private readonly List<Sound> loadedSounds;
        public IReadOnlyList<Sound> LoadedSounds => loadedSounds;

        private readonly SoundChannel[][] playingChannels = new SoundChannel[2][];
        private readonly object threadDeathMutex = new object();

        public bool CanDetectDisconnect { get; private set; }

        public bool Disconnected { get; private set; }

        private Thread streamingThread;

        private Vector3 listenerPosition;
        public Vector3 ListenerPosition
        {
            get { return listenerPosition; }
            set
            {
                if (Disabled) { return; }
                listenerPosition = value;
                Al.Listener3f(Al.Position,value.X,value.Y,value.Z);
                int alError = Al.GetError();
                if (alError != Al.NoError)
                {
                    throw new Exception("Failed to set listener position: " + Al.GetErrorString(alError));
                }
            }
        }

        private readonly float[] listenerOrientation = new float[6];
        public Vector3 ListenerTargetVector
        {
            get { return new Vector3(listenerOrientation[0], listenerOrientation[1], listenerOrientation[2]); }
            set
            {
                if (Disabled) { return; }
                listenerOrientation[0] = value.X; listenerOrientation[1] = value.Y; listenerOrientation[2] = value.Z;
                Al.Listenerfv(Al.Orientation, listenerOrientation);
                int alError = Al.GetError();
                if (alError != Al.NoError)
                {
                    throw new Exception("Failed to set listener target vector: " + Al.GetErrorString(alError));
                }
            }
        }
        public Vector3 ListenerUpVector
        {
            get { return new Vector3(listenerOrientation[3], listenerOrientation[4], listenerOrientation[5]); }
            set
            {
                if (Disabled) { return; }
                listenerOrientation[3] = value.X; listenerOrientation[4] = value.Y; listenerOrientation[5] = value.Z;
                Al.Listenerfv(Al.Orientation, listenerOrientation);
                int alError = Al.GetError();
                if (alError != Al.NoError)
                {
                    throw new Exception("Failed to set listener up vector: " + Al.GetErrorString(alError));
                }
            }
        }

        private float listenerGain;
        public float ListenerGain
        {
            get { return listenerGain; }
            set
            {
                if (Disabled) { return; }
                if (Math.Abs(ListenerGain - value) < 0.001f) { return; }
                listenerGain = value;
                Al.Listenerf(Al.Gain, listenerGain);
                int alError = Al.GetError();
                if (alError != Al.NoError)
                {
                    throw new Exception("Failed to set listener gain: " + Al.GetErrorString(alError));
                }
            }
        }
        
        public float PlaybackAmplitude
        {
            get
            {
                if (Disabled) { return 0.0f; }
                float aggregateAmplitude = 0.0f;
                //NOTE: this is obviously not entirely accurate;
                //It assumes a linear falloff model, and assumes that audio
                //is simply added together to produce the final result.
                //Adjustments may be needed under certain scenarios.
                for (int i = 0; i < 2; i++)
                {
                    foreach (SoundChannel soundChannel in playingChannels[i].Where(ch => ch != null))
                    {
                        float amplitude = soundChannel.CurrentAmplitude;
                        amplitude *= soundChannel.Gain;
                        float dist = Vector3.Distance(ListenerPosition, soundChannel.Position ?? ListenerPosition);
                        if (dist > soundChannel.Near)
                        {
                            amplitude *= 1.0f - Math.Min(1.0f, (dist - soundChannel.Near) / (soundChannel.Far - soundChannel.Near));
                        }
                        aggregateAmplitude += amplitude;
                    }
                }
                return aggregateAmplitude;
            }
        }

        public float CompressionDynamicRangeGain { get; private set; }

        private float voipAttenuatedGain;
        private double lastAttenuationTime;
        public float VoipAttenuatedGain
        {
            get { return voipAttenuatedGain; }
            set
            {
                lastAttenuationTime = Timing.TotalTime;
                voipAttenuatedGain = value;
            }
        }

        public int LoadedSoundCount
        {
            get { return loadedSounds.Count; }
        }
        public int UniqueLoadedSoundCount
        {
            get { return loadedSounds.Select(s => s.Filename).Distinct().Count(); }
        }

        private class CategoryModifier
        {
            public float[] GainMultipliers;
            public bool Muffle;

            public CategoryModifier(int gainMultiplierIndex, float gain, bool muffle)
            {
                Muffle = muffle;
                GainMultipliers = new float[gainMultiplierIndex + 1];
                for (int i = 0; i < GainMultipliers.Length; i++)
                {
                    if (i == gainMultiplierIndex)
                    {
                        GainMultipliers[i] = gain;
                    }
                    else
                    {
                        GainMultipliers[i] = 1.0f;
                    }
                }
            }

            public void SetGainMultiplier(int index, float gain)
            {
                if (GainMultipliers.Length < index + 1)
                {
                    int oldLength = GainMultipliers.Length;
                    Array.Resize(ref GainMultipliers, index + 1);
                    for (int i = oldLength; i < GainMultipliers.Length; i++)
                    {
                        GainMultipliers[i] = 1.0f;
                    }
                }
                GainMultipliers[index] = gain;
            }
        }

        private readonly Dictionary<string, CategoryModifier> categoryModifiers = new Dictionary<string, CategoryModifier>();

        public SoundManager()
        {
            loadedSounds = new List<Sound>();
            streamingThread = null;

            sourcePools = new SoundSourcePool[2];
            playingChannels[(int)SourcePoolIndex.Default] = new SoundChannel[SOURCE_COUNT];
            playingChannels[(int)SourcePoolIndex.Voice] = new SoundChannel[16];

            string deviceName = GameSettings.CurrentConfig.Audio.AudioOutputDevice;

            if (string.IsNullOrEmpty(deviceName))
            {
                deviceName = Alc.GetString((IntPtr)null, Alc.DefaultDeviceSpecifier);
            }

#if (!OSX)
            var audioDeviceNames = Alc.GetStringList((IntPtr)null, Alc.AllDevicesSpecifier);
            if (audioDeviceNames.Any() && !audioDeviceNames.Any(n => n.Equals(deviceName, StringComparison.OrdinalIgnoreCase)))
            {
                deviceName = audioDeviceNames[0];
            }
#endif
            if (GameSettings.CurrentConfig.Audio.AudioOutputDevice != deviceName)
            {
                SetAudioOutputDevice(deviceName);
            }

            InitializeAlcDevice(deviceName);

            ListenerPosition = Vector3.Zero;
            ListenerTargetVector = new Vector3(0.0f, 0.0f, 1.0f);
            ListenerUpVector = new Vector3(0.0f, -1.0f, 0.0f);

            CompressionDynamicRangeGain = 1.0f;
        }

        private static void SetAudioOutputDevice(string deviceName)
        {
            var config = GameSettings.CurrentConfig;
            config.Audio.AudioOutputDevice = deviceName;
            GameSettings.SetCurrentConfig(config);
        }
        
        public bool InitializeAlcDevice(string deviceName)
        {
            ReleaseResources(true);

            DebugConsole.NewMessage($"Attempting to open ALC device \"{deviceName}\"");

            alcDevice = IntPtr.Zero;
            int alcError;
            for (int i = 0; i < 3; i++)
            {
                alcDevice = Alc.OpenDevice(deviceName);
                if (alcDevice == IntPtr.Zero)
                {
                    alcError = Alc.GetError(IntPtr.Zero);
                    DebugConsole.NewMessage($"ALC device initialization attempt #{i + 1} failed: device is null (error code {Alc.GetErrorString(alcError)})");
                    if (!string.IsNullOrEmpty(deviceName))
                    {
                        deviceName = null;
                        DebugConsole.NewMessage($"Switching to default device...");
                    }
                }
                else
                {
                    alcError = Alc.GetError(alcDevice);
                    if (alcError != Alc.NoError)
                    {
                        DebugConsole.NewMessage($"ALC device initialization attempt #{i + 1} failed: error code {Alc.GetErrorString(alcError)}");
                        bool closed = Alc.CloseDevice(alcDevice);
                        if (!closed)
                        {
                            DebugConsole.NewMessage($"Failed to close ALC device");
                        }
                        alcDevice = IntPtr.Zero;
                    }
                    else
                    {
                        break;
                    }
                }
            }
            if (alcDevice == IntPtr.Zero)
            {
                DebugConsole.ThrowError("ALC device creation failed too many times!");
                Disabled = true;
                return false;
            }

            CanDetectDisconnect = Alc.IsExtensionPresent(alcDevice, "ALC_EXT_disconnect");
            alcError = Alc.GetError(alcDevice);
            if (alcError != Alc.NoError)
            {
                DebugConsole.ThrowError("Error determining if disconnect can be detected: " + alcError.ToString() + ". Disabling audio playback...");
                Disabled = true;
                return false;
            }

            Disconnected = false;

            int[] alcContextAttrs = new int[] { };
            alcContext = Alc.CreateContext(alcDevice, alcContextAttrs);
            if (alcContext == null)
            {
                DebugConsole.ThrowError("Failed to create an ALC context! (error code: " + Alc.GetError(alcDevice).ToString() + "). Disabling audio playback...");
                Disabled = true;
                return false;
            }

            if (!Alc.MakeContextCurrent(alcContext))
            {
                DebugConsole.ThrowError("Failed to assign the current ALC context! (error code: " + Alc.GetError(alcDevice).ToString() + "). Disabling audio playback...");
                Disabled = true;
                return false;
            }

            alcError = Alc.GetError(alcDevice);
            if (alcError != Alc.NoError)
            {
                DebugConsole.ThrowError("Error after assigning ALC context: " + Alc.GetErrorString(alcError) + ". Disabling audio playback...");
                Disabled = true;
                return false;
            }

            Al.DistanceModel(Al.LinearDistanceClamped);

            int alError = Al.GetError();
            if (alError != Al.NoError)
            {
                DebugConsole.ThrowError("Error setting distance model: " + Al.GetErrorString(alError) + ". Disabling audio playback...");
                Disabled = true;
                return false;
            }

            sourcePools[(int)SourcePoolIndex.Default] = new SoundSourcePool(SOURCE_COUNT);
            sourcePools[(int)SourcePoolIndex.Voice] = new SoundSourcePool(16);

            ReloadSounds();

            Disabled = false;

            return true;
        }

        public Sound LoadSound(string filename, bool stream = false)
        {
            if (Disabled) { return null; }

            if (!File.Exists(filename))
            {
                throw new System.IO.FileNotFoundException("Sound file \"" + filename + "\" doesn't exist!");
            }

            Sound newSound = new OggSound(this, filename, stream, null);
            lock (loadedSounds)
            {
                loadedSounds.Add(newSound);
            }
            return newSound;
        }

        public Sound LoadSound(ContentXElement element, bool stream = false, string overrideFilePath = null)
        {
            if (Disabled) { return null; }

            string filePath = overrideFilePath ?? element.GetAttributeContentPath("file")?.Value ?? "";
            if (!File.Exists(filePath))
            {
                throw new System.IO.FileNotFoundException($"Sound file \"{filePath}\" doesn't exist! Content package \"{(element.ContentPackage?.Name ?? "Unknown")}\".");
            }

            var newSound = new OggSound(this, filePath, stream, xElement: element)
            {
                BaseGain = element.GetAttributeFloat("volume", 1.0f)
            };
            float range = element.GetAttributeFloat("range", 1000.0f);
            newSound.BaseNear = range * 0.4f;
            newSound.BaseFar = range;

            lock (loadedSounds)
            {
                loadedSounds.Add(newSound);
            }
            return newSound;
        }

        public SoundChannel GetSoundChannelFromIndex(SourcePoolIndex poolIndex, int ind)
        {
            if (Disabled || ind < 0 || ind >= playingChannels[(int)poolIndex].Length) return null;
            return playingChannels[(int)poolIndex][ind];
        }

        public uint GetSourceFromIndex(SourcePoolIndex poolIndex, int srcInd)
        {
            if (Disabled || srcInd < 0 || srcInd >= sourcePools[(int)poolIndex].ALSources.Length) return 0;

            if (!Al.IsSource(sourcePools[(int)poolIndex].ALSources[srcInd]))
            {
                throw new Exception("alSources[" + srcInd.ToString() + "] is invalid!");
            }

            return sourcePools[(int)poolIndex].ALSources[srcInd];
        }

        public int AssignFreeSourceToChannel(SoundChannel newChannel)
        {
            if (Disabled) { return -1; }

            //remove a channel that has stopped
            //or hasn't even been assigned
            int poolIndex = (int)newChannel.Sound.SourcePoolIndex;

            lock (playingChannels[poolIndex])
            {
                for (int i = 0; i < playingChannels[poolIndex].Length; i++)
                {
                    if (playingChannels[poolIndex][i] == null || !playingChannels[poolIndex][i].IsPlaying)
                    {
                        if (playingChannels[poolIndex][i] != null) { playingChannels[poolIndex][i].Dispose(); }
                        playingChannels[poolIndex][i] = newChannel;
                        return i;
                    }
                }
            }

            //we couldn't get a free source to assign to this channel!
            return -1;
        }

#if DEBUG
        public void DebugSource(int ind)
        {
            for (int i = 0; i < sourcePools[0].ALSources.Length; i++)
            {
                Al.Sourcef(sourcePools[0].ALSources[i], Al.MaxGain, i == ind ? 1.0f : 0.0f);
                Al.Sourcef(sourcePools[0].ALSources[i], Al.MinGain, 0.0f);
            }
        }
#endif

        public bool IsPlaying(Sound sound)
        {
            if (Disabled) { return false; }
            lock (playingChannels[(int)sound.SourcePoolIndex])
            {
                for (int i = 0; i < playingChannels[(int)sound.SourcePoolIndex].Length; i++)
                {
                    if (playingChannels[(int)sound.SourcePoolIndex][i] != null && 
                        playingChannels[(int)sound.SourcePoolIndex][i].Sound == sound)
                    {
                        if (playingChannels[(int)sound.SourcePoolIndex][i].IsPlaying) return true;
                    }
                }
            }
            return false;
        }

        public int CountPlayingInstances(Sound sound)
        {
            if (Disabled) { return 0; }
            int count = 0;
            lock (playingChannels[(int)sound.SourcePoolIndex])
            {
                for (int i = 0; i < playingChannels[(int)sound.SourcePoolIndex].Length; i++)
                {
                    if (playingChannels[(int)sound.SourcePoolIndex][i] != null && 
                        playingChannels[(int)sound.SourcePoolIndex][i].Sound.Filename == sound.Filename)
                    {
                        if (playingChannels[(int)sound.SourcePoolIndex][i].IsPlaying) { count++; };
                    }
                }
            }
            return count;
        }

        public SoundChannel GetChannelFromSound(Sound sound)
        {
            if (Disabled) { return null; }
            lock (playingChannels[(int)sound.SourcePoolIndex])
            {
                for (int i = 0; i < playingChannels[(int)sound.SourcePoolIndex].Length; i++)
                {
                    if (playingChannels[(int)sound.SourcePoolIndex][i] != null && 
                        playingChannels[(int)sound.SourcePoolIndex][i].Sound == sound)
                    {
                        if (playingChannels[(int)sound.SourcePoolIndex][i].IsPlaying) return playingChannels[(int)sound.SourcePoolIndex][i];
                    }
                }
            }
            return null;
        }

        public void KillChannels(Sound sound)
        {
            if (Disabled) { return; }
            lock (playingChannels[(int)sound.SourcePoolIndex])
            {
                for (int i = 0; i < playingChannels[(int)sound.SourcePoolIndex].Length; i++)
                {
                    if (playingChannels[(int)sound.SourcePoolIndex][i]!=null && playingChannels[(int)sound.SourcePoolIndex][i].Sound == sound)
                    {
                        playingChannels[(int)sound.SourcePoolIndex][i]?.Dispose();
                        playingChannels[(int)sound.SourcePoolIndex][i] = null;
                    }
                }
            }
        }

        public void RemoveSound(Sound sound)
        {
            lock (loadedSounds)
            {
                for (int i = 0; i < loadedSounds.Count; i++)
                {
                    if (loadedSounds[i] == sound)
                    {
                        loadedSounds.RemoveAt(i);
                        return;
                    }
                }
            }
        }

        public void MoveSoundToPosition(Sound sound, int pos)
        {
            lock (loadedSounds)
            {
                int index = loadedSounds.IndexOf(sound);
                if (index >= 0)
                {
                    loadedSounds.SiftElement(index, pos);
                }
            }
        }

        public void SetCategoryGainMultiplier(string category, float gain, int index=0)
        {
            if (Disabled) { return; }
            category = category.ToLower();
            lock (categoryModifiers)
            {
                if (!categoryModifiers.ContainsKey(category))
                {
                    categoryModifiers.Add(category, new CategoryModifier(index, gain, false));
                }
                else
                {
                    categoryModifiers[category].SetGainMultiplier(index, gain);
                }
            }

            for (int i = 0; i < playingChannels.Length; i++)
            {
                lock (playingChannels[i])
                {
                    for (int j = 0; j < playingChannels[i].Length; j++)
                    {
                        if (playingChannels[i][j] != null && playingChannels[i][j].IsPlaying)
                        {
                            playingChannels[i][j].Gain = playingChannels[i][j].Gain; //force all channels to recalculate their gain
                        }
                    }
                }
            }
        }

        public float GetCategoryGainMultiplier(string category, int index = -1)
        {
            if (Disabled) { return 0.0f; }
            category = category.ToLower();
            lock (categoryModifiers)
            {
                if (categoryModifiers == null || !categoryModifiers.TryGetValue(category, out CategoryModifier categoryModifier)) { return 1.0f; }
                if (index < 0)
                {
                    float accumulatedMultipliers = 1.0f;
                    for (int i = 0; i < categoryModifier.GainMultipliers.Length; i++)
                    {
                        accumulatedMultipliers *= categoryModifier.GainMultipliers[i];
                    }
                    return accumulatedMultipliers;
                }
                else
                {
                    return categoryModifier.GainMultipliers[index];
                }
            }
        }

        public void SetCategoryMuffle(string category, bool muffle)
        {
            if (Disabled) { return; }

            category = category.ToLower();
            lock (categoryModifiers)
            {
                if (!categoryModifiers.ContainsKey(category))
                {
                    categoryModifiers.Add(category, new CategoryModifier(0, 1.0f, muffle));
                }
                else
                {
                    categoryModifiers[category].Muffle = muffle;
                }
            }

            for (int i = 0; i < playingChannels.Length; i++)
            {
                lock (playingChannels[i])
                {
                    for (int j = 0; j < playingChannels[i].Length; j++)
                    {
                        if (playingChannels[i][j] != null && playingChannels[i][j].IsPlaying)
                        {
                            if (playingChannels[i][j]?.Category.ToLower() == category) { playingChannels[i][j].Muffled = muffle; }
                        }
                    }
                }
            }
        }

        public bool GetCategoryMuffle(string category)
        {
            if (Disabled) { return false; }

            category = category.ToLower();
            lock (categoryModifiers)
            {
                if (categoryModifiers == null || !categoryModifiers.TryGetValue(category, out CategoryModifier categoryModifier)) { return false; }
                return categoryModifier.Muffle;
            }
        }

        public void Update()
        {
            if (Disconnected || Disabled) { return; }

            if (CanDetectDisconnect)
            {
                Alc.GetInteger(alcDevice, Alc.EnumConnected, out int isConnected);
                int alcError = Alc.GetError(alcDevice);
                if (alcError != Alc.NoError)
                {
                    throw new Exception("Failed to determine if device is connected: " + alcError.ToString());
                }

                if (isConnected == 0)
                {
                    if (!GameMain.Instance.HasLoaded)
                    {
                        //wait for loading to finish so we don't start releasing and reloading sounds when they're being loaded,
                        //or throw an error mid-loading that'd prevent the content package from being enabled
                        return;
                    }
                    DebugConsole.ThrowError("Playback device has been disconnected. You can select another available device in the settings.");
                    SetAudioOutputDevice("<disconnected>");
                    Disconnected = true;
                    return;
                }
            }

            if (GameMain.Client != null && GameSettings.CurrentConfig.Audio.VoipAttenuationEnabled)
            {
                if (Timing.TotalTime > lastAttenuationTime+0.2)
                {
                    voipAttenuatedGain = voipAttenuatedGain * 0.9f + 0.1f;
                }
            }
            else
            {
                voipAttenuatedGain = 1.0f;
            }
            SetCategoryGainMultiplier("default", VoipAttenuatedGain, 1);
            SetCategoryGainMultiplier("ui", VoipAttenuatedGain, 1);
            SetCategoryGainMultiplier("waterambience", VoipAttenuatedGain, 1);
            SetCategoryGainMultiplier("music", VoipAttenuatedGain, 1);

            if (GameSettings.CurrentConfig.Audio.DynamicRangeCompressionEnabled)
            {
                float targetGain = (Math.Min(1.0f, 1.0f / PlaybackAmplitude) - 1.0f) * 0.5f + 1.0f;
                if (targetGain < CompressionDynamicRangeGain)
                {
                    //if the target gain is lower than the current gain, lower the current gain immediately to prevent clipping
                    CompressionDynamicRangeGain = targetGain;
                }
                else
                {
                    //otherwise, let it rise back smoothly
                    CompressionDynamicRangeGain = (targetGain) * 0.05f + CompressionDynamicRangeGain * 0.95f;
                }
            }
            else
            {
                CompressionDynamicRangeGain = 1.0f;
            }

            if (streamingThread == null || streamingThread.ThreadState.HasFlag(ThreadState.Stopped))
            {
                bool startedStreamThread = false;
                for (int i = 0; i < playingChannels.Length; i++)
                {
                    lock (playingChannels[i])
                    {
                        for (int j = 0; j < playingChannels[i].Length; j++)
                        {
                            if (playingChannels[i][j] == null) { continue; }
                            if (playingChannels[i][j].IsStream && playingChannels[i][j].IsPlaying)
                            {
                                InitStreamThread();
                                startedStreamThread = true;
                            }
                            if (startedStreamThread) { break; }
                        }
                    }
                    if (startedStreamThread) { break; }
                }
            }
        }

        public void ApplySettings()
        {
            SetCategoryGainMultiplier("default", GameSettings.CurrentConfig.Audio.SoundVolume, 0);
            SetCategoryGainMultiplier("ui", GameSettings.CurrentConfig.Audio.UiVolume, 0);
            SetCategoryGainMultiplier("waterambience", GameSettings.CurrentConfig.Audio.SoundVolume, 0);
            SetCategoryGainMultiplier("music", GameSettings.CurrentConfig.Audio.MusicVolume, 0);
            SetCategoryGainMultiplier("voip", Math.Min(GameSettings.CurrentConfig.Audio.VoiceChatVolume, 1.0f), 0);
        }
        
        public void InitStreamThread()
        {
            if (Disabled) { return; }
            bool isStreamThreadDying;
            lock (threadDeathMutex)
            {
                isStreamThreadDying = !areStreamsPlaying;
            }
            if (streamingThread == null || streamingThread.ThreadState.HasFlag(ThreadState.Stopped) || isStreamThreadDying)
            {
                if (streamingThread != null && !streamingThread.Join(1000))
                {
                    DebugConsole.ThrowError("Sound stream thread join timed out!");
                }
                areStreamsPlaying = true;
                streamingThread = new Thread(UpdateStreaming)
                {
                    Name = "SoundManager Streaming Thread",
                    IsBackground = true //this should kill the thread if the game crashes
                };
                streamingThread.Start();
            }
        }

        bool areStreamsPlaying = false;
        ManualResetEvent streamMre = null;

        void UpdateStreaming()
        {
            streamMre = new ManualResetEvent(false);
            bool killThread = false;
            while (!killThread)
            {
                killThread = true;
                for (int i = 0; i < playingChannels.Length; i++)
                {
                    lock (playingChannels[i])
                    {
                        for (int j = 0; j < playingChannels[i].Length; j++)
                        {
                            if (playingChannels[i][j] == null) { continue; }
                            if (playingChannels[i][j].IsStream)
                            {
                                if (playingChannels[i][j].IsPlaying)
                                {
                                    killThread = false;
                                    playingChannels[i][j].UpdateStream();
                                }
                                else
                                {
                                    playingChannels[i][j].Dispose();
                                    playingChannels[i][j] = null;
                                }
                            }
                            else if (playingChannels[i][j].FadingOutAndDisposing)
                            {
                                playingChannels[i][j].Gain -= 0.1f;
                                if (playingChannels[i][j].Gain <= 0.0f)
                                {
                                    playingChannels[i][j].Dispose();
                                    playingChannels[i][j] = null;
                                }
                            }
                        }
                    }
                }
                streamMre.WaitOne(10);
                streamMre.Reset();
                lock (threadDeathMutex)
                {
                    areStreamsPlaying = !killThread;
                }
            }
        }

        public void ForceStreamUpdate()
        {
            streamMre?.Set();
        }

        private void ReloadSounds()
        {
            for (int i = loadedSounds.Count - 1; i >= 0; i--)
            {
                loadedSounds[i].InitializeALBuffers();
            }
        }

        private void ReleaseResources(bool keepSounds)
        {
            for (int i = 0; i < playingChannels.Length; i++)
            {
                lock (playingChannels[i])
                {
                    for (int j = 0; j < playingChannels[i].Length; j++)
                    {
                        if (playingChannels[i][j] != null) playingChannels[i][j].Dispose();
                    }
                }
            }

            streamingThread?.Join();
            for (int i = loadedSounds.Count - 1; i >= 0; i--)
            {
                if (keepSounds)
                {
                    loadedSounds[i].DeleteALBuffers();
                }
                else
                {
                    loadedSounds[i].Dispose();
                }
            }
            sourcePools[(int)SourcePoolIndex.Default]?.Dispose();
            sourcePools[(int)SourcePoolIndex.Voice]?.Dispose();

            SoundBuffers.ClearPool();
        }

        public void Dispose()
        {
            if (Disabled) { return; }

            ReleaseResources(false);

            if (!Alc.MakeContextCurrent(IntPtr.Zero))
            {
                throw new Exception("Failed to detach the current ALC context! (error code: " + Alc.GetError(alcDevice).ToString() + ")");
            }

            Alc.DestroyContext(alcContext);
            
            if (!Alc.CloseDevice(alcDevice))
            {
                throw new Exception("Failed to close ALC device!");
            }
        }
    }
}
