﻿Imports System.Net
Imports System.ComponentModel
Namespace DQ
    ''' <summary>
    ''' The Structure used for holding the data to download files.
    ''' </summary>
    ''' <remarks></remarks>
    Friend Structure DownloadItem
        Private DURL As String
        Private DDownloadLocation As String
        Private dFileName As String
        ''' <summary>
        ''' Get or Set the URL of the Current Download Item.
        ''' </summary>
        ''' <value>The URL to download from.</value>
        ''' <returns>Returns the current URL of the file.</returns>
        ''' <remarks></remarks>
        Friend Property URL() As String
            Get
                Return DURL
            End Get
            Set(ByVal value As String)
                DURL = value
            End Set
        End Property
        ''' <summary>
        ''' Get or Set the download location of the item.
        ''' </summary>
        ''' <value>The new location to save to (including filename)</value>
        ''' <returns>Returns the direct path to save to (including filename)</returns>
        ''' <remarks></remarks>
        Friend Property SaveTo() As String
            Get
                Return DDownloadLocation
            End Get
            Set(ByVal value As String)
                DDownloadLocation = value
                Dim Array() As String = DDownloadLocation.Split("\"c)
                dFileName = Array(Array.GetUpperBound(0)).Split("."c)(0)
            End Set
        End Property
        ''' <summary>
        ''' Returns only the filename of the item to download. (this is pulled from the Filepath)
        ''' </summary>
        ''' <value>This is Readonly, you cannot set the FileName.</value>
        ''' <returns>Returns the FileName.</returns>
        ''' <remarks></remarks>
        Friend ReadOnly Property FileName() As String
            Get
                Return dFileName
            End Get
        End Property
        ''' <summary>
        ''' Returns the FilePath of the File to be downloaded, excluding the filename.
        ''' </summary>
        ''' <value>ReadOnly Property, cannot set the value.</value>
        ''' <returns>Returns the FilePath of the downloading file.</returns>
        ''' <remarks></remarks>
        Friend ReadOnly Property FilePath() As String
            Get
                Return DDownloadLocation.Replace(dFileName, String.Empty).Trim("\"c)
            End Get
        End Property
    End Structure
    ''' <summary>
    ''' The actual class that handles the download managing.
    ''' </summary>
    Public Class ASQS
        Public Event Percentage(ByVal currentFile As String, ByVal prog As Integer, ByVal rate As String)
        Public Event CompletedFile(ByVal file As String)
        Public Event CompletedQueue()
        Public Event ItemAdded()

        Private QueueStarted As Boolean = False
        Private QueueList As New List(Of DownloadItem)
        Private WebClient As New WebClient

        Private SpeedCalculate As New Stopwatch


        'These next three are used internally by the class only.
        'Not saying the others aren't, but they are used because 
        'I don't feel like rewriting a bunch of code.

        Private Progress As Integer = 0I
        Private CurrentFile As String = String.Empty
        Private dFilePath As String = String.Empty

        ''' <summary>
        ''' Add an Item into the download queue.
        ''' </summary>
        ''' <param name="url">The URL to download from. This is a direct HTTP Filepath.</param>
        ''' <param name="saveLocation">The place to save the file (including the filename!!)</param>
        ''' <remarks></remarks>
        Friend Sub AddToQueue(ByVal url As String, ByVal saveLocation As String)
            Dim NewItem As New DownloadItem
            NewItem.URL = url
            NewItem.SaveTo = saveLocation
            If Not IsItemInQueue(NewItem, False) Then QueueList.Add(NewItem) : RaiseEvent ItemAdded()
        End Sub
        ''' <summary>
        ''' Begin Processing the Queue if not already started.
        ''' </summary>
        ''' <remarks></remarks>
        Friend Sub StartQueue()
            If QueueStarted = False AndAlso QueueList.Count > 0 Then QueueStarted = True : Call DownloadQueue()
        End Sub
        Friend Sub StopQueue()
            WebClient.CancelAsync()
        End Sub
        ''' <summary>
        ''' The actual downloader.
        ''' </summary>
        ''' <remarks></remarks>
        Private Sub DownloadQueue()
            Try
                'Add our handlers for progress changing & completing.
                AddHandler WebClient.DownloadFileCompleted, AddressOf DownloadCompleted
                AddHandler WebClient.DownloadProgressChanged, AddressOf ChangeInProgress
                'Get the current item in the queue.
                Dim CurrentDownload As DownloadItem = QueueList(0)
                'Get the URL
                Dim url As String = CurrentDownload.URL
                'Get the location to save to.
                Dim sFile As String = CurrentDownload.SaveTo
                'Get the current filename
                CurrentFile = CurrentDownload.FileName
                dFilePath = CurrentDownload.FilePath
                'Begin downloading.
                WebClient.DownloadFileAsync(New Uri(url), sFile)
                SpeedCalculate.Start()
            Catch ex As Exception
                MessageBox.Show(ex.ToString)
            End Try
        End Sub
        Private Sub ChangeInProgress(ByVal sender As Object, ByVal e As DownloadProgressChangedEventArgs)
            'Whenever progress is changed, set the 
            'current file progress to our return variable.
            Dim CurBytes As Long = e.BytesReceived
            Dim StreamEnd As Long = e.TotalBytesToReceive
            If SpeedCalculate.Elapsed.Seconds > 0 Then
                Dim Speed As Integer = CInt((CurBytes / SpeedCalculate.Elapsed.TotalSeconds))
                Dim NewSpeed As Integer = Speed
                Dim count As Integer = 0I
                Dim ending As String = String.Empty
                Do Until NewSpeed <= 1024
                    NewSpeed = CInt(NewSpeed / 1024)
                    count += 1
                Loop
                Select Case count
                    Case 0
                        ending = "Bytes/sec"
                    Case 1
                        ending = "Kb/sec"
                    Case 2
                        ending = "Mb/sec"
                    Case 3
                        ending = "Gb/sec"
                End Select
                RaiseEvent Percentage(Me.CurrentItem().FileName, e.ProgressPercentage, String.Format("{0} {1}", NewSpeed.ToString, ending))
            End If
        End Sub
        Private Sub DownloadCompleted(ByVal sender As Object, ByVal e As AsyncCompletedEventArgs)
            SpeedCalculate.Stop()
            SpeedCalculate.Reset()
            Try
                'Was the queue cancelled?
                Select Case e.Cancelled
                    'It was not cancelled.
                    Case False
                        'Was there an error?
                        If e.Error IsNot Nothing Then
                            'Fuck, something happened...
                            'We can try again, but I'd rather tell the user and exit the sub.
                            MessageBox.Show(e.Error.ToString & Environment.NewLine & "Please download the file again!")
                            RemoveHandler WebClient.DownloadFileCompleted, AddressOf DownloadCompleted
                            RemoveHandler WebClient.DownloadProgressChanged, AddressOf ChangeInProgress
                            Exit Sub
                        End If
                        'Remove the handlers to prevent memory leaks/problems/crap.
                        RemoveHandler WebClient.DownloadFileCompleted, AddressOf DownloadCompleted
                        RemoveHandler WebClient.DownloadProgressChanged, AddressOf ChangeInProgress
                        'Remove the current item from queue.
                        QueueList.RemoveAt(0)
                        'Now see if the count is > 0
                        If QueueList.Count > 0 Then
                            'Notify the user that a download completed.
                            RaiseEvent CompletedFile(CurrentFile)
                            'Since it's not, we can
                            'step into the next one!
                            Call DownloadQueue()
                        Else
                            'Alas, no more items.
                            'Set the variable QueueStarted to False
                            QueueStarted = False
                            'Reset progress
                            Progress = 0I
                            'Reset file name to nothing.
                            CurrentFile = String.Empty
                            'Tell the user that we're done with the queue.
                            RaiseEvent CompletedQueue()
                        End If
                    Case True
                        'IT WAS CANCELED, OMG!
                        'Act like we ran out of items..fast!
                        RaiseEvent Percentage(CurrentFile, 0I, "0")
                        IO.File.Delete(QueueList(0).SaveTo) 'to remove corrupted/incompleted downloads
                        CurrentFile = String.Empty
                        QueueStarted = False
                End Select
            Catch ex As Exception
                'Oops...guess something went wrong...
                'act like we ran out of items again!
                Progress = 0I
                CurrentFile = String.Empty
                'but show this for the user.
                MessageBox.Show(ex.ToString)
            End Try
        End Sub
        ''' <summary>
        ''' Return the current filename!
        ''' </summary>
        ''' <value>ReadOnly</value>
        ''' <returns>Returns the name of the Current File, excluding path, including extension.</returns>
        ''' <remarks></remarks>
        <Obsolete("Use the CurrentItem Property instead.")> Friend ReadOnly Property File() As String
            Get
                Return QueueList(0).FileName
            End Get
        End Property
        ''' <summary>
        ''' Returns the current DownloadItem for you to manipulate.
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Friend ReadOnly Property CurrentItem() As DownloadItem
            Get
                Return QueueList(0)
            End Get
        End Property
        ''' <summary>
        ''' Returns the current progress. THIS IS NOW OBSOLETE, HANDLE THE PERCENTAGE EVENT!!!
        ''' </summary>
        ''' <value>ReadOnly!</value>
        ''' <returns>Returns the Current File Downloading Progress as an Integer</returns>
        ''' <remarks>OBSOLETE!</remarks>
        <Obsolete("Handle the Percentage Event instead, much better!")> Friend ReadOnly Property CurrentProgress() As Integer
            Get
                Return Progress
            End Get
        End Property
        ''' <summary>
        ''' Returns the filepath the file is being downloaded on.
        ''' </summary>
        ''' <value>ReadOnly</value>
        ''' <returns>The Filepath of the file minus the name and ending backslash.</returns>
        ''' <remarks></remarks>
        <Obsolete("Use the CurrentItem Property instead.")> Friend ReadOnly Property FilePath() As String
            Get
                Return QueueList(0).FilePath
            End Get
        End Property
        ''' <summary>
        ''' Returns a True or False if the Item is in the Queue.
        ''' </summary>
        ''' <param name="itemName">The Filename to check for.</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Friend Function IsItemInQueue(ByVal itemName As String) As Boolean
            For Each item As DownloadItem In QueueList
                If item.FileName.ToLower = itemName.ToLower Then Return True
            Next
            Return False
        End Function
        ''' <summary>
        ''' Returns a True or False if the Item is in the Queue.
        ''' </summary>
        ''' <param name="itemName">The DownloadItem to check.</param>
        ''' <param name="add">Add it to the download list or not.</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Friend Function IsItemInQueue(ByVal itemName As DownloadItem, ByVal add As Boolean) As Boolean
            For Each item As DownloadItem In QueueList
                If item.Equals(itemName) Then Return True
            Next
            If add Then QueueList.Add(itemName)
            Return False
        End Function
        ''' <summary>
        ''' Returns the Queue list for you to edit.
        ''' </summary>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Friend ReadOnly Property ReturnQueueList() As List(Of DownloadItem)
            Get
                Return QueueList
            End Get
        End Property
        ''' <summary>
        ''' Remove an item based on it's name.
        ''' </summary>
        ''' <param name="item">The name of the the DownloadItem.</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Friend Function Remove(ByVal item As String) As Boolean
            For Each DQ_Item As DownloadItem In QueueList
                If DQ_Item.FileName = item Then QueueList.Remove(DQ_Item)
                Return True
            Next
            Return False
        End Function
        ''' <summary>
        ''' Remove a DownloadItem.
        ''' </summary>
        ''' <param name="item">The Structure to remove.</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Friend Function Remove(ByVal item As DownloadItem) As Boolean
            QueueList.Remove(item)
        End Function
    End Class
End Namespace