' Uncomment the define below to write tlsssl log to the console screen.
'#define LOGTLSSSL
Imports System.Security.Cryptography.X509Certificates
Imports System.Text
Imports ComponentPro.Net
Imports ComponentPro.Net.Mail
Imports ComponentPro.Security.Certificates

Namespace ImapSamples
	Friend NotInheritable Class ConsoleUtil

		Private Sub New()
		End Sub

		Public Const TextColorCommandResponse As ConsoleColor = ConsoleColor.DarkGray ' Text color for response logs.
		Public Const TextColorError As ConsoleColor = ConsoleColor.Red ' Text color for error logs.
		Public Const TextColorInfo As ConsoleColor = ConsoleColor.Green ' Text color for information logs.

		''' <summary>
		''' Writes line a string to the console screen with the specified color.
		''' </summary>
		''' <param name="color">The text color.</param>
		''' <param name="msg">The string.</param>
		Public Shared Sub WriteLine(ByVal color As ConsoleColor, ByVal msg As String)
			Console.ForegroundColor = color
			Console.WriteLine(msg)
			Console.ResetColor()
		End Sub

		''' <summary>
		''' Writes line a string to the console with the specified color.
		''' </summary>
		''' <param name="color">The text color.</param>
		''' <param name="msg">The string format.</param>
		''' <param name="args">An array of objects to write using format.</param>
		Public Shared Sub WriteLine(ByVal color As ConsoleColor, ByVal msg As String, ParamArray ByVal args() As Object)
			Console.ForegroundColor = color
			Console.WriteLine(msg, args)
			Console.ResetColor()
		End Sub

		''' <summary>
		''' Writes line an error message to the console.
		''' </summary>
		''' <param name="msg">The error message.</param>
		Public Shared Sub WriteError(ByVal msg As String)
			Console.ForegroundColor = TextColorError
			Console.WriteLine("ERROR: " & msg)
			Console.ResetColor()
		End Sub

		''' <summary>
		''' Writes line an information to the console.
		''' </summary>
		''' <param name="msg">The information to write.</param>
		Public Shared Sub WriteInfo(ByVal msg As String)
			Console.ForegroundColor = TextColorInfo
			Console.WriteLine(msg)
			Console.ResetColor()
		End Sub

		''' <summary>
		''' Writes line a string to the console with the specified color.
		''' </summary>
		''' <param name="msg">The string format.</param>
		''' <param name="args">An array of objects to write using format.</param>
		Public Shared Sub WriteInfo(ByVal msg As String, ParamArray ByVal args() As Object)
			Console.ForegroundColor = TextColorInfo
			Console.WriteLine(msg, args)
			Console.ResetColor()
		End Sub

		''' <summary>
		''' Parses a string into an array of parameters.
		''' </summary>
		''' <param name="param">The string to parse.</param>
		''' <returns>Parsed parameters.</returns>
		Public Shared Function ParseParams(ByVal param As String) As String()
			Dim arr As New List(Of String)()

			Dim start As Integer = 0
			Dim instr As Boolean = False
			Dim pc As Char = " "c

			For i As Integer = 0 To param.Length - 1
				Dim c As Char = param.Chars(i)

				' Start a string?
				If c = """"c Then
					instr = Not instr
				ElseIf c = " "c AndAlso pc <> " "c AndAlso (Not instr) Then
					' Removes all leading and trailing occurrences of a set of "space, tab and qoute".
					Dim p As String = param.Substring(start, i - start).Trim(New Char(){ControlChars.Tab, " "c, """"c})
					' Add to the parameters list.
					arr.Add(p)
					start = i + 1
				End If
				pc = c
			Next i

			Dim s As String = param.Substring(start).Trim(New Char() { ControlChars.Tab, " "c, """"c })
			arr.Add(s)

			Return arr.ToArray()
		End Function
	End Class

	Friend Class ImapConsoleClient
		Private ReadOnly _client As Imap ' Imap object.
		Private _operationTime As Date = Date.MinValue ' Start time of an operation.

		Public Sub New()
			' Create Imap object and set event handlers
			_client = New Imap()
			_client.Timeout = 25000
			AddHandler _client.CommandResponse, AddressOf ResponseRead
			AddHandler _client.Progress, AddressOf Progress
			AddHandler _client.CertificateReceived, AddressOf _client_CertificateReceived
			AddHandler _client.CertificateRequired, AddressOf _client_CertificateRequired
		End Sub

		''' <summary>
		''' Returns all issues of the given certificate.
		''' </summary>
		''' <param name="status">The certificate verification result.</param>
		''' <param name="code">The error code.</param>
		''' <returns>Certificate problems.</returns>
		Private Shared Function GetCertProblem(ByVal status As CertificateVerificationStatus, ByVal code As Integer) As String
			Select Case status
				Case CertificateVerificationStatus.TimeNotValid
					Return "Server's certificate has expired or is not valid yet."

				Case CertificateVerificationStatus.Revoked
					Return "Server's certificate has been revoked."

				Case CertificateVerificationStatus.UnknownCA
					Return "Server's certificate was issued by an unknown authority."

				Case CertificateVerificationStatus.RootNotTrusted
					Return "Server's certificate was issued by an untrusted authority."

				Case CertificateVerificationStatus.IncompleteChain
					Return "Server's certificate does not chain up to a trusted root authority."

				Case CertificateVerificationStatus.Malformed
					Return "Server's certificate is malformed."

				Case CertificateVerificationStatus.CNNotMatch
					Return "Server hostname does not match the certificate."

				Case CertificateVerificationStatus.UnknownError
					Return String.Format("Error {0:x} encountered while validating server's certificate.", code)

				Case Else
					Return status.ToString()
			End Select
		End Function

		Private Shared Sub _client_CertificateRequired(ByVal sender As Object, ByVal e As ComponentPro.Security.CertificateRequiredEventArgs)
			' Load certificates from the local machine.
			Dim my As New X509Store(StoreName.My, StoreLocation.CurrentUser)
			my.Open(OpenFlags.ReadOnly)

			' Retrieve a list of available certificates.
			Dim certs As X509Certificate2Collection = my.Certificates

			' If no certificate found, return.
			If certs.Count = 0 Then
				e.Certificates = Nothing
				Return
			End If

			' Show all certificates.
			Console.WriteLine("Select certificate:")
			For i As Integer = 0 To certs.Count
				If i = 0 Then
					Console.WriteLine(String.Format("{0}. [Nothing, skip this step]", i))
					Continue For
				End If

				Console.WriteLine(String.Format("{0}. {1}", i, certs(i - 1).SubjectName.Name))
			Next i

			' And ask user to choose an appropriate certificate.
			Do
				Console.Write(String.Format("Select certificate [0 - {0}]: ", certs.Count))

				Dim certIndex As Integer

				Try
					certIndex = Integer.Parse(Console.ReadLine())
				Catch
					Console.WriteLine("ERROR: Wrong certificate index input!")
					Continue Do
				End Try

				If certIndex > 0 AndAlso certIndex <= certs.Count Then
					e.Certificates = New X509Certificate2Collection(certs(certIndex))
					Return
				End If

				If certIndex = 0 Then
					Exit Do
				End If

				Console.WriteLine(String.Format("ERROR: You must enter number between 0 and {0}.", certs.Count))
			Loop
		End Sub

		Private Shared Sub _client_CertificateReceived(ByVal sender As Object, ByVal e As ComponentPro.Security.CertificateReceivedEventArgs)
			Dim cert As X509Certificate2 = e.ServerCertificates(0)

			Dim status As CertificateVerificationStatus = e.Status

			Dim values() As CertificateVerificationStatus = CType(System.Enum.GetValues(GetType(CertificateVerificationStatus)), CertificateVerificationStatus())

			Dim sbIssues As New StringBuilder()
			For i As Integer = 0 To values.Length - 1
				' Matches the validation status?
				If (status And values(i)) = 0 Then
					Continue For
				End If

				' The issue is processed.
				status = status Xor values(i)

				sbIssues.AppendFormat("{0}" & vbCrLf, GetCertProblem(values(i), e.ErrorCode))
			Next i

			Console.WriteLine("Issue: " & sbIssues.ToString())

			Console.ForegroundColor = ConsoleUtil.TextColorInfo
			Console.WriteLine("Subject: " & cert.SubjectName.Name)
			Console.WriteLine("Issuer: " & cert.IssuerName.Name)
			Console.WriteLine("Effective Date: " & cert.NotBefore)
			Console.WriteLine("Expiry Date: " & cert.NotAfter)
			Console.ResetColor()
			Console.Write("Do you want to accept this certificate (Add to trusted list, Yes, No) [a,y,n]?")

			Dim response As String = Console.ReadLine().Trim().ToLower()

			' Add certiticate of the issuer CA to the trusted list.
			If response = "a" Then
				e.AddToTrustedRoot = True
			ElseIf response = "y" Then
				e.Accept = True
			End If
		End Sub

		<STAThread> _
		Public Shared Sub Main(ByVal args() As String)
			' Print out copyright.
			Console.ForegroundColor = ConsoleUtil.TextColorInfo
			Console.WriteLine("UltimateMail Console Client Demo")
			Console.WriteLine("UltimateMail is available at http://www.componentpro.com")
			Console.WriteLine()
			Console.ResetColor()

			Dim client As New ImapConsoleClient()

			' If the program started with parameters.
			If args.Length > 0 Then
				Try
					' Open a new connection and authenticate with the specified parameters.
					If client.OpenConnection(args) Then
						client.AuthenticateUser(Nothing)
					End If
				Catch e As Exception
					ConsoleUtil.WriteError(e.Message)
				End Try
			End If

			' Go to the main loop.
			client.MainLoop()
		End Sub

		''' <summary>
		''' Processes the parameter list and return a list of options and params. An option is recognized when it has '-' at the beginning.
		''' </summary>
		''' <param name="parameters">The list of parameters.</param>
		''' <param name="min">The minimum number of items in the input param list (for validation).</param>
		''' <param name="max">The maximum number of items in the input param list (for validation)</param>
		''' <param name="minopts">The minimum number of options (for validation)</param>
		''' <param name="maxopts">The maximum number of options (for validation)</param>
		''' <param name="outOptions">The output option list.</param>
		''' <param name="minparams">The minimum number of parameters (for validation)</param>
		''' <param name="maxparams">The maximum number of parameters (for validation)</param>
		''' <param name="outParams">The output parameter list.</param>
		''' <returns>true if the given input param list is valid; otherwise is false.</returns>
		Private Shared Function GetOptionsAndParams(ByVal parameters() As String, ByVal min As Integer, ByVal max As Integer, ByVal minopts As Integer, ByVal maxopts As Integer, ByRef outOptions() As String, ByVal minparams As Integer, ByVal maxparams As Integer, ByRef outParams() As String) As Boolean
			' If the input parameter is not specified.
			If parameters Is Nothing Then
				outOptions = Nothing
				outParams = Nothing
				Return min = 0
			End If

			If parameters.Length < min OrElse parameters.Length > max Then
				outOptions = Nothing
				outParams = Nothing
				Return False
			End If

			Dim opts As New List(Of String)()
			Dim prs As New List(Of String)()

			For Each s As String In parameters
				If s.Chars(0) = "-"c Then
					opts.Add(s)
				Else
					prs.Add(s)
				End If
			Next s

			Dim correct As Boolean = opts.Count >= minopts AndAlso opts.Count <= maxopts AndAlso prs.Count >= minparams AndAlso prs.Count <= maxparams

			If correct Then
				If opts.Count = 0 Then
					outOptions = Nothing
				Else
					outOptions = opts.ToArray()
				End If
				If prs.Count = 0 Then
					outParams = Nothing
				Else
					outParams = prs.ToArray()
				End If
			Else
				outOptions = Nothing
				outParams = Nothing
			End If

			Return correct
		End Function

		''' <summary>
		''' Reads password line.
		''' </summary>
		''' <returns>The read password.</returns>
		Public Shared Function ReadLine() As String
			Dim line As New StringBuilder()
			Dim complete As Boolean = False

			Do While Not complete
				Dim key As ConsoleKeyInfo = Console.ReadKey(True)
				Select Case key.Key
					Case ConsoleKey.Enter
						complete = True

					Case ConsoleKey.Backspace
						If line.Length > 0 Then
							line = line.Remove(line.Length - 1, 1)
							Console.Write(key.KeyChar)
							Console.Write(" ")
							Console.Write(key.KeyChar)
						End If

					Case Else
						If (key.KeyChar >= " ") OrElse (key.Key = ConsoleKey.Tab) Then
							line = line.Append(key.KeyChar)
							Console.Write("*")
						End If
				End Select
			Loop

			Console.WriteLine()
			Return line.ToString()
		End Function

		''' <summary>
		''' Handles the client's ResponseRead event.
		''' </summary>
		''' <param name="sender">The client object.</param>
		''' <param name="e">The event arguments.</param>
		Public Sub ResponseRead(ByVal sender As Object, ByVal e As CommandResponseEventArgs)
			If e.Command IsNot Nothing Then
				ConsoleUtil.WriteLine(ConsoleUtil.TextColorCommandResponse, e.Command)
			Else
				ConsoleUtil.WriteLine(ConsoleUtil.TextColorCommandResponse, e.Response)
			End If
		End Sub

		''' <summary>
		''' Handles the client's Progress event.
		''' </summary>
		''' <param name="sender">The client object.</param>
		''' <param name="e">The event arguments.</param>
		Public Sub Progress(ByVal sender As Object, ByVal e As ImapProgressEventArgs)
			If e.State = MailClientTransferState.None Then
				Return
			End If

			If (Date.Now.Subtract(_operationTime)).TotalSeconds <= 1 Then
				Return
			End If

			ConsoleUtil.WriteInfo(e.BytesTransferred & " bytes.")
			_operationTime = Date.Now
		End Sub

