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

Namespace ScanImapSample
	Partial Public Class ScanImap
		Inherits Form
		Private ReadOnly _exception As Boolean
		Private _mailsDir As String ' Full path to the directory that stores downloaded messasges.
		Private ReadOnly _customSignatures As New List(Of SignatureInfo)() ' List of custom signature.
		Private _settings As ScanSettings

		Public Sub New()
			' The try catch block below is no necessary if you are owning a Production license.
			Try
				InitializeComponent()
			Catch exc As ComponentPro.Licensing.BounceInspector.UltimateLicenseException
				MessageBox.Show(exc.Message, "Error")
				_exception = True
			End Try

			Util.PopulateEnum(GetType(SecurityMode), cbxSecurity)
			Util.PopulateEnum(GetType(ImapAuthenticationMethod), cbxAuthentication)

			' Load settings from the Registry.
			LoadFormSettings()

			' Setup the mail storing directory.
			If Not System.IO.Directory.Exists(MailsDir) Then
				System.IO.Directory.CreateDirectory(MailsDir)
			End If

#If Not Framework4_5 Then
			AddHandler Me.imapClient.AuthenticateCompleted, AddressOf _client_AuthenticateCompleted
			AddHandler Me.imapClient.ConnectCompleted, AddressOf _client_ConnectCompleted

			AddHandler Me.bounceFilter.ProcessMessagesCompleted, AddressOf _filter_ProcessMessagesCompleted
#End If
		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

		Private Sub LoadFormSettings()
			' Load settings from the Registry.
			txtServer.Text = CStr(Util.GetProperty("Server", String.Empty))
			txtPort.Text = Util.GetIntProperty("Port", 143).ToString()
			txtUserName.Text = CStr(Util.GetProperty("UserName", String.Empty))
			txtPassword.Text = CStr(Util.GetProperty("Password", String.Empty))
			cbxAuthentication.SelectedIndex = Util.GetIntProperty("Authentication", 0)
			txtPath.Text = CStr(Util.GetProperty("MailsDir", MailsDir))
			txtFolder.Text = CStr(Util.GetProperty("Folder", "INBOX"))
			chkDelete.Checked = Util.GetIntProperty("Delete", 0) = 1
			cbxSecurity.SelectedIndex = Util.GetIntProperty("Sec", 0)
			txtCertificate.Text = CStr(Util.GetProperty("Cert", String.Empty))
		End Sub

		Private Sub SaveFormSettings()
			' Save settings to the Registry.
			Util.SaveProperty("Server", txtServer.Text)
			Util.SaveProperty("Port", txtPort.Text)
			Util.SaveProperty("UserName", txtUserName.Text)
			Util.SaveProperty("Password", txtPassword.Text)
			Util.SaveProperty("Authentication", cbxAuthentication.SelectedIndex)
			Util.SaveProperty("MailsDir", txtPath.Text)
			Util.SaveProperty("Folder", txtFolder.Text)
			If chkDelete.Checked Then
				Util.SaveProperty("Delete",1)
			Else
				Util.SaveProperty("Delete",0)
			End If
			Util.SaveProperty("Sec", cbxSecurity.SelectedIndex)
			Util.SaveProperty("Cert", txtCertificate.Text)
		End Sub

		''' <summary>
		''' Gets the path to the directory that stores downloaded messages.
		''' </summary>
		Public ReadOnly Property MailsDir() As String
			Get
				_mailsDir = System.IO.Path.GetFullPath(AppDomain.CurrentDomain.BaseDirectory & "..\..\Mails")
				If Not System.IO.Directory.Exists(_mailsDir) Then
					_mailsDir = AppDomain.CurrentDomain.BaseDirectory & "Mails"
				End If

				Return _mailsDir
			End Get
		End Property

		''' <summary>
		''' Handles the Form's Close event.
		''' </summary>
		''' <param name="e">The event arguments.</param>
		Protected Overrides Sub OnClosed(ByVal e As EventArgs)
			MyBase.OnClosed(e)

			' Save setting before closing.
			SaveFormSettings()
		End Sub

		''' <summary>
		''' Handles the Settings button's Click event.
		''' </summary>
		''' <param name="sender">The button object.</param>
		''' <param name="e">The event arguments.</param>
		Private Sub btnSettings_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnSettings.Click
			Dim dlg As New Settings()
			dlg.ShowDialog()
		End Sub

		''' <summary>
		''' Handles the CustomSignature's Click event.
		''' </summary>
		''' <param name="sender">The button object.</param>
		''' <param name="e">The event arguments.</param>
		Private Sub btnCustomSignature_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnCustomSignature.Click
			Dim dlg As New CustomSignatures(bounceFilter, _customSignatures)
			dlg.ShowDialog()
		End Sub

		''' <summary>
		''' Handles the Scan's Click event.
		''' </summary>
		''' <param name="sender">The button object.</param>
		''' <param name="e">The event arguments.</param>
