//  
//   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.IO;
using System.Collections.Generic;
using System.Text;

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

namespace Rebex.Samples
{
    /// <summary>
    /// The working class of the simple command line ZIP archive manipulation utility.
    /// </summary>
    public class ZipSession
    {
        /// <summary>
        /// ZIP archive root path.
        /// </summary>
        private readonly string RootPath = Path.DirectorySeparatorChar.ToString();

        /// <summary>
        /// ZIP archive.
        /// </summary>
        private ZipArchive _archive;

        /// <summary>
        /// Recursive operation mode.
        /// </summary>
        private TraversalMode _recursive;

        /// <summary>
        /// Current directory - ZipArchive doesn't have this concept, so we have to simulate it.
        /// </summary>
        private string _currentDirectory;

        #region Helper methods

        /// <summary>
        /// Parses arguments string into its parts.
        /// </summary>
        /// <param name="arguments">Raw arguments string.</param>
        /// <param name="minParts">Minimum allowed parts.</param>
        /// <param name="maxParts">Maximum allowed parts.</param>
        /// <returns>Argument list.</returns>
        private static string[] Split(string arguments, int minParts, int maxParts)
        {
            // sample input: "one two three"
            // sample output: {"one", "two", "three"}

            // sample input: "one \"two three\""
            // sample output: {"one", "two three"}

            List<string> list = new List<string>();
            bool quoted = false;
            char previous = ' ';
            StringBuilder argument = new StringBuilder();
            for (int i = 0; i < arguments.Length; i++)
            {
                char current = arguments[i];
                switch (current)
                {
                    case ' ':
                        if (quoted)
                            goto default;

                        if (previous != ' ')
                        {
                            list.Add(argument.ToString());
                            argument.Length = 0;
                        }
                        break;

                    case '"':
                        if (previous == '"')
                            argument.Append('"');
                        else if (!quoted && previous != ' ')
                            throw new SampleException(string.Format("Unexpected character in an argument at position {0}.", i));

                        quoted = !quoted;
                        break;

                    default:
                        if (!quoted && previous == '"')
                            throw new SampleException(string.Format("Unexpected character in an argument at position {0}.", i));

                        argument.Append(current);
                        break;
                }
                previous = current;
            }

            if (previous != ' ')
                list.Add(argument.ToString());

            if (list.Count < minParts || list.Count > maxParts)
                throw new SampleException("Invalid number of arguments.");

            return list.ToArray();
        }

        /// <summary>
        /// Parses arguments string with a single argument.
        /// </summary>
        /// <param name="arguments">Raw arguments string.</param>
        /// <returns>Argument value.</returns>
        private static string One(string arguments)
        {
            return Split(arguments, 1, 1)[0];
        }

        /// <summary>
        /// Parses arguments string with a zero or one argument.
        /// </summary>
        /// <param name="arguments">Raw arguments string.</param>
        /// <returns>Argument value or null.</returns>
        private static string OneOrNone(string arguments)
        {
            string[] parts = Split(arguments, 0, 1);
            if (parts.Length == 1)
                return parts[0];
            else
                return null;
        }

        /// <summary>
        /// Reads password without displaying its characters.
        /// </summary>
        /// <returns>Password.</returns>
        private static string ReadPassword()
        {
            StringBuilder line = new StringBuilder();
            bool complete = false;

            while (!complete)
            {
                ConsoleKeyInfo key = Console.ReadKey(true);
                switch (key.Key)
                {
                    case ConsoleKey.Escape:
                        Console.WriteLine();
                        return null;

                    case ConsoleKey.Enter:
                        complete = true;
                        break;

                    case ConsoleKey.Backspace:
                        if (line.Length > 0)
                        {
                            line = line.Remove(line.Length - 1, 1);
                            Console.Write(key.KeyChar);
                            Console.Write(" ");
                            Console.Write(key.KeyChar);
                        }
                        break;

                    default:
                        if (((int)key.KeyChar >= 0x20) || (key.Key == ConsoleKey.Tab))
                        {
                            line = line.Append(key.KeyChar);
                            Console.Write("*");
                        }
                        break;
                }
            }

            Console.WriteLine();
            return line.ToString();
        }

        #endregion

