﻿Imports System.Net
Imports System.ComponentModel

Public Class AsyncDownloadManager
    Implements IDisposable

    '//fields
    Private userState As Object

    '//events
    Public Event DownloadCompleted As AsyncCompletedEventHandler

    '//properties
    Private _innerQueue As Queue(Of QueuedItem)
    Protected ReadOnly Property InnerQueue() As Queue(Of QueuedItem)
        Get
            If Me._innerQueue Is Nothing Then
                Me._innerQueue = New Queue(Of QueuedItem)()
            End If
            Return Me._innerQueue
        End Get
    End Property

    Private WithEvents _innerClient As WebClient
    Protected ReadOnly Property InnerClient() As WebClient
        Get
            If Me._innerClient Is Nothing Then
                Me._innerClient = New WebClient()
            End If
            Return Me._innerClient
        End Get
    End Property

    Public ReadOnly Property IsBusy() As Boolean
        Get
            SyncLock Me.InnerClient
                Return Me.InnerClient.IsBusy
            End SyncLock
        End Get
    End Property

    '//methods
    Public Sub BeginDownload()
        Me.BeginDownload(Nothing)
    End Sub

    Public Sub BeginDownload(ByVal userState As Object)
        Me.userState = userState

        '//create a thread, and run this
        Dim thread = New Threading.Thread(AddressOf Me.ContinueDownload)
        With thread
            .IsBackground = True

            '//being the download
            .Start()
        End With
    End Sub

    Public Sub Queue(ByVal uri As Uri, ByVal fileName As String)
        SyncLock Me.InnerClient
            If Me.InnerClient.IsBusy Then
                Throw New InvalidOperationException("Cannot queue items while the client is busy.")
            End If
        End SyncLock
        SyncLock Me.InnerQueue
            Me.InnerQueue.Enqueue(New QueuedItem(uri, fileName))
        End SyncLock
    End Sub

    Public Sub Queue(ByVal address As String, ByVal fileName As String)
        Me.Queue(New Uri(address), fileName)
    End Sub

    Public Sub Dispose() Implements IDisposable.Dispose
        If Me._innerClient IsNot Nothing Then
            Me._innerClient.Dispose()
        End If
        Me._innerClient = Nothing
    End Sub

    Protected Overridable Sub OnDownloadComplete(ByVal e As AsyncCompletedEventArgs)
        Dim handler = Me.DownloadCompletedEvent
        If handler IsNot Nothing Then
            handler.Invoke(Me, e)
        End If
    End Sub

    Protected Sub OnDownloadComplete() Handles _innerClient.DownloadFileCompleted
        Me.ContinueDownload()
    End Sub

    Private Sub ContinueDownload()
        If Me.CanDownload() Then
            Me.Download()
        Else
            Me.OnDownloadComplete(New AsyncCompletedEventArgs(Nothing, False, Me.userState))
        End If
    End Sub

    Private Function CanDownload() As Boolean
        SyncLock Me.InnerQueue
            Return Me.InnerQueue.Count > 0
        End SyncLock
    End Function

    Private Sub Download()
        SyncLock Me.InnerQueue
            If Me.InnerQueue.Count > 0 Then
                Dim item = Me.InnerQueue.Dequeue()

                Me.InnerClient.DownloadFileAsync(item.Uri, item.FileName)
            End If
        End SyncLock
    End Sub

    '//nested types
    Protected Class QueuedItem

        '//properties
        Private _uri As Uri
        Public ReadOnly Property Uri() As Uri
            Get
                Return Me._uri
            End Get
        End Property

        Private _fileName As String
        Public ReadOnly Property FileName() As String
            Get
                Return Me._fileName
            End Get
        End Property

        '//constructors
        Public Sub New(ByVal uri As Uri, ByVal fileName As String)
            Me._uri = uri
            Me._fileName = fileName
        End Sub

    End Class

End Class