//  
//   Rebex Sample Code License
// 
//   Copyright 2026, REBEX CR s.r.o.
//   All rights reserved.
//   https://www.rebex.net/
// 
//   Permission to use, copy, modify, and/or distribute this software for any
//   purpose with or without fee is hereby granted.
// 
//   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
//   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
//   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
//   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
//   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
//   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
//   OTHER DEALINGS IN THE SOFTWARE.
// 

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Windows.Forms.ComponentModel;
using System.IO;
using System.Threading.Tasks;

using Rebex.IO;
using Rebex.IO.Compression;

namespace Rebex.Samples
{
    /// <summary>
    /// Main WinFormZip application form.
    /// </summary>
    public partial class MainForm : Form
    {
        // ZIP archive
        private ZipArchive _archive;

        // options dialog
        private OptionsDialog _optionsDialog;

        // progress dialog
        private ProgressDialog _progressDialog;

        // problem dialog
        private ProblemDetectedDialog _problemDialog;

        /// <summary>
        /// Initializes the form and load a ZIP file if specified.
        /// </summary>
        public MainForm()
        {
            CheckForIllegalCrossThreadCalls = true;

            InitializeComponent();

            // create new instance of the options dialog
            _optionsDialog = new OptionsDialog();

            // assign a sorter to the ListView
            listViewRight.ListViewItemSorter = new ListViewColumnSorter();

            // select a default button
            btnOpenArchive.Select();

            // enables or disables form controls as needed
            UpdateButtonsEnabled();
        }

        #region Properties

        /// <summary>
        /// Gets a value indicating whether the opened archive is writable (mostly used for enabling of UI controls).
        /// </summary>
        public bool IsArchiveWriteable
        {
            get { return (_archive != null && !_archive.IsReadOnly); }
        }

        /// <summary>
        /// Gets a path to a directory to which ZIP files were extracted recently (set to an archive's directory by default).
        /// </summary>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public string LastUsedExtractDirectory
        {
            get
            {
                if (string.IsNullOrEmpty(_lastUsedExtractDirectory) && _archive != null)
                    return Path.GetDirectoryName(_archive.FilePath);
                else
                    return _lastUsedExtractDirectory;
            }
            set
            {
                _lastUsedExtractDirectory = value;
            }
        }
        private string _lastUsedExtractDirectory;

        #endregion

        #region Button events

        /// <summary>
        /// 'New archive' click event - create a new ZIP archive.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnNewArchive_Click(object sender, EventArgs e)
        {
            // ask for new zip file name
            saveFileDialog.Title = "Create a new ZIP archive";
            saveFileDialog.DefaultExt = ".zip";
            saveFileDialog.Filter = "ZIP files (*.zip)|*.zip";

            if (saveFileDialog.ShowDialog(this) == DialogResult.OK)
            {
                // create new archive
                CreateNewArchive(saveFileDialog.FileName);
            }
        }

        /// <summary>
        /// 'Open archive' click event - open an existing ZIP archive.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnOpenArchive_Click(object sender, EventArgs e)
        {
            // ask the zipped file to open
            openFileDialog.Title = "Open a ZIP archive";
            openFileDialog.Filter = "ZIP files (*.zip)|*.zip";
            openFileDialog.Multiselect = false;

            if (openFileDialog.ShowDialog(this) == DialogResult.OK)
            {
                // open existing archive
                OpenArchive(openFileDialog.FileName);
            }
        }

        /// <summary>
        /// 'Add files' click event - add files to the ZIP archive.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnAddFiles_Click(object sender, EventArgs e)
        {
            openFileDialog.Title = "Add files to the ZIP archive";
            openFileDialog.Filter = "All files (*.*)|*.*";
            openFileDialog.Multiselect = true;

            if (openFileDialog.ShowDialog(this) == DialogResult.OK)
            {
                // Add files to Archive
                string target = Utils.RootPath;
                if (treeViewLeft.SelectedNode != null)
                {
                    ZipItem item = (ZipItem)treeViewLeft.SelectedNode.Tag;
                    target = item.Path;
                }

                AddFiles(GetFileSet(openFileDialog.FileNames), target);
            }
        }

