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

namespace ImapDownloader
{
    public partial class Imap : 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 Imap()
        {
            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 = "993";
            //txtUserName.Text = "someone@gmail.com";
            //txtPassword.Text = "password";
            txtFolder.Text = "Inbox";

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

            // Prepare the Mails folder for storing downloaded messages.
            if (!System.IO.Directory.Exists(MailsDir))
                System.IO.Directory.CreateDirectory(MailsDir);

#if !Framework4_5
            this.imapClient.SelectCompleted += this.imapClient_SelectFolderCompleted;
            this.imapClient.ListMessagesCompleted += this.imapClient_ListMessagesCompleted;
            this.imapClient.ConnectCompleted += this.imapClient_ConnectCompleted;
            this.imapClient.DownloadMessageCompleted += this.imapClient_DownloadMessageCompleted;
            this.imapClient.DisconnectCompleted += this.imapClient_DisconnectCompleted;
            this.imapClient.AuthenticateCompleted += this.imapClient_AuthenticateCompleted;
#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
        
        {
            if (btnDownload.Text == "Download")
            {
                int port;

                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;
                }

                if (string.IsNullOrEmpty(txtFolder.Text))
                {
                    MessageBox.Show("Folder name cannot be empty");
                    return;
                }

                _disconnect = false;
                _disconnected = false;

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

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

                btnDownload.Text = "Cancel";
                // Change image.
                this.btnDownload.Image = ImapDownloader.Properties.Resources.Stop;
                btnProxy.Enabled = false;

                WriteLine("Connecting to the IMAP 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
                {
                    // Asynchronously connects to the IMAP server.
                    await imapClient.ConnectAsync(txtServer.Text, port, sec);
                }
                catch (Exception ex)
                {
                    ShowError(ex);
                    Disconnect();
                    return;
                }
                
                Authenticate();
#else
                // Connects to the IMAP server.
                imapClient.ConnectAsync(txtServer.Text, port, sec);
#endif
            }
            else
            {
                imapClient.Cancel();
                // Waits for the last pending operation.
                _disconnect = true;
                btnDownload.Enabled = false;
                //btnClose.Enabled = false;
            }
        }

#if !Framework4_5
        /// <summary>
        /// Handles the imap client's ConnectCompleted event.
        /// </summary>
        /// <param name="sender">The imap client object.</param>
        /// <param name="e">The event arguments.</param>
        private void imapClient_ConnectCompleted(object sender, ExtendedAsyncCompletedEventArgs<string> e)
        {
            if (e.Error != null)
            {
                ShowError(e.Error);
                Disconnect();
            }
            else
                Authenticate();
        }
#endif
        
#if Framework4_5
        async void Authenticate()
#else
        void Authenticate()
#endif
        
        {
            // Disconnects if user clicks on the Close button or the Cancel button.
            if (_disconnect)
            {
                Disconnect();
                return;
            }
            WriteLine("Connected.");
            WriteLine("Authorizing as {0}...", txtUserName.Text);

#if Framework4_5
            try
            {
                await imapClient.AuthenticateAsync(txtUserName.Text, txtPassword.Text);
            }
            catch (Exception ex)
            {
                ShowError(ex);
                Disconnect();
                return;
            }

            SelectFolder();
#else
            imapClient.AuthenticateAsync(txtUserName.Text, txtPassword.Text);
#endif
        }

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

#if Framework4_5
        async void SelectFolder()
#else
        void SelectFolder()
#endif        
        {
            if (_disconnect)
            {
                Disconnect();
                return;
            }
            WriteLine("Logged In.");
            WriteLine("Selecting working folder {0}...", txtFolder.Text);

#if Framework4_5
            try
            {
                // Asynchronously select a folder to download messages.
                await imapClient.SelectAsync(txtFolder.Text, true);
            }
            catch (Exception ex)
            {
                ShowError(ex);
                Disconnect();
                return;
            }

            ListMessages();
#else
            // Asynchronously select a folder to download messages.
            imapClient.SelectAsync(txtFolder.Text, true);
#endif
        }

#if !Framework4_5
        /// <summary>
        /// Handles the imap client's ConnectCompleted event.
        /// </summary>
        /// <param name="sender">The imap client object.</param>
        /// <param name="e">The event arguments.</param>
        private void imapClient_SelectFolderCompleted(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();
                return;
            }

            // Get current folder.
            Folder folder = imapClient.WorkingFolder;
            WriteLine("Folder selected and {0} messages found.", folder.TotalMessages);