        /// <summary>
        /// Checks whether a ZIP archive is currently open.
        /// </summary>
        private void CheckArchiveOpen()
        {
            if (_archive == null)
                throw new SampleException("No ZIP archive is currently open.");
        }

        /// <summary>
        /// Displays help - a list of supported commands and their arguments.
        /// </summary>
        public void Help()
        {
            Console.WriteLine("Supported commands:");
            Console.WriteLine("  open zip-file-path        - open an existing ZIP archive");
            Console.WriteLine("  create zip-file-path      - create a new ZIP archive");
            Console.WriteLine("  close                     - close the currently opened ZIP archive");
            Console.WriteLine("  exit                      - exit this utility");
            Console.WriteLine("  recursive [on|off|deep]   - toggle or display recursive mode");
            Console.WriteLine("  level [0-9]               - set or display compression level");
            Console.WriteLine("  password [password]       - set password for add/extract");
            Console.WriteLine("  dir                       - display the list of items in the current directory");
            Console.WriteLine("  cd archive-dir            - change the current directory");
            Console.WriteLine("  mkdir archive-dir         - create a new directory in a ZIP archive");
            Console.WriteLine("  rmdir archive-dir         - delete an empty directory from a ZIP archive");
            Console.WriteLine("  delete path               - delete a file in a ZIP archive");
            Console.WriteLine("  info archive-item         - display information about the specified ZIP item");
            Console.WriteLine("  move from-path to-path    - move/rename an item in a ZIP archive");
            Console.WriteLine("  extract archive-path-or-mask [target-dir]    - extract the specified ZIP item(s)");
            Console.WriteLine("  add source-path-or-mask [archive-dir]        - add the specified item(s) into the ZIP archive");
        }

        /// <summary>
        /// Opens or creates a ZIP archive at the specified path.
        /// </summary>
        /// <param name="path">Path to a ZIP archive.</param>
        /// <param name="createNew">True to create a file, false to open existing file.</param>
        public void Open(string path, bool createNew)
        {
            // close a currently opened ZIP archive (if open)
            Close();

            try
            {
                // set ZIP archive mode
                ArchiveOpenMode mode;
                if (createNew)
                    mode = ArchiveOpenMode.CreateNew;
                else
                    mode = ArchiveOpenMode.Open;

                // open a ZIP archive - create an instance of ZipArchive object
                _archive = new ZipArchive(path, mode);

                // save all changes immediately
                _archive.SaveMode = ArchiveSaveMode.Immediate;

                // set up event handlers
                _archive.ProgressChanged += new EventHandler<ZipProgressChangedEventArgs>(Event_ProgressChanged);
                _archive.ProblemDetected += new EventHandler<ZipProblemDetectedEventArgs>(Event_ProblemDetected);
                _archive.PasswordRequired += new EventHandler<ZipPasswordRequiredEventArgs>(Event_PasswordRequired);
            }
            catch (UnauthorizedAccessException error)
            {
                // display an error if access denied
                Console.WriteLine(error.Message);
                return;
            }
            catch (DirectoryNotFoundException error)
            {
                // display directory-not-found error
                Console.WriteLine(error.Message);
                return;
            }
            catch (FileNotFoundException error)
            {
                // display file-not-found error
                Console.WriteLine(error.Message);
                return;
            }

            // display basic information about the ZIP archive
            if (!createNew)
            {
                Console.WriteLine("ZIP archive at '{0}' opened.", _archive.FilePath);
                if (!string.IsNullOrEmpty(_archive.Comment))
                {
                    Console.WriteLine("Comment: {0}", _archive.Comment);
                }
            }
            else
            {
                Console.WriteLine("ZIP archive created at '{0}'.", _archive.FilePath);
            }

            // set current directory to ZIP archive's root
            _currentDirectory = RootPath;
        }

        /// <summary>
        /// Closes the currently opened ZIP archive.
        /// </summary>
        public void Close()
        {
            if (_archive == null)
                return;

            Console.WriteLine("Closing ZIP archive {0}.", _archive.FilePath);

            // close the ZIP archive
            _archive.Close(ArchiveSaveAction.None);
            _archive = null;
        }

