using System;
using System.ComponentModel;
using System.Text;
using System.Windows.Forms;
using ComponentPro;
using ComponentPro.Net;
using ComponentPro.Net.Mail;

namespace Pop3Downloader
{
    public partial class Pop3Form : Form
    {
        private readonly bool _exception;
        string _mailsDir;
        string _workingDir;
        bool _disconnect;
        bool _disconnected;

        /// <summary>
        /// Gets the working directory.
        /// </summary>
        public string WorkingDir
        {
            get
            {
                if (_workingDir == null)
                {
                    _workingDir = AppDomain.CurrentDomain.BaseDirectory.EndsWith("\\") ? AppDomain.CurrentDomain.BaseDirectory : (AppDomain.CurrentDomain.BaseDirectory + '\\');
                }

                return _workingDir;
            }
        }

        /// <summary>
        /// Gets the directory which is used to store mails.
        /// </summary>
        public string MailsDir
        {
            get
            {
                if (_mailsDir == null)
                {
                    _mailsDir = WorkingDir;

                    if (!System.IO.Directory.Exists(_mailsDir + "..\\..\\Mails"))
                        _mailsDir += "Mails";
                    else
                        _mailsDir += "..\\..\\Mails";
                }

                return _mailsDir;
            }
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        public Pop3Form()
        {
            try
            {
                InitializeComponent();
            }
            catch (ComponentPro.Licensing.Mail.UltimateLicenseException exc)
            {
                MessageBox.Show(exc.Message, "Error");
                _exception = true;
                return;
            }

            cbxSecurity.SelectedIndex = 0;
            // The following settings are for a gmail Mailbox:
            //cbxSecurity.SelectedIndex = 2;
            //txtServer.Text = "myserver";
            //txtPort.Text = "995";
            //txtUserName.Text = "someone@gmail.com";
            //txtPassword.Text = "password";

            pop3.Timeout = 20000;
            pop3.Proxy.ProxyType = ProxyType.None;

            if (!System.IO.Directory.Exists(MailsDir))
                System.IO.Directory.CreateDirectory(MailsDir);

#if !Framework4_5
            this.pop3.DisconnectCompleted += this.pop3Client_DisconnectCompleted;
            this.pop3.ListMessagesCompleted += this.pop3Client_ListMessagesCompleted;
            this.pop3.AuthenticateCompleted += this.pop3Client_LoginCompleted;
            this.pop3.ConnectCompleted += this.pop3Client_ConnectCompleted;
#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();
        }

        /// <summary>
        /// Adds a line to the result listbox.
        /// </summary>
        /// <param name="str">Text to write.</param>
        private void WriteLine(string str)
        {
            lsbResult.Items.Add(str);
        }

        /// <summary>
        /// Adds a formatted line to the result listbox.
        /// </summary>
        /// <param name="str">Formatted text to write.</param>
        /// <param name="parameters">The parameters for the formatted text.</param>
        private void WriteLine(string str, params object[] parameters)
        {
            lsbResult.Items.Add(string.Format(str, parameters));
        }

        /// <summary>
        /// Handles the Download button's Click event.
        /// </summary>
        /// <param name="sender">The button object.</param>
        /// <param name="e">The event arguments.</param>
#if Framework4_5
        async void btnDownload_Click(object sender, EventArgs e)
#else
        void btnDownload_Click(object sender, EventArgs e)
#endif
        {
            // Downloading?
            if (btnDownload.Text == "Download")
            {
                int port;

                // Validate server name, port, user name and password.
                if (string.IsNullOrEmpty(txtServer.Text))
                {
                    MessageBox.Show("Please enter server name", "Error");
                    return;
                }

                try
                {
                    port = int.Parse(txtPort.Text);
                    if (port < 1 || port > 65535)
                    {
                        MessageBox.Show("Invalid port, port must be from 1->65535", "Error");
                        return;
                    }
                }
                catch (FormatException)
                {
                    MessageBox.Show("Invalid port, port must be from 1->65535", "Error");
                    return;
                }

                if (string.IsNullOrEmpty(txtUserName.Text))
                {
                    MessageBox.Show("Please enter user name");
                    return;
                }

                if (string.IsNullOrEmpty(txtPassword.Text))
                {
                    MessageBox.Show("Please enter password");
                    return;
                }

                _disconnect = false;
                _disconnected = false;

                grbAuth.Enabled = false;
                grbServer.Enabled = false;
                //grbStatus.Enabled = false;

                btnDownload.Text = "Cancel";
                this.btnDownload.Image = Pop3Downloader.Properties.Resources.Stop;
                btnProxy.Enabled = false;

                WriteLine("Connecting to the POP3 server {0}:{1}, security: {2}...",
                    txtServer.Text, port, cbxSecurity.Text);

                SecurityMode sec;

                switch (cbxSecurity.SelectedIndex)
                {
                    case 0:
                        sec = SecurityMode.None;
                        break;

                    case 1:
                        sec = SecurityMode.Explicit;
                        break;

                    default:
                        sec = SecurityMode.Implicit;
                        break;
                }

#if Framework4_5
                try
                {
                    await pop3.ConnectAsync(txtServer.Text, port, sec);
                }
                catch (Exception ex)
                {
                    ShowError(ex);
                    Disconnect();
                    return;
                }

                Login();
#else
                // Asynchronously connect to the POP server.
                pop3.ConnectAsync(txtServer.Text, port, sec);
#endif
            }
            else
            {
                pop3.Cancel();
                // Wait for the last pending operation.
                _disconnect = true;
                btnDownload.Enabled = false;
            }
        }


#if !Framework4_5
        /// <summary>
        /// Handles the pop3 client's ConnectCompleted event.
        /// </summary>
        /// <param name="sender">The pop3 client object.</param>
        /// <param name="e">The event arguments.</param>
        private void pop3Client_ConnectCompleted(object sender, ExtendedAsyncCompletedEventArgs<string> e)
        {
            if (e.Error != null)
            {
                ShowError(e.Error);
                Disconnect();
                return;
            }
            
            WriteLine("Connected.");

            Login();
        }
#endif
        
#if Framework4_5
        async void Login()
#else
        void Login()
#endif
        {
            // Disconnects if user clicks on the Close button or the Cancel button.
            if (_disconnect)
            {
                Disconnect();
                return;
            }
            WriteLine("Authorizing as {0}...", txtUserName.Text);

#if Framework4_5
            try
            {
                // Asynchronously login to the server.
                await pop3.AuthenticateAsync(txtUserName.Text, txtPassword.Text);
            }
            catch (Exception ex)
            {
                ShowError(ex);
                Disconnect();
                return;
            }

            ListMessages();
#else
            // Asynchronously login to the server.
            pop3.AuthenticateAsync(txtUserName.Text, txtPassword.Text);    
#endif
        }


#if !Framework4_5
        /// <summary>
        /// Handles the pop3 client's AuthenticateCompleted event.
        /// </summary>
        /// <param name="sender">The pop3 client object.</param>
        /// <param name="e">The event arguments.</param>
        private void pop3Client_LoginCompleted(object sender, AsyncCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                ShowError(e.Error);
                Disconnect();
                return;
            }
            
            ListMessages();
        }
#endif

#if Framework4_5
        async void ListMessages()
#else
        void ListMessages()
#endif
        {
            if (_disconnect)
            {
                // Disconnect.
                Disconnect();
                return;
            }

            WriteLine("Logged In.");

            try
            {
                Pop3MailboxStat info = pop3.GetMailboxStat();
                // Print out total messages.
                WriteLine("{0} messages found.", info.MessageCount);
            }
            catch (Exception ex)
            {
                ShowError(ex);
                Disconnect();
                return;
            }
            
            WriteLine("Retrieving the list of messages...");

#if Framework4_5
            try
            {
                // Asynchronously get message list.
                _list = await pop3.ListMessagesAsync(Pop3EnvelopeParts.UniqueId | Pop3EnvelopeParts.MessageInboxIndex | Pop3EnvelopeParts.Size);      
            }
            catch (Exception ex)
            {
                ShowError(ex);
                Disconnect();
                return;
            }

            DownloadEmls();
#else
            // Asynchronously get message list.
            pop3.ListMessagesAsync(Pop3EnvelopeParts.UniqueId | Pop3EnvelopeParts.MessageInboxIndex | Pop3EnvelopeParts.Size);      
#endif
        }

#if !Framework4_5
        /// <summary>
        /// Handles the pop3 client's ListMessagesCompleted event.
        /// </summary>
        /// <param name="sender">The pop3 client object.</param>
        /// <param name="e">The event arguments.</param>
        private void pop3Client_ListMessagesCompleted(object sender, ExtendedAsyncCompletedEventArgs<Pop3MessageCollection> e)
        {
            if (e.Error != null)
            {
                ShowError(e.Error);
                Disconnect();
                return;
            }

            _list = e.Result;

            DownloadEmls();
        }
#endif
        
