using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Windows.Forms;
using ScanPop3Sample.Security;
using ComponentPro;
using ComponentPro.Net;
using ComponentPro.Net.Mail;
using ComponentPro.Security.Certificates;

namespace ScanPop3Sample
{
    public partial class ScanPop3 : Form
    {
        private bool _exception;
        private string _mailsDir; // Full path to the directory that stores downloaded messasges.
        private readonly List<SignatureInfo> _customSignatures = new List<SignatureInfo>(); // List of custom signature.
        ScanSettings _settings;

        public ScanPop3()
        {
            // The try catch block below is no necessary if you are owning a Production license.
            try
            {
                InitializeComponent();
            }
            catch (ComponentPro.Licensing.BounceInspector.UltimateLicenseException exc)
            {
                MessageBox.Show(exc.Message, "Error");
                _exception = true;
            }

            Util.PopulateEnum(typeof(Pop3AuthenticationMethod), cbxAuthentication);
            Util.PopulateEnum(typeof(SecurityMode), cbxSecurity);

            // Load settings from the Registry.
            LoadFormSettings();

            // Setup the mail storing directory.
            if (!System.IO.Directory.Exists(MailsDir))
                System.IO.Directory.CreateDirectory(MailsDir);

#if !Framework4_5
            this.pop3Client.AuthenticateCompleted += this.pop3Client_AuthenticateCompleted;
            this.pop3Client.ConnectCompleted += this.pop3Client_ConnectCompleted;

            this.bounceFilter.ProcessMessagesCompleted += this.bounceFilter_ProcessMessagesCompleted;
#endif
        }

        /// <summary>
        /// Handles the Form's Load event.
        /// </summary>
        /// <param name="e">The event arguments.</param>
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            if (_exception)
                this.Close();
        }

        private void LoadFormSettings()
        {
            // Load settings from the Registry.
            txtServer.Text = (string)Util.GetProperty("Server", string.Empty);
            txtPort.Text = Util.GetIntProperty("Port", 110).ToString();
            txtUserName.Text = (string)Util.GetProperty("UserName", string.Empty);
            txtPassword.Text = (string)Util.GetProperty("Password", string.Empty);
            cbxAuthentication.SelectedIndex = Util.GetIntProperty("Authentication", 0);
            txtPath.Text = (string)Util.GetProperty("MailsDir", MailsDir);
            chkDelete.Checked = Util.GetIntProperty("Delete", 0) == 1;
            cbxSecurity.SelectedIndex = Util.GetIntProperty("Sec", 0);
            txtCertificate.Text = (string) Util.GetProperty("Cert", string.Empty);
        }