#If Framework4_5 Then
		Private Async Sub btnScan_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnScan.Click
#Else
		Private Sub btnScan_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnScan.Click
#End If
			' Returns if the server name is empty.
			If txtServer.Text.Length = 0 Then
				MessageBox.Show("Please enter IMAP server address", "BounceInspector", MessageBoxButtons.OK)
				Return
			End If

			Dim port As Integer
			Try
				port = Integer.Parse(txtPort.Text)
			Catch exc As Exception
				Util.ShowError(exc, "Invalid port")
				Return
			End Try
			If port < 0 OrElse port > 65535 Then
				MessageBox.Show("Invalid port number", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
				Return
			End If

			If txtUserName.Text.Length = 0 Then
				MessageBox.Show("Please enter user name for authentication", "BounceInspector", MessageBoxButtons.OK)
				Return
			End If

			If txtPassword.Text.Length = 0 Then
				MessageBox.Show("Please enter password for authentication", "BounceInspector", MessageBoxButtons.OK)
				Return
			End If

			If txtFolder.Text.Length = 0 Then
				MessageBox.Show("Please enter IMAP folder to download messages", "BounceInspector", MessageBoxButtons.OK)
				Return
			End If

			' Disable controls and enable the progress bar.
			EnableProgress(True)

			' Clear the result list items.
			ltvResult.Items.Clear()
			tsbOpen.Enabled = False

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

			' Allow delete bounced emails?
			bounceFilter.AllowDelete = chkDelete.Checked
			bounceFilter.AllowInboxDelete = chkDelete.Checked

			' Load settings
			_settings = Settings.LoadConfig()

			Dim proxy As New WebProxyEx()
			imapClient.Proxy = proxy
			' If proxy is specified.
			If _settings.ProxyServer.Length > 0 AndAlso _settings.ProxyPort > 0 Then
				proxy.Server = _settings.ProxyServer
				proxy.Port = _settings.ProxyPort
				proxy.UserName = _settings.ProxyUser
				proxy.Password = _settings.ProxyPassword
				proxy.Domain = _settings.ProxyDomain
				proxy.ProxyType = _settings.ProxyType
				proxy.AuthenticationMethod = _settings.ProxyMethod
			End If

			lblStatus.Text = String.Format("Connecting to {0}:{1}...", txtServer.Text, port)

			' Asynchronously connect to the IMAP server.
			imapClient.Timeout = _settings.Timeout * 1000

			Dim securityMode As SecurityMode = CType(cbxSecurity.SelectedItem, SecurityMode)

#If Framework4_5 Then
			Try
				Await imapClient.ConnectAsync(txtServer.Text, port, securityMode)
			Catch ex As Exception
			End Try
#Else
			imapClient.ConnectAsync(txtServer.Text, port, securityMode)
#End If
		End Sub

		''' <summary>
		''' Handles the Browse button's event handler.
		''' </summary>
		''' <param name="sender">The button object.</param>
		''' <param name="e">The event arguments.</param>
		Private Sub btnBrowse_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnBrowse.Click
			' Open Folder Browser Dialog.
			Dim dlg As New FolderBrowserDialog()
			dlg.SelectedPath = txtPath.Text
			If dlg.ShowDialog() = System.Windows.Forms.DialogResult.OK Then
				txtPath.Text = dlg.SelectedPath
			End If
		End Sub

		Private Sub PostProcessMessages(ByVal result As BounceResultCollection)
			EnableProgress(False)
			' Disconnect when finish.
			imapClient.Disconnect()
			If result IsNot Nothing Then
				If result.HasErrors Then
					Dim dlg As New Errors()
					dlg.SetErrors(result)
					dlg.ShowDialog()
				End If

				Dim found As String = String.Format("{0}/{1} bounced e-mails found.", result.BounceCount, result.Count)

				If result.CancelledIndex <> -1 Then
					MessageBox.Show("Operation has been cancelled by user. " & found, "BounceInspector", MessageBoxButtons.OK)
				Else
					MessageBox.Show(found, "BounceInspector", MessageBoxButtons.OK)
				End If
			End If
		End Sub

		#Region "Bounce Inspector & Client"

#If Not Framework4_5 Then
		''' <summary>
		''' Handles the BounceInspector's ProcessMessagesCompleted event.
		''' </summary>
		''' <param name="sender">The BounceInspector object.</param>
		''' <param name="e">The event arguments.</param>
		Private Sub _filter_ProcessMessagesCompleted(ByVal sender As Object, ByVal e As ExtendedAsyncCompletedEventArgs(Of BounceResultCollection))
			If e.Error IsNot Nothing Then
				Util.ShowError(e.Error)
			Else
				lblStatus.Text = "Completed"
			End If

			PostProcessMessages(e.Result)
		End Sub
#End If

#If Framework4_5 Then
		Private Async Sub ProcessMessages()
#Else
		Private Sub ProcessMessages()
#End If
			lblStatus.Text = "Processing messages..."

			Dim dest As String = txtPath.Text
			If dest.Trim().Length = 0 Then ' When destination directory is empty, all message will be downloaded and processed in memory.
				dest = Nothing
			End If

			Dim col As ImapMessageCollection

			If chkArrivedSince.Checked OrElse chkArrivedBefore.Checked Then
				imapClient.Select(txtFolder.Text)

				Dim since As Date
				If chkArrivedSince.Checked Then
					since = dateTimePickerSince.Value
				Else
					since = Date.MinValue
				End If
				Dim before As Date
				If chkArrivedBefore.Checked Then
					before = dateTimePickerBefore.Value
				Else
					before = Date.MaxValue
				End If

				col = imapClient.ListMessages(ImapEnvelopeParts.UniqueId Or ImapEnvelopeParts.MessageInboxIndex, ImapCriterion.ArrivedBetween(since, before))
			Else
				col = Nothing
			End If

#If Framework4_5 Then
			Dim result As BounceResultCollection = Nothing
			Try
				' Asynchronously process messages.
				result = Await bounceFilter.ProcessMessagesAsync(imapClient, txtFolder.Text, col, dest)
				lblStatus.Text = "Completed"
			Catch ex As Exception
				Util.ShowError(e.Error)
			End Try

			PostProcessMessages(result)
#Else
			' Asynchronously process messages.
			bounceFilter.ProcessMessagesAsync(imapClient, txtFolder.Text, col, dest)
#End If
		End Sub

		#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 imapClient_CertificateReceived(ByVal sender As Object, ByVal e As ComponentPro.Security.CertificateReceivedEventArgs) Handles imapClient.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 imapClient_CertificateRequired(ByVal sender As Object, ByVal e As ComponentPro.Security.CertificateRequiredEventArgs) Handles imapClient.CertificateRequired
			' If the client cert file is specified.
			If Not String.IsNullOrEmpty(txtCertificate.Text) 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(txtCertificate.Text, 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 Login()
			lblStatus.Text = "Logging in with user name: " & txtUserName.Text
			' Login with the provided user name and password.

			Dim ia As ImapAuthenticationMethod = CType(cbxAuthentication.SelectedItem, ImapAuthenticationMethod)

#If Framework4_5 Then
			Try
				Await imapClient.AuthenticateAsync(txtUserName.Text, txtPassword.Text, pa)
			Catch ex As Exception
				imapClient.Disconnect()
				Util.ShowError(e.Error)
				EnableProgress(False)
				Return
			End Try

			ProcessMessages()
#Else
			imapClient.AuthenticateAsync(txtUserName.Text, txtPassword.Text, ia)
#End If
		End Sub

#If Not Framework4_5 Then
		''' <summary>
		''' Handles the IMAP Client's ConnectCompleted event.
		''' </summary>
		''' <param name="sender">The ImapClient 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)
				EnableProgress(False)
				Return
			End If

			Login()
		End Sub

		''' <summary>
		''' Handles the IMAP Client's AuthenticateCompleted event.
		''' </summary>
		''' <param name="sender">The ImapClient 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
				imapClient.Disconnect()
				Util.ShowError(e.Error)
				EnableProgress(False)
				Return
			End If

			ProcessMessages()
		End Sub
#End If

		Private Sub bounceFilter_Processed(ByVal sender As Object, ByVal e As ProcessedEventArgs) Handles bounceFilter.Processed
			' Handle the bounce Processed event here.
		End Sub

		Private Sub _filter_Progress(ByVal sender As Object, ByVal e As ProgressEventArgs) Handles bounceFilter.Progress
			If Not IsDisposed Then
				Invoke(New EventHandler(Of ProgressEventArgs)(AddressOf Progress), sender, e)
			End If
		End Sub

		''' <summary>
		''' Handles the BounceInspector's Progress event.
		''' </summary>
		''' <param name="sender">The BounceInspector object.</param>
		''' <param name="e">The event arguments.</param>
		Private Sub Progress(ByVal sender As Object, ByVal e As ProgressEventArgs)
			Dim r As BounceResult = e.Result

			If r.MailMessage Is Nothing Then
				Return
			End If

			progressBar.Value = e.Current * 100 / e.Total
			lblStatus.Text = "Processed mail with subject: '" & r.MailMessage.Subject & "'"

			If r.Identified Then
				' Add to the result list view.
				Dim item As ListViewItem
				If r.FilePath IsNot Nothing Then
					If r.Addresses.Length > 0 Then
						item = New ListViewItem(New String() {Path.GetFileName(r.FilePath), r.MailMessage.Subject,r.Addresses(0), r.BounceCategory.Name, r.BounceType.Name, (r.FileDeleted OrElse r.InboxDeleted).ToString(), r.Dsn.Action.ToString(), r.Dsn.DiagnosticCode })
					Else
						item = New ListViewItem(New String() {Path.GetFileName(r.FilePath), r.MailMessage.Subject,String.Empty, r.BounceCategory.Name, r.BounceType.Name, (r.FileDeleted OrElse r.InboxDeleted).ToString(), r.Dsn.Action.ToString(), r.Dsn.DiagnosticCode })
					End If
				Else
					If r.Addresses.Length > 0 Then
						item = New ListViewItem(New String() {String.Empty, r.MailMessage.Subject,r.Addresses(0), r.BounceCategory.Name, r.BounceType.Name, (r.FileDeleted OrElse r.InboxDeleted).ToString(), r.Dsn.Action.ToString(), r.Dsn.DiagnosticCode })
					Else
						item = New ListViewItem(New String() {String.Empty, r.MailMessage.Subject,String.Empty, r.BounceCategory.Name, r.BounceType.Name, (r.FileDeleted OrElse r.InboxDeleted).ToString(), r.Dsn.Action.ToString(), r.Dsn.DiagnosticCode })
					End If
				End If

				item.Tag = r.FilePath

				ltvResult.Items.Add(item)
			End If
		End Sub

		''' <summary>
		''' Enables/disables progress bar, disable/enables controls.
		''' </summary>
		Private Sub EnableProgress(ByVal enable As Boolean)
			progressBar.Enabled = enable
			progressBar.Value = 0
			btnAbort.Enabled = enable
			btnCustomSignature.Enabled = Not enable
			btnScan.Enabled = Not enable
			grbAuth.Enabled = Not enable
			grbFolder.Enabled = Not enable
			grbServer.Enabled = Not enable
			chkDelete.Enabled = Not enable
			lblStatus.Text = String.Empty

			' Disable/Enable the Form's Close button.
			Util.EnableCloseButton(Me, (Not enable))
		End Sub

		#End Region

		''' <summary>
		''' Handles the Abort button's event handler.
		''' </summary>
		''' <param name="sender">The button object.</param>
		''' <param name="e">The event arguments.</param>
		Private Sub btnAbort_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnAbort.Click
			' Abort processing.
			imapClient.Cancel()
			bounceFilter.Cancel()
		End Sub

		Private Sub btnCertBrowse_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnCertBrowse.Click
			Dim dlg As New OpenFileDialog()
			dlg.Title = "Select a certificate file"
			dlg.FileName = txtCertificate.Text
			dlg.Filter = "All files|*.*"
			dlg.FilterIndex = 1
			If dlg.ShowDialog() = System.Windows.Forms.DialogResult.OK Then
				txtCertificate.Text = dlg.FileName
			End If
		End Sub

		Private Sub ltvResult_SelectedIndexChanged(ByVal sender As Object, ByVal e As EventArgs) Handles ltvResult.SelectedIndexChanged
			tsbOpen.Enabled = ltvResult.SelectedItems.Count > 0 AndAlso ltvResult.SelectedItems(0).Tag IsNot Nothing
		End Sub

		Private Sub ltvResult_DoubleClick(ByVal sender As Object, ByVal e As EventArgs) Handles ltvResult.DoubleClick
			If tsbOpen.Enabled Then
				tsbOpen_Click(sender, e)
			End If
		End Sub

		Private Sub tsbOpen_Click(ByVal sender As Object, ByVal e As EventArgs) Handles tsbOpen.Click
			Dim form As New MessageViewer(CStr(ltvResult.SelectedItems(0).Tag))
			form.ShowDialog(Me)
		End Sub

		Private Sub chkArrivedFrom_CheckStateChanged(ByVal sender As Object, ByVal e As EventArgs) Handles chkArrivedSince.CheckStateChanged
			dateTimePickerSince.Enabled = chkArrivedSince.Checked
		End Sub

		Private Sub chkArrivedTo_CheckedChanged(ByVal sender As Object, ByVal e As EventArgs) Handles chkArrivedBefore.CheckedChanged
			dateTimePickerBefore.Enabled = chkArrivedBefore.Checked
		End Sub
	End Class

	''' <summary>
	''' Contains signature information for local uses.
	''' </summary>
	Public Class SignatureInfo
		Public Index As Integer
		Public RegexPattern As String
		Public CategoryCode As Integer
		Public TypeCode As Integer
		Public Delete As Boolean
		Public SignaturePart As BounceSignaturePart
	End Class
End Namespace