//  
//   Rebex Sample Code License
// 
//   Copyright 2025, REBEX CR s.r.o.
//   All rights reserved.
//   https://www.rebex.net/
// 
//   Permission to use, copy, modify, and/or distribute this software for any
//   purpose with or without fee is hereby granted.
// 
//   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
//   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
//   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
//   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
//   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
//   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
//   OTHER DEALINGS IN THE SOFTWARE.
// 

using System;
using System.IO;

namespace Rebex.Samples
{
    /// <summary>
    /// The RoundBuffer class is an efficient implementation of a FIFO buffer.
    /// Data can be read and written fluently without necessity of moving the data inside the buffer.
    /// </summary>
    public class RoundBuffer
    {
        private readonly object _sync = new object();
        private readonly byte[] _buffer;
        private int _start;
        private int _used;

        /// <summary>
        /// Gets byte at the offset position in the current round buffer.
        /// </summary>
        /// <param name="offset">
        /// The zero-based byte offset in the current round buffer.
        /// </param>
        /// <returns>
        /// Byte at the offset position in the current round buffer.
        /// </returns>
        public byte this[int offset]
        {
            get
            {
                if (offset < 0 || offset >= _used)
                    throw new InvalidOperationException("Invalid offset.");

                offset = (_start + offset) % _buffer.Length;
                return _buffer[offset];
            }
        }

        /// <summary>
        /// Gets maximum capacity of the current round buffer.
        /// </summary>
        public int MaximumCapacity
        {
            get { return _buffer.Length; }
        }

        /// <summary>
        /// Gets number of bytes remaining in the current round buffer.
        /// </summary>
        public int Remaining
        {
            get { return _buffer.Length - _used; }
        }

        /// <summary>
        /// Gets length of the current round buffer.
        /// </summary>
        public int Length
        {
            get { return _used; }
        }

        /// <summary>
        /// Initialize round buffer and ensures capacity to maximumCapacity.
        /// </summary>
        /// <param name="maximumCapacity"></param>
        public RoundBuffer(int maximumCapacity)
        {
            _buffer = new byte[maximumCapacity];
        }

        /// <summary>
        /// Writes data from the specified stream to the current round buffer and advances the current position 
        /// within this round buffer by the number of bytes written.
        /// </summary>
        /// <param name="stream">Input stream.</param>
        /// <param name="count">
        /// The maximum number of bytes to be written from the stream to the current round buffer.
        /// </param>
        /// <returns>
        /// The total number of bytes written into the round buffer. This can be less than the
        /// number of bytes requested if that many bytes are not currently available,
        /// or zero (0) if the end of the stream has been reached.
        /// </returns>
        public int CopyFrom(Stream stream, int count)
        {
            lock (_sync)
            {
                if (count > Remaining)
                    throw new InvalidOperationException("Not enough space in the buffer.");

                if (count == 0)
                    return 0;

                int bytesRead = 0;
                int end = (_start + _used) % _buffer.Length;

                if (_start > end)
                {
                    bytesRead = stream.Read(_buffer, end, count);
                }
                else
                {
                    int bytesToRead = Math.Min(count, _buffer.Length - end);
                    bytesRead = stream.Read(_buffer, end, bytesToRead);

                    if (bytesRead == 0)
                        return 0;

                    if (bytesRead == bytesToRead)
                    {
                        count -= bytesRead;
                        bytesRead += stream.Read(_buffer, 0, count);
                    }
                }

                _used += bytesRead;
                return bytesRead;
            }
        }

        /// <summary>
        /// Writes a sequence of bytes to the current round buffer and advances the current position 
        /// within this round buffer by the number of bytes written.
        /// </summary>
        /// <param name="buffer">
        /// An array of bytes. This method copies count bytes from buffer to the current round buffer.
        /// </param>
        /// <param name="offset">
        /// The zero-based byte offset in buffer at which to begin copying bytes to the current round buffer.
        /// </param>
        /// <param name="count">
        /// The number of bytes to be written to the current round buffer.
        /// </param>
        public void Write(byte[] buffer, int offset, int count)
        {
            lock (_sync)
            {
                if (count > Remaining)
                    throw new InvalidOperationException("Not enough space in the buffer.");

                int end = (_start + _used) % _buffer.Length;
                _used += count;

                int freeToEnd = _buffer.Length - end;
                if (count <= freeToEnd)
                {
                    Array.Copy(buffer, offset, _buffer, end, count);
                    return;
                }

                Array.Copy(buffer, offset, _buffer, end, freeToEnd);
                Array.Copy(buffer, offset + freeToEnd, _buffer, 0, count - freeToEnd);
            }
        }

        /// <summary>
        /// Reads data from the current round buffer and stores it to the specified buffer.
        /// </summary>
        /// <param name="buffer">
        /// An array of bytes. When this method returns, the buffer contains 
        /// the specified byte array with the values between offset and (offset + count - 1) 
        /// replaced by the bytes read from the current round buffer.
        /// </param>
        /// <param name="offset">
        /// The zero-based byte offset in buffer at which to begin storing 
        /// the data read from the current stream.
        /// </param>
        /// <param name="count">
        /// The maximum number of bytes to be read from the current round buffer.
        /// </param>
        /// <returns>
        /// The total number of bytes read into the buffer. This can be less than the
        /// number of bytes requested if that many bytes are not currently available,
        /// or zero (0) if the current round buffer is empty.
        /// </returns>
        public int Read(byte[] buffer, int offset, int count)
        {
            lock (_sync)
            {
                count = Math.Min(count, _used);
                if (count == 0)
                    return 0;

                int usedToEnd = _buffer.Length - _start;
                if (count <= usedToEnd)
                {
                    Array.Copy(_buffer, _start, buffer, offset, count);
                    _start = (_start + count) % _buffer.Length;
                }
                else
                {
                    Array.Copy(_buffer, _start, buffer, offset, usedToEnd);
                    _start = count - usedToEnd;
                    Array.Copy(_buffer, 0, buffer, offset + usedToEnd, _start);
                }
                _used -= count;
                return count;
            }
        }
    }
}
