'  
'   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.
' 

Imports System
Imports System.IO

''' <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 _sync As New Object()
    Private ReadOnly _buffer As Byte()
    Private _start As Integer
    Private _used As Integer

    ''' <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>
    Default Public ReadOnly Property Item(ByVal offset As Integer) As Byte
        Get
            If offset < 0 OrElse offset >= _used Then
                Throw New InvalidOperationException("Invalid offset.")
            End If

            offset = (_start + offset) Mod _buffer.Length
            Return _buffer(offset)
        End Get
    End Property

    ''' <summary>
    ''' Gets maximum capacity of the current round buffer.
    ''' </summary>
    Public ReadOnly Property MaximumCapacity() As Integer
        Get
            Return _buffer.Length
        End Get
    End Property

    ''' <summary>
    ''' Gets number of bytes remaining in the current round buffer.
    ''' </summary>
    Public ReadOnly Property Remaining() As Integer
        Get
            Return _buffer.Length - _used
        End Get
    End Property

    ''' <summary>
    ''' Gets length of the current round buffer.
    ''' </summary>
    Public ReadOnly Property Length() As Integer
        Get
            Return _used
        End Get
    End Property

    ''' <summary>
    ''' Initialize round buffer and ensures capacity to maximumCapacity.
    ''' </summary>
    ''' <param name="maximumCapacity"></param>
    Public Sub New(ByVal maximumCapacity As Integer)
        _buffer = New Byte(maximumCapacity - 1) {}
    End Sub

    ''' <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 Function CopyFrom(ByVal stream As Stream, ByVal count As Integer) As Integer
        SyncLock _sync
            If count > Remaining Then
                Throw New InvalidOperationException("Not enough space in the buffer.")
            End If

            If count = 0 Then
                Return 0
            End If

            Dim bytesRead As Integer = 0
            Dim [end] As Integer = (_start + _used) Mod _buffer.Length

            If _start > [end] Then
                bytesRead = stream.Read(_buffer, [end], count)
            Else
                Dim bytesToRead As Integer = Math.Min(count, _buffer.Length - [end])
                bytesRead = stream.Read(_buffer, [end], bytesToRead)

                If bytesRead = 0 Then
                    Return 0
                End If

                If bytesRead = bytesToRead Then
                    count -= bytesRead
                    bytesRead += stream.Read(_buffer, 0, count)
                End If
            End If

            _used += bytesRead
            Return bytesRead
        End SyncLock
    End Function

    ''' <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 Sub Write(ByVal buffer As Byte(), ByVal offset As Integer, ByVal count As Integer)
        SyncLock _sync
            If count > Remaining Then
                Throw New InvalidOperationException("Not enough space in the buffer.")
            End If

            Dim [end] As Integer = (_start + _used) Mod _buffer.Length
            _used += count

            Dim freeToEnd As Integer = _buffer.Length - [end]
            If count <= freeToEnd Then
                Array.Copy(buffer, offset, _buffer, [end], count)
                Return
            End If

            Array.Copy(buffer, offset, _buffer, [end], freeToEnd)
            Array.Copy(buffer, offset + freeToEnd, _buffer, 0, count - freeToEnd)
        End SyncLock
    End Sub

    ''' <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 Function Read(ByVal buffer As Byte(), ByVal offset As Integer, ByVal count As Integer) As Integer
        SyncLock _sync
            count = Math.Min(count, _used)
            If count = 0 Then
                Return 0
            End If

            Dim usedToEnd As Integer = _buffer.Length - _start
            If count <= usedToEnd Then
                Array.Copy(_buffer, _start, buffer, offset, count)
                _start = (_start + count) Mod _buffer.Length
            Else
                Array.Copy(_buffer, _start, buffer, offset, usedToEnd)
                _start = count - usedToEnd
                Array.Copy(_buffer, 0, buffer, offset + usedToEnd, _start)
            End If
            _used -= count
            Return count
        End SyncLock
    End Function
End Class