#If LOGTLSSSL Then
		Public Shared Sub TlsSslLogEventReceived(ByVal sender As Object, ByVal args As TlsSslLogEventArgs)
			Console.ForegroundColor = ConsoleUtil.TextColorSecure

			Select Case args.Group
				Case TlsSslLogEventGroup.Alert
					Console.Write("alert: ")
				Case TlsSslLogEventGroup.Info
					Console.Write("info: ")
				Case TlsSslLogEventGroup.StateChange
					Console.Write("state: ")
			End Select

			Console.Write(args.Type)

			Select Case args.Source
				Case TlsSslLogEventSource.Sent
					Console.Write(" sent.")
				Case TlsSslLogEventSource.Received
					Console.Write(" received.")
			End Select

			Console.WriteLine()

			If args.Type <> TlsSslLogEventType.Secured Then
				GoTo Finish
			End If

			Dim client As Imap = CType(sender, Imap)
			Console.WriteLine("Connection was secured using Cipher Protocol {0}.", client.SecureSocket.Cipher.Protocol)
			Console.WriteLine("Connection is using cipher {0}.", client.SecureSocket.Cipher)
		Finish:
			Console.ResetColor()
		End Sub
#End If

		''' <summary>
		''' Shows all supported commands.
		''' </summary>
		Private Shared Sub ShowHelp()
			Console.ForegroundColor = ConsoleUtil.TextColorInfo
			Console.WriteLine("!           get           head")
			Console.WriteLine("?           list          info")
			Console.WriteLine("connect     folders       rename")
			Console.WriteLine("disconnect  select        deselect")
			Console.WriteLine("bye         create        delete")
			Console.WriteLine("login       purge         deletefolder")
			Console.WriteLine("close       quit          ")
			Console.WriteLine("user")
			Console.ResetColor()
		End Sub

		''' <summary>
		''' Closes the connection.
		''' </summary>
		Private Sub CloseConnection()
			If _client.State = MailClientState.Disconnected Then
				ConsoleUtil.WriteError("Not connected.")
				Return
			End If

			ConsoleUtil.WriteInfo("Disconnecting...")
			_client.Disconnect()
		End Sub

		''' <summary>
		''' Opens a new connection.
		''' </summary>
		''' <param name="parameters">The input parameters.</param>
		''' <returns>true if successful; otherwise is false.</returns>
		Private Function OpenConnection(ByVal parameters() As String) As Boolean
			Try
				Dim host As String

				If _client.State <> MailClientState.Disconnected Then
					ConsoleUtil.WriteError("Already connected. Disconnect first.")
					Return False
				End If

				If parameters Is Nothing OrElse parameters.Length = 0 Then
					ConsoleUtil.WriteInfo("Usage: hostname [port] [security]" & vbCrLf & "   port       The server port (eg. 143)" & vbCrLf & "   security   This can be explicit(ex) or implicit(im).")
					Do
						Console.Write("Connect to: ")
						host = Console.ReadLine()
						If host.Trim().Length = 0 Then
							ConsoleUtil.WriteError("Host name cannot be empty")
						Else
							Exit Do
						End If
					Loop

					parameters = ConsoleUtil.ParseParams(host)
					If parameters.Length = 0 Then
						ConsoleUtil.WriteError("Host cannot be empty")
						Return False
					End If
				End If

				If parameters.Length > 3 Then
					ConsoleUtil.WriteInfo("Usage: hostname [port] [security]" & vbCrLf & "   port    The server port (eg. 143)" & vbCrLf & "   security   This can be explicit(ex) or implicit(im).")
					Return False
				End If

				host = parameters(0)
				Dim port As Integer = 0
				Dim securityMode As SecurityMode = SecurityMode.None
				Select Case parameters.Length
					Case 1 ' only server name is specified.
						port = 21
					Case 2 ' server name and port are specified
						Try
							port = Integer.Parse(parameters(1))
						Catch
							port = -1
						End Try
						If port <= 0 OrElse port > 65535 Then
							ConsoleUtil.WriteError("Invalid port number.")
							Return False
						End If
					Case 3 ' server name, port, and security mode are specified.
						Try
							port = Integer.Parse(parameters(1))
						Catch
							port = -1
						End Try
						If port <= 0 OrElse port > 65535 Then
							ConsoleUtil.WriteError("Invalid port number.")
							Return False
						End If

						If String.Compare(parameters(2), "implicit", True) = 0 OrElse String.Compare(parameters(2), "im") = 0 Then
							securityMode = SecurityMode.Implicit
						ElseIf String.Compare(parameters(2), "explicit", True) = 0 OrElse String.Compare(parameters(2), "ex") = 0 Then
							securityMode = SecurityMode.Explicit
						End If
				End Select

				_client.Connect(host, port, securityMode)
				Return True
			Catch e As Exception
				ConsoleUtil.WriteError(e.Message)
				Return False
			End Try
		End Function

		''' <summary>
		''' Authenticates user. If the specified authentication parameter is not specified, it will prompt user for username and password.
		''' </summary>
		''' <param name="parameters">The input parameters.</param>
		Private Sub AuthenticateUser(ByVal parameters() As String)
			If _client.State = MailClientState.Disconnected Then
				ConsoleUtil.WriteError("Not connected.")
				Return
			End If

			Dim user As String = Nothing

			If parameters Is Nothing OrElse parameters.Length = 0 Then
				Console.Write("User: ")
				user = Console.ReadLine()
			ElseIf parameters.Length <> 1 Then
				ConsoleUtil.WriteInfo("Usage: auth [username]")
			Else
				user = parameters(0)
			End If

			Console.Write("Password: ")
			Dim pass As String = ReadLine()

			' Login to the server.
			_client.Authenticate(user, pass)
		End Sub

		''' <summary>
		''' Closes the connection, if connected, and exit the program.
		''' </summary>
		Private Sub Quit()
			If _client.State <> MailClientState.Disconnected Then
				CloseConnection()
			End If

			ConsoleUtil.WriteInfo("Thanks for using UltimateMail .NET")
		End Sub

		#Region "Messages and Folders"

		''' <summary>
		''' Retrieves and display IMAP folder list.
		''' </summary>
		''' <param name="parameters">The parsed parameter list.</param>
		''' <param name="folder">The mailbox name.</param>
		Private Sub GetFolderList(ByVal parameters() As String, ByVal folder As String)
			If _client.State = MailClientState.Disconnected Then
				ConsoleUtil.WriteError("Not connected.")
				Return
			End If

			Dim list As FolderCollection = _client.ListFolders(folder)

			For Each f As Folder In list
				Console.WriteLine("Folder: {0}", f.Name)
			Next f
		End Sub

		''' <summary>
		''' Selects or shows folder info.
		''' </summary>
		''' <param name="parameters">The parsed parameter list.</param>
		''' <param name="foldername">Mailbox name.</param>
		''' <param name="showInfoOnly">Indicates whether to show folder information only.</param>
		Private Sub SelectFolder(ByVal parameters() As String, ByVal foldername As String, ByVal showInfoOnly As Boolean)
			If _client.State = MailClientState.Disconnected Then
				ConsoleUtil.WriteError("Not connected.")
				Return
			End If

			If String.IsNullOrEmpty(foldername) Then
				If showInfoOnly Then
					ConsoleUtil.WriteInfo("Usage: info foldername")
				Else
					ConsoleUtil.WriteInfo("Usage: select foldername")
				End If
				Return
			End If

			_client.Select(foldername, showInfoOnly)
			Console.WriteLine("Folder: {0}", _client.WorkingFolder.Name)
			Console.WriteLine("Unread messages: {0}", _client.WorkingFolder.NotSeenMessages)
			Console.WriteLine("Recent messages: {0}", _client.WorkingFolder.RecentMessages)
			Console.WriteLine("Total messages: {0}", _client.WorkingFolder.TotalMessages)
		End Sub

		''' <summary>
		''' Closes currently selected folder.
		''' </summary>
		Private Sub DeselectFolder()
			If _client.State = MailClientState.Disconnected Then
				ConsoleUtil.WriteError("Not connected.")
				Return
			End If

			_client.Deselect()
		End Sub

		''' <summary>
		''' Creates new folder.
		''' </summary>
		''' <param name="parameters">The parsed parameter list.</param>
		''' <param name="foldername">The folder name to create.</param>
		Private Sub CreateFolder(ByVal parameters() As String, ByVal foldername As String)
			If _client.State = MailClientState.Disconnected Then
				ConsoleUtil.WriteError("Not connected.")
				Return
			End If

			If parameters.Length = 0 Then
				ConsoleUtil.WriteInfo("Usage: create foldername")
				Return
			End If

			_client.CreateFolder(foldername)
		End Sub

		''' <summary>
		''' Renames folder.
		''' </summary>
		''' <param name="parameters">The parsed parameter list.</param>
		''' <param name="rawParams">The raw parameters.</param>
		Private Sub RenameFolder(ByVal parameters() As String, ByVal rawParams As String)
			If _client.State = MailClientState.Disconnected Then
				ConsoleUtil.WriteError("Not connected.")
				Return
			End If

			' Validate and process parameters.
			If parameters Is Nothing OrElse parameters.Length <> 2 Then
				ConsoleUtil.WriteInfo("Usage: rename oldfolder newfolder" & vbCrLf & "   oldfolder         Folder to be renamed." & vbCrLf & "   newfolder         New folder name.")
				Return
			End If

			' rename folder
			_client.RenameFolder(parameters(0), parameters(1))
		End Sub

		''' <summary>
		''' Deletes specified folder.
		''' </summary>
		''' <param name="parameters">The parsed parameter list.</param>
		''' <param name="folderName">The name of the folder to be deleted.</param>
		Private Sub DeleteFolder(ByVal parameters() As String, ByVal folderName As String)
			If _client.State = MailClientState.Disconnected Then
				ConsoleUtil.WriteError("Not connected.")
				Return
			End If

			_client.DeleteFolder(folderName)
		End Sub

		''' <summary>
		''' Removes messages marked as deleted.
		''' </summary>
		Private Sub Purge()
			If _client.State = MailClientState.Disconnected Then
				ConsoleUtil.WriteError("Not connected.")
				Return
			End If

			_client.Purge()
		End Sub

		''' <summary>
		''' Shows headers of one message.
		''' </summary>
		''' <param name="parameters">The parsed parameter list.</param>
		''' <param name="uniqueId">Message unique ID.</param>
		Private Sub ShowHeaders(ByVal parameters() As String, ByVal uniqueId As String)
			' check session state.
			If _client.State = MailClientState.Disconnected Then
				ConsoleUtil.WriteError("Not connected.")
				Return
			End If

			' Show help if message unique ID is empty.
			If String.IsNullOrEmpty(uniqueId) Then
				ConsoleUtil.WriteInfo("Usage: head uniqueID")
				Return
			End If

			' Download message headers.
			Dim messageInfo As ImapMessage = _client.DownloadImapMessage(uniqueId, ImapEnvelopeParts.FullHeaders)

			If messageInfo Is Nothing Then
				ConsoleUtil.WriteError(String.Format("Message with the specified UniqueID '{0}' not found.", uniqueId))
			Else
				' Show headers
				For i As Integer = 0 To messageInfo.Headers.Count - 1
					Console.WriteLine(messageInfo.Headers(i))
				Next i
			End If
		End Sub

		''' <summary>
		''' Downloads a message.
		''' </summary>
		''' <param name="parameters">The parsed parameter list.</param>
		''' <param name="rawParams">The raw parameters.</param>
		Private Sub DownloadMessage(ByVal parameters() As String, ByVal rawParams As String)
			If _client.State = MailClientState.Disconnected Then
				ConsoleUtil.WriteError("Not connected.")
				Return
			End If

			Dim prs() As String = Nothing
			Dim opts() As String = Nothing
			' Validate and process parameters.
			If Not GetOptionsAndParams(parameters, 1, 2, 0, 0, opts, 1, 2, prs) Then
				ConsoleUtil.WriteInfo("Usage: get uniqueID [destination path]" & vbCrLf & "   uniqueID           The message unique id to search for." & vbCrLf & "   destination path   The path of the local file. This cannot be a directory.")
				Return
			End If

			If prs.Length = 1 Then
				' Display message to console.
				_client.DownloadMessage(prs(0), Console.OpenStandardOutput())
			Else
				' Download message and save it to a local file.
				_client.DownloadMessage(prs(0), prs(1))
			End If
		End Sub

		''' <summary>
		''' Shows message list.
		''' </summary>
		Private Sub GetMessageList()
			If _client.State = MailClientState.Disconnected Then
				ConsoleUtil.WriteError("Not connected.")
				Return
			End If

			ConsoleUtil.WriteInfo("Getting message list...")

			' Get message list.
			Dim messageList As ImapMessageCollection = _client.ListMessages()

			If messageList.Count = 0 Then
				ConsoleUtil.WriteError("No messages found.")
				Return
			End If

			' Show header.
			Console.WriteLine("------------------------------------------------------------------------------")
			Console.WriteLine("|   Message Unique ID | From                                                 |")
			Console.WriteLine("|                     | Subject                                              |")
			Console.WriteLine("| #Seq.               | Date                                            Size |")
			Console.WriteLine("------------------------------------------------------------------------------")

			' Display messages.
			For Each message As ImapMessage In messageList
				Console.WriteLine("| {0} | {1} |" & vbLf & "|                     | {2} |" & vbLf & "| {3}                | {4}       {5} |", message.UniqueId.PadLeft(19), message.From.ToString().PadRight(52), message.Subject.Substring(0, Math.Min(52, message.Subject.Length)).PadRight(52), message.MessageInboxIndex.ToString().PadRight(4), message.ReceivedDate.LocalTime.ToString("yyyy-MM-dd HH:mm:ss").PadRight(36), FormatSize(message.Size).PadLeft(9))
				Console.WriteLine("+----------------------------------------------------------------------------+")
			Next message
		End Sub

		''' <summary>
		''' Deletes a message.
		''' </summary>
		''' <param name="parameters">The parsed parameter list.</param>
		''' <param name="uniqueId">Message unique ID.</param>
		Private Sub DeleteMessage(ByVal parameters() As String, ByVal uniqueId As String)
			If _client.State = MailClientState.Disconnected Then
				ConsoleUtil.WriteError("Not connected.")
				Return
			End If

			If String.IsNullOrEmpty(uniqueId) Then
				ConsoleUtil.WriteInfo("Usage: delete uniqueID")
				Return
			End If

			' Mark message as deleted
			_client.DeleteMessage(uniqueId)
		End Sub

		#End Region

		''' <summary>
		''' Processes user input commands.
		''' </summary>
		Public Sub MainLoop()
			Do
				Console.Write("Imap> ")
				Dim command As String = Console.ReadLine().Trim()
				Dim arr() As String = ConsoleUtil.ParseParams(command)

				Dim cmd As String = arr(0)
				If cmd.Length = 0 Then
					Continue Do
				End If

				Dim parameters(arr.Length - 2) As String
				For i As Integer = 0 To parameters.Length - 1
					parameters(i) = arr(i + 1)
				Next i

				Dim n As Integer = command.IndexOfAny(New Char() { ControlChars.Tab, " "c })
				Dim rawParams As String
				If n <> -1 Then
					rawParams = command.Substring(n + 1).Trim(New Char() { ControlChars.Tab, " "c, """"c })
				Else
					rawParams = String.Empty
				End If

				Try
					Select Case cmd
						Case "bye"
							GoTo CaseLabel1
						Case "!"
							GoTo CaseLabel1
						Case "exit"
							GoTo CaseLabel1
						Case "quit"
						CaseLabel1:
							Quit()
							Return
						Case "disconnect"
							GoTo CaseLabel2
						Case "close"
						CaseLabel2:
							CloseConnection()
						Case "connect"
							GoTo CaseLabel3
						Case "open"
						CaseLabel3:
							If Not OpenConnection(parameters) Then
								Exit Select
							End If
							parameters = Nothing
							GoTo CaseLabel4
						Case "user"
						CaseLabel4:
							AuthenticateUser(parameters)
						Case "?"
							GoTo CaseLabel5
						Case "help"
						CaseLabel5:
							ShowHelp()
						Case "head"
							ShowHeaders(parameters, rawParams)
						Case "list"
							GetMessageList()
						Case "get", "retr"
							DownloadMessage(parameters, rawParams)
						Case "folders"
							GetFolderList(parameters, rawParams)
						Case "select"
							SelectFolder(parameters, rawParams, False)
						Case "info"
							SelectFolder(parameters, rawParams, True)
						Case "deselect"
							DeselectFolder()
						Case "create"
							CreateFolder(parameters, rawParams)
						Case "rename"
							RenameFolder(parameters, rawParams)
						Case "deletefolder"
							DeleteFolder(parameters, rawParams)
						Case "del", "delete"
							DeleteMessage(parameters, rawParams)
						Case "purge"
							Purge()
						Case Else
							ConsoleUtil.WriteError(String.Format("'{0}' is not recognized as a supported command.", cmd))
					End Select
				Catch exc As Exception
					Dim e As ImapException = TryCast(exc, ImapException)
					ConsoleUtil.WriteError(exc.Message)
					If e IsNot Nothing AndAlso e.Status <> MailClientExceptionStatus.ProtocolError AndAlso e.Status <> MailClientExceptionStatus.ConnectionClosed AndAlso e.Message.IndexOf("not supported") = 0 Then
						_client.Disconnect()
					End If
				End Try
			Loop
		End Sub

		''' <summary>
		''' Returns a formatted file size in bytes, kbytes, or mbytes.
		''' </summary>
		''' <param name="size">The input file size.</param>
		''' <returns>The formatted file size.</returns>
		Public Shared Function FormatSize(ByVal size As Long) As String
			If size < 1024 Then
				Return size & " B"
			End If
			If size < 1024*1024 Then
				Return String.Format("{0:#.#} KB", size/1024.0F)
			End If
			If size < 1024*1024*1024 Then
				Return String.Format("{0:#.#} MB", size/1024.0F/1024.0F)
			Else
				Return size.ToString()
			End If
		End Function
	End Class
End Namespace