Imports DigitalData.Modules.Base
Imports DigitalData.Modules.EDMI.API.Client
Imports DigitalData.Modules.Logging

Namespace DocumentResultList

    Public Class Cache
        Inherits BaseClass
        Implements ICollection(Of Document)

        Private Const _DefaultCapacity As Long = 1000

        Public Shared ReadOnly Property DefaultCapacity As Long
            Get
                Return _DefaultCapacity
            End Get
        End Property

        Friend ReadOnly List As New LinkedList(Of Document)
        Private ReadOnly Index As New Dictionary(Of String, LinkedListNode(Of Document))
        Private ReadOnly Lock As New Object

        Public Sub New(pLogConfig As LogConfig)
            Me.New(pLogConfig, _DefaultCapacity)
        End Sub

        Public Sub New(pLogConfig As LogConfig, pCapacity As Long)
            MyBase.New(pLogConfig)

            If pCapacity < 0 Then
                Throw New InvalidOperationException("DocumentResultCache capacity must be positive.")
            End If

            Logger.Debug("Initializing DocumentResultCache with capacity of [{0}] bytes.", pCapacity)

            Me.Capacity = pCapacity
        End Sub

        Public Event DiscardingOldestItem As EventHandler
        Public Property Capacity As Long
        Public Property Count As Integer Implements ICollection(Of Document).Count

        Public ReadOnly Property Oldest As Document
            Get
                Return List.First.Value
            End Get
        End Property

        Public Sub Add(pItem As Document) Implements ICollection(Of Document).Add
            Logger.Debug("Adding document [{0}].", pItem.Id)

            SyncLock Lock
                If Index.ContainsKey(pItem.Id) Then
                    List.Remove(Index(pItem.Id))
                    Index(pItem.Id) = List.AddLast(pItem)
                    Return
                End If

                If Count >= Capacity AndAlso Capacity <> 0 Then
                    RaiseEvent DiscardingOldestItem(Me, New EventArgs())
                    Remove(Oldest)
                End If

                Index.Add(pItem.Id, List.AddLast(pItem))

                If pItem.Contents IsNot Nothing Then
                    Count += pItem.Contents?.Length
                End If
            End SyncLock
        End Sub

        Public Function Contains(pItem As Document) As Boolean Implements ICollection(Of Document).Contains
            Return Index.ContainsKey(pItem.Id)
        End Function

        Public Function Contains(pDocumentId As Long) As Boolean
            Return Index.ContainsKey(pDocumentId)
        End Function

        Public Sub CopyTo(pArray As Document(), pIndex As Integer) Implements ICollection(Of Document).CopyTo
            SyncLock Lock
                For Each item As Document In Me
                    pArray(Math.Min(System.Threading.Interlocked.Increment(pIndex), pIndex - 1)) = item
                Next
            End SyncLock
        End Sub

        Public Sub Clear() Implements ICollection(Of Document).Clear
            SyncLock Lock
                List.Clear()
                Index.Clear()
            End SyncLock
        End Sub

        Public ReadOnly Property IsReadOnly As Boolean Implements ICollection(Of Document).IsReadOnly
            Get
                Return False
            End Get
        End Property

        Public Function Remove(pItem As Document) As Boolean Implements ICollection(Of Document).Remove
            Logger.Debug("Removing document [{0}].", pItem.Id)

            SyncLock Lock

                If Index.ContainsKey(pItem.Id) Then
                    List.Remove(Index(pItem.Id))
                    Index.Remove(pItem.Id)

                    If pItem.Contents IsNot Nothing Then
                        Count -= pItem.Contents.Length
                    End If

                    Return True
                End If

                Return False
            End SyncLock
        End Function

        Private Iterator Function GetEnumerator() As IEnumerator(Of Document) Implements ICollection(Of Document).GetEnumerator
            Dim node As LinkedListNode(Of Document) = List.First

            While node IsNot Nothing
                Yield node.Value
                node = node.[Next]
            End While
        End Function

        Private Function IEnumerable_GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator
            Return DirectCast(List, IEnumerable).GetEnumerator()
        End Function
    End Class
End Namespace