        /// <summary>
        /// 'Add directory' click event - add a directory to the ZIP archive.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnAddDirectory_Click(object sender, EventArgs e)
        {
            folderBrowserDialog.Description = "Add directory to the ZIP archive";
            folderBrowserDialog.SelectedPath = LastUsedExtractDirectory;
            folderBrowserDialog.ShowNewFolderButton = false;

            if (folderBrowserDialog.ShowDialog(this) == DialogResult.OK)
            {
                // Add directory to archive
                string target = Utils.RootPath;
                if (treeViewLeft.SelectedNode != null)
                {
                    ZipItem item = (ZipItem)treeViewLeft.SelectedNode.Tag;
                    target = item.Path;
                }
                AddFiles(GetFileSet(folderBrowserDialog.SelectedPath), target);
            }
        }

        /// <summary>
        /// 'Extract' click event - extract selected files.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnExtract_Click(object sender, EventArgs e)
        {
            ExtractSelectedMenuItem_Click(sender, e);
        }

        /// <summary>
        /// 'Extract all' click event - extract the whole ZIP archive.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnExtractAll_Click(object sender, EventArgs e)
        {
            ExtractFiles(new FileSet(Utils.RootPath, "*"));
        }

        /// <summary>
        /// Handles an event to open an Options dialog.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnOptions_Click(object sender, EventArgs e)
        {
            _optionsDialog.ShowDialog(this, _archive);
        }

        #endregion

        #region TreeView related events

        /// <summary>
        /// TreeView mouse-up event - show context menu.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void treeViewLeft_MouseUp(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Right)
            {
                // get the clicked node
                TreeNode clickedNode = treeViewLeft.GetNodeAt(e.X, e.Y);
                if (clickedNode != null)
                {
                    // select the clicked node
                    treeViewLeft.SelectedNode = clickedNode;

                    ZipItem item = (ZipItem)clickedNode.Tag;

                    // enable/disable context menu items
                    MoveDirectoryMenuItem.Enabled = IsArchiveWriteable && !item.IsRootDirectory;
                    ExtractDirectoryMenuItem.Enabled = (_archive != null);
                    DeleteDirectoryMenuItem.Enabled = IsArchiveWriteable;
                    CreateDirectoryMenuItem.Enabled = IsArchiveWriteable;

                    // show context menu
                    contextMenuTreeView.Show(treeViewLeft, e.Location);
                }
            }
        }

