﻿Imports System.Threading

''' <summary>
''' Provides functionality to execute a method after a pause.
''' </summary>
Public Module Pauser

#Region " Types "

    ''' <summary>
    ''' Contains information about how to invoke a method.
    ''' </summary>
    Private Class InvocationInfo
        ''' <summary>
        ''' The method to invoke.
        ''' </summary>
        Public method As [Delegate]
        ''' <summary>
        ''' The arguments to pass to the method.
        ''' </summary>
        Public arguments As Object()
        ''' <summary>
        ''' The context in which to invoke the method.
        ''' </summary>
        Public context As SynchronizationContext
    End Class

#End Region 'Types

#Region " Fields "

    ''' <summary>
    ''' Contains information about methods to invoke keyed on the <see cref="Timers.Timer">Timer</see> used to delay their invocation.
    ''' </summary>
    Private ReadOnly invocationInfoByTimer As New Dictionary(Of Timers.Timer, InvocationInfo)

#End Region 'Fields

#Region " Event Handlers "

    ''' <summary>
    ''' Handles the <see cref="Timers.Timer.Elapsed">Elapsed</see> event of the <see cref="Timers.Timer">Timer</see> used to deley the invocation of a method.
    ''' </summary>
    ''' <param name="sender">
    ''' The <b>Timer</b> that has elapsed.
    ''' </param>
    ''' <param name="e">
    ''' The data for the event.
    ''' </param>
    Private Sub Timer_Elapsed(sender As Object, e As Timers.ElapsedEventArgs)
        Dim timer = DirectCast(sender, Timers.Timer)

        'Get the method information associated with the Timer.
        Dim info = invocationInfoByTimer(timer)

        'The Timer is no longer needed.
        invocationInfoByTimer.Remove(timer)
        timer.Dispose()

        If info.context Is Nothing Then
            'This is not a Windows GUI app so execute the method on the current thread.
            Execute(info)
        Else
            'This is a Windows GUI app so execute the method on the original thread.
            info.context.Post(AddressOf Execute, info)
        End If
    End Sub

#End Region 'Event Handlers

#Region " Methods "

    ''' <summary>
    ''' Executes a method after a pause.
    ''' </summary>
    ''' <param name="pause">
    ''' The period of time after which to execute the method.
    ''' </param>
    ''' <param name="method">
    ''' Refers to the method to execute.
    ''' </param>
    ''' <param name="arguments">
    ''' The arguments to pass to the method when executing it.
    ''' </param>
    Public Sub ExecuteAfterPause(pause As Integer, method As [Delegate], ParamArray arguments As Object())
        'Create a Timer to provide the delay that will raise its Elapsed event only once.
        Dim timer As New Timers.Timer(pause) With {.AutoReset = False}

        'Package up the information about the method to execute.
        'The context is required so that the method is invoked on the same thread as we are executing on now.
        Dim info As New InvocationInfo With {.method = method,
                                             .arguments = arguments,
                                             .context = SynchronizationContext.Current}

        invocationInfoByTimer.Add(timer, info)
        AddHandler timer.Elapsed, AddressOf Timer_Elapsed
        timer.Start()
    End Sub

    ''' <summary>
    ''' Executes the method.
    ''' </summary>
    ''' <param name="info">
    ''' Contains information about the method to invoke.
    ''' </param>
    Private Sub Execute(info As Object)
        With DirectCast(info, InvocationInfo)
            .method.DynamicInvoke(.arguments)
        End With
    End Sub

#End Region 'Methods

End Module