        /// <summary>
        /// Displays a list of files and subdirectories in the current directory in a ZIP archive.
        /// </summary>
        public void Dir()
        {
            CheckArchiveOpen();

            // get the list of items in the current directory
            ZipItemCollection items = _archive.GetItems(Path.Combine(_currentDirectory, "*"), TraversalMode.NonRecursive);

            // sort the files by path (directories first)
            items.Sort();

            // display directory path and number of items
            Console.WriteLine("Directory '{0}' contains {1} item{2}.", _currentDirectory, items.Count, items.Count != 1 ? "s" : "");

            // display the items
            foreach (ZipItem item in items)
            {
                if (item.LastWriteTime.HasValue)
                    Console.Write(item.LastWriteTime.Value.ToString("u").TrimEnd('Z'));
                else
                    Console.Write(" ".PadRight(19, ' '));

                if (item.IsDirectory)
                {
                    Console.Write("   <DIR>".PadRight(15, ' '));
                }
                else
                {
                    Console.Write(item.Length.ToString().PadLeft(15, ' '));
                }

                Console.Write(" {0}", item.Name);
                Console.WriteLine();
            }
        }

        /// <summary>
        /// Changes the current directory in a ZIP archive.
        /// </summary>
        /// <param name="path">Path in a ZIP archive.</param>
        public void ChangeDirectory(string path)
        {
            CheckArchiveOpen();

            // change the current directory if an argument was specified
            if (path != null)
            {
                // expand path
                path = Path.Combine(_currentDirectory, path);

                // get item info - fail if the path doesn't exist or if it's not a directory
                ZipItem item = _archive.GetItem(path);
                if (item == null || !item.IsDirectory)
                    throw new SampleException("Directory doesn't exist.");

                // set the current directory to the item's path
                _currentDirectory = item.Path;
            }

            // display current directory
            Console.WriteLine("Current path is '{0}'.", _currentDirectory);
        }

        /// <summary>
        /// Creates a new directory in a ZIP archive.
        /// </summary>
        /// <param name="path">Path in a ZIP archive.</param>
        public void CreateDirectory(string path)
        {
            CheckArchiveOpen();

            // expand path
            path = Path.Combine(_currentDirectory, path);

            // create a new directory
            ZipItem directory = _archive.CreateDirectory(path);

            // display its path
            Console.WriteLine("Directory '{0}' was created.", directory.Path);
        }

        /// <summary>
        /// Deletes the specified directory in a ZIP archive.
        /// </summary>
        /// <param name="path">Path in a ZIP archive.</param>
        public void RemoveDirectory(string path)
        {
            CheckArchiveOpen();

            // expand path
            path = Path.Combine(_currentDirectory, path);

            // get item info - fail if the path doesn't exist or if it's not a directory
            ZipItem item = _archive.GetItem(path);
            if (item == null || !item.IsDirectory)
                throw new SampleException("Directory doesn't exist.");

            // if the directory is not empty, ask whether to delete it anyway
            if (!item.IsEmptyDirectory)
            {
                Console.Write("Directory is not empty. Remove anyway? [y/n] ");
                string answer = Console.ReadLine();
                if (!answer.Equals("y", StringComparison.InvariantCultureIgnoreCase))
                    return;
            }

            // delete the item recursively and remove unused space
            ArchiveOperationResult result = item.Delete(TraversalMode.Recursive, ArchiveSaveAction.Shrink);

            // display information about the results
            Console.WriteLine("Directory '{0}' (and {1} file{2}) was removed.", item.Path, result.FilesAffected, result.FilesAffected == 1 ? "s" : "");

            // in case the current directory was deleted, find the first upper-level directory that exists
            bool currentDirectoryRemoved = false;
            ZipItem currentDir = _archive.GetItem(_currentDirectory);
            while (currentDir == null || !currentDir.IsDirectory)
            {
                _currentDirectory = Path.Combine(_currentDirectory, "..");
                currentDir = _archive.GetItem(_currentDirectory);
                currentDirectoryRemoved = true;
            }

            // display a new current directory if the original one was removed
            if (currentDirectoryRemoved)
            {
                _currentDirectory = currentDir.Path;
                Console.WriteLine("Current path changed to '{0}'.", _currentDirectory);
            }
        }