        Pop3MessageCollection _list;
        int _index;
        private long _messageSize;

#if Framework4_5
        async void DownloadEmls()
#else
        void DownloadEmls()
#endif
        {
            if (_disconnect)
            {
                Disconnect();
                return;
            }

            WriteLine("List of messages retrieved.");
            _index = 0;

#if Framework4_5
            try
            {
                // Download EML files.
                while (!_disconnect)
                {
                    await DownloadEml();
                }
            }
            catch (Exception ex)
            {
                if (!(ex is OperationCanceledException))
                    ShowError(ex);
            }
            finally
            {
                Disconnect();
            }
#else
            // Get EML messages.
            DownloadEml();
#endif
        }

        /// <summary>
        /// Downloads EML files.
        /// </summary>
#if Framework4_5
        async System.Threading.Tasks.Task<long> DownloadEml()
#else
        private void DownloadEml()
#endif
        {
            Retry:
            if (_disconnect)
            {
                // Close the connection.
                Disconnect();
#if Framework4_5
                throw new OperationCanceledException();
#else
                return;
#endif
            }

            // Not finished?
            if (_index < _list.Count)
            {
                Pop3Message message = _list[_index++];
                string fileName = MailsDir + "\\" + GetFilename(message.UniqueId) + ".eml";

                if (System.IO.File.Exists(fileName))
                {
                    WriteLine("Skipping message '{0}'...", message.UniqueId);
                    goto Retry;
                }

                _messageSize = message.Size;
                progressBar.Value = 0;

                WriteLine("Retrieving message '{0}'...", message.UniqueId);

                // Continue downloading messages.
#if Framework4_5
                return await pop3.DownloadMessageAsync(message.MessageInboxIndex, fileName);
#else
                pop3.DownloadMessageAsync(message.MessageInboxIndex, fileName, pop3Client_DownloadMessageCompleted, null);
#endif
            }
            else
            {
                // Close the connection when finish.
                Disconnect();
#if Framework4_5
                throw new OperationCanceledException();
#endif
            }
        }

#if !Framework4_5
        /// <summary>
        /// Handles the pop3 client's DownloadMessageCompleted event.
        /// </summary>
        /// <param name="sender">The pop3 client object.</param>
        /// <param name="e">The event arguments.</param>
        private void pop3Client_DownloadMessageCompleted(object sender, ExtendedAsyncCompletedEventArgs<long> e)
        {
            if (e.Error != null)
            {
                ShowError(e.Error);
                Disconnect();
                return;
            }

            progressBarTotal.Value = _index * 100 / _list.Count;

            // Download EML files.
            DownloadEml();
        }
#endif

