Imports System.Collections
Imports System.Threading

Namespace Collections.Concurrent

    ''' <summary>
    ''' Provides an implementation of a blocking and bounded queue.
    ''' </summary>
    ''' <remarks></remarks>
    Public Class BlockingQueue(Of T)

        ''' <summary>
        ''' Initializes a default instance of the <see cref="Mercury.Mobile.Collections.Concurrent.BlockingQueue(Of T)"/> class.
        ''' </summary>
        ''' <remarks></remarks>
        Public Sub New()
            Me.New(-1)
        End Sub

        ''' <summary>
        ''' Initializes a new instance of the <see cref="Mercury.Mobile.Collections.Concurrent.BlockingQueue(Of T)"/> 
        ''' class with the specified bounded capacity.
        ''' </summary>
        ''' <param name="capacity">The maximum capacity of the queue before enqueue blocks.</param>
        ''' <remarks></remarks>
        Public Sub New(ByVal capacity As Integer)
            Me._capacity = capacity
        End Sub

        Private _queue As Queue(Of T) = New Queue(Of T)()
        Private _mutex As Mutex = New Mutex()
        Private _signal As ManualResetEvent = New ManualResetEvent(False)
        Private _capacity As Integer

        ''' <summary>
        ''' Gets the number of elements currently in the queue.
        ''' </summary>
        ''' <returns>The number of elements currently in the queue.</returns>
        ''' <remarks></remarks>
        Public ReadOnly Property Count As Integer
            Get
                SyncLock Me._queue
                    Return Me._queue.Count
                End SyncLock
            End Get
        End Property

        ''' <summary>
        ''' Gets the maximum number of elements that can be stored in the queue before Enqueue blocks.
        ''' </summary>
        ''' <returns>The maximum number of elements that can be stored in the queue before Enqueue blocks.</returns>
        ''' <remarks></remarks>
        Public ReadOnly Property Capacity As Integer
            Get
                Return Me._capacity
            End Get
        End Property

        ''' <summary>
        ''' Removes all elements from the queue.
        ''' </summary>
        ''' <remarks></remarks>
        Public Sub Clear()
            SyncLock Me._queue
                Me._queue.Clear()
            End SyncLock
        End Sub

        ''' <summary>
        ''' Determines whether the specified item is in the queue.
        ''' </summary>
        ''' <param name="item">The item to test.</param>
        ''' <returns>True if the specified item is in the queue; otherwise, false.</returns>
        ''' <remarks></remarks>
        Public Function Contains(ByVal item As T) As Boolean
            SyncLock Me._queue
                Return Me._queue.Contains(item)
            End SyncLock
        End Function

        ''' <summary>
        ''' Adds the specified item to the end of the queue.
        ''' </summary>
        ''' <param name="item">The item to add.</param>
        ''' <remarks></remarks>
        Public Sub Enqueue(ByVal item As T)
            Me._mutex.WaitOne()
            Try
                Do While Me.Capacity <> -1 AndAlso Me._queue.Count = Me.Capacity
                    Me._mutex.ReleaseMutex()
                    Me._signal.WaitOne()
                    Me._mutex.WaitOne()
                Loop
                Me._queue.Enqueue(item)
                ' If the queue was empty, the event should be set, possibly waking waiter.
                If Me._queue.Count = 1 Then _
                    Me._signal.Set()
            Finally
                Me._mutex.ReleaseMutex()
            End Try
        End Sub

        ''' <summary>
        ''' Removes and returns the item at the beginning of the queue.
        ''' </summary>
        ''' <returns>The item at the beginning of the queue.</returns>
        ''' <remarks></remarks>
        Public Function Dequeue() As T
            Dim value As T = Nothing
            Dim taken As Boolean = True
            Me._mutex.WaitOne()
            Try
                Do While Me._queue.Count = 0
                    taken = False
                    Me._mutex.ReleaseMutex()
                    Me._signal.WaitOne()
                    Me._mutex.WaitOne()
                    taken = True
                Loop
                value = Me._queue.Dequeue()
                ' If we emptied the queue, set to non-signaled.
                If Me._queue.Count = 0 Then _
                    Me._signal.Reset()
            Finally
                If taken Then _
                    Me._mutex.ReleaseMutex()
            End Try
            Return value
        End Function

        ''' <summary>
        ''' Returns the item at the beginning of the queue without removing it.
        ''' </summary>
        ''' <returns>The item at the beginning of the queue</returns>
        ''' <remarks></remarks>
        Public Function Peek() As T
            SyncLock Me._queue
                Return Me._queue.Peek()
            End SyncLock
        End Function

        ''' <summary>
        ''' Returns a consuming IEnumerable(of T) for items in the blocking queue.
        ''' </summary>
        ''' <returns>A consuming IEnumerable(of T) for items in the blocking queue.</returns>
        ''' <remarks></remarks>
        Public Function GetConsumingEnumerable() As IEnumerable(Of T)
            Return New Iterator(Of T, Object)(AddressOf _ConsumingIterator, Nothing)
        End Function

        Private Function _ConsumingIterator(ByRef item As T, ByRef state As Object) As Boolean
            If Me._queue.Count = 0 Then _
                Return False
            item = Me.Dequeue()
            If Me._queue.Count = 0 Then _
                Return False
            Return True
        End Function

    End Class

End Namespace