        /// <summary>
        /// Deletes the specified file in a ZIP archive.
        /// </summary>
        /// <param name="path">Path in a ZIP archive.</param>
        public void DeleteFile(string path)
        {
            CheckArchiveOpen();

            // expand path
            path = Path.Combine(_currentDirectory, path);

            // get item info - fail if the path doesn't exist or if it's not a file
            ZipItem item = _archive.GetItem(path);
            if (item == null || !item.IsFile)
                throw new SampleException("File doesn't exist.");

            // delete the item and remove unused space
            item.Delete(ArchiveSaveAction.Shrink);

            Console.WriteLine("File '{0}' was deleted.", item.Path);
        }

        /// <summary>
        /// Move an item (file or directory) in a ZIP archive to a new path.
        /// </summary>
        /// <param name="arguments"></param>
        public void Move(string[] arguments)
        {
            CheckArchiveOpen();

            // expand paths
            string fromPath = Path.Combine(_currentDirectory, arguments[0]);
            string toPath = Path.Combine(_currentDirectory, arguments[1]);

            // move the item
            _archive.Move(fromPath, toPath);

            Console.WriteLine("Moved successfully.");
        }

        /// <summary>
        /// Displays information about an item (file or directory) in a ZIP archive.
        /// </summary>
        /// <param name="path">Path in a ZIP archive.</param>
        public void Info(string path)
        {
            CheckArchiveOpen();

            // expand path
            path = Path.Combine(_currentDirectory, path);

            // get item info - fail if the path doesn't exist
            ZipItem item = _archive.GetItem(path);
            if (item == null)
                throw new SampleException("Item doesn't exist.");

            // display item info

            Console.WriteLine("Item path: {0}", item.Path);
            Console.WriteLine("Item type: {0}", item.ItemType);
            if (item.IsEncrypted)
                Console.WriteLine("Encryption algorithm: {0}", item.EncryptionAlgorithm);
            if (item.IsFile)
            {
                Console.WriteLine("Original length: {0}", item.Length);
                Console.WriteLine("Compressed length: {0}", item.CompressedLength);
                Console.WriteLine("Compression method: {0}", item.CompressionMethod);
                Console.WriteLine("CRC value: 0x{0:X8}", item.Crc32);
            }
            if (item.CreationTime.HasValue)
                Console.WriteLine("Creation date: {0}", item.CreationTime.Value.ToString("u").TrimEnd('Z'));
            if (item.LastWriteTime.HasValue)
                Console.WriteLine("Last modification date: {0}", item.LastWriteTime.Value.ToString("u").TrimEnd('Z'));
            if (item.LastAccessTime.HasValue)
                Console.WriteLine("Last access date: {0}", item.LastAccessTime.Value.ToString("u").TrimEnd('Z'));
            if (!string.IsNullOrEmpty(item.Comment))
                Console.WriteLine("Comment: {0}", item.Comment);
        }

        /// <summary>
        /// Sets or displays recursive level.
        /// </summary>
        /// <param name="mode">Mode.</param>
        public void Recursive(string mode)
        {
            // set recursive mode if specified
            switch (mode)
            {
                case null:
                    break;
                case "on":
                case "1":
                    _recursive = TraversalMode.Recursive;
                    break;
                case "off":
                case "0":
                    _recursive = TraversalMode.NonRecursive;
                    break;
                case "deep":
                    _recursive = TraversalMode.MatchFilesDeep;
                    break;
                default:
                    throw new SampleException("Invalid argument.");
            }

            // display current recursive mode
            Console.Write("Recursive mode: ");
            switch (_recursive)
            {
                case TraversalMode.Recursive:
                    Console.Write("on");
                    break;
                case TraversalMode.MatchFilesDeep:
                    Console.Write("deep");
                    break;
                case TraversalMode.NonRecursive:
                    Console.Write("off");
                    break;
            }
            Console.WriteLine();
        }

        /// <summary>
        /// Sets or displays compression level.
        /// </summary>
        /// <param name="rawlevel">Compression level.</param>
        public void Level(string rawLevel)
        {
            CheckArchiveOpen();

            if (rawLevel != null)
            {
                // if an argument was specified, try parsing it as a value of 0 to 9
                int level;
                if (!int.TryParse(rawLevel, out level) || level < 0 || level > 9)
                    throw new SampleException("Invalid level.");

                // set the compression level
                _archive.CompressionLevel = level;
            }

            // display current compression level
            Console.WriteLine("Compression level: {0}", _archive.CompressionLevel);
        }

