Imports System.ComponentModel
Imports System.Security.Cryptography.X509Certificates
Imports System.Text
Imports ComponentPro
Imports ImapClient.Security
Imports ComponentPro.Net.Mail
Imports ComponentPro.Security.Certificates

Namespace ImapClient
	Partial Public Class ImapClient
		Private _cancelling As Boolean ' User has cancelled the operation?

		''' <summary>
		''' Sets disconnected state.
		''' </summary>
		Private Sub SetDisconnectedState()
			' Clear folder list and message list.
			listView.Items.Clear()
			treeView.Nodes.Clear()

			' Set state to not connected.
			_state = ConnectionState.NotConnected
			' Enable and disable controls.
			EnableDialog(True)

			toolStripProgressBar.Visible = False
			toolStripProgressCancelButton.Visible = False
			toolStripProgressLabel.Visible = False
			tsbLogin.Visible = True
			tsbLogout.Visible = False
			tsbRefresh.Enabled = False
			'refreshToolStripMenuItem.Enabled = false;
			'loginToolStripMenuItem.Text = "&Login...";

			EnableContextMenus(False)

			'newMailboxToolStripMenuItem.Enabled = false;
			'renameMailboxToolStripMenuItem.Enabled = false;
			'deleteMaillboxToolStripMenuItem.Enabled = false;

			_currentFolder = String.Empty

			' Make sure all toolbar buttons' states are updated. 
			listView_SelectedIndexChanged(Nothing, Nothing)

			SetStatusText("Ready")
		End Sub

		Private Sub EnableContextMenus(ByVal enable As Boolean)
			listViewContextMenuStrip.Enabled = enable
			treeViewContextMenuStrip.Enabled = enable
		End Sub

		Private Sub Reconnect(ByVal exc As Exception)
			If exc IsNot Nothing Then
				Util.ShowError(exc)
			End If

			treeView.Nodes.Clear()
			listView.Items.Clear()
			' Disconnect and login again.
			client.Disconnect()

			Connect()
		End Sub

		''' <summary>
		''' Refreshes the folder tree view.
		''' </summary>
		Private Sub RefreshFolderList()
			RefreshFolderList(_currentFolder)
		End Sub

		''' <summary>
		''' Refreshes the folder tree view.
		''' </summary>
		''' <param name="folder">The folder name.</param>
		Private Async Sub RefreshFolderList(ByVal folder As String)
			If folder Is Nothing Then
				Return
			End If

			' Disable controls.
			EnableDialog(False)

			If folder.Length = 0 Then
				SetStatusText("Retrieving root folder list...")
			Else
				SetStatusText("Retrieving folder list under {0}...", folder)
			End If

			Dim col As FolderCollection
			Try
				' Asynchronously get folder list.
				col = Await client.ListFoldersAsync(folder, False, False)
			Catch exc As Exception
				Util.ShowError(exc)
				EnableDialog(True)
				Return
			End Try

			ShowFolderList(col)
		End Sub

		Private Sub ShowFolderList(ByVal col As FolderCollection)
			Dim node As TreeNode
			Dim selectNode As Boolean = False
			' Select the right node.
			If _currentFolder.Length = 0 Then
				node = treeView.Nodes(0)
			Else
				node = treeView.SelectedNode

				If node Is Nothing Then
					node = treeView.Nodes(0)
					selectNode = True
				End If
			End If

			' Clear child nodes.
			node.Nodes.Clear()

			' Loop through the retrieved list.
			For Each i As Folder In col
				' Add selectable node only.
				If i.Selectable Then
					Dim n As TreeNode = node.Nodes.Add(i.ShortName)
					n.ImageIndex = 1
					n.SelectedImageIndex = 1
					n.Tag = i.Name
				End If
			Next i
			' Make it expanded.
			node.Expand()

			If selectNode Then
				' Select the current folder.
				SelectFolder(_currentFolder)
			End If

			If _currentFolder.Length > 0 Then
				' Refresh the message list accordingly.
				RefreshMessageList()
			End If

			EnableDialog(True)
		End Sub


		''' <summary>
		''' Selects a folder.
		''' </summary>
		''' <param name="folder">The folder name. It can be 'folder1/folder11/folder111'</param>
		Private Sub SelectFolder(ByVal folder As String)
			' Split the provided folder path by '/' character.
			Dim arr() As String = folder.Split("/"c)
			Dim i As Integer = 0
			Dim nodes As TreeNodeCollection = treeView.Nodes(0).Nodes

			For Each shortname As String In arr
				For Each node As TreeNode In nodes
					If shortname = node.Text Then
						i += 1
						If i = arr.Length Then
							_noFireBeforeSelectEvent = True
							treeView.SelectedNode = node
							_noFireBeforeSelectEvent = False

							client.Select(folder)
						ElseIf Not node.Checked Then ' Folder list is not parsed.
							' Then retrieve a list of child folder for this node.
							Dim col As FolderCollection = client.ListFolders(CStr(node.Tag))
							For Each item As Folder In col
								' Add selectable folder only.
								If item.Selectable Then
									Dim n As TreeNode = node.Nodes.Add(item.ShortName)
									n.ImageIndex = 1
									n.SelectedImageIndex = 1
									n.Tag = item.Name
								End If
							Next item
							node.Expand()
							node.Checked = True
							nodes = node.Nodes
						End If
					End If
				Next node
			Next shortname
		End Sub

		''' <summary>
		''' Refreshes the message list.
		''' </summary>
		Private Async Sub RefreshMessageList()
			SetStatusText("Retrieving message list...")

			' Wake up GUI.
			Application.DoEvents()

			' Enable progress bar control.
			EnableProgress(True, client.GetFolderInfo(_currentFolder).TotalMessages)

			Dim col As ImapMessageCollection

			Try
				' Asynchronously get message list.
				col = Await client.ListMessagesAsync(ImapEnvelopeParts.UniqueId)
			Catch exc As Exception
				Util.ShowError(exc)
				EnableProgress(False, 0)
				Return
			End Try

			ShowMessageList(col)
		End Sub


		Private Async Sub DownloadImapMessage(ByVal info As MessageListInfo)
		Retry:

			Dim m As ImapMessage = info.List(info.Index)

			If _cancelling Then
				Return
			End If

			Dim msg As ImapMessage = Nothing
			Try
				' Retrieve message information.
				msg = Await client.DownloadImapMessageAsync(m.UniqueId, ImapEnvelopeParts.Envelope)
			Catch ex As Exception
				If Not HandleException(ex) Then
					Return
				End If
			End Try

			ShowMessage(msg, info)

			If _state = ConnectionState.Disconnecting Then
				Disconnect()
				Return
			End If

			If (Not _cancelling) AndAlso info.Index < info.List.Count Then
				GoTo Retry
			End If

			EnableProgress(False, 0)
			' Update the state.
			listView_SelectedIndexChanged(Nothing, Nothing)
		End Sub

		Private Function HandleException(ByVal ex As Exception) As Boolean
			Dim ie As ImapException = TryCast(ex, ImapException)
			If ie Is Nothing OrElse ie.Status <> MailClientExceptionStatus.OperationCancelled Then
				Util.ShowError(ie)
				EnableProgress(False, 0)
				' Update the state.
				listView_SelectedIndexChanged(Nothing, Nothing)
				Return False
			End If

			Return True
		End Function

		Private Function ShowMessage(ByVal msg As ImapMessage, ByVal info As MessageListInfo) As Boolean
			info.Index += 1

			If msg IsNot Nothing Then
				' If From property is empty then use the Sender property.
				Dim [from] As String = msg.From.ToString()
				If [from].Length = 0 AndAlso msg.Sender IsNot Nothing Then
					[from] = msg.Sender.ToString()
				End If
				Dim arr() As String
				If msg.Date IsNot Nothing Then
					If (msg.Flags And ImapMessageFlags.Deleted) <> 0 Then
						arr = New String() { [from], msg.Subject,msg.Date.ToString(),"Delete", Util.FormatSize(msg.Size) }
					Else
						arr = New String() { [from], msg.Subject,msg.Date.ToString(),String.Empty, Util.FormatSize(msg.Size) }
					End If
				Else
					If (msg.Flags And ImapMessageFlags.Deleted) <> 0 Then
						arr = New String() { [from], msg.Subject,"1/1/1900","Delete", Util.FormatSize(msg.Size) }
					Else
						arr = New String() { [from], msg.Subject,"1/1/1900",String.Empty, Util.FormatSize(msg.Size) }
					End If
				End If
				Dim item As New ListViewItem(arr)
				If (msg.Flags And ImapMessageFlags.Seen) = 0 Then
					' New message.
					item.Font = New Font(item.Font, item.Font.Style Or FontStyle.Bold)
					item.ImageIndex = 2
				Else
					' Seen message.
					item.ImageIndex = 3
				End If
				item.Tag = New ListItemTagInfo(msg.MessageInboxIndex, msg.UniqueId, msg.ReceivedDate, msg.Size)

				' Add to the list.
				listView.Items.Add(item)
				' Update the status text.
				toolStripProgressLabel.Text = String.Format("{0}/{1} message(s) retrieved", info.Index, info.List.Count)
			Else
				toolStripProgressBar.Maximum -= 1
			End If

			If info.Index < toolStripProgressBar.Maximum Then
				toolStripProgressBar.Value = info.Index
			End If

			Return info.Index < info.List.Count
		End Function


		Private Class MessageListInfo
			Public Index As Integer
			Public List As ImapMessageCollection
		End Class

		Private Sub ShowMessageList(ByVal col As ImapMessageCollection)
			' Clear the message list.
			listView.Items.Clear()

			' Update the progress bar control.
			toolStripProgressBar.Maximum = col.Count
			toolStripProgressBar.Value = 0

			Dim info As New MessageListInfo()
			info.List = col

			If info.List.Count > 0 Then
				DownloadImapMessage(info)
			Else
				EnableProgress(False, 0)
				' Update the state.
				listView_SelectedIndexChanged(Nothing, Nothing)
			End If
		End Sub

		#Region "Event handlers"

		''' <summary>
		''' Handles the client's ListProcessing event.
		''' </summary>
		''' <param name="sender">The client object.</param>
		''' <param name="e"></param>
		Private Sub client_ListProcessing(ByVal sender As Object, ByVal e As ImapListProcessingEventArgs)
			' If the message list is being processed and the number of processed lines is valid.
			If e.ListType = ImapListProcessingType.MessageList AndAlso toolStripProgressBar.Maximum > e.ProcessedLines AndAlso e.ProcessedLines > 0 Then
				' Update the progress bar and progress status.
				toolStripProgressBar.Value = e.ProcessedLines
				toolStripProgressLabel.Text = String.Format("{0}/{1} message(s) processed", e.ProcessedLines, toolStripProgressBar.Maximum)
			End If
		End Sub



		Private Async Sub SelectDefaultFolder()
			' If disconnecting
			If _state = ConnectionState.Disconnecting Then
				Disconnect()
				Return
			End If

			' Add the user name as the root node.
			Dim node As TreeNode = treeView.Nodes.Add(_settings.UserName)
			' Set root node image.
			node.ImageIndex = 0
			' Selected image as well.
			node.SelectedImageIndex = 0
			' Use Checked property as the node is Loaded state.
			node.Checked = True
			'loginToolStripMenuItem.Text = "&Logout";

			' Select the default folder.
			Await client.SelectAsync("INBOX")

			' Refresh the folder list.
			RefreshFolderList(String.Empty)

			' Set Ready state.
			SetStatusText("Ready")
			_state = ConnectionState.Ready
			' Hide progress bar.
			toolStripProgressBar.Visible = False
			toolStripProgressCancelButton.Visible = False
			toolStripProgressLabel.Visible = False
			tsbLogin.Visible = False
			tsbLogout.Visible = True
			tsbRefresh.Enabled = True
			'refreshToolStripMenuItem.Enabled = true;

			EnableContextMenus(True)

			' Focus to the tree view.
			treeView.Focus()

			' Update states of the toolbar buttons.
			listView_SelectedIndexChanged(Nothing, Nothing)

			' Start the session timeout timer.
			sessionTimeoutTimer.Enabled = True
			sessionTimeoutTimer.Interval = 1000

			EnableDialog(True)
		End Sub


		Private Async Sub Authenticate()
			If _state = ConnectionState.Disconnecting Then
				Disconnect()
				Return
			End If
			SetStatusText("Logging in as {0}...", _settings.UserName)

			Try
				' Asynchronously login to the IMAP server.
				Await client.AuthenticateAsync(_settings.UserName, _settings.Password, _settings.Method)
			Catch exc As Exception
				Util.ShowError(exc)
				Disconnect()
				Return
			End Try

			SelectDefaultFolder()
		End Sub

		''' <summary>
		''' Handles the client's Progress event.
		''' </summary>
		''' <param name="sender">The IMAP client object.</param>
		''' <param name="e">The event arguments.</param>
		Private Sub client_Progress(ByVal sender As Object, ByVal e As ImapProgressEventArgs) Handles client.Progress
			If e.Length > 0 AndAlso (e.State = MailClientTransferState.Downloading OrElse e.State = MailClientTransferState.Uploading) Then
				' Update the status text.
				If e.State = MailClientTransferState.Downloading Then
					toolStripProgressLabel.Text = String.Format("{0}/{1} downloaded", Util.FormatSize(e.BytesTransferred), Util.FormatSize(e.Length))
				Else
					toolStripProgressLabel.Text = String.Format("{0}/{1} uploaded", Util.FormatSize(e.BytesTransferred), Util.FormatSize(e.Length))
				End If

				toolStripProgressBar.Value = CInt(Fix(e.Percentage))
				' Wake up GUI.
				Application.DoEvents()
			End If
		End Sub

		#End Region

		#Region "Folder"

		''' <summary>
		''' Deletes the selected folder.
		''' </summary>
		Private Sub DeleteFolder()
			Dim folder As String = _currentFolder

			EnableDialog(False)

			Try
				SetStatusText("Deleting folder {0}...", folder)
				Application.DoEvents()
				client.DeleteFolder(folder)

				'//if (folder == _currentFolder)
				'//{
				'    _currentFolder = (string)treeView.SelectedNode.Parent.Tag;
				'    if (_currentFolder == null)
				'        _currentFolder = string.Empty;
				'//}

				treeView.SelectedNode.Parent.Checked = False ' Reset folder list downloaded state.
				treeView.SelectedNode = treeView.SelectedNode.Parent

				'RefreshFolderList();
			Catch exc As Exception
				Util.ShowError(exc)
			Finally
				EnableDialog(True)
			End Try
		End Sub

		''' <summary>
		''' Creates a new folder.
		''' </summary>
		Private Sub NewFolder()
			Try
				' Create a new TreeNode.
				Dim node As New TreeNode()
				' Get the parent node.
				Dim parentNode As TreeNode = treeView.SelectedNode
				parentNode.Nodes.Add(node)
				node.ImageIndex = 1
				node.SelectedImageIndex = 1
				' Expand the parent node.
				parentNode.Expand()
				' Begin edit folder name.
				node.BeginEdit()
			Catch exc As Exception
				Util.ShowError(exc)
			End Try
		End Sub

		''' <summary>
		''' Purges all messages that are marked as delete.
		''' </summary>
		Private Sub PurgeMessages()
			Try
				' Purge messages.
				client.Purge()
				' Refresh the message list.
				RefreshMessageList()
			Catch exc As Exception
				Util.ShowError(exc)
			End Try
		End Sub

		#End Region

		#Region "Message"

		''' <summary>
		''' Uploads message from local file.
		''' </summary>
		Private Sub UploadMessage()
			Dim dlg As New OpenFileDialog()
			dlg.Filter = "Email files (*.eml)|*.eml|All files (*.*)|*.*"
			dlg.FilterIndex = 1
			' Show open file dialog to select file to upload.
			If dlg.ShowDialog() = DialogResult.OK Then
				Try
					EnableProgress(True, 100)
					Application.DoEvents()

					' Upload the selected message file.
					client.UploadMessage(_currentFolder, dlg.FileName)
					RefreshMessageList()
				Catch exc As Exception
					If _cancelling Then
						Reconnect(Nothing)
					Else
						Reconnect(exc)
					End If
				Finally
					EnableProgress(False, 0)
				End Try
			End If
		End Sub

		''' <summary>
		''' Deletes selected messages.
		''' </summary>
		Private Sub DeleteMessages()
			' Show a confirmation message box.
			If MessageBox.Show(String.Format("Are you sure you want to delete {0} message(s)?", listView.SelectedItems.Count), "Imap Client Demo", MessageBoxButtons.YesNo, MessageBoxIcon.Question) = DialogResult.No Then
				Return
			End If

			' Enable progress bar, and disable other controls.
			EnableProgress(True, listView.SelectedItems.Count)
			Try
				Dim value As Integer = 0
				For Each item As ListViewItem In listView.SelectedItems
					' User has cancelled the operation?
					If _cancelling Then
						Exit For
					End If
					Application.DoEvents()
					' Delete a message.
					client.DeleteMessage(CType(item.Tag, ListItemTagInfo).SequenceNumber)
					_messagesDelete = True
					item.SubItems(3).Text = "Delete"
					item.Font = New Font(item.Font, item.Font.Style Or FontStyle.Strikeout)
					value += 1
					toolStripProgressBar.Value = value
					toolStripProgressLabel.Text = String.Format("{0}/{1} message(s) deleted", value, listView.SelectedItems.Count)
				Next item
			Catch exc As Exception
				Reconnect(exc)
			Finally
				EnableProgress(False, 0)
			End Try
		End Sub

		''' <summary>
		''' Undeletes messages.
		''' </summary>
		Private Sub UndeleteMessages()
			' Enable progress bar and disable other controls.
			EnableProgress(True, listView.SelectedItems.Count)
			Try
				Dim value As Integer = 0
				For Each item As ListViewItem In listView.SelectedItems
					' User has cancelled the operation.
					If _cancelling Then
						Exit For
					End If
					' Wake up GUI.
					Application.DoEvents()
					' Undelete the selected messages.
					client.UndeleteMessage(CType(item.Tag, ListItemTagInfo).SequenceNumber)
					item.SubItems(3).Text = String.Empty
					item.Font = New Font(item.Font, (item.Font.Style Or FontStyle.Strikeout) Xor FontStyle.Strikeout)
					' Update the progress bar and progress status.
					value += 1
					toolStripProgressBar.Value = value
					toolStripProgressLabel.Text = String.Format("{0}/{1} message(s) undeleted", value, listView.SelectedItems.Count)
				Next item
			Catch exc As Exception
				Util.ShowError(exc)
			Finally
				EnableProgress(False, 0)
			End Try
		End Sub

		Private _copyFolder As String
		Private _copyInfo As ImapMessageSet
		''' <summary>
		''' Copies messages
		''' </summary>
		Private Sub CopyMessage()
			' Create a new ImapMessageIdSet that contains information about the messages being copied.
			_copyInfo = New ImapMessageSet()
			' Add selected messages to the message set;
			For Each item As ListViewItem In listView.SelectedItems
				_copyInfo.Add(CType(item.Tag, ListItemTagInfo).UniqueId)
			Next item
			' Save the selected folder.
			_copyFolder = _currentFolder
		End Sub

		''' <summary>
		''' Pastes the copied messages to the specified destination folder.
		''' </summary>
		''' <param name="destFolder">The destination folder that stores the copied messages.</param>
		Public Sub PasteMessage(ByVal destFolder As String)
			' Select the previously selected folder.
			client.Select(_copyFolder)
			' Copy messages.
			client.CopyMessage(_copyInfo, destFolder)
			' Select the current working folder.
			client.Select(_currentFolder)

			' Refresh the message list.
			RefreshMessageList()
		End Sub

		#End Region

		#Region "Certificate"

		''' <summary>
		''' Returns all issues of the given certificate.
		''' </summary>
		Private Shared Function GetCertProblem(ByVal status As CertificateVerificationStatus, ByVal code As Integer, ByRef showAddTrusted As Boolean) 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
					showAddTrusted = True
					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 Sub client_CertificateReceived(ByVal sender As Object, ByVal e As ComponentPro.Security.CertificateReceivedEventArgs) Handles client.CertificateReceived
			Dim dlg As New CertValidator()

			Dim status As CertificateVerificationStatus = e.Status

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

			Dim sbIssues As New StringBuilder()
			Dim showAddTrusted As Boolean = False
			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, showAddTrusted))
			Next i

			dlg.Certificate = e.ServerCertificates(0)
			dlg.Issues = sbIssues.ToString()
			dlg.ShowAddToTrustedList = showAddTrusted

			dlg.ShowDialog()

			e.AddToTrustedRoot = dlg.AddToTrustedList
			e.Accept = dlg.Accepted
		End Sub

		Private Sub client_CertificateRequired(ByVal sender As Object, ByVal e As ComponentPro.Security.CertificateRequiredEventArgs) Handles client.CertificateRequired
			' If the client cert file is specified.
			If Not String.IsNullOrEmpty(_settings.Cert) Then
				' Load Certificate.
				Dim passdlg As New PasswordPrompt()
				' Ask for cert's passpharse
				If passdlg.ShowDialog() = System.Windows.Forms.DialogResult.OK Then
					Dim clientCert As New X509Certificate2(_settings.Cert, passdlg.Password)
					e.Certificates = New X509Certificate2Collection(clientCert)
					Return
				End If

				' Password has not been provided.
			End If
			Dim dlg As New CertProvider()
			dlg.ShowDialog()
			' Get the selected certificate.
			e.Certificates = New X509Certificate2Collection(dlg.SelectedCertificate)
		End Sub

		#End Region
	End Class
End Namespace