            WriteLine("Retrieving the list of messages...");
#if Framework4_5
            try
            {
                // Asynchronously get message list.
                _list = await imapClient.ListMessagesAsync(ImapEnvelopeParts.UniqueId);
            }
            catch (Exception ex)
            {
                ShowError(ex);
                Disconnect();
                return;
            }

            WriteLine("List of messages retrieved.");
            _index = 0;
            try
            {
                // Download EML files.
                while (!_disconnect)
                {
                    await DownloadEml();
                }
            }
            catch (Exception ex)
            {
                if (!(ex is OperationCanceledException))
                    ShowError(ex);
            }
            finally
            {
                Disconnect();
            }
#else
            // Asynchronously get message list.
            imapClient.ListMessagesAsync(ImapEnvelopeParts.UniqueId);
#endif
        }

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

            _list = e.Result;
            WriteLine("List of messages retrieved.");
            _index = 0;
            // Download EML files.
            DownloadEml();
        }
#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>
        /// Disconnects to the IMAP server.
        /// </summary>
#if Framework4_5
        async void Disconnect()
#else
        void Disconnect()
#endif        
        {
            WriteLine("Disconnecting...");
#if Framework4_5
            try
            {
                await imapClient.DisconnectAsync();
            }
            catch (Exception ex)
            {
                ShowError(ex);
            }
            finally
            {
                SetDisconnectedStatus();
            }
#else
            imapClient.DisconnectAsync();
#endif
        }

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

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

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

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

            _disconnected = true;
            _disconnect = false;
        }

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

            SetDisconnectedStatus();
        }
#endif

        ImapMessageCollection _list;
        int _index;

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

        /// <summary>
        /// Downloads EML files.
        /// </summary>
#if Framework4_5
        System.Threading.Tasks.Task<long> DownloadEml()
#else
        void DownloadEml()
#endif
            
        {
            Retry:
            if (_disconnect)
            {
                Disconnect();
#if Framework4_5
                throw new OperationCanceledException();
#else
                return;
#endif
            }
            if (_index < _list.Count)
            {
                ImapMessage message = _list[_index++];
                string fileName = MailsDir + "\\" + GetFilename(message.UniqueId) + ".eml";

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

                WriteLine("Retrieving message '{0}'...", message.UniqueId);
                progressBarTotal.Value = _index * 100 / _list.Count;
#if Framework4_5
                // Asynchronously get message.
                return imapClient.DownloadMessageAsync(message.UniqueId, fileName);
#else
                // Asynchronously get message.
                imapClient.DownloadMessageAsync(message.UniqueId, fileName);
                return; // Continue downloading.
#endif
            }
            else
            {
                Disconnect();
#if Framework4_5
                throw new OperationCanceledException();
#else
                return;
#endif
            }
        }

        /// <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 button 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 = imapClient.Proxy.Server;
            proxy.Port = imapClient.Proxy.Port;
            proxy.UserName = imapClient.Proxy.UserName;
            proxy.Password = imapClient.Proxy.Password;
            proxy.Domain = imapClient.Proxy.Domain;
            proxy.AuthenticationMethod = imapClient.Proxy.AuthenticationMethod;
            proxy.Type = imapClient.Proxy.ProxyType;
            // Popups the ProxySetting dialog.
            if (proxy.ShowDialog() == DialogResult.OK)
            {
                // Updates settings.
                imapClient.Proxy.Server = proxy.ProxyServer;
                if (proxy.Port > 0)
                    imapClient.Proxy.Port = proxy.Port;
                imapClient.Proxy.UserName = proxy.UserName;
                imapClient.Proxy.Password = proxy.Password;
                imapClient.Proxy.Domain = proxy.Domain;
                imapClient.Proxy.AuthenticationMethod = proxy.AuthenticationMethod;
                imapClient.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)
        {
            // Connected?
            if (btnDownload.Text == "Cancel")
            {
                // Disconnect.
                _disconnect = true;

                // Wait for the completion.
                while (!_disconnected)
                {
                    System.Threading.Thread.Sleep(50);
                    System.Windows.Forms.Application.DoEvents();
                }
            }

            base.OnClosing(e);
        }

        private void imapClient_Progress(object sender, ImapProgressEventArgs e)
        {
            if (e.State == MailClientTransferState.Downloading)
            {
                progressBar.Value = (int) e.Percentage;
                Application.DoEvents();
            }
        }
    }
}