        /// <summary>
        /// Sets password to be used for compression (and decompression if needed).
        /// </summary>
        public void Password(string password)
        {
            CheckArchiveOpen();

            // if no password was specified, ask for it
            if (password == null)
            {
                Console.Write("Enter password (Esc to reset): ");
                password = ReadPassword();
            }

            // set password for compression and decompression
            _archive.Password = password;

            if (password != null)
                Console.WriteLine("Password set.");
            else
                Console.WriteLine("Password reset.");
        }

        /// <summary>
        /// Extracts (decompresses) items from a ZIP archive.
        /// </summary>
        /// <param name="arguments"></param> 
        public void Extract(string[] arguments)
        {
            CheckArchiveOpen();

            // expand paths
            string archivePathOrMask = Path.Combine(_currentDirectory, arguments[0]);
            string targetDirectoryPath;
            if (arguments.Length == 2)
                targetDirectoryPath = arguments[1];
            else
                targetDirectoryPath = ".";

            // extract items from a ZIP archive
            ArchiveOperationResult result = _archive.Extract(archivePathOrMask, targetDirectoryPath, _recursive);

            // display information about the results
            Console.WriteLine("{0} {1} extracted to '{2}'.",
                result.FilesAffected,
                result.FilesAffected == 1 ? "file was" : "files were",
                Path.GetFullPath(targetDirectoryPath));
        }

        /// <summary>
        /// Adds (compresses) items to a ZIP archive.
        /// </summary>
        /// <param name="arguments"></param>
        public void AddFiles(string[] arguments)
        {
            CheckArchiveOpen();

            // expand paths
            string sourcePathOrMask = arguments[0];
            string archiveDirectoryPath;
            if (arguments.Length == 2)
                archiveDirectoryPath = Path.Combine(_currentDirectory, arguments[1]);
            else
                archiveDirectoryPath = _currentDirectory;

            // add items to a ZIP archive
            ArchiveOperationResult result = _archive.Add(sourcePathOrMask, archiveDirectoryPath, _recursive);

            // display information about the results
            Console.WriteLine("{0} {1} added.", result.FilesAffected, result.FilesAffected == 1 ? "file was" : "files were");
        }

        /// <summary>
        /// Event handler that displays progress of extract (decompress) or add (compress) operations.
        /// </summary>
        /// <param name="sender">Sender object.</param>
        /// <param name="e">Event arguments.</param>
        private void Event_ProgressChanged(object sender, ZipProgressChangedEventArgs e)
        {
            // determine source path
            string sourcePath;
            if (e.Operation == ArchiveOperation.Extract)
                sourcePath = e.ArchiveItemPath;
            else
                sourcePath = e.ExternalItemPath;

            // display info about file processing
            switch (e.OperationStep)
            {
                case ArchiveOperationStep.FileProcessing:
                    // file processing started
                    Console.Write("{0} {1}: ", e.Operation, sourcePath);
                    break;
                case ArchiveOperationStep.FileDataBlockProcessed:
                    // file is being processed
                    Console.Write("\r{0} {1}: {2}%", e.Operation, sourcePath, e.CurrentFileProgressPercentage);
                    break;
                case ArchiveOperationStep.FileProcessed:
                    // file processing done
                    Console.WriteLine();
                    break;
            }
        }