        /// <summary>
        /// TreeView key-down event - handle a 'Delete' key to delete a directory within the ZIP archive.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void treeViewLeft_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Delete)
            {
                // behave just like 'Delete directory' context menu item
                DeleteDirectoryMenuItem_Click(sender, e);
            }
        }

        /// <summary>
        /// 'Extract directory' menu item click - extract a directory from the ZIP archive.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ExtractDirectoryMenuItem_Click(object sender, EventArgs e)
        {
            if (treeViewLeft.SelectedNode != null)
            {
                ZipItem item = (ZipItem)treeViewLeft.SelectedNode.Tag;
                ExtractFiles(GetFileSet(item.Path));
            }
        }

        /// <summary>
        /// 'Delete directory' menu item click - delete a directory from the ZIP archive.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void DeleteDirectoryMenuItem_Click(object sender, EventArgs e)
        {
            if (treeViewLeft.SelectedNode != null)
            {
                ZipItem item = (ZipItem)treeViewLeft.SelectedNode.Tag;

                DialogResult deleteQuestionResult = MessageBox.Show(
                    this,
                    string.Format("Are you sure you want to delete directory '{0}'?", item.Name),
                    "Delete confirmation",
                    MessageBoxButtons.YesNoCancel,
                    MessageBoxIcon.Question,
                    MessageBoxDefaultButton.Button2);

                if (deleteQuestionResult == DialogResult.Yes)
                {
                    DeleteFiles(GetFileSet(item.Path));
                }
            }
        }

        /// <summary>
        /// 'Move directory' menu item click - move a directory within the ZIP archive.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MoveDirectoryMenuItem_Click(object sender, EventArgs e)
        {
            if (treeViewLeft.SelectedNode != null)
            {
                ZipItem item = (ZipItem)treeViewLeft.SelectedNode.Tag;
                MoveDirectory(item.Path);
            }
        }

        /// <summary>
        /// 'Create directory' menu item click - create a new directory in the ZIP archive.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CreateDirectoryMenuItem_Click(object sender, EventArgs e)
        {
            if (treeViewLeft.SelectedNode != null)
            {
                ZipItem item = (ZipItem)treeViewLeft.SelectedNode.Tag;
                CreateArchiveDirectory(item.Path);
            }
            else
            {
                CreateArchiveDirectory(Utils.RootPath);
            }
        }

        /// <summary>
        /// TreeView after-select event - fill ListView with currently selected directory content.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void treeViewLeft_AfterSelect(object sender, TreeViewEventArgs e)
        {
            Utils.FillListView(_archive, listViewRight, e.Node);
        }

        #endregion

        #region ListView related events

        /// <summary>
        /// 'Extract files' menu item click - extracts selected file(s) from the ZIP archive.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ExtractSelectedMenuItem_Click(object sender, EventArgs e)
        {
            if (listViewRight.SelectedItems.Count > 0)
            {
                List<string> filesToExtract = new List<string>();
                for (int i = 0; i < listViewRight.SelectedItems.Count; i++)
                {
                    ZipItem item = (ZipItem)listViewRight.SelectedItems[i].Tag;
                    filesToExtract.Add(item.Path);
                }
                ExtractFiles(GetFileSet(filesToExtract));
            }
        }

        /// <summary>
        /// 'Rename file' menu item click - renames the selected file within the ZIP archive.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void RenameSelectedMenuItem_Click(object sender, EventArgs e)
        {
            if (listViewRight.SelectedItems.Count > 0)
            {
                ZipItem item = (ZipItem)listViewRight.SelectedItems[0].Tag;
                RenameFile(item.Path);
            }
        }

        /// <summary>
        /// 'Delete files' menu item click - delete selected file(s) from the ZIP archive.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void DeleteSelectedMenuItem_Click(object sender, EventArgs e)
        {
            if (listViewRight.SelectedItems.Count > 0)
            {
                DialogResult deleteQuestionResult = MessageBox.Show(
                    this,
                    string.Format("Are you sure you want to delete selected {0}?", listViewRight.SelectedItems.Count == 1 ? "file" : "files"),
                    "Delete confirmation",
                    MessageBoxButtons.YesNoCancel,
                    MessageBoxIcon.Question,
                    MessageBoxDefaultButton.Button2);

                if (deleteQuestionResult == DialogResult.Yes)
                {
                    List<string> filesToDelete = new List<string>();
                    for (int i = 0; i < listViewRight.SelectedItems.Count; i++)
                    {
                        ZipItem item = (ZipItem)listViewRight.SelectedItems[i].Tag;
                        filesToDelete.Add(item.Path);
                    }
                    DeleteFiles(GetFileSet(filesToDelete));
                }
            }
        }

        /// <summary>
        /// ListView column click - sorts the ListView.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void listViewRight_ColumnClick(object sender, ColumnClickEventArgs e)
        {
            // update sorter column
            ListViewColumnSorter sorter = (ListViewColumnSorter)listViewRight.ListViewItemSorter;
            sorter.SetSortingColumn(e.Column);

            // perform the sort using new sort column
            listViewRight.Sort();
        }

        /// <summary>
        /// ListView key-down event - delete selected file(s) from the ZIP archive.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void listViewRight_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Delete)
            {
                // behave just like 'Delete files' context menu item
                DeleteSelectedMenuItem_Click(sender, e);
            }
        }

        /// <summary>
        /// ListView drag-enter event - enables drag'n'drop functionality.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void listViewRight_DragEnter(object sender, DragEventArgs e)
        {
            if ((e.AllowedEffect & DragDropEffects.Copy) != 0 && IsArchiveWriteable)
            {
                string[] paths = e.Data.GetData(DataFormats.FileDrop) as string[];
                if (paths != null && paths.Length > 0)
                    e.Effect = DragDropEffects.Copy;
            }
        }

        /// <summary>
        /// ListView drag-drop event - finish the drag'n'drop process.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void listViewRight_DragDrop(object sender, DragEventArgs e)
        {
            string[] paths = e.Data.GetData(DataFormats.FileDrop) as string[];
            if (paths != null && paths.Length > 0)
            {
                string target = Utils.RootPath;
                if (treeViewLeft.SelectedNode != null)
                {
                    ZipItem item = (ZipItem)treeViewLeft.SelectedNode.Tag;
                    target = item.Path;
                }
                AddFiles(GetFileSet(paths), target);
            }
        }

        /// <summary>
        /// ListView ContextMenu opening event - display the correct menu items.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void contextMenuListView_Opening(object sender, CancelEventArgs e)
        {
            // enable/disable context menu items
            ExtractSelectedMenuItem.Enabled = (_archive != null && listViewRight.SelectedItems.Count > 0);
            RenameSelectedMenuItem.Enabled = (IsArchiveWriteable && listViewRight.SelectedItems.Count == 1);
            DeleteSelectedMenuItem.Enabled = (IsArchiveWriteable && listViewRight.SelectedItems.Count > 0);
        }

        /// <summary>
        /// ListView selected index changed event - enable or disable the Extract button.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void listViewRight_SelectedIndexChanged(object sender, EventArgs e)
        {
            btnExtract.Enabled = (_archive != null && listViewRight.SelectedItems.Count > 0);
        }

        #endregion

        #region Form events

        /// <summary>
        /// MainForm closing event - close the ZIP archive.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            CloseArchive();
        }

        #endregion

        #region Archive global events

        /// <summary>
        /// ZipArchive ProgressChanged event - updates progress indicators.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ProgressChanged(object sender, ZipProgressChangedEventArgs e)
        {
            if (_progressDialog != null)
                _progressDialog.ProgressChanged(e);
        }

        /// <summary>
        /// ZipArchive ShrinkProgress event - updates shrink progress indicator.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ShrinkProgress(object sender, ZipShrinkProgressEventArgs e)
        {
            if (e.ItemsProcessed == e.ItemsTotal)
            {
                statusLabel.Text = "Shrinking finished";
                statusProgress.Visible = false;
                statusStrip.Refresh();
            }
            else
            {
                statusLabel.Text = "Shrinking...";
                statusProgress.Visible = true;
                if (statusProgress.Value != e.ProgressPercentage)
                    statusProgress.Value = e.ProgressPercentage;
            }
        }

        /// <summary>
        /// ZipArchive ProblemDetected event - asks the user what to do when a problem occurs.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ProblemDetected(object sender, ZipProblemDetectedEventArgs e)
        {
            if (_problemDialog == null)
                _problemDialog = new ProblemDetectedDialog();

            _problemDialog.ShowDialog(this, e);
        }

        /// <summary>
        /// ZipArchive PasswordRequired event - asks the user for password when needed.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void PasswordRequired(object sender, ZipPasswordRequiredEventArgs e)
        {
            string comment;
            if (e.Reason == ArchivePasswordReason.PasswordRequired)
                comment = "Please enter a password.";
            else
                comment = "Entered password is incorrect or file is corrupted.";

            PasswordDialog dialog = new PasswordDialog(e.ArchiveItemPath, comment);

            switch (dialog.ShowDialog(this))
            {
                case DialogResult.OK:
                    e.Password = dialog.Password;
                    break;
                case DialogResult.Ignore:
                    e.Action = ArchivePasswordActions.SkipFile;
                    break;
                default:
                    e.Action = ArchivePasswordActions.Cancel;
                    break;
            }
        }

        #endregion

        #region Extract files

        /// <summary>
        /// Extracts specified files/directories from the archive.
        /// </summary>
        /// <param name="filesToExtract">Collection of files or directories to extract.</param>
        private void ExtractFiles(FileSet filesToExtract)
        {
            folderBrowserDialog.Description = string.Format("Select directory to extract");
            folderBrowserDialog.SelectedPath = LastUsedExtractDirectory;
            folderBrowserDialog.ShowNewFolderButton = true;

            if (folderBrowserDialog.ShowDialog(this) != DialogResult.OK)
                return;

            LastUsedExtractDirectory = folderBrowserDialog.SelectedPath;

            SetEnabled(false);
            statusLabel.Text = "Extracting files...";
            CreateProgressDialog("Extracting files");

            // start asynchronous Extract operation
            _archive.ExtractAsync(filesToExtract, folderBrowserDialog.SelectedPath, 0, 0).ContinueWith(ExtractCompleted);
        }

        private void ExtractCompleted(Task<ArchiveOperationResult> task)
        {
            if (this.InvokeRequired)
            {
                Invoke(new Action<Task<ArchiveOperationResult>>(ExtractCompleted), task);
                return;
            }

            if (task.IsCanceled)
            {
                ShowMessage("Operation was cancelled.");
            }
            else if (task.IsFaulted)
            {
                foreach (Exception error in task.Exception.InnerExceptions)
                {
                    if (error is ZipException)
                        ShowMessage(error.Message);
                    else
                        ShowMessage(string.Format("Error occured: {0}", error));
                }
            }
            else
            {
                statusLabel.Text = "Done";
                ShowMessage(string.Format("Extracted {0} {1}.", task.Result.FilesAffected, task.Result.FilesAffected == 1 ? "file" : "files"));
            }

            FinishJob();
        }

        #endregion

        #region Compress files

        /// <summary>
        /// Adds specified files/directories to the archive.
        /// </summary>
        /// <param name="filesToAdd">Collection of files or directories to add.</param>
        /// <param name="targetDirectory">Path to a directory where to add specified files or directories.</param>
        private void AddFiles(FileSet filesToAdd, string targetDirectory)
        {
            SetEnabled(false);
            statusLabel.Text = "Compressing files...";
            CreateProgressDialog("Compressing files");

            // start asynchronous Add operation
            _archive.AddAsync(filesToAdd, targetDirectory, 0, 0).ContinueWith(AddCompleted);
        }

        private void AddCompleted(Task<ArchiveOperationResult> task)
        {
            if (this.InvokeRequired)
            {
                Invoke(new Action<Task<ArchiveOperationResult>>(AddCompleted), task);
                return;
            }

            // refresh the TreeView content (ListView is refreshed as well)
            FillTreeView();

            if (task.IsCanceled)
            {
                ShowMessage("Operation was cancelled.");
            }
            else if (task.IsFaulted)
            {
                foreach (Exception error in task.Exception.InnerExceptions)
                {
                    if (error is ZipException)
                        ShowMessage(error.Message);
                    else
                        ShowMessage(string.Format("Error occured: {0}", error));
                }
            }
            else
            {
                statusLabel.Text = "Done";
                ShowMessage(string.Format("Added {0} {1}.", task.Result.FilesAffected, task.Result.FilesAffected == 1 ? "file" : "files"));
            }

            FinishJob();
        }

        #endregion

        #region Delete files

        /// <summary>
        /// Deletes specified files/directories within the archive.
        /// </summary>
        /// <param name="itemsToDelete">Collection of files or directories to delete.</param>
        private void DeleteFiles(FileSet itemsToDelete)
        {
            SetEnabled(false);
            statusLabel.Text = "Deleting...";

            // start asynchronous Delete operation
            _archive.DeleteAsync(itemsToDelete, ArchiveSaveAction.Auto).ContinueWith(DeleteCompleted);
        }

        private void DeleteCompleted(Task<ArchiveOperationResult> task)
        {
            if (this.InvokeRequired)
            {
                Invoke(new Action<Task<ArchiveOperationResult>>(DeleteCompleted), task);
                return;
            }

            // refresh the TreeView content (ListView is refreshed as well)
            FillTreeView();

            if (task.IsCanceled)
            {
                ShowMessage("Operation was cancelled.");
            }
            else if (task.IsFaulted)
            {
                foreach (Exception error in task.Exception.InnerExceptions)
                {
                    if (error is ZipException)
                        ShowMessage(error.Message);
                    else
                        ShowMessage(string.Format("Error occured: {0}", error));
                }
            }
            else
            {
                statusLabel.Text = "Done";
                ShowMessage(string.Format("Deleted {0} {1}.", task.Result.FilesAffected, task.Result.FilesAffected == 1 ? "file" : "files"));
            }

            FinishJob();
        }

        #endregion

        #region Move/Rename directory/file

        /// <summary>
        /// Moves specified file/directory within the archive.
        /// </summary>
        /// <param name="itemPath">Path to a file or directory to move.</param>
        private void MoveDirectory(string itemPath)
        {
            NewNameDialog dialog = new NewNameDialog(itemPath, false);
            if (dialog.ShowDialog(this) != DialogResult.OK)
                return;

            SetEnabled(false);
            statusLabel.Text = string.Format("Moving directory: {0}", itemPath);

            // start asynchronous Move operation			
            _archive.MoveAsync(itemPath, dialog.NewName).ContinueWith(MoveCompleted);
        }

        private void MoveCompleted(Task task)
        {
            if (this.InvokeRequired)
            {
                Invoke(new Action<Task>(MoveCompleted), task);
                return;
            }

            // refresh the TreeView content (ListView is refreshed as well)
            FillTreeView();

            if (task.IsCanceled)
            {
                ShowMessage("Operation was cancelled.");
            }
            else if (task.IsFaulted)
            {
                foreach (Exception error in task.Exception.InnerExceptions)
                {
                    if (error is ZipException)
                        ShowMessage(error.Message);
                    else
                        ShowMessage(string.Format("Error occured: {0}", error));
                }
            }

            FinishJob();
        }

        /// <summary>
        /// Renames specified file/directory within the archive.
        /// </summary>
        /// <param name="itemPath">Path to a file or directory to rename.</param>
        private void RenameFile(string itemPath)
        {
            ZipItem zipItem = _archive.GetItem(itemPath);

            NewNameDialog dialog = new NewNameDialog(zipItem.Name, true);
            if (dialog.ShowDialog(this) != DialogResult.OK)
                return;

            SetEnabled(false);
            statusLabel.Text = string.Format("Renaming file: {0}", itemPath);

            try
            {
                // rename item synchronously (this is not time expensive operation)
                // optionaly the _archive.MoveAsync(zipItem.Path, "renamed path") method can be used
                zipItem.Rename(dialog.NewName);
            }
            catch (ZipException error)
            {
                ShowMessage(error.Message);
            }
            catch (Exception error)
            {
                ShowMessage(string.Format("Error occured: {0}", error));
            }
            finally
            {
                FillTreeView();
                FinishJob();
            }
        }

        #endregion

        #region Create directory

        /// <summary>
        /// Creates a new directory at the specified path within the archive.
        /// </summary>
        /// <param name="basePath">A path to a directory where to create new directory.</param>
        private void CreateArchiveDirectory(string basePath)
        {
            NewNameDialog dialog = new NewNameDialog("", false);
            if (dialog.ShowDialog(this) != DialogResult.OK)
                return;

            string path = Path.Combine(basePath, dialog.NewName);

            SetEnabled(false);
            statusLabel.Text = string.Format("Creating directory: {0}", path);

            try
            {
                _archive.CreateDirectory(path);
            }
            catch (ZipException error)
            {
                ShowMessage(error.Message);
            }
            catch (Exception error)
            {
                ShowMessage(string.Format("Error occurred: {0}", error));
            }
            finally
            {
                FillTreeView();
                FinishJob();
            }
        }

        #endregion

        #region Archive methods

        /// <summary>
        /// Creates a new ZIP archive at the specified path.
        /// </summary>
        /// <param name="zipFilePath">A path to a ZIP archive.</param>
        private void CreateNewArchive(string zipFilePath)
        {
            CloseArchive();

            try
            {
                // create a new archive
                _archive = new ZipArchive(zipFilePath, ArchiveOpenMode.Create, ArchiveAccessMode.ReadWrite);

                Initialize();
            }
            catch (ZipException ex)
            {
                ShowMessage(ex.Message);
            }
        }

        /// <summary>
        /// Opens an existing archive at specified path.
        /// </summary>
        /// <param name="zipFilePath">A path to a ZIP archive.</param>
        public void OpenArchive(string zipFilePath)
        {
            CloseArchive();

            try
            {
                // open an existing archive
                _archive = new ZipArchive(zipFilePath, ArchiveOpenMode.Open);

                Initialize();
            }
            catch (ZipException ex)
            {
                ShowMessage(ex.Message);
            }
        }

        /// <summary>
        /// Initializes recently opened archive and prepares the UI.
        /// </summary>
        private void Initialize()
        {
            _archive.ProgressChanged += new EventHandler<ZipProgressChangedEventArgs>(ProgressChanged);
            _archive.ShrinkProgress += new EventHandler<ZipShrinkProgressEventArgs>(ShrinkProgress);
            _archive.ProblemDetected += new EventHandler<ZipProblemDetectedEventArgs>(ProblemDetected);
            _archive.PasswordRequired += new EventHandler<ZipPasswordRequiredEventArgs>(PasswordRequired);

            _optionsDialog.Initialize(_archive);

            FillTreeView();

            UpdateButtonsEnabled();

            treeViewLeft.Nodes[0].Expand();

            treeViewLeft.Select();
        }

        /// <summary>
        /// Closes the archive.
        /// </summary>
        private void CloseArchive()
        {
            if (_archive != null)
            {
                _archive.Close(ArchiveSaveAction.None);
                _archive = null;
            }
        }

        /// <summary>
        /// Converts list of full paths to a FileSet.
        /// </summary>
        /// <param name="filePaths">List of full paths.</param>
        /// <returns>A FileSet initialized with the list argument.</returns>
        private FileSet GetFileSet(IList<string> filePaths)
        {
            FileSet set = new FileSet();

            if (filePaths.Count == 0)
                return set;

            set.BasePath = Path.GetDirectoryName(filePaths[0]);
            foreach (string path in filePaths)
            {
                set.Include(Path.GetFileName(path), TraversalMode.NonRecursive);
            }
            return set;
        }

        /// <summary>
        /// Converts full path to a FileSet.
        /// </summary>
        /// <param name="fullPath">A full path.</param>
        /// <returns>A FileSet initialized with the path argument.</returns>
        private FileSet GetFileSet(string fullPath)
        {
            string basePath = Path.GetDirectoryName(fullPath);
            string name = Path.GetFileName(fullPath);
            if (string.IsNullOrEmpty(basePath))
                return new FileSet(fullPath, "*", TraversalMode.Recursive);
            else
                return new FileSet(basePath, name, TraversalMode.Recursive);
        }

        #endregion

        #region Various UI methods

        /// <summary>
        /// Enables or disables buttons based on archive writeability.
        /// </summary>
        private void UpdateButtonsEnabled()
        {
            btnAddDirectory.Enabled = IsArchiveWriteable;
            btnAddFiles.Enabled = IsArchiveWriteable;
            btnExtract.Enabled = (_archive != null && listViewRight.SelectedItems.Count > 0);
            btnExtractAll.Enabled = (_archive != null);
        }

        /// <summary>
        /// Enables or disables form controls.
        /// </summary>
        /// <param name="enabled">Whether to enable or disable form controls.</param>
        private void SetEnabled(bool enabled)
        {
            btnAddDirectory.Enabled = IsArchiveWriteable && enabled;
            btnAddFiles.Enabled = IsArchiveWriteable && enabled;
            btnExtract.Enabled = listViewRight.SelectedItems.Count > 0 && enabled;
            btnExtractAll.Enabled = enabled;

            btnOpenArchive.Enabled = enabled;
            btnNewArchive.Enabled = enabled;
            btnOptions.Enabled = enabled;

            treeViewLeft.Enabled = enabled;
            listViewRight.Enabled = enabled;

            if (enabled)
                base.Cursor = Cursors.Default;
            else
                base.Cursor = Cursors.WaitCursor;
        }

        /// <summary>
        /// Enables form controls and releases helper objects.
        /// </summary>
        private void FinishJob()
        {
            SetEnabled(true);
            statusLabel.Text = "Ready";

            if (_progressDialog != null)
                _progressDialog.Close();

            _progressDialog = null;
            _problemDialog = null;
        }

        /// <summary>
        /// Shows the specified message.
        /// </summary>
        /// <param name="message">A message to show.</param>
        private void ShowMessage(string message)
        {
            GeneralDialog.ShowDialog(this, message);
        }

        /// <summary>
        /// Creates and displays the progress dialog.
        /// </summary>
        /// <param name="title">A title of the progress dialog.</param>
        private void CreateProgressDialog(string title)
        {
            _progressDialog = new ProgressDialog(_archive, title);
            _progressDialog.Show(this);
        }

        /// <summary>
        /// Fill TreeView with the content of the ZIP archive and also fill ListView if necessary.
        /// </summary>
        private void FillTreeView()
        {
            Utils.FillTreeView(treeViewLeft, _archive);

            // selecting the TreeView node causes the treeViewLeft_AfterSelect method to be called 
            // which fills the ListView with the apropriate content
            if (treeViewLeft.SelectedNode == null)
                treeViewLeft.SelectedNode = treeViewLeft.Nodes[0];
        }

        #endregion
    }
}