// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System;

namespace HoloToolkit.Unity
{
    /// <summary>
    /// Helper class for transmitting data over network.
    /// </summary>
    public class CircularBuffer
    {
        public CircularBuffer(int size, bool allowOverwrite = false, int padding = 4)
        {
            data = new byte[size];
            readWritePadding = padding;
            this.allowOverwrite = allowOverwrite;
        }

        public int TotalCapacity
        {
            get
            {
                return data.Length - readWritePadding;
            }
        }

        public int UsedCapacity
        {
            get
            {
                if (writeOffset >= readOffset)
                {
                    return writeOffset - readOffset;
                }
                int firstChunk = data.Length - readOffset;
                int secondChunk = writeOffset;
                return firstChunk + secondChunk;
            }
        }

        public void Reset()
        {
            readOffset = 0;
            writeOffset = 0;
        }

        public int Write(Array src, int srcReadPosBytes, int byteCount)
        {
            int maxWritePos;
            bool wrappedAround = writeOffset < readOffset;
            if (!wrappedAround)
            {
                maxWritePos = (readOffset != 0 || allowOverwrite) ? data.Length : data.Length - readWritePadding;
            }
            else
            {
                maxWritePos = allowOverwrite ? data.Length : readOffset - readWritePadding;
            }

            int chunkSize = Math.Min(byteCount, maxWritePos - writeOffset);
            int writeEnd = writeOffset + chunkSize;
            bool needToMoveReadOffset = wrappedAround ? writeEnd >= readOffset : (writeEnd == data.Length && readOffset == 0);
            if (needToMoveReadOffset)
            {
                if (!allowOverwrite)
                {
                    throw new Exception("Circular buffer logic error. Overwriting data.");
                }
                readOffset = (writeEnd + readWritePadding) % data.Length;
            }

            Buffer.BlockCopy(src, srcReadPosBytes, data, writeOffset, chunkSize);
            writeOffset = (writeOffset + chunkSize) % data.Length;

            int bytesWritten = chunkSize;
            int remaining = byteCount - bytesWritten;
            if (bytesWritten > 0 && remaining > 0)
            {
                bytesWritten += Write(src, srcReadPosBytes + chunkSize, remaining);
            }

            return bytesWritten;
        }

        public int Read(Array dst, int dstWritePosBytes, int byteCount)
        {
            if (readOffset == writeOffset)
            {
                return 0;
            }

            int maxReadPos;
            if (readOffset > writeOffset)
            {
                maxReadPos = data.Length;
            }
            else
            {
                maxReadPos = writeOffset;
            }

            int chunkSize = Math.Min(byteCount, maxReadPos - readOffset);

            Buffer.BlockCopy(data, readOffset, dst, dstWritePosBytes, chunkSize);
            readOffset = (readOffset + chunkSize) % data.Length;

            int bytesRead = chunkSize;
            int remaining = byteCount - bytesRead;
            if (bytesRead > 0 && remaining > 0)
            {
                bytesRead += Read(dst, dstWritePosBytes + bytesRead, remaining);
            }

            return bytesRead;
        }

        private byte[] data;
        private int writeOffset;
        private int readOffset;

        private readonly int readWritePadding;
        private readonly bool allowOverwrite;
    }
}