using System;

namespace Barotrauma.Networking
{
    class VoipQueue : IDisposable
    {
        public const int BUFFER_COUNT = 8;
        protected int[] bufferLengths;
        protected byte[][] buffers;
        protected int newestBufferInd;
        protected bool firstRead;

        public int EnqueuedTotalLength
        {
            get
            {
                int enqueuedTotalLength = 0;
                for (int i = 0; i < BUFFER_COUNT; i++)
                {
                    enqueuedTotalLength += bufferLengths[i];
                }
                return enqueuedTotalLength;
            }
        }

        public byte[] BufferToQueue
        {
            get;
            protected set;
        }

        public virtual byte QueueID
        {
            get;
            protected set;
        }

        public UInt16 LatestBufferID
        {
            get;
            protected set;
        }

        public bool CanSend
        {
            get;
            protected set;
        }

        public bool CanReceive
        {
            get;
            protected set;
        }

        public bool ForceLocal
        {
            get;
            set;
        }

        public DateTime LastReadTime
        {
            get;
            private set;
        }

        public VoipQueue(byte id, bool canSend, bool canReceive)
        {
            BufferToQueue = new byte[VoipConfig.MAX_COMPRESSED_SIZE];
            newestBufferInd = BUFFER_COUNT - 1;
            bufferLengths = new int[BUFFER_COUNT];
            buffers = new byte[BUFFER_COUNT][];
            for (int i = 0; i < BUFFER_COUNT; i++)
            {
                buffers[i] = new byte[VoipConfig.MAX_COMPRESSED_SIZE];
            }
            QueueID = id;
            CanSend = canSend;
            CanReceive = canReceive;
            LatestBufferID = BUFFER_COUNT - 1;
            firstRead = true;

            LastReadTime = DateTime.Now;
        }

        public void EnqueueBuffer(int length)
        {
            if (length > byte.MaxValue) { return; }

            newestBufferInd = (newestBufferInd + 1) % BUFFER_COUNT;

            int enqueuedTotalLength = EnqueuedTotalLength;

            bufferLengths[newestBufferInd] = length;
            BufferToQueue.CopyTo(buffers[newestBufferInd], 0);

            if ((enqueuedTotalLength + length) > 0) { LatestBufferID++; }
        }

        public void RetrieveBuffer(int id, out int outSize, out byte[] outBuf)
        {
            lock (buffers)
            {
                if (id >= LatestBufferID - (BUFFER_COUNT - 1) && id <= LatestBufferID)
                {
                    int index = newestBufferInd - (LatestBufferID - id); 
                    if (index < 0) { index += BUFFER_COUNT; }
                    outSize = bufferLengths[index];
                    outBuf = buffers[index];
                    return;
                }
            }
            outSize = -1;
            outBuf = null;
        }

        public virtual void Write(IWriteMessage msg)
        {
            if (!CanSend) { throw new Exception("Called Write on a VoipQueue not set up for sending"); }

            msg.WriteUInt16((UInt16)LatestBufferID);
            msg.WriteBoolean(ForceLocal); msg.WritePadBits();
            lock (buffers)
            {
                for (int i = 0; i < BUFFER_COUNT; i++)
                {
                    int index = (newestBufferInd + i + 1) % BUFFER_COUNT;

                    msg.WriteByte((byte)bufferLengths[index]);
                    msg.WriteBytes(buffers[index], 0, bufferLengths[index]);
                }
            }
        }

        public virtual bool Read(IReadMessage msg, bool discardData = false)
        {
            if (!CanReceive) { throw new Exception("Called Read on a VoipQueue not set up for receiving"); }

            UInt16 incLatestBufferID = msg.ReadUInt16();
            if ((firstRead || NetIdUtils.IdMoreRecent(incLatestBufferID, LatestBufferID)) && !discardData)
            {
                ForceLocal = msg.ReadBoolean(); msg.ReadPadBits();

                firstRead = false;
                lock (buffers)
                {
                    for (int i = 0; i < BUFFER_COUNT; i++)
                    {
                        bufferLengths[i] = msg.ReadByte();
                        buffers[i] = msg.ReadBytes(bufferLengths[i]);
                    }
                }
                newestBufferInd = BUFFER_COUNT - 1;
                LatestBufferID = incLatestBufferID;
                LastReadTime = DateTime.Now;
                return true;
            }
            else
            {
                msg.ReadBoolean(); msg.ReadPadBits();
                for (int i = 0; i < BUFFER_COUNT; i++)
                {
                    byte len = msg.ReadByte();
                    msg.BitPosition += len * 8;
                }
                return false;
            }
        }

        public virtual void Dispose() { }
    }
}