        /// <summary>
        /// Disconnects to the POP server.
        /// </summary>
#if Framework4_5
        async void Disconnect()
#else
        void Disconnect()
#endif
        {
            WriteLine("Disconnecting...");
#if Framework4_5
            try
            {
                await pop3.DisconnectAsync(false);
            }
            catch (Exception ex)
            {
                ShowError(ex);
            }

            SetDisconnectedStatus();
#else
            pop3.DisconnectAsync(false);
#endif
        }

        void SetDisconnectedStatus()
        {
            WriteLine("Disconnected.");

            grbAuth.Enabled = true;
            grbServer.Enabled = true;
            //grbStatus.Enabled = true;

            btnDownload.Text = "Download";
            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Pop3Downloader.Pop3Form));
            this.btnDownload.Image = (System.Drawing.Image)resources.GetObject("btnDownload.Image");
            btnProxy.Enabled = true;
            btnDownload.Enabled = true;

            progressBarTotal.Value = 0;
            progressBar.Value = 0;
        }

#if !Framework4_5
        /// <summary>
        /// Handles the pop3 client's DisconnectCompleted event.
        /// </summary>
        /// <param name="sender">The pop3 client object.</param>
        /// <param name="e">The event arguments.</param>
        private void pop3Client_DisconnectCompleted(object sender, ExtendedAsyncCompletedEventArgs<string> e)
        {
            if (e.Error != null)
            {
                ShowError(e.Error);
            }

            SetDisconnectedStatus();
        }
