using Barotrauma.Networking;
using Concentus.Structs;
using Microsoft.Xna.Framework;
using OpenAL;
using System;

namespace Barotrauma.Sounds
{
    class VoipSound : Sound
    {
        public override double? DurationSeconds => null;

        public override SoundManager.SourcePoolIndex SourcePoolIndex
        {
            get
            {
                return SoundManager.SourcePoolIndex.Voice;
            }
        }

        public new bool IsPlaying
        {
            get
            {
                return soundChannel != null && soundChannel.IsPlaying;
            }
        }

        private readonly VoipQueue queue;
        private int bufferID = 0;
        
        private SoundChannel soundChannel;

        private readonly OpusDecoder decoder;

        public bool UseRadioFilter;
        public bool UseMuffleFilter;

        public bool UsingRadio;

        public float Near { get; private set; }
        public float Far { get; private set; }

        private readonly BiQuad[] muffleFilters = new BiQuad[]
        {
            new LowpassFilter(VoipConfig.FREQUENCY, 800)
        };
        private readonly BiQuad[] radioFilters = new BiQuad[]
        {
            new BandpassFilter(VoipConfig.FREQUENCY, 2000)
        };
        private const float PostRadioFilterBoost = 1.2f;

        private float gain;
        public float Gain
        {
            get { return soundChannel == null ? 0.0f : gain; }
            set
            {
                if (soundChannel == null) { return; }
                gain = value;
                soundChannel.Gain = value * GameSettings.CurrentConfig.Audio.VoiceChatVolume * client.VoiceVolume;
            }
        }

        public float CurrentAmplitude
        {
            get { return soundChannel?.CurrentAmplitude ?? 0.0f; }
        }

        private Client client;

        public VoipSound(Client targetClient, SoundManager owner, VoipQueue q) : base(owner, $"VoIP ({targetClient.Name})", true, true, getFullPath: false)
        {
            client = targetClient;
            decoder = VoipConfig.CreateDecoder();

            ALFormat = Al.FormatMono16;
            SampleRate = VoipConfig.FREQUENCY;

            queue = q;
            bufferID = queue.LatestBufferID;

            soundChannel = null;

            SoundChannel chn = new SoundChannel(this, 1.0f, null, 1.0f, 0.4f, 1.0f, SoundManager.SoundCategoryVoip, false);
            soundChannel = chn;
            Gain = 1.0f;
        }

        public override float GetAmplitudeAtPlaybackPos(int playbackPos)
        {
            throw new NotImplementedException(); //TODO: implement?
        }

        public void SetPosition(Vector3? pos)
        {
            soundChannel.Position = pos;
        }

        public void SetRange(float near, float far)
        {
            soundChannel.Near = Near = near;
            soundChannel.Far = Far = far;
        }

        public void ApplyFilters(short[] buffer, int readSamples)
        {
            float finalGain = gain * GameSettings.CurrentConfig.Audio.VoiceChatVolume * client.VoiceVolume;
            for (int i = 0; i < readSamples; i++)
            {
                float fVal = ToolBox.ShortAudioSampleToFloat(buffer[i]);

                if (finalGain > 1.0f) //TODO: take distance into account?
                {
                    fVal = Math.Clamp(fVal * finalGain, -1f, 1f);
                }

                if (UseMuffleFilter)
                {                
                    foreach (var filter in muffleFilters)
                    {
                        fVal = filter.Process(fVal);
                    }
                }
                if (UseRadioFilter)
                {
                    foreach (var filter in radioFilters)
                    {
                        fVal = Math.Clamp(filter.Process(fVal) * PostRadioFilterBoost, -1f, 1f);
                    }
                }
                buffer[i] = ToolBox.FloatToShortAudioSample(fVal);
            }
        }

        public override SoundChannel Play(float gain, float range, Vector2 position, bool muffle = false)
        {
            throw new InvalidOperationException();
        }

        public override SoundChannel Play(Vector3? position, float gain, float freqMult = 1.0f, bool muffle = false)
        {
            throw new InvalidOperationException();
        }

        public override SoundChannel Play(float gain)
        {
            throw new InvalidOperationException();
        }

        public override SoundChannel Play()
        {
            throw new InvalidOperationException();
        }

        public override int FillStreamBuffer(int samplePos, short[] buffer)
        {
            queue.RetrieveBuffer(bufferID, out int compressedSize, out byte[] compressedBuffer);
            try
            {
                if (compressedSize > 0)
                {
                    decoder.Decode(compressedBuffer, 0, compressedSize, buffer, 0, VoipConfig.BUFFER_SIZE);
                    bufferID++;
                    return VoipConfig.BUFFER_SIZE;
                }
                if (bufferID < queue.LatestBufferID - (VoipQueue.BUFFER_COUNT - 1)) bufferID = queue.LatestBufferID - (VoipQueue.BUFFER_COUNT - 1);
            }
            catch (Exception e)
            {
                DebugConsole.ThrowError($"Failed to decode Opus buffer (buffer size {compressedBuffer.Length}, packet size {compressedSize})", e);
                bufferID = queue.LatestBufferID - (VoipQueue.BUFFER_COUNT - 1);
            }

            return 0;
        }

        public override void Dispose()
        {
            if (soundChannel != null)
            {
                soundChannel.Dispose();
                soundChannel = null;
            }
            base.Dispose();
        }
    }
}