        /// <summary>
        /// Event handler that is called when a problem is detected during extract (decompress) or add (compress) operations.
        /// </summary>
        /// <param name="sender">Sender object.</param>
        /// <param name="e">Event arguments.</param>
        private void Event_ProblemDetected(object sender, ZipProblemDetectedEventArgs e)
        {
            // we only handle 'file exists' problems
            if (e.ProblemType == ArchiveProblemType.FileExists)
            {
                // determine the target path
                string targetPath;
                if (e.Operation == ArchiveOperation.Add)
                    targetPath = e.ArchiveItemPath;
                else
                    targetPath = e.ExternalItemPath;

                // construct a question about possible actions
                StringBuilder question = new StringBuilder();
                question.AppendFormat("Replace {0}?", targetPath);

                if (e.IsActionPossible(ArchiveProblemActions.Overwrite))
                    question.Append(" [y]es");

                if (e.IsActionPossible(ArchiveProblemActions.Skip))
                    question.Append(" [n]o");

                if (e.IsActionPossible(ArchiveProblemActions.Cancel))
                    question.Append(" [c]ancel");

                question.Append(": ");


                ArchiveProblemActions action = 0;
                while (action == 0)
                {
                    // display the question and read its response
                    Console.Write(question);
                    string answer = Console.ReadLine();

                    switch (answer.ToLowerInvariant())
                    {
                        case "y":
                            action = ArchiveProblemActions.Overwrite;
                            break;
                        case "n":
                            action = ArchiveProblemActions.Skip;
                            break;
                        case "c":
                            action = ArchiveProblemActions.Cancel;
                            break;
                    }
                }

                // set the desired action
                e.Action = action;
            }
        }

        /// <summary>
        /// Event handler that is called when a password is required during extract (decompress) operation.
        /// </summary>
        /// <param name="sender">Sender object.</param>
        /// <param name="e">Event arguments.</param>
        private void Event_PasswordRequired(object sender, ZipPasswordRequiredEventArgs e)
        {
            // display the reason
            switch (e.Reason)
            {
                case ArchivePasswordReason.IncorrectPasswordOrCorruptedData:
                    Console.WriteLine("Incorrect password or corrupted data.");
                    break;
                case ArchivePasswordReason.PasswordRequired:
                default:
                    Console.WriteLine("Password required.");
                    break;
            }

            // ask for password
            Console.Write("Enter password (Esc to cancel): ");
            string password = ReadPassword();

            // set passoword or cancel operation
            if (password == null)
                e.Action = ArchivePasswordActions.Cancel;
            else
                e.Password = password;
        }

        /// <summary>
        /// The main working group.
        /// </summary>
        public void Run()
        {
            Console.WriteLine("Type 'help' to see a list of available commands.");

            // main command loop
            while (true)
            {
                Console.Write("> ");

                // read a command
                string command = Console.ReadLine();
                if (command == null)
                    break;

                // parse the command into "command" and "arguments" parts
                string arguments;
                int firstSpace = command.IndexOf(' ');
                if (firstSpace >= 0)
                {
                    arguments = command.Substring(firstSpace + 1);
                    command = command.Substring(0, firstSpace);
                }
                else
                {
                    arguments = "";
                }

                try
                {
                    // execute the desired command
                    switch (command.ToLowerInvariant())
                    {
                        case "help":
                            Help();
                            break;
                        case "open":
                            Open(One(arguments), false);
                            break;
                        case "create":
                            Open(One(arguments), true);
                            break;
                        case "close":
                            Close();
                            break;
                        case "quit":
                        case "exit":
                            Close();
                            return;
                        case "dir":
                            Dir();
                            break;
                        case "cd":
                            ChangeDirectory(OneOrNone(arguments));
                            break;
                        case "mkdir":
                        case "md":
                            CreateDirectory(One(arguments));
                            break;
                        case "rmdir":
                        case "rm":
                            RemoveDirectory(One(arguments));
                            break;
                        case "delete":
                            DeleteFile(One(arguments));
                            break;
                        case "move":
                            Move(Split(arguments, 2, 2));
                            break;
                        case "info":
                            Info(One(arguments));
                            break;
                        case "recursive":
                            Recursive(OneOrNone(arguments));
                            break;
                        case "level":
                            Level(OneOrNone(arguments));
                            break;
                        case "password":
                            Password(OneOrNone(arguments));
                            break;
                        case "extract":
                            Extract(Split(arguments, 1, 2));
                            break;
                        case "add":
                            AddFiles(Split(arguments, 1, 2));
                            break;
                        default:
                            Console.WriteLine("Unknown command '{0}'.", command);
                            break;
                    }
                }
                catch (SampleException error)
                {
                    // display an error reported by the ZipConsole sample
                    Console.WriteLine(error.Message);
                }
                catch (ZipException error)
                {
                    // display an error reported by the ZIP component
                    Console.WriteLine(error.Message);
                }
            }
        }
    }
}