#endif

        /// <summary>
        /// Shows an error.
        /// </summary>
        /// <param name="exc">Exception object.</param>
        private static void ShowError(Exception exc)
        {
            string str;

            if (exc.InnerException != null)
            {
                str = string.Format(null, "An error occurred: {0}\r\n{1}", exc.Message, exc.InnerException.Message);
            }
            else
                str = string.Format(null, "An error occurred: {0}", exc.Message);

            MessageBox.Show(str, "Error");
        }

        /// <summary>
        /// Returns a uniquely correct file name from the specified unique message ID.
        /// </summary>
        /// <param name="uniqueId">The unique id.</param>
        /// <returns>The corrected file name.</returns>
        private static string GetFilename(string uniqueId)
        {
            // Characters allowed in the filename
            //string allowed = " .-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";            
            const string allowed = " .-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

            // Replace invalid charactes with its hex representation
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < uniqueId.Length; i++)
            {
                if (allowed.IndexOf(uniqueId[i]) < 0)
                    sb.AppendFormat("_{0:X2}", (int)uniqueId[i]);
                else
                    sb.Append(uniqueId[i]);
            }
            return sb.ToString();
        }

        /// <summary>
        /// Handles the proxy settings button's Click event.
        /// </summary>
        /// <param name="sender">The pop3 client object.</param>
        /// <param name="e">The event arguments.</param>
        private void btnProxy_Click(object sender, EventArgs e)
        {
            // Creates new ProxySettings dialog and initialize all parameters.
            ProxySettings proxy = new ProxySettings();
            proxy.ProxyServer = pop3.Proxy.Server;
            proxy.Port = pop3.Proxy.Port;
            proxy.UserName = pop3.Proxy.UserName;
            proxy.Password = pop3.Proxy.Password;
            proxy.AuthenticationMethod = pop3.Proxy.AuthenticationMethod;
            proxy.Type = pop3.Proxy.ProxyType;
            // Popups the ProxySetting dialog.
            if (proxy.ShowDialog() == DialogResult.OK)
            {
                // Updates settings.
                pop3.Proxy.Server = proxy.ProxyServer;
                if (proxy.Port > 0)
                    pop3.Proxy.Port = proxy.Port;
                pop3.Proxy.UserName = proxy.UserName;
                pop3.Proxy.Password = proxy.Password;
                pop3.Proxy.AuthenticationMethod = proxy.AuthenticationMethod;
                pop3.Proxy.ProxyType = proxy.Type;
            }
        }

        /// <summary>
        /// Handles the form's Closing event.
        /// </summary>
        /// <param name="e">The event arguments.</param>
        protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
        {
            // Have a connection?
            if (btnDownload.Text == "Cancel")
            {
                // We want to disconnect.
                _disconnect = true;

                // While the last operation is not completed.
                while (!_disconnected)
                {
                    System.Threading.Thread.Sleep(50);
                    System.Windows.Forms.Application.DoEvents();
                }
            }

            base.OnClosing(e);
        }

        private void pop3Client_Progress(object sender, Pop3ProgressEventArgs e)
        {
            if (e.State == MailClientTransferState.Downloading)
                progressBar.Value = (int)e.CalculatePercentage(_messageSize);
        }
    }
}