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

Namespace Pop3Samples
	Partial Public Class MainForm
		Inherits Form
		''' <summary>
		''' Defines connection states.
		''' </summary>
		Private Enum ConnectionState
			NotConnected
			Connecting
			Ready
			Disconnecting
		End Enum

		Private ReadOnly _exception As Boolean ' Incidates whether a licensing exception has occurred.

		Private _settings As LoginInfo ' Login information.
		Private _state As ConnectionState ' Current connection state.
		Private _lastColumnToSort As Integer
		Private _lastSortOrder As SortOrder

		Public Sub New()
			Application.EnableVisualStyles()

			Try
				InitializeComponent()
			Catch exc As Exception
				If TypeOf exc Is ComponentPro.Licensing.Mail.UltimateLicenseException Then
					MessageBox.Show(exc.Message, "Error")
					_exception = True
					Return
				End If

				Throw
			End Try

			AddHandler Me.client.DisconnectCompleted, AddressOf client_DisconnectCompleted
			AddHandler Me.client.ListMessagesCompleted, AddressOf client_ListMessagesCompleted
			AddHandler Me.client.AuthenticateCompleted, AddressOf client_AuthenticateCompleted
			AddHandler Me.client.CertificateReceived, AddressOf client_CertificateReceived
			AddHandler Me.client.CertificateRequired, AddressOf client_CertificateRequired
			AddHandler Me.client.ConnectCompleted, AddressOf client_ConnectCompleted
		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)
			' Close the application if an exception occurred.
			If _exception Then
				Me.Close()
			End If

			' Load settings from the Registry.
			_settings = LoginInfo.LoadConfig()
		End Sub

		''' <summary>
		''' Handles the form's Closed event.
		''' </summary>
		''' <param name="e">The event arguments.</param>
		Protected Overrides Sub OnClosed(ByVal e As EventArgs)
			' Make sure to close the POP3 session before leaving.
			If tsbLogout.Visible Then
				' Close the connection.
				Disconnect()

				' Wait for the completion.
				Do While _state <> ConnectionState.NotConnected
					System.Threading.Thread.Sleep(50)
					' Wake up the GUI.
					System.Windows.Forms.Application.DoEvents()
				Loop
			End If

			' And save the settings.
			_settings.SaveConfig()

			MyBase.OnClosed(e)
		End Sub

		Private Sub EnableDialog(ByVal enable As Boolean)
			listView.Enabled = enable
			tsbLogin.Enabled = enable
			tsbLogout.Enabled = enable
			tsbRefresh.Enabled = enable AndAlso _state = ConnectionState.Ready
			tsbCancel.Visible = False

			Util.EnableCloseButton(Me, enable)
		End Sub

		''' <summary>
		''' Sets the status text.
		''' </summary>
		''' <param name="str">The new status text.</param>
		Private Sub SetStatusText(ByVal str As String)
			toolStripStatusLabel.Text = str
		End Sub

		''' <summary>
		''' Sets the status text.
		''' </summary>
		''' <param name="str">The new status format text.</param>
		''' <param name="parameters">The parameters.</param>
		Private Sub SetStatusText(ByVal str As String, ParamArray ByVal parameters() As Object)
			toolStripStatusLabel.Text = String.Format(str, parameters)
		End Sub

		''' <summary>
		''' Closes the connection and release used resources.
		''' </summary>
		Private Sub Disconnect()
			' Disable the session timeout timer.
			sessionTimeoutTimer.Enabled = False
			SetStatusText("Applying changes...")
			Application.DoEvents()
			For Each i As ListViewItem In listView.Items
				If i.SubItems(6).Text = "Delete" Then
					client.Delete(CInt(Fix(i.Tag)))
				End If
			Next i

			SetStatusText("Disconnecting...")
			' Set Disconnecting state.
			_state = ConnectionState.Disconnecting

			' Asynchronously disconnect.
			client.DisconnectAsync()
		End Sub

		''' <summary>
		''' Sets disconnected state.
		''' </summary>
		Private Sub SetDisconnectedUIState()
			SetStatusText("Ready")
			' 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
			listView.Items.Clear()
			tsbDelete.Enabled = False
			tsbUndelete.Enabled = False
			tsbOpen.Enabled = False
			tsbSaveAs.Enabled = False

			' Make sure all toolbar buttons' states are updated. 
			lvm_SelectedIndexChanged(Nothing, Nothing)
		End Sub

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

			' Set disconnected state.
			SetDisconnectedUIState()
		End Sub

		''' <summary>
		''' Logins to the POP3 server.
		''' </summary>
		Private Sub Connect()
			' Set timeout.
			client.Timeout = _settings.Timeout

			Dim proxy As New WebProxyEx()
			client.Proxy = proxy
			' Setup proxy.
			If _settings.ProxyServer.Length > 0 AndAlso _settings.ProxyPort > 0 Then
				proxy.Server = _settings.ProxyServer
				proxy.Port = _settings.ProxyPort
				proxy.UserName = _settings.ProxyUserName
				proxy.Password = _settings.ProxyPassword
				proxy.Domain = _settings.ProxyDomain
				proxy.ProxyType = _settings.ProxyType
				proxy.AuthenticationMethod = _settings.ProxyAuthenticationMethod
			End If

			SetStatusText("Connecting to {0}:{1}...", _settings.Server, _settings.Port)
			_state = ConnectionState.Connecting
			' Disable controls.
			EnableDialog(False)

			tsbCancel.Visible = True

			' Connect to the POP3 server.
			client.ConnectAsync(_settings.Server, _settings.Port, _settings.SecurityMode)
		End Sub

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

			Authenticate()
		End Sub



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

			' Asynchronously login to the POP3 server.
			client.AuthenticateAsync(_settings.UserName, _settings.Password, _settings.Method)
		End Sub

		''' <summary>
		''' Handles the client's AuthenticateCompleted event.
		''' </summary>
		''' <param name="sender">The client object.</param>
		''' <param name="e">The event arguments.</param>
		Private Sub client_AuthenticateCompleted(ByVal sender As Object, ByVal e As AsyncCompletedEventArgs)
			If e.Error IsNot Nothing Then
				Util.ShowError(e.Error)
				' Close the connection if an error occurred.
				Disconnect()
				Return
			End If

			' Refresh the message list.
			RefreshList()
		End Sub

		''' <summary>
		''' Refreshes the message list.
		''' </summary>
		Private Sub RefreshList()
			' If disconnecting?
			If _state = ConnectionState.Disconnecting Then
				Disconnect()
				Return
			End If

			SetStatusText("Retrieving message list...")

			If (_settings.DownloadOption And Pop3EnvelopeParts.FullHeaders) = Pop3EnvelopeParts.FullHeaders Then
				' Enable progress bar control.
				toolStripProgressBar.Visible = True
				toolStripProgressCancelButton.Visible = True
				toolStripProgressLabel.Visible = True
				toolStripProgressLabel.Text = String.Empty
				toolStripProgressBar.Value = 0
			End If

			' Asynchronously get message list.
			client.ListMessagesAsync(_settings.DownloadOption)
		End Sub

		Private Sub ShowMessageList(ByVal list As Pop3MessageCollection)
			If _state = ConnectionState.Disconnecting Then
				Disconnect()
				Return
			End If

			' Clear the message list.
			listView.Items.Clear()

			If (_settings.DownloadOption And Pop3EnvelopeParts.FullHeaders) = Pop3EnvelopeParts.FullHeaders Then
				For Each msg As Pop3Message In list
					' 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
						arr = New String(6) { msg.MessageInboxIndex.ToString(), msg.UniqueId, msg.Size.ToString(), [from], msg.Subject,msg.Date.ToString(), String.Empty }
					Else
						arr = New String(6) { msg.MessageInboxIndex.ToString(), msg.UniqueId, msg.Size.ToString(), [from], msg.Subject,"1/1/1900", String.Empty }
					End If
					Dim lvi As New ListViewItem(arr)
					lvi.Tag = msg.MessageInboxIndex
					listView.Items.Add(lvi)
				Next msg
			Else
				For Each msg As Pop3Message In list
					Dim arr() As String = { msg.MessageInboxIndex.ToString(), msg.UniqueId, msg.Size.ToString(), String.Empty, String.Empty, String.Empty, String.Empty }
					Dim item As New ListViewItem(arr)
					item.Tag = msg.MessageInboxIndex
					listView.Items.Add(item)
				Next msg
			End If

			SetStatusText("Ready")
			_state = ConnectionState.Ready
			EnableDialog(True)

			toolStripProgressBar.Visible = False
			toolStripProgressCancelButton.Visible = False
			toolStripProgressLabel.Visible = False
			tsbLogin.Visible = False
			tsbLogout.Visible = True
			tsbRefresh.Enabled = True

			lvm_SelectedIndexChanged(Me, Nothing)
		End Sub

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

			ShowMessageList(e.Result)
		End Sub

		''' <summary>
		''' Handles the client's MessageListProgress event.
		''' </summary>
		''' <param name="sender">The client object.</param>
		''' <param name="e"></param>
		Private Sub client_MessageListProgress(ByVal sender As Object, ByVal e As Pop3MessageListProgressEventArgs) Handles client.MessageListProgress
			toolStripProgressBar.Maximum = e.Total
			toolStripProgressBar.Value = e.Downloaded
			toolStripProgressLabel.Text = String.Format("{0}/{1} message(s) downloaded", e.Downloaded, e.Total)
		End Sub

		''' <summary>
		''' Handles the client's Progress event.
		''' </summary>
		''' <param name="sender">The POP3 client object.</param>
		''' <param name="e">The event arguments.</param>
		Private Sub _client_Progress(ByVal sender As Object, ByVal e As Pop3ProgressEventArgs)
			toolStripProgressLabel.Text = String.Format("{0}/{1} downloaded", Util.FormatSize(e.BytesTransferred), Util.FormatSize(_messageSize))
			If e.BytesTransferred < _messageSize Then
				toolStripProgressBar.Value = CInt(Fix(e.BytesTransferred * 100 \ _messageSize))
			End If
			Application.DoEvents()
		End Sub

		#Region "List View"

		Private Sub lvm_SelectedIndexChanged(ByVal sender As Object, ByVal e As EventArgs) Handles listView.SelectedIndexChanged
			Dim enable As Boolean = listView.SelectedItems.Count > 0
			Dim undoable As Boolean = enable AndAlso (listView.SelectedItems.Count > 1 OrElse listView.SelectedItems(0).SubItems(6).Text = "Delete")

			tsbDelete.Enabled = enable
			tsbUndelete.Enabled = undoable
			tsbOpen.Enabled = enable
			tsbSaveAs.Enabled = enable
		End Sub

		Private Sub listView_MouseDoubleClick(ByVal sender As Object, ByVal e As MouseEventArgs) Handles listView.MouseDoubleClick
			tsbOpen_Click(sender, Nothing)
		End Sub

		Private Sub listView_ColumnClick(ByVal sender As Object, ByVal e As ColumnClickEventArgs) Handles listView.ColumnClick
			If listView.Items.Count = 0 Then
				Return
			End If

			Dim sortOrder As SortOrder
			If _lastColumnToSort = e.Column Then
				If _lastSortOrder = SortOrder.Ascending Then
					sortOrder = SortOrder.Descending
				Else
					sortOrder = SortOrder.Ascending
				End If
			Else
				sortOrder = SortOrder.Ascending
			End If

			' Sort by name, size, or date time?
			Select Case e.Column
				Case 0, 2
					listView.ListViewItemSorter = New ListViewItemSizeSorter(e.Column, sortOrder)

				Case 1, 3, 4, 6
					listView.ListViewItemSorter = New ListViewItemNameSorter(e.Column, sortOrder)

				Case 5
					listView.ListViewItemSorter = New ListViewItemDateSorter(e.Column, sortOrder)
			End Select

			_lastColumnToSort = e.Column
			_lastSortOrder = sortOrder
		End Sub

		#End Region

		#Region "Menu Items"

		Private Sub toolStripProgressCancelButton_ButtonClick(ByVal sender As Object, ByVal e As EventArgs) Handles toolStripProgressCancelButton.ButtonClick
			client.Cancel()
		End Sub

		Private Sub openContextMenuItem_Click(ByVal sender As Object, ByVal e As EventArgs) Handles openContextMenuItem.Click
			tsbOpen_Click(sender, Nothing)
		End Sub

		Private Sub deleteContextMenuItem_Click(ByVal sender As Object, ByVal e As EventArgs) Handles deleteContextMenuItem.Click
			tsbDelete_Click(sender, Nothing)
		End Sub

		Private Sub undeleteContextMenuItem_Click(ByVal sender As Object, ByVal e As EventArgs) Handles undeleteContextMenuItem.Click
			tsbUndelete_Click(sender, Nothing)
		End Sub

		Private Sub saveMessageAsContextMenuItem_Click(ByVal sender As Object, ByVal e As EventArgs) Handles saveMessageAsContextMenuItem.Click
			tsbSaveAs_Click(sender, Nothing)
		End Sub

		Private Sub sessionTimeoutTimer_Tick(ByVal sender As Object, ByVal e As EventArgs) Handles sessionTimeoutTimer.Tick
			If (Not client.IsBusy) AndAlso _state = ConnectionState.Ready AndAlso (Not client.IsConnected) Then
				SetDisconnectedUIState()
			End If
		End Sub

		#End Region

		#Region "Toolbar"

		Private Sub tsbOpen_Click(ByVal sender As Object, ByVal e As EventArgs) Handles tsbOpen.Click
			_messageSize = Long.Parse(listView.SelectedItems(0).SubItems(2).Text)
			Dim form As New MessageViewer(client, CInt(Fix(listView.SelectedItems(0).Tag)), _messageSize)
			If form.ShowDialog() = System.Windows.Forms.DialogResult.Abort Then
				' Disconnect and login again.
				client.Disconnect()
				Connect()
			End If
		End Sub

		Private _messageSize As Long
		Private Sub tsbSaveAs_Click(ByVal sender As Object, ByVal e As EventArgs) Handles tsbSaveAs.Click
			Dim sav As New SaveFileDialog()
			sav.Filter = "Email files (*.eml)|*.eml|All files (*.*)|*.*"
			sav.FilterIndex = 1
			sav.Title = "Save the mail as"

			' Show the Save File as dialog.
			If sav.ShowDialog() = System.Windows.Forms.DialogResult.OK Then
				Try
					EnableDialog(False)

					' Get sequence no.
					Dim sequenceId As Integer = CInt(Fix(listView.SelectedItems(0).Tag))
					_messageSize = Long.Parse(listView.SelectedItems(0).SubItems(2).Text)
					AddHandler client.Progress, AddressOf _client_Progress
					toolStripProgressLabel.Visible = True
					toolStripProgressBar.Visible = True
					toolStripProgressBar.Maximum = 100
					toolStripProgressBar.Value = 0
					toolStripProgressCancelButton.Visible = True
					toolStripStatusLabel.Visible = False
					client.DownloadMessage(sequenceId, sav.FileName)
				Catch exc As Exception
					Dim pexc As Pop3Exception = TryCast(exc, Pop3Exception)
					If pexc Is Nothing OrElse pexc.Status <> MailClientExceptionStatus.OperationCancelled Then
						Util.ShowError(exc)
					End If
					client.Disconnect()
					Connect()
				Finally
					RemoveHandler client.Progress, AddressOf _client_Progress
					toolStripProgressLabel.Visible = False
					toolStripProgressBar.Visible = False
					toolStripProgressCancelButton.Visible = False
					toolStripStatusLabel.Visible = True
					EnableDialog(True)
				End Try
			End If
		End Sub

		Private Sub tsbLogin_Click(ByVal sender As Object, ByVal e As EventArgs) Handles tsbLogin.Click
			' Show the Login form.
			Dim form As New Login(_settings)
			If form.ShowDialog() = System.Windows.Forms.DialogResult.OK Then
				Connect()
			End If
		End Sub

		Private Sub tsbLogout_Click(ByVal sender As Object, ByVal e As EventArgs) Handles tsbLogout.Click
			Disconnect()
		End Sub

		Private Sub tsbRefresh_Click(ByVal sender As Object, ByVal e As EventArgs) Handles tsbRefresh.Click
			EnableDialog(False)
			RefreshList()
		End Sub

		Private Sub tsbDelete_Click(ByVal sender As Object, ByVal e As EventArgs) Handles tsbDelete.Click
			For Each i As ListViewItem In listView.SelectedItems
				i.SubItems(6).Text = "Delete"
			Next i

			lvm_SelectedIndexChanged(sender, Nothing)
		End Sub

		Private Sub tsbUndelete_Click(ByVal sender As Object, ByVal e As EventArgs) Handles tsbUndelete.Click
			For Each i As ListViewItem In listView.SelectedItems
				i.SubItems(6).Text = String.Empty
			Next i

			lvm_SelectedIndexChanged(sender, Nothing)
		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)
			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)
			' 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

		Private Sub tsbCancel_Click(ByVal sender As Object, ByVal e As EventArgs) Handles tsbCancel.Click
			client.Cancel()
			tsbCancel.Visible = False
		End Sub
	End Class
End Namespace
