Imports System.Threading

Namespace Threading

    ''' <summary>
    ''' A data structure representing a value that will be generated in the future.
    ''' </summary>
    ''' <typeparam name="T">The type of the value produced by this future.</typeparam>
    ''' <remarks></remarks>
    Public Class Future(Of T)
        Implements IDisposable

        Private m_state As Integer = 0      ' Volatile: 0=unstarted, 1=running, 2=done
        Private m_value As T
        Private m_exception As Exception    ' Volatile
        Private m_func As Func(Of T)
        Private m_event As ManualResetEvent = New ManualResetEvent(False)
        Private disposedValue As Boolean = False

        ''' <summary>
        ''' Initializes a new instance of the <see cref="Mercury.Mobile.Threading.Future(Of T)"/> class.
        ''' </summary>
        ''' <param name="func">A delegate to a method that returns a value of the specified type.</param>
        ''' <remarks></remarks>
        Public Sub New(ByVal func As Func(Of T))
            Me.New(func, True)
        End Sub

        ''' <summary>
        ''' Initializes a new instance of the <see cref="Mercury.Mobile.Threading.Future(Of T)"/> class.
        ''' </summary>
        ''' <param name="func">A delegate to a method that returns a value of the specified type.</param>
        ''' <remarks></remarks>
        Public Sub New(ByVal func As Func(Of T), ByVal scheduleToRun As Boolean)
            Me.m_func = func
            If scheduleToRun Then _
                ThreadPool.QueueUserWorkItem(s_callback, Me)
        End Sub

        ''' <summary>
        ''' Gets the exception that occurred on this future.
        ''' </summary>
        ''' <returns>The exception that occurred on this future.</returns>
        ''' <remarks></remarks>
        Public ReadOnly Property Exception() As Exception
            Get
                Return Volatile.VolatileRead(Me.m_exception)
            End Get
        End Property

        ''' <summary>
        ''' Gets the value of this future.
        ''' </summary>
        ''' <returns>The value of this future.</returns>
        ''' <remarks></remarks>
        Public ReadOnly Property Value() As T
            Get
                If Volatile.VolatileRead(Me.m_state) <> 2 AndAlso Not (Me.TryRun()) Then _
                    Me.m_event.WaitOne()
                If Volatile.VolatileRead(Me.m_exception) IsNot Nothing Then _
                    Throw Volatile.VolatileRead(Me.m_exception)
                Return Me.m_value
            End Get
        End Property

        ''' <summary>
        ''' Executes the future delegate.
        ''' </summary>
        ''' <returns>True if the future delegate was executed; otherwise, false.</returns>
        ''' <remarks></remarks>
        Public Function Run() As Boolean
            Return Me.TryRun()
        End Function

        ''' <summary>
        ''' Waits until the future value has been resolved.
        ''' </summary>
        ''' <returns>True if the future value was resolved; otherwise, false.</returns>
        ''' <remarks></remarks>
        Public Function WaitUntil() As Boolean
            Return Me.WaitUntil(-1)
        End Function

        ''' <summary>
        ''' Waits until the future value has been resolved or the specified timeout was exceeded.
        ''' </summary>
        ''' <returns>True if the future value was resolved; otherwise, false.</returns>
        ''' <remarks></remarks>
        Public Function WaitUntil(ByVal millisecondsTimeout As Integer) As Boolean
            If Volatile.VolatileRead(Me.m_state) <> 2 Then _
                Me.m_event.WaitOne(millisecondsTimeout, False)
            Return Volatile.VolatileRead(Me.m_state) = 2
        End Function

        Private Shared s_callback As WaitCallback = AddressOf Run
        Private Shared Sub Run(ByVal obj As Object)
            CType(obj, Future(Of T)).TryRun()
        End Sub

        Private Function TryRun() As Boolean
            If Volatile.VolatileRead(Me.m_state) = 0 AndAlso Interlocked.CompareExchange(Me.m_state, 1, 0) = 0 Then
                Try
                    Me.m_value = Me.m_func()
                Catch ex As Exception
                    Volatile.VolatileWrite(Me.m_exception, ex)
                Finally
                    Volatile.VolatileWrite(Me.m_state, 2)
                    Me.m_event.Set()
                End Try
                Return True
            End If
            Return False
        End Function

        Private Sub SetValue(ByVal value As T, ByVal ex As Exception)
            If Volatile.VolatileRead(Me.m_state) = 0 AndAlso Interlocked.CompareExchange(Me.m_state, 1, 0) = 0 Then
                Me.m_value = value
                Volatile.VolatileWrite(Me.m_exception, ex)
                Volatile.VolatileWrite(Me.m_state, 2)
                Me.m_event.Set()
            Else
                Throw New InvalidOperationException("A future's value can only be set once.")
            End If
        End Sub

#Region "IDisposable"

        Protected Overridable Sub Dispose(ByVal disposing As Boolean)
            If Not Me.disposedValue Then
                Dim evt = Volatile.VolatileRead(Me.m_event)
                If evt IsNot Nothing Then
                    evt.Close()
                    Me.m_event = Nothing
                End If
            End If
            Me.disposedValue = True
        End Sub

        Public Sub Dispose() Implements IDisposable.Dispose
            Dispose(True)
            GC.SuppressFinalize(Me)
        End Sub

#End Region

    End Class

End Namespace