        private void 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("Delete", chkDelete.Checked ? 1 : 0);
            Util.SaveProperty("Sec", cbxSecurity.SelectedIndex);
            Util.SaveProperty("Cert", txtCertificate.Text);
        }

        /// <summary>
        /// Gets the path to the directory that stores downloaded messages.
        /// </summary>
        public string MailsDir
        {
            get
            {
                _mailsDir = System.IO.Path.GetFullPath(AppDomain.CurrentDomain.BaseDirectory + "..\\..\\Mails");
                if (!System.IO.Directory.Exists(_mailsDir))
                    _mailsDir = AppDomain.CurrentDomain.BaseDirectory + "Mails";

                return _mailsDir;
            }
        }

        /// <summary>
        /// Handles the Form's Close event.
        /// </summary>
        /// <param name="e">The event arguments.</param>
        protected override void OnClosed(EventArgs e)
        {
            base.OnClosed(e);

            // Save setting before closing.
            SaveFormSettings();
        }

        #region Certificate

        /// <summary>
        /// Returns all issues of the given certificate.
        /// </summary>
        private static string GetCertProblem(CertificateVerificationStatus status, int code, ref bool showAddTrusted)
        {
            switch (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);

                default:
                    return status.ToString();
            }
        }

        void pop3Client_CertificateReceived(object sender, ComponentPro.Security.CertificateReceivedEventArgs e)
        {
            CertValidator dlg = new CertValidator();

            CertificateVerificationStatus status = e.Status;

            CertificateVerificationStatus[] values = (CertificateVerificationStatus[])Enum.GetValues(typeof(CertificateVerificationStatus));

            StringBuilder sbIssues = new StringBuilder();
            bool showAddTrusted = false;
            for (int i = 0; i < values.Length; i++)
            {
                // Matches the validation status?
                if ((status & values[i]) == 0)
                    continue;

                // The issue is processed.
                status ^= values[i];

                sbIssues.AppendFormat("{0}\r\n", GetCertProblem(values[i], e.ErrorCode, ref showAddTrusted));
            }

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

            dlg.ShowDialog();

            e.AddToTrustedRoot = dlg.AddToTrustedList;
            e.Accept = dlg.Accepted;
        }

        private void pop3Client_CertificateRequired(object sender, ComponentPro.Security.CertificateRequiredEventArgs e)
        {
            // If the client cert file is specified.
            if (!string.IsNullOrEmpty(txtCertificate.Text))
            {
                // Load Certificate.
                PasswordPrompt passdlg = new PasswordPrompt();
                // Ask for cert's passpharse
                if (passdlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                {
                    X509Certificate2 clientCert = new X509Certificate2(txtCertificate.Text, passdlg.Password);
                    e.Certificates = new X509Certificate2Collection(clientCert);
                    return;
                }

                // Password has not been provided.
            }
            CertProvider dlg = new CertProvider();
            dlg.ShowDialog();
            // Get the selected certificate.
            e.Certificates = new X509Certificate2Collection(dlg.SelectedCertificate);
        }

        #endregion

        /// <summary>
        /// Handles the Settings button's Click event.
        /// </summary>
        /// <param name="sender">The button object.</param>
        /// <param name="e">The event arguments.</param>
        private void btnSettings_Click(object sender, EventArgs e)
        {
            Settings dlg = new Settings();
            dlg.ShowDialog();
        }

        /// <summary>
        /// Handles the CustomSignature's Click event.
        /// </summary>
        /// <param name="sender">The button object.</param>
        /// <param name="e">The event arguments.</param>
        private void btnCustomSignature_Click(object sender, EventArgs e)
        {
            CustomSignatures dlg = new CustomSignatures(bounceFilter, _customSignatures);
            dlg.ShowDialog();
        }

        /// <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
        async 
#endif
        void btnScan_Click(object sender, EventArgs e)        
        {
            // Returns if the server name is empty.
            if (txtServer.Text.Length == 0)
            {
                MessageBox.Show("Please enter Pop3 server address", "BounceInspector", MessageBoxButtons.OK);
                return;
            }

            int port;
            try
            {
                port = int.Parse(txtPort.Text);
            }
            catch (Exception exc)
            {
                Util.ShowError(exc);
                return;
            }
            if (port < 0 || port > 65535)
            {
                MessageBox.Show("Invalid port number", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            if (txtUserName.Text.Length == 0)
            {
                MessageBox.Show("Please enter user name for authentication", "BounceInspector", MessageBoxButtons.OK);
                return;
            }

            if (txtPassword.Text.Length == 0)
            {
                MessageBox.Show("Please enter password for authentication", "BounceInspector", MessageBoxButtons.OK);
                return;
            }

            // 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 (!System.IO.Directory.Exists(MailsDir))
                System.IO.Directory.CreateDirectory(MailsDir);

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

            // Load settings
            _settings = Settings.LoadConfig();

            WebProxyEx proxy = new WebProxyEx();
            pop3Client.Proxy = proxy;
            // If proxy is specified.
            if (_settings.ProxyServer.Length > 0 && _settings.ProxyPort > 0)
            {
                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;
            }

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

            // Asynchronously connect to the POP3 server.
            pop3Client.Timeout = _settings.Timeout * 1000;

            SecurityMode securityMode = (SecurityMode)cbxSecurity.SelectedItem;

#if Framework4_5
            try
            {
                await pop3Client.ConnectAsync(txtServer.Text, port, securityMode);
            }
            catch (Exception ex)
            {
                Util.ShowError(exc);
                EnableProgress(false);
                return;
            }

            Login();
#else
            pop3Client.ConnectAsync(txtServer.Text, port, securityMode);
#endif
        }

#if Framework4_5
        async
#endif
        void Login()
        {
            lblStatus.Text = "Logging in with username: " + txtUserName.Text;
            // Login with the provided user name and password.
            Pop3AuthenticationMethod pa = (Pop3AuthenticationMethod)cbxAuthentication.SelectedItem;

#if Framework4_5
            try
            {
                await pop3Client.AuthenticateAsync(txtUserName.Text, txtPassword.Text, pa);
            }
            catch (Exception ex)
            {
                pop3Client.Disconnect();
                Util.ShowError(e.Error);
                EnableProgress(false);
                return;
            }

            ProcessMessages();
#else
            pop3Client.AuthenticateAsync(txtUserName.Text, txtPassword.Text, pa);      
#endif
        }

        /// <summary>
        /// Handles the Browse button's event handler.
        /// </summary>
        /// <param name="sender">The button object.</param>
        /// <param name="e">The event arguments.</param>
        private void btnBrowse_Click(object sender, EventArgs e)
        {
            // Open Folder Browser Dialog.
            FolderBrowserDialog dlg = new FolderBrowserDialog();
            dlg.SelectedPath = txtPath.Text;
            if (dlg.ShowDialog() == DialogResult.OK)
            {
                txtPath.Text = dlg.SelectedPath;
            }
        }

        private void tsbOpen_Click(object sender, EventArgs e)
        {
            MessageViewer form = new MessageViewer((string)ltvResult.SelectedItems[0].Tag);
            form.ShowDialog(this);
        }

        /// <summary>
        /// Enables/disables progress bar, disable/enables controls.
        /// </summary>
        private void EnableProgress(bool enable)
        {
            progressBar.Enabled = enable; progressBar.Value = 0;
            btnAbort.Enabled = enable;
            btnCustomSignature.Enabled = !enable;
            btnScan.Enabled = !enable;
            grbAuth.Enabled = !enable;
            grbFolder.Enabled = !enable;
            grbServer.Enabled = !enable;
            chkDelete.Enabled = !enable;
            lblStatus.Text = string.Empty;

            // Disable/Enable the Form's Close button.
            Util.EnableCloseButton(this, !enable);
        }

        #region POP3 & BounceInspector

        void PostProcessMessages(BounceResultCollection result)
        {
            EnableProgress(false);
            // Disconnect when finish.
            pop3Client.Disconnect();

            if (result != null)
            {
                if (result.HasErrors)
                {
                    Errors dlg = new Errors();
                    dlg.SetErrors(result);
                    dlg.ShowDialog();
                }

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

                MessageBox.Show(result.CancelledIndex != -1 ? "Operation has been cancelled by user. " + found : found, "BounceInspector", MessageBoxButtons.OK);
            }
        }

        /// <summary>
        /// Handles the BounceInspector's ProcessMessagesCompleted event.
        /// </summary>
        /// <param name="sender">The BounceInspector object.</param>
        /// <param name="e">The event arguments.</param>
        void bounceFilter_ProcessMessagesCompleted(object sender, ExtendedAsyncCompletedEventArgs<BounceResultCollection> e)
        {
            if (e.Error != null)
                Util.ShowError(e.Error);
            else
                lblStatus.Text = "Completed";

            PostProcessMessages(e.Result);
        }

        /// <summary>
        /// Handles the POP3 Client's AuthenticateCompleted event.
        /// </summary>
        /// <param name="sender">The Pop3Client object.</param>
        /// <param name="e">The event arguments.</param>
        void pop3Client_AuthenticateCompleted(object sender, AsyncCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                pop3Client.Disconnect();
                Util.ShowError(e.Error);
                EnableProgress(false);
                return;
            }

            ProcessMessages();
        }

#if Framework4_5
        async
#endif
        void ProcessMessages()
        {
            string dest = txtPath.Text;
            if (dest.Trim().Length == 0)  // When destination directory is empty, all message will be downloaded and processed in memory.
                dest = null;

#if Framework4_5
            BounceResultCollection result = null;
            try
            {
                // Asynchronously process messages.
                result = await bounceFilter.ProcessMessagesAsync(pop3Client, dest);
                lblStatus.Text = "Completed";
            }
            catch (Exception ex)
            {
                Util.ShowError(e.Error);
            }

            PostProcessMessages(result);
#else
            // Asynchronously process messages.
            bounceFilter.ProcessMessagesAsync(pop3Client, dest);
#endif
        }

        /// <summary>
        /// Handles the POP3 Client's AuthenticateCompleted event.
        /// </summary>
        /// <param name="sender">The Pop3Client object.</param>
        /// <param name="e">The event arguments.</param>
        void pop3Client_ConnectCompleted(object sender, ExtendedAsyncCompletedEventArgs<string> e)
        {            
            if (e.Error != null)
            {
                Util.ShowError(e.Error);
                EnableProgress(false);
                return;
            }

            Login();
        }

        private void bounceFilter_Processed(object sender, ProcessedEventArgs e)
        {
            // Handle the bounce Processed event here.
        }

        /// <summary>
        /// Handles the BounceInspector's Progress event.
        /// </summary>
        /// <param name="sender">The BounceInspector object.</param>
        /// <param name="e">The event arguments.</param>
        void bounceFilter_Progress(object sender, ProgressEventArgs e)
        {
            BounceResult r = e.Result;
            
            if (r.MailMessage == null)
                return;

            progressBar.Value = (int)e.Percentage;
            lblStatus.Text = "Processed mail with subject: '" + r.MailMessage.Subject + "'";

            if (r.Identified)
            {
                // Add to the result list view.
                ListViewItem item = new ListViewItem(new string[] {
                                    r.FilePath != null ? Path.GetFileName(r.FilePath) : string.Empty,
                                    r.MailMessage.Subject,
                                    r.Addresses.Length > 0 ? r.Addresses[0] : string.Empty,
                                    r.BounceCategory.Name,
                                    r.BounceType.Name,
                                    (r.FileDeleted || r.InboxDeleted).ToString(),
                                    r.Dsn.Action.ToString(),
                                    r.Dsn.DiagnosticCode
                            });
                item.Tag = r.FilePath;

                ltvResult.Items.Add(item);
            }
        }

        #endregion

        /// <summary>
        /// Handles the Abort button's event handler.
        /// </summary>
        /// <param name="sender">The button object.</param>
        /// <param name="e">The event arguments.</param>
        private void btnAbort_Click(object sender, EventArgs e)
        {
            pop3Client.Cancel();
            bounceFilter.Cancel();
        }

        private void ltvResult_SelectedIndexChanged(object sender, EventArgs e)
        {
            tsbOpen.Enabled = ltvResult.SelectedItems.Count > 0 && ltvResult.SelectedItems[0].Tag != null;
        }

        private void ltvResult_DoubleClick(object sender, EventArgs e)
        {
            if (tsbOpen.Enabled)
                tsbOpen_Click(sender, e);
        }

        private void btnCertBrowse_Click(object sender, EventArgs e)
        {
            OpenFileDialog dlg = 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)
            {
                txtCertificate.Text = dlg.FileName;
            }
        }
    }

    /// <summary>
    /// Contains signature information for local uses.
    /// </summary>
    public class SignatureInfo
    {
        public int Index;
        public string RegexPattern;
        public int CategoryCode;
        public int TypeCode;
        public bool Delete;
        public BounceSignaturePart SignaturePart;
    }    
}