Imports System.ComponentModel
Imports System.Text
Imports ComponentPro
Imports ComponentPro.Net
Imports ComponentPro.Net.Mail

Namespace ImapDownloader
	Partial Public Class Imap
		Inherits Form
		Private ReadOnly _exception As Boolean
		Private _mailsDir As String
		Private _workingDir As String
		Private _disconnect As Boolean
		Private _disconnected As Boolean

		''' <summary>
		''' Gets the working directory.
		''' </summary>
		Public ReadOnly Property WorkingDir() As String
			Get
				If _workingDir Is Nothing Then
					If AppDomain.CurrentDomain.BaseDirectory.EndsWith("\") Then
						_workingDir = AppDomain.CurrentDomain.BaseDirectory
					Else
						_workingDir = (AppDomain.CurrentDomain.BaseDirectory & AscW("\"c))
					End If
				End If

				Return _workingDir
			End Get
		End Property

		''' <summary>
		''' Gets the directory which is used to store mails.
		''' </summary>
		Public ReadOnly Property MailsDir() As String
			Get
				If _mailsDir Is Nothing Then
					_mailsDir = WorkingDir

					If Not System.IO.Directory.Exists(_mailsDir & "..\..\Mails") Then
						_mailsDir &= "Mails"
					Else
						_mailsDir &= "..\..\Mails"
					End If
				End If

				Return _mailsDir
			End Get
		End Property

		''' <summary>
		''' Constructor.
		''' </summary>
		Public Sub New()
			Try
				InitializeComponent()
			Catch exc As ComponentPro.Licensing.Mail.UltimateLicenseException
				MessageBox.Show(exc.Message, "Error")
				_exception = True
				Return
			End Try

			cbxSecurity.SelectedIndex = 0
			' The following settings are for a gmail Mailbox:
			'cbxSecurity.SelectedIndex = 2;
			'txtServer.Text = "myserver";
			'txtPort.Text = "993";
			'txtUserName.Text = "someone@gmail.com";
			'txtPassword.Text = "password";
			txtFolder.Text = "Inbox"

			imapClient.Timeout = 20000
			imapClient.Proxy.ProxyType = ProxyType.None

			' Prepare the Mails folder for storing downloaded messages.
			If Not System.IO.Directory.Exists(MailsDir) Then
				System.IO.Directory.CreateDirectory(MailsDir)
			End If

			AddHandler Me.imapClient.SelectCompleted, AddressOf imapClient_SelectFolderCompleted
			AddHandler Me.imapClient.ListMessagesCompleted, AddressOf imapClient_ListMessagesCompleted
			AddHandler Me.imapClient.ConnectCompleted, AddressOf imapClient_ConnectCompleted
			AddHandler Me.imapClient.DownloadMessageCompleted, AddressOf imapClient_DownloadMessageCompleted
			AddHandler Me.imapClient.DisconnectCompleted, AddressOf imapClient_DisconnectCompleted
			AddHandler Me.imapClient.AuthenticateCompleted, AddressOf imapClient_AuthenticateCompleted
		End Sub

		''' <summary>
		''' Handles the form's Load event.
		''' </summary>
		''' <param name="e">The event arguments.</param>
		Protected Overrides Sub OnLoad(ByVal e As EventArgs)
			MyBase.OnLoad(e)
			If _exception Then
				Me.Close()
			End If
		End Sub

		''' <summary>
		''' Adds a line to the result listbox.
		''' </summary>
		''' <param name="str">Text to write.</param>
		Private Sub WriteLine(ByVal str As String)
			lsbResult.Items.Add(str)
		End Sub

		''' <summary>
		''' Adds a formatted line to the result listbox.
		''' </summary>
		''' <param name="str">Formatted text to write.</param>
		''' <param name="parameters">The parameters for the formatted text.</param>
		Private Sub WriteLine(ByVal str As String, ParamArray ByVal parameters() As Object)
			lsbResult.Items.Add(String.Format(str, parameters))
		End Sub

		''' <summary>
		''' Handles the Download button's Click event.
		''' </summary>
		''' <param name="sender">The button object.</param>
		''' <param name="e">The event arguments.</param>
		Private Sub btnDownload_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnDownload.Click

			If btnDownload.Text = "Download" Then
				Dim port As Integer

				If String.IsNullOrEmpty(txtServer.Text) Then
					MessageBox.Show("Please enter server name", "Error")
					Return
				End If

				Try
					port = Integer.Parse(txtPort.Text)
					If port < 1 OrElse port > 65535 Then
						MessageBox.Show("Invalid port, port must be from 1->65535", "Error")
						Return
					End If
				Catch e1 As FormatException
					MessageBox.Show("Invalid port, port must be from 1->65535", "Error")
					Return
				End Try

				If String.IsNullOrEmpty(txtUserName.Text) Then
					MessageBox.Show("Please enter user name")
					Return
				End If

				If String.IsNullOrEmpty(txtPassword.Text) Then
					MessageBox.Show("Please enter password")
					Return
				End If

				If String.IsNullOrEmpty(txtFolder.Text) Then
					MessageBox.Show("Folder name cannot be empty")
					Return
				End If

				_disconnect = False
				_disconnected = False

				grbAuth.Enabled = False
				grbFolder.Enabled = False
				grbServer.Enabled = False
				'grbStatus.Enabled = false;

				progressBarTotal.Value = 0
				progressBar.Value = 0

				btnDownload.Text = "Cancel"
				' Change image.
				Me.btnDownload.Image = My.Resources.Stop
				btnProxy.Enabled = False

				WriteLine("Connecting to the IMAP server {0}:{1}, security: {2}...", txtServer.Text, port, cbxSecurity.Text)

				Dim sec As SecurityMode

				Select Case cbxSecurity.SelectedIndex
					Case 0
						sec = SecurityMode.None

					Case 1
						sec = SecurityMode.Explicit

					Case Else
						sec = SecurityMode.Implicit
				End Select

				' Connects to the IMAP server.
				imapClient.ConnectAsync(txtServer.Text, port, sec)
			Else
				imapClient.Cancel()
				' Waits for the last pending operation.
				_disconnect = True
				btnDownload.Enabled = False
				'btnClose.Enabled = false;
			End If
		End Sub

		''' <summary>
		''' Handles the imap client's ConnectCompleted event.
		''' </summary>
		''' <param name="sender">The imap client object.</param>
		''' <param name="e">The event arguments.</param>
		Private Sub imapClient_ConnectCompleted(ByVal sender As Object, ByVal e As ExtendedAsyncCompletedEventArgs(Of String))
			If e.Error IsNot Nothing Then
				ShowError(e.Error)
				Disconnect()
			Else
				Authenticate()
			End If
		End Sub

		Private Sub Authenticate()

			' Disconnects if user clicks on the Close button or the Cancel button.
			If _disconnect Then
				Disconnect()
				Return
			End If
			WriteLine("Connected.")
			WriteLine("Authorizing as {0}...", txtUserName.Text)

			imapClient.AuthenticateAsync(txtUserName.Text, txtPassword.Text)
		End Sub

		''' <summary>
		''' Handles the imap client's ConnectCompleted event.
		''' </summary>
		''' <param name="sender">The imap client object.</param>
		''' <param name="e">The event arguments.</param>
		Private Sub imapClient_AuthenticateCompleted(ByVal sender As Object, ByVal e As AsyncCompletedEventArgs)
			If e.Error IsNot Nothing Then
				ShowError(e.Error)
				Disconnect()
				Return
			End If
			SelectFolder()
		End Sub

		Private Sub SelectFolder()
			If _disconnect Then
				Disconnect()
				Return
			End If
			WriteLine("Logged In.")
			WriteLine("Selecting working folder {0}...", txtFolder.Text)

			' Asynchronously select a folder to download messages.
			imapClient.SelectAsync(txtFolder.Text, True)
		End Sub

		''' <summary>
		''' Handles the imap client's ConnectCompleted event.
		''' </summary>
		''' <param name="sender">The imap client object.</param>
		''' <param name="e">The event arguments.</param>
		Private Sub imapClient_SelectFolderCompleted(ByVal sender As Object, ByVal e As AsyncCompletedEventArgs)
			If e.Error IsNot Nothing Then
				ShowError(e.Error)
				Disconnect()
				Return
			End If

			ListMessages()
		End Sub

		Private Sub ListMessages()
			If _disconnect Then
				Disconnect()
				Return
			End If

			' Get current folder.
			Dim folder As Folder = imapClient.WorkingFolder
			WriteLine("Folder selected and {0} messages found.", folder.TotalMessages)

			WriteLine("Retrieving the list of messages...")
			' Asynchronously get message list.
			imapClient.ListMessagesAsync(ImapEnvelopeParts.UniqueId)
		End Sub

		''' <summary>
		''' Handles the imap client's ConnectCompleted event.
		''' </summary>
		''' <param name="sender">The imap client object.</param>
		''' <param name="e">The event arguments.</param>
		Private Sub imapClient_ListMessagesCompleted(ByVal sender As Object, ByVal e As ExtendedAsyncCompletedEventArgs(Of ImapMessageCollection))
			If e.Error IsNot Nothing Then
				ShowError(e.Error)
				Disconnect()
				Return
			End If

			_list = e.Result
			WriteLine("List of messages retrieved.")
			_index = 0
			' Download EML files.
			DownloadEml()
		End Sub

		''' <summary>
		''' Shows an error.
		''' </summary>
		''' <param name="exc">Exception object.</param>
		Private Shared Sub ShowError(ByVal exc As Exception)
			Dim str As String

			If exc.InnerException IsNot Nothing Then
				str = String.Format(Nothing, "An error occurred: {0}" & vbCrLf & "{1}", exc.Message, exc.InnerException.Message)
			Else
				str = String.Format(Nothing, "An error occurred: {0}", exc.Message)
			End If

			MessageBox.Show(str, "Error")
		End Sub

		''' <summary>
		''' Disconnects to the IMAP server.
		''' </summary>
		Private Sub Disconnect()
			WriteLine("Disconnecting...")
			imapClient.DisconnectAsync()
		End Sub

		Private Sub SetDisconnectedStatus()
			WriteLine("Disconnected.")

			grbAuth.Enabled = True
			grbFolder.Enabled = True
			grbServer.Enabled = True
			'grbStatus.Enabled = true;

			progressBarTotal.Value = 0
			progressBar.Value = 0

			btnDownload.Text = "Download"
			Dim resources As New System.ComponentModel.ComponentResourceManager(GetType(ImapDownloader.Imap))
			Me.btnDownload.Image = CType(resources.GetObject("btnDownload.Image"), System.Drawing.Image)
			btnProxy.Enabled = True
			btnDownload.Enabled = True

			_disconnected = True
			_disconnect = False
		End Sub

		''' <summary>
		''' Handles the imap client's DisconnectCompleted event.
		''' </summary>
		''' <param name="sender">The imap client object.</param>
		''' <param name="e">The event arguments.</param>
		Private Sub imapClient_DisconnectCompleted(ByVal sender As Object, ByVal e As ExtendedAsyncCompletedEventArgs(Of String))
			If e.Error IsNot Nothing Then
				ShowError(e.Error)
			End If

			SetDisconnectedStatus()
		End Sub

		Private _list As ImapMessageCollection
		Private _index As Integer

		''' <summary>
		''' Handles the imap client's DownloadMessageCompleted event.
		''' </summary>
		''' <param name="sender">The imap client object.</param>
		''' <param name="e">The event arguments.</param>
		Private Sub imapClient_DownloadMessageCompleted(ByVal sender As Object, ByVal e As ExtendedAsyncCompletedEventArgs(Of Long))
			If e.Error IsNot Nothing Then
				ShowError(e.Error)
				Disconnect()
				Return
			End If

			' Download EML files.
			DownloadEml()
		End Sub

		''' <summary>
		''' Downloads EML files.
		''' </summary>
		Private Sub DownloadEml()

			Retry:
			If _disconnect Then
				Disconnect()
				Return
			End If
			If _index < _list.Count Then
				Dim message As ImapMessage = _list(_index)
				_index += 1
				Dim fileName As String = MailsDir & "\" & GetFilename(message.UniqueId) & ".eml"

				' Skip if it exists,
				If System.IO.File.Exists(fileName) Then
					WriteLine("Skipping message '{0}'...", message.UniqueId)
					GoTo Retry
				End If

				WriteLine("Retrieving message '{0}'...", message.UniqueId)
				progressBarTotal.Value = _index * 100 \ _list.Count
				' Asynchronously get message.
				imapClient.DownloadMessageAsync(message.UniqueId, fileName)
				Return ' Continue downloading.
			Else
				Disconnect()
				Return
			End If
		End Sub

		''' <summary>
		''' Returns a uniquely correct file name from the specified unique message ID.
		''' </summary>
		''' <param name="uniqueId">The unique id.</param>
		''' <returns>The corrected file name.</returns>
		Private Shared Function GetFilename(ByVal uniqueId As String) As String
			' Characters allowed in the filename
			'string allowed = " .-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";            
			Const allowed As String = " .-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"

			' Replace invalid charactes with its hex representation
			Dim sb As New StringBuilder()
			For i As Integer = 0 To uniqueId.Length - 1
				If allowed.IndexOf(uniqueId.Chars(i)) < 0 Then
					sb.AppendFormat("_{0:X2}", System.Convert.ToInt32(uniqueId.Chars(i)))
				Else
					sb.Append(uniqueId.Chars(i))
				End If
			Next i
			Return sb.ToString()
		End Function

		''' <summary>
		''' Handles the proxy settings button's Click event.
		''' </summary>
		''' <param name="sender">The button object.</param>
		''' <param name="e">The event arguments.</param>
		Private Sub btnProxy_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnProxy.Click
			' Creates new ProxySettings dialog and initialize all parameters.
			Dim proxy As New ProxySettings()
			proxy.ProxyServer = imapClient.Proxy.Server
			proxy.Port = imapClient.Proxy.Port
			proxy.UserName = imapClient.Proxy.UserName
			proxy.Password = imapClient.Proxy.Password
			proxy.Domain = imapClient.Proxy.Domain
			proxy.AuthenticationMethod = imapClient.Proxy.AuthenticationMethod
			proxy.Type = imapClient.Proxy.ProxyType
			' Popups the ProxySetting dialog.
			If proxy.ShowDialog() = System.Windows.Forms.DialogResult.OK Then
				' Updates settings.
				imapClient.Proxy.Server = proxy.ProxyServer
				If proxy.Port > 0 Then
					imapClient.Proxy.Port = proxy.Port
				End If
				imapClient.Proxy.UserName = proxy.UserName
				imapClient.Proxy.Password = proxy.Password
				imapClient.Proxy.Domain = proxy.Domain
				imapClient.Proxy.AuthenticationMethod = proxy.AuthenticationMethod
				imapClient.Proxy.ProxyType = proxy.Type
			End If
		End Sub

		''' <summary>
		''' Handles the form's Closing event.
		''' </summary>
		''' <param name="e">The event arguments.</param>
		Protected Overrides Sub OnClosing(ByVal e As System.ComponentModel.CancelEventArgs)
			' Connected?
			If btnDownload.Text = "Cancel" Then
				' Disconnect.
				_disconnect = True

				' Wait for the completion.
				Do While Not _disconnected
					System.Threading.Thread.Sleep(50)
					System.Windows.Forms.Application.DoEvents()
				Loop
			End If

			MyBase.OnClosing(e)
		End Sub

		Private Sub imapClient_Progress(ByVal sender As Object, ByVal e As ImapProgressEventArgs) Handles imapClient.Progress
			If e.State = MailClientTransferState.Downloading Then
				progressBar.Value = CInt(Fix(e.Percentage))
				Application.DoEvents()
			End If
		End Sub
	End Class
End Namespace
