using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using BurnOutSharp.Tools;

namespace BurnOutSharp.FileType
{
    public class Valve : IScannable
    {
        /// <inheritdoc/>
        public bool ShouldScan(byte[] magic)
        {
            // Disabling scanning due to errors with HLLib
            // TODO: Port HLLib to C# to avoid P/Invoke issues
            return false;

            // GCF
            if (magic.StartsWith(new byte?[] { 0x01, 0x00, 0x00, 0x00 }))
                return true;

            // PAK
            if (magic.StartsWith(new byte?[] { 0x50, 0x41, 0x43, 0x4b }))
                return true;

            // SGA
            if (magic.StartsWith(new byte?[] { 0x5f, 0x41, 0x52, 0x43, 0x48, 0x49, 0x56, 0x45 }))
                return true;

            // VPK
            if (magic.StartsWith(new byte?[] { 0x55, 0xaa, 0x12, 0x34 }))
                return true;

            // WAD
            if (magic.StartsWith(new byte?[] { 0x57, 0x41, 0x44, 0x33 }))
                return true;

            return false;
        }

        /// <inheritdoc/>
        public ConcurrentDictionary<string, ConcurrentQueue<string>> Scan(Scanner scanner, string file)
        {
            if (!File.Exists(file))
                return null;

            using (var fs = File.OpenRead(file))
            {
                return Scan(scanner, fs, file);
            }
        }

        // TODO: Add stream opening support
        /// <inheritdoc/>
        public ConcurrentDictionary<string, ConcurrentQueue<string>> Scan(Scanner scanner, Stream stream, string file)
        {
            // If the Valve archive itself fails
            try
            {
                string tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
                Directory.CreateDirectory(tempPath);

                string[] args = new string[]
                {
				"-p", file,
				"-x", "root",
				"-x", "'extract .'",
				"-x", "exit",
				"-d", tempPath,
                };

                Run(args);

                // Collect and format all found protections
                var protections = scanner.GetProtections(tempPath);

                // If temp directory cleanup fails
                try
                {
                    Directory.Delete(tempPath, true);
                }
                catch { }

                // Remove temporary path references
                Utilities.StripFromKeys(protections, tempPath);

                return protections;
            }
            catch { }

            return null;
        }

        // This code was copied over from HLExtract.NET\Program.cs
        // It was copied over due to access issues with the existing class.
        // This is the only part that should be kept in sync manually
        #region HLExtract.Net Main Program

        private static bool bSilent = false;
        private static bool bPause = true;
        private static string sDestination = string.Empty;

        private static int Run(string[] args)
        {
            // Arguments.
            string sPackage = string.Empty;
            List<string> Commands = new List<string>();
            string sNCFRootPath = string.Empty;

            bool bFileMapping = false;
            bool bQuickFileMapping = false;
            bool bVolatileAccess = false;
            bool bWriteAccess = false;
            bool bOverwriteFiles = true;

            // Package stuff.
            HLLib.HLPackageType ePackageType = HLLib.HLPackageType.HL_PACKAGE_NONE;
            uint uiPackage = HLLib.HL_ID_INVALID;
            uint uiMode = (uint)HLLib.HLFileMode.HL_MODE_INVALID;

            if(HLLib.hlGetUnsignedInteger(HLLib.HLOption.HL_VERSION) < HLLib.HL_VERSION_NUMBER)
            {
                Console.WriteLine("Wrong HLLib version: v{0}.", HLLib.hlGetString(HLLib.HLOption.HL_VERSION));
                return 1;
            }

            // Process switches.
            if(args.Length == 1)
            {
                sPackage = args[0];
            }
            else
            {
                for(int i = 0; i < args.Length; i++)
                {
                    if(string.Equals(args[i], "-p", StringComparison.CurrentCultureIgnoreCase) || string.Equals(args[i], "--package", StringComparison.CurrentCultureIgnoreCase))
                    {
                        if(sPackage.Length == 0 && i + 1 < args.Length)
                        {
                            sPackage = args[++i];
                        }
                        else
                        {
                            return 2;
                        }
                    }
                    else if(string.Equals(args[i], "-d", StringComparison.CurrentCultureIgnoreCase) || string.Equals(args[i], "--dest", StringComparison.CurrentCultureIgnoreCase))
                    {
                        if(sDestination.Length == 0 && i + 1 < args.Length)
                        {
                            sDestination = args[++i];
                        }
                        else
                        {
                            return 2;
                        }
                    }
                    else if(string.Equals(args[i], "-x", StringComparison.CurrentCultureIgnoreCase) || string.Equals(args[i], "--execute", StringComparison.CurrentCultureIgnoreCase))
                    {
                        if(sDestination.Length == 0 && i + 1 < args.Length)
                        {
                            Commands.Add(args[++i]);
                        }
                        else
                        {
                            return 2;
                        }
                    }
                    else if(string.Equals(args[i], "-n", StringComparison.CurrentCultureIgnoreCase) || string.Equals(args[i], "--ncfroot", StringComparison.CurrentCultureIgnoreCase))
                    {
                        if(sNCFRootPath.Length == 0 && i + 1 < args.Length)
                        {
                            sNCFRootPath = args[++i];
                        }
                        else
                        {
                            return 2;
                        }
                    }
                    else if(string.Equals(args[i], "-s", StringComparison.CurrentCultureIgnoreCase) || string.Equals(args[i], "--silent", StringComparison.CurrentCultureIgnoreCase))
                    {
                        bSilent = true;
                    }
                    else if(string.Equals(args[i], "-u", StringComparison.CurrentCultureIgnoreCase) || string.Equals(args[i], "--no-pause", StringComparison.CurrentCultureIgnoreCase))
                    {
                        bPause = false;
                    }
                    else if(string.Equals(args[i], "-m", StringComparison.CurrentCultureIgnoreCase) || string.Equals(args[i], "--filemapping", StringComparison.CurrentCultureIgnoreCase))
                    {
                        bFileMapping = true;
                    }
                    else if(string.Equals(args[i], "-q", StringComparison.CurrentCultureIgnoreCase) || string.Equals(args[i], "--quick-filemapping", StringComparison.CurrentCultureIgnoreCase))
                    {
                        bFileMapping = true;
                        bQuickFileMapping = true;
                    }
                    else if(string.Equals(args[i], "-v", StringComparison.CurrentCultureIgnoreCase) || string.Equals(args[i], "--volatile", StringComparison.CurrentCultureIgnoreCase))
                    {
                        bVolatileAccess = true;
                    }
                    else if(string.Equals(args[i], "-w", StringComparison.CurrentCultureIgnoreCase) || string.Equals(args[i], "--write", StringComparison.CurrentCultureIgnoreCase))
                    {
                        bWriteAccess = true;
                    }
                    else if(string.Equals(args[i], "-o", StringComparison.CurrentCultureIgnoreCase) || string.Equals(args[i], "--no-overwrite", StringComparison.CurrentCultureIgnoreCase))
                    {
                        bOverwriteFiles = false;
                    }
                    else
                    {
                        return 2;
                    }
                }
            }

            // Make sure we have something to do.
            if(sPackage.Length == 0)
            {
                return 2;
            }

            // If the destination directory is not specified, make it the input directory.
            if(sDestination.Length == 0)
            {
                int iIndex = sPackage.LastIndexOfAny(new char[]{'\\', '/'});
                if(iIndex != -1)
                {
                    sDestination = sPackage.Substring(0, iIndex + 1);
                }
            }

            HLLib.hlInitialize();

            // Keep the delegates alive so they don't get garbage collected.
            HLLib.HLExtractItemStartProc HLExtractItemStartProc = new HLLib.HLExtractItemStartProc(ExtractItemStartCallback);
            HLLib.HLExtractItemEndProc HLExtractItemEndProc = new HLLib.HLExtractItemEndProc(ExtractItemEndCallback);
            HLLib.HLExtractFileProgressProc HLExtractFileProgressProc = new HLLib.HLExtractFileProgressProc(FileProgressCallback);
            HLLib.HLValidateFileProgressProc HLValidateFileProgressProc = new HLLib.HLValidateFileProgressProc(FileProgressCallback);
            HLLib.HLDefragmentFileProgressExProc HLDefragmentFileProgressProc = new HLLib.HLDefragmentFileProgressExProc(DefragmentProgressCallback);

            HLLib.hlSetBoolean(HLLib.HLOption.HL_OVERWRITE_FILES, bOverwriteFiles);
            HLLib.hlSetVoid(HLLib.HLOption.HL_PROC_EXTRACT_ITEM_START, Marshal.GetFunctionPointerForDelegate(HLExtractItemStartProc));
            HLLib.hlSetVoid(HLLib.HLOption.HL_PROC_EXTRACT_ITEM_END, Marshal.GetFunctionPointerForDelegate(HLExtractItemEndProc));
            HLLib.hlSetVoid(HLLib.HLOption.HL_PROC_EXTRACT_FILE_PROGRESS, Marshal.GetFunctionPointerForDelegate(HLExtractFileProgressProc));
            HLLib.hlSetVoid(HLLib.HLOption.HL_PROC_VALIDATE_FILE_PROGRESS, Marshal.GetFunctionPointerForDelegate(HLValidateFileProgressProc));
            HLLib.hlSetVoid(HLLib.HLOption.HL_PROC_DEFRAGMENT_PROGRESS_EX, Marshal.GetFunctionPointerForDelegate(HLDefragmentFileProgressProc));

            // Get the package type from the filename extension.
            ePackageType = HLLib.hlGetPackageTypeFromName(sPackage);

            // If the above fails, try getting the package type from the data at the start of the file.
            if(ePackageType == HLLib.HLPackageType.HL_PACKAGE_NONE && File.Exists(sPackage))
            {
                FileStream Reader = null;
                try
                {
                    byte[] lpBuffer = new byte[HLLib.HL_DEFAULT_PACKAGE_TEST_BUFFER_SIZE];
                    Reader = new FileStream(sPackage, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                    int iBytesRead = Reader.Read(lpBuffer, 0, lpBuffer.Length);
                    if(iBytesRead > 0)
                    {
                        IntPtr lpBytesRead = Marshal.AllocHGlobal(iBytesRead);
                        try
                        {
                            Marshal.Copy(lpBuffer, 0, lpBytesRead, iBytesRead);
                            ePackageType = HLLib.hlGetPackageTypeFromMemory(lpBytesRead, (uint)iBytesRead);
                        }
                        finally
                        {
                            Marshal.FreeHGlobal(lpBytesRead);
                        }
                    }
                }
                finally
                {
                    if(Reader != null)
                    {
                        Reader.Close();
                    }
                }
            }

            if(ePackageType == HLLib.HLPackageType.HL_PACKAGE_NONE)
            {
                Console.WriteLine("Error loading {0}:", sPackage);
                Console.WriteLine("Unsupported package type.");

                HLLib.hlShutdown();
                return 3;
            }

            // Create a package element, the element is allocated by the library and cleaned
            // up by the library.  An ID is generated which must be bound to apply operations
            // to the package.
            if(!HLLib.hlCreatePackage(ePackageType, out uiPackage))
            {
                Console.WriteLine("Error loading {0}:", sPackage);
                Console.WriteLine(HLLib.hlGetString(HLLib.HLOption.HL_ERROR_SHORT_FORMATED));

                HLLib. hlShutdown();
                return 3;
            }

            HLLib.hlBindPackage(uiPackage);

            uiMode = (uint)HLLib.HLFileMode.HL_MODE_READ;
            uiMode |= !bFileMapping ? (uint)HLLib.HLFileMode.HL_MODE_NO_FILEMAPPING : 0;
            uiMode |= bQuickFileMapping ? (uint)HLLib.HLFileMode.HL_MODE_QUICK_FILEMAPPING : 0;
            uiMode |= bVolatileAccess ? (uint)HLLib.HLFileMode.HL_MODE_VOLATILE : 0;
            uiMode |= bWriteAccess ? (uint)HLLib.HLFileMode.HL_MODE_WRITE : 0;

            // Open the package.
            // Of the above modes, only HL_MODE_READ is required.  HL_MODE_WRITE is present
            // only for future use.  File mapping is recommended as an efficient way to load
            // packages.  Quick file mapping maps the entire file (instead of bits as they are
            // needed) and thus should only be used in Windows 2000 and up (older versions of
            // Windows have poor virtual memory management which means large files won't be able
            // to find a continues block and will fail to load).  Volatile access allows HLLib
            // to share files with other applications that have those file open for writing.
            // This is useful for, say, loading .gcf files while Steam is running.
            if(!HLLib.hlPackageOpenFile(sPackage, uiMode))
            {
                Console.WriteLine("Error loading {0}:", sPackage);
                Console.WriteLine(HLLib.hlGetString(HLLib.HLOption.HL_ERROR_SHORT_FORMATED));

                HLLib. hlShutdown();
                return 3;
            }

            // If we have a .ncf file, the package file data is stored externally.  In order to
            // validate the file data etc., HLLib needs to know where to look.  Tell it where.
            if(ePackageType == HLLib.HLPackageType.HL_PACKAGE_NCF && sNCFRootPath.Length > 0)
            {
                HLLib.hlNCFFileSetRootPath(sNCFRootPath);
            }

            if(!bSilent)
                Console.WriteLine("{0} opened.", sPackage);

            // Interactive console mode.
            EnterConsole(uiPackage, Commands);

            // Close the package.
            HLLib.hlPackageClose();

            if(!bSilent)
                Console.WriteLine("{0} closed.", sPackage);

            // Free up the allocated memory.
            HLLib.hlDeletePackage(uiPackage);

            HLLib.hlShutdown();

            return 0;
        }

        private static readonly uint MAX_PATH_SIZE = 512;

        private static string GetPath(IntPtr pItem)
        {
            string sPath = string.Empty;
            IntPtr lpPath = IntPtr.Zero;
            try
            {
                lpPath = Marshal.AllocHGlobal((int)MAX_PATH_SIZE);
                HLLib.hlItemGetPath(pItem, lpPath, MAX_PATH_SIZE);
                sPath = Marshal.PtrToStringAnsi(lpPath);
            }
            finally
            {
                if(lpPath != IntPtr.Zero)
                {
                    Marshal.FreeHGlobal(lpPath);
                }
            }
            return sPath;
        }

#region Progress Callbacks
        private static uint uiProgressLast;

        private static void ProgressStart()
        {
            uiProgressLast = 0;
            Console.Write("0%");
        }

        private static void ProgressUpdate(UInt64 uiBytesDone, UInt64 uiBytesTotal)
        {
            if(!bSilent)
            {
                uint uiProgress = uiBytesTotal == 0 ? 100 : (uint)(uiBytesDone * 100 / uiBytesTotal);
                while(uiProgress >= uiProgressLast + 10)
                {
                    uiProgressLast += 10;
                    if(uiProgressLast == 100)
                    {
                        Console.Write("100% ");
                    }
                    else if(uiProgressLast == 50)
                    {
                        Console.Write("50%");
                    }
                    else
                    {
                        Console.Write(".");
                    }
                }
            }
        }

        private static void ExtractItemStartCallback(IntPtr pItem)
        {
            if(!bSilent)
            {
                if(HLLib.hlItemGetType(pItem) == HLLib.HLDirectoryItemType.HL_ITEM_FILE)
                {
                    Console.Write("  Extracting {0}: ", HLLib.hlItemGetName(pItem));
                    ProgressStart();
                }
                else
                {
                    Console.WriteLine("  Extracting {0}:", HLLib.hlItemGetName(pItem));
                }
            }
        }

        private static void FileProgressCallback(IntPtr pFile, uint uiBytesExtracted, uint uiBytesTotal, ref bool pCancel)
        {
            ProgressUpdate((UInt64)uiBytesExtracted, (UInt64)uiBytesTotal);
        }

        private static void ExtractItemEndCallback(IntPtr pItem, bool bSuccess)
        {
            if(bSuccess)
            {
                if(!bSilent)
                {
                    uint uiSize = 0;
                    HLLib.hlItemGetSize(pItem, out uiSize);
                    if(HLLib.hlItemGetType(pItem) == HLLib.HLDirectoryItemType.HL_ITEM_FILE)
                    {
                        Console.WriteLine("OK ({0} B)", uiSize);
                    }
                    else
                    {
                        Console.WriteLine("  Done {0}: OK ({1} B)", HLLib.hlItemGetName(pItem), uiSize);
                    }
                }
            }
            else
            {
                if(!bSilent)
                {
                    if(HLLib.hlItemGetType(pItem) == HLLib.HLDirectoryItemType.HL_ITEM_FILE)
                    {
                        Console.WriteLine("Errored");
                        Console.WriteLine("    {0}", HLLib.hlGetString(HLLib.HLOption.HL_ERROR_SHORT_FORMATED));
                    }
                    else
                    {
                        Console.WriteLine("  Done {0}: Errored", HLLib.hlItemGetName(pItem));
                    }
                }
                else
                {
                    if(HLLib.hlItemGetType(pItem) == HLLib.HLDirectoryItemType.HL_ITEM_FILE)
                    {
                        Console.WriteLine("  Error extracting {0}:", GetPath(pItem));
                        Console.WriteLine("    {0}", HLLib.hlGetString(HLLib.HLOption.HL_ERROR_SHORT_FORMATED));
                    }
                    else
                    {
                        Console.WriteLine("  Error extracting {0}.", GetPath(pItem));
                    }
                }
            }
        }

        private static void DefragmentProgressCallback(IntPtr pFile, uint uiFilesDefragmented, uint uiFilesTotal, UInt64 uiBytesDefragmented, UInt64 uiBytesTotal, ref bool pCancel)
        {
            ProgressUpdate(uiBytesDefragmented, uiBytesTotal);
        }
#endregion

        private static readonly string[] ValidationNames = new string[] { "OK", "Assumed OK", "Incomplete", "Corrupt", "Canceled", "Error" };

        private static string GetValidation(HLLib.HLValidation eValidation)
        {
            if(eValidation >= HLLib.HLValidation.HL_VALIDATES_OK && eValidation <= HLLib.HLValidation.HL_VALIDATES_ERROR)
            {
                return ValidationNames[(uint)eValidation];
            }
            return string.Empty;
        }

        private static HLLib.HLValidation Validate(IntPtr pItem)
        {
            HLLib.HLValidation eValidation = HLLib.HLValidation.HL_VALIDATES_OK, eTest;

            switch(HLLib.hlItemGetType(pItem))
            {
            case HLLib.HLDirectoryItemType.HL_ITEM_FOLDER:
                if(!bSilent)
                {
                    Console.WriteLine("  Validating {0}:", HLLib.hlItemGetName(pItem));
                }

                uint uiItemCount = HLLib.hlFolderGetCount(pItem);
                for(uint i = 0; i < uiItemCount; i++)
                {
                    eTest = Validate(HLLib.hlFolderGetItem(pItem, i));
                    if(eTest > eValidation)
                    {
                        eValidation = eTest;
                    }
                }

                if(!bSilent)
                {
                    Console.WriteLine("  Done {0}: {1}", HLLib.hlItemGetName(pItem), GetValidation(eValidation));
                }
                break;
            case HLLib.HLDirectoryItemType.HL_ITEM_FILE:
                if(!bSilent)
                {
                    Console.Write("  Validating {0}: ", HLLib.hlItemGetName(pItem));
                    ProgressStart();
                }

                eValidation = HLLib.hlFileGetValidation(pItem);

                if(bSilent)
                {
                    switch(eValidation)
                    {
                    case HLLib.HLValidation.HL_VALIDATES_INCOMPLETE:
                    case HLLib.HLValidation.HL_VALIDATES_CORRUPT:
                        Console.WriteLine("  Validating {0}: {1}", GetPath(pItem), GetValidation(eValidation));
                        break;
                    }
                }
                else
                {
                    Console.WriteLine(GetValidation(eValidation));
                }
                break;
            }

            return eValidation;
        }

        private static void EnterConsole(uint uiPackage, List<string> Commands)
        {
            IntPtr pItem = HLLib.hlPackageGetRoot();

            while(true)
            {
                string sLine;
                if(Commands.Count > 0)
                {
                    sLine = Commands[0].Trim();

                    Console.WriteLine("{0}>{1}", HLLib.hlItemGetName(pItem), sLine);
                    Commands.RemoveAt(0);
                }
                else
                {
                    // Command prompt.
                    Console.Write("{0}>", HLLib.hlItemGetName(pItem));

                    // Get and parse line. -- Commented out because we don't want it to read anything in automatic mode
                    //sLine = Console.ReadLine().Trim();
                    sLine = null;
                }
                if(sLine == null)
                {
                    break;
                }

                int iIndex;
                for(iIndex = 0; iIndex < sLine.Length; iIndex++)
                {
                    if(!Char.IsLetter(sLine[iIndex]))
                    {
                        break;
                    }
                }

                string sCommand, sArgument;
                if(iIndex == sLine.Length)
                {
                    sCommand = sLine;
                    sArgument = string.Empty;
                }
                else
                {
                    sCommand = sLine.Substring(0, iIndex).TrimEnd();
                    sArgument = sLine.Substring(iIndex).TrimStart();
                }

                // Cycle through commands.

                //
                // Directory listing.
                // Good example of CDirectoryItem::GetType().
                //
                if(String.Equals(sCommand, "dir", StringComparison.CurrentCultureIgnoreCase))
                {
                    uint uiItemCount = HLLib.hlFolderGetCount(pItem);
                    uint uiFolderCount = 0, uiFileCount = 0;

                    Console.WriteLine("Directory of {0}:", GetPath(pItem));

                    Console.WriteLine();

                    if(sArgument.Length == 0)
                    {
                        // List all items in the current folder.
                        for(uint i = 0; i < uiItemCount; i++)
                        {
                            IntPtr pSubItem = HLLib.hlFolderGetItem(pItem, i);
                            if(HLLib.hlItemGetType(pSubItem) == HLLib.HLDirectoryItemType.HL_ITEM_FOLDER)
                            {
                                uiFolderCount++;
                                Console.WriteLine("  <{0}>", HLLib.hlItemGetName(pSubItem));
                            }
                            else if(HLLib.hlItemGetType(pSubItem) == HLLib.HLDirectoryItemType.HL_ITEM_FILE)
                            {
                                uiFileCount++;
                                Console.WriteLine("  {0}", HLLib.hlItemGetName(pSubItem));
                            }
                        }
                    }
                    else
                    {
                        IntPtr pSubItem = HLLib.hlFolderFindFirst(pItem, sArgument, HLLib.HLFindType.HL_FIND_ALL | HLLib.HLFindType.HL_FIND_NO_RECURSE);
                        while(pSubItem != IntPtr.Zero)
                        {
                            if(HLLib.hlItemGetType(pSubItem) == HLLib.HLDirectoryItemType.HL_ITEM_FOLDER)
                            {
                                uiFolderCount++;
                                Console.WriteLine("  <{0}>", HLLib.hlItemGetName(pSubItem));
                            }
                            else if(HLLib.hlItemGetType(pSubItem) == HLLib.HLDirectoryItemType.HL_ITEM_FILE)
                            {
                                uiFileCount++;
                                Console.WriteLine("  {0}", HLLib.hlItemGetName(pSubItem));
                            }

                            pSubItem = HLLib.hlFolderFindNext(pItem, pSubItem, sArgument, HLLib.HLFindType.HL_FIND_ALL | HLLib.HLFindType.HL_FIND_NO_RECURSE);
                        }
                    }

                    Console.WriteLine();

                    // Could also have used hlFolderGetFolderCount() and
                    // hlFolderGetFileCount().

                    Console.WriteLine("Summary:");
                    Console.WriteLine();
                    Console.WriteLine("  {0} Folder{1}.", uiFolderCount, uiFolderCount != 1 ? "s" : "");
                    Console.WriteLine("  {0} File{1}.", uiFileCount, uiFileCount != 1 ? "s" : "");
                    Console.WriteLine();
                }
                //
                // Change directory.
                // Good example of CDirectoryFolder::GetParent() and item casting.
                //
                else if(String.Equals(sCommand, "cd", StringComparison.CurrentCultureIgnoreCase))
                {
                    if(sArgument.Length == 0)
                    {
                        Console.WriteLine("No argument for command cd supplied.");
                    }
                    else
                    {
                        if(String.Equals(sArgument, ".", StringComparison.CurrentCultureIgnoreCase))
                        {

                        }
                        else if(String.Equals(sArgument, "..", StringComparison.CurrentCultureIgnoreCase))
                        {
                            if(HLLib.hlItemGetParent(pItem) != IntPtr.Zero)
                            {
                                pItem = HLLib.hlItemGetParent(pItem);
                            }
                            else
                            {
                                Console.WriteLine("Folder does not have a parent.");
                            }
                        }
                        else
                        {
                            bool bFound = false;
                            uint uiItemCount = HLLib.hlFolderGetCount(pItem);
                            for(uint i = 0; i < uiItemCount; i++)
                            {
                                IntPtr pSubItem = HLLib.hlFolderGetItem(pItem, i);
                                if(HLLib.hlItemGetType(pSubItem) == HLLib.HLDirectoryItemType.HL_ITEM_FOLDER && String.Equals(sArgument, HLLib.hlItemGetName(pSubItem), StringComparison.CurrentCultureIgnoreCase))
                                {
                                    bFound = true;
                                    pItem = pSubItem;
                                    break;
                                }
                            }

                            if(!bFound)
                            {
                                Console.WriteLine("{0} not found.", sArgument);
                            }
                        }
                    }
                }
                //
                // Go to the root folder.
                //
                else if(String.Equals(sCommand, "root", StringComparison.CurrentCultureIgnoreCase))
                {
                    pItem = HLLib.hlPackageGetRoot();
                }
                //
                // Item information.
                // Good example of CPackageUtility helper functions.
                //
                else if(String.Equals(sCommand, "info", StringComparison.CurrentCultureIgnoreCase))
                {
                    if(sArgument.Length == 0)
                    {
                        Console.WriteLine("No argument for command info supplied.");
                    }
                    else
                    {
                        IntPtr pSubItem = HLLib.hlFolderGetItemByPath(pItem, sArgument, HLLib.HLFindType.HL_FIND_ALL);

                        if(pSubItem != IntPtr.Zero)
                        {
                            Console.WriteLine("Information for {0}:", GetPath(pSubItem));
                            Console.WriteLine();

                            switch(HLLib.hlItemGetType(pSubItem))
                            {
                            case HLLib.HLDirectoryItemType.HL_ITEM_FOLDER:
                                Console.WriteLine("  Type: Folder");
                                Console.WriteLine("  Size: {0} B", HLLib.hlFolderGetSizeEx(pSubItem, true));
                                Console.WriteLine("  Size On Disk: {0} B", HLLib.hlFolderGetSizeOnDiskEx(pSubItem, true));
                                Console.WriteLine("  Folders: {0}", HLLib.hlFolderGetFolderCount(pSubItem, true));
                                Console.WriteLine("  Files: {0}", HLLib.hlFolderGetFileCount(pSubItem, true));
                                break;
                            case HLLib.HLDirectoryItemType.HL_ITEM_FILE:
                                Console.WriteLine("  Type: File");
                                Console.WriteLine("  Extractable: {0}", HLLib.hlFileGetExtractable(pSubItem) != 0 ? "True" : "False");
                                //Console.WriteLine("  Validates: {0}", HLLib.hlFileGetValidates(pSubItem) ? "True" : "False");
                                Console.WriteLine("  Size: {0} B", HLLib.hlFileGetSize(pSubItem));
                                Console.WriteLine("  Size On Disk: {0} B", HLLib.hlFileGetSizeOnDisk(pSubItem));
                                break;
                            }

                            uint uiAttributeCount = HLLib.hlPackageGetItemAttributeCount();
                            for(uint i = 0; i < uiAttributeCount; i++)
                            {
                                HLLib.HLAttribute Attribute;
                                if(HLLib.hlPackageGetItemAttribute(pSubItem, (HLLib.HLPackageAttribute)i, out Attribute))
                                {
                                    if(Attribute.eAttributeType != HLLib.HLAttributeType.HL_ATTRIBUTE_INVALID)
                                    {
                                        Console.WriteLine("  {0}: {1}", Attribute.GetName(), Attribute.ToString());
                                    }
                                }
                            }

                            Console.WriteLine();
                        }
                        else
                        {
                            Console.WriteLine("{0} not found.", sArgument);
                        }
                    }
                }
                //
                // Extract item.
                // Good example of CPackageUtility extract functions.
                //
                else if(String.Equals(sCommand, "extract", StringComparison.CurrentCultureIgnoreCase))
                {
                    if(sArgument.Length == 0)
                    {
                        Console.WriteLine("No argument for command extract supplied.");
                    }
                    else
                    {
                        IntPtr pSubItem;
                        if(String.Equals(sArgument, ".", StringComparison.CurrentCultureIgnoreCase))
                        {
                            pSubItem = pItem;
                        }
                        else
                        {
                            pSubItem = HLLib.hlFolderGetItemByName(pItem, sArgument, HLLib.HLFindType.HL_FIND_ALL);
                        }

                        if(pSubItem != IntPtr.Zero)
                        {
                            // Extract the item.
                            // Item is extracted to cDestination\Item->GetName().
                            if(!bSilent)
                            {
                                Console.WriteLine("Extracting {0}...", HLLib.hlItemGetName(pSubItem));
                                Console.WriteLine();
                            }

                            HLLib.hlItemExtract(pSubItem, sDestination);

                            if(!bSilent)
                            {
                                Console.WriteLine("");
                                Console.WriteLine("Done.");
                            }
                        }
                        else
                        {
                            Console.WriteLine("{0} not found.", sArgument);
                        }
                    }
                }
                //
                // Validate item.
                // Validates the checksums of each item.
                //
                else if(String.Equals(sCommand, "validate", StringComparison.CurrentCultureIgnoreCase))
                {
                    if(sArgument.Length == 0)
                    {
                        Console.WriteLine("No argument for command extract supplied.");
                    }
                    else
                    {
                        IntPtr pSubItem;
                        if(String.Equals(sArgument, ".", StringComparison.CurrentCultureIgnoreCase))
                        {
                            pSubItem = pItem;
                        }
                        else
                        {
                            pSubItem = HLLib.hlFolderGetItemByName(pItem, sArgument, HLLib.HLFindType.HL_FIND_ALL);
                        }

                        if(pSubItem != IntPtr.Zero)
                        {
                            if(!bSilent)
                            {
                                Console.WriteLine("Validating {0}...", HLLib.hlItemGetName(pSubItem));
                                Console.WriteLine();
                            }

                            Validate(pSubItem);

                            if(!bSilent)
                            {
                                Console.WriteLine();
                                Console.WriteLine("Done.");
                            }
                        }
                        else
                        {
                            Console.WriteLine("{0} not found.", sArgument);
                        }
                    }
                }
                //
                // Defragment package.
                // Validates the checksums of each item.
                //
                else if(String.Equals(sCommand, "defragment", StringComparison.CurrentCultureIgnoreCase))
                {
                    bool bForceDefragment = false;
                    if(sArgument.Length != 0)
                    {
                        try
                        {
                            bForceDefragment = Convert.ToBoolean(sArgument);
                        }
                        catch
                        {
                        }
                    }

                    if(!bSilent)
                    {
                        Console.WriteLine("Defragmenting...");
                        Console.WriteLine();
                    }

                    HLLib.hlSetBoolean(HLLib.HLOption.HL_FORCE_DEFRAGMENT, bForceDefragment);

                    Console.Write("  Progress: ");
                    ProgressStart();
                    if(!HLLib.hlPackageDefragment())
                    {
                        Console.Write(" {0}", HLLib.hlGetString(HLLib.HLOption.HL_ERROR_SHORT_FORMATED));
                    }

                    if(!bSilent)
                    {
                        Console.WriteLine();

                        Console.WriteLine();
                        Console.WriteLine("Done.");
                    }
                }
                //
                // Find items.
                // Good example of recursive directory navigation (Search() function).
                //
                else if(String.Equals(sCommand, "find", StringComparison.CurrentCultureIgnoreCase))
                {
                    if(sArgument.Length == 0)
                    {
                        Console.WriteLine("No argument for command find supplied.");
                    }
                    else
                    {
                        // Search for the requested items.
                        if(!bSilent)
                        {
                            Console.WriteLine("Searching for {0}...", sArgument);
                            Console.WriteLine();
                        }

                        uint uiFolderCount = 0, uiFileCount = 0;
                        IntPtr pSubItem = HLLib.hlFolderFindFirst(pItem, sArgument, HLLib.HLFindType.HL_FIND_ALL);
                        while(pSubItem != IntPtr.Zero)
                        {
                            switch(HLLib.hlItemGetType(pSubItem))
                            {
                                case HLLib.HLDirectoryItemType.HL_ITEM_FOLDER:
                                    uiFolderCount++;

                                    Console.WriteLine("  Found folder: {0}", GetPath(pSubItem));
                                    break;
                                case HLLib.HLDirectoryItemType.HL_ITEM_FILE:
                                    uiFileCount++;

                                    Console.WriteLine("  Found file: {0}", GetPath(pSubItem));
                                    break;
                            }

                            pSubItem = HLLib.hlFolderFindNext(pItem, pSubItem, sArgument, HLLib.HLFindType.HL_FIND_ALL);
                        }

                        if(!bSilent)
                        {
                            if(uiFolderCount != 0 || uiFileCount != 0)
                            {
                                Console.WriteLine();
                            }

                            Console.WriteLine("  {0} folder{1} and {2} file{3} found.", uiFolderCount, uiFolderCount != 1 ? "s" : "", uiFileCount, uiFileCount != 1 ? "s" : "");
                            Console.WriteLine();
                        }
                    }
                }
                //
                // Type files.
                // Good example of reading files into memory.
                //
                else if(String.Equals(sCommand, "type", StringComparison.CurrentCultureIgnoreCase))
                {
                    if(sArgument.Length == 0)
                    {
                        Console.WriteLine("No argument for command type supplied.");
                    }
                    else
                    {
                        IntPtr pSubItem = HLLib.hlFolderGetItemByName(pItem, sArgument, HLLib.HLFindType.HL_FIND_FILES);

                        if(pSubItem != IntPtr.Zero)
                        {
                            if(!bSilent)
                            {
                                Console.WriteLine("Type for {0}:", GetPath(pSubItem));
                                Console.WriteLine();
                            }

                            IntPtr pStream;
                            if(HLLib.hlFileCreateStream(pSubItem, out pStream))
                            {
                                if(HLLib.hlStreamOpen(pStream, (uint)HLLib.HLFileMode.HL_MODE_READ))
                                {
                                    char iChar;
                                    while(HLLib.hlStreamReadChar(pStream, out iChar))
                                    {
                                        if((iChar >= ' ' && iChar <= '~') || iChar == '\n' || iChar == '\t')
                                        {
                                            Console.Write(iChar);
                                        }
                                    }

                                    HLLib.hlStreamClose(pStream);
                                }
                                else
                                {
                                    Console.WriteLine("Error typing {0}:", HLLib.hlItemGetName(pSubItem));
                                    Console.WriteLine(HLLib.hlGetString(HLLib.HLOption.HL_ERROR_SHORT_FORMATED));
                                }

                                HLLib.hlFileReleaseStream(pSubItem, pStream);
                            }
                            else
                            {
                                Console.WriteLine("Error typing {0}:", HLLib.hlItemGetName(pSubItem));
                                Console.WriteLine(HLLib.hlGetString(HLLib.HLOption.HL_ERROR_SHORT_FORMATED));
                            }

                            if(!bSilent)
                            {
                                Console.WriteLine();
                                Console.WriteLine("Done.");
                            }
                        }
                        else
                        {
                            Console.WriteLine("{0} not found.", sArgument);
                        }
                    }
                }
                //
                // Open item.
                // Good example of opening packages inside packages.
                //
                else if(String.Equals(sCommand, "open", StringComparison.CurrentCultureIgnoreCase))
                {
                    if(sArgument.Length == 0)
                    {
                        Console.WriteLine("No argument for command open supplied.");
                    }
                    else
                    {
                        IntPtr pSubItem = HLLib.hlFolderGetItemByName(pItem, sArgument, HLLib.HLFindType.HL_FIND_FILES);

                        if(pSubItem != IntPtr.Zero)
                        {
                            IntPtr pStream;
                            if(HLLib.hlFileCreateStream(pSubItem, out pStream))
                            {
                                if(HLLib.hlStreamOpen(pStream, (uint)HLLib.HLFileMode.HL_MODE_READ))
                                {
                                    HLLib.HLPackageType ePackageType = HLLib.hlGetPackageTypeFromStream(pStream);

                                    uint uiSubPackage;
                                    if(HLLib.hlCreatePackage(ePackageType, out uiSubPackage))
                                    {
                                        HLLib.hlBindPackage(uiSubPackage);
                                        if(HLLib.hlPackageOpenStream(pStream, (uint)HLLib.HLFileMode.HL_MODE_READ))
                                        {
                                            if(!bSilent)
                                                Console.WriteLine("{0} opened.", HLLib.hlItemGetName(pSubItem));

                                            EnterConsole(uiSubPackage, Commands);

                                            HLLib.hlPackageClose();

                                            if(!bSilent)
                                                Console.WriteLine("{0} closed.", HLLib.hlItemGetName(pSubItem));
                                        }
                                        else
                                        {
                                            Console.WriteLine("Error opening {0}:", HLLib.hlItemGetName(pSubItem));
                                            Console.WriteLine(HLLib.hlGetString(HLLib.HLOption.HL_ERROR_SHORT_FORMATED));
                                        }

                                        HLLib.hlDeletePackage(uiSubPackage);

                                        HLLib.hlBindPackage(uiPackage);
                                    }
                                    else
                                    {
                                        Console.WriteLine("Error opening {0}:", HLLib.hlItemGetName(pSubItem));
                                        Console.WriteLine(HLLib.hlGetString(HLLib.HLOption.HL_ERROR_SHORT_FORMATED));
                                    }

                                    HLLib.hlStreamClose(pStream);
                                }
                                else
                                {
                                    Console.WriteLine("Error opening {0}:", HLLib.hlItemGetName(pSubItem));
                                    Console.WriteLine(HLLib.hlGetString(HLLib.HLOption.HL_ERROR_SHORT_FORMATED));
                                }

                                HLLib.hlFileReleaseStream(pSubItem, pStream);
                            }
                            else
                            {
                                Console.WriteLine("Error opening {0}:", HLLib.hlItemGetName(pSubItem));
                                Console.WriteLine(HLLib.hlGetString(HLLib.HLOption.HL_ERROR_SHORT_FORMATED));
                            }
                        }
                        else
                        {
                            Console.WriteLine("{0} not found.", sArgument);
                        }
                    }
                }
                //
                // Clear screen.
                //
                else if(String.Equals(sCommand, "status", StringComparison.CurrentCultureIgnoreCase))
                {
                    Console.WriteLine("Total size: {0} B", HLLib.hlGetUnsignedLong(HLLib.HLOption.HL_PACKAGE_SIZE));
                    Console.WriteLine("Total mapping allocations: {0}", HLLib.hlGetUnsignedInteger(HLLib.HLOption.HL_PACKAGE_TOTAL_ALLOCATIONS));
                    Console.WriteLine("Total mapping memory allocated: {0} B", HLLib.hlGetUnsignedLong(HLLib.HLOption.HL_PACKAGE_TOTAL_MEMORY_ALLOCATED));
                    Console.WriteLine("Total mapping memory used: {0} B", HLLib.hlGetUnsignedLong(HLLib.HLOption.HL_PACKAGE_TOTAL_MEMORY_USED));

                    uint uiAttributeCount = HLLib.hlPackageGetAttributeCount();
                    for(uint i = 0; i < uiAttributeCount; i++)
                    {
                        HLLib.HLAttribute Attribute;
                        if(HLLib.hlPackageGetAttribute((HLLib.HLPackageAttribute)i, out Attribute))
                        {
                            if(Attribute.eAttributeType != HLLib.HLAttributeType.HL_ATTRIBUTE_INVALID)
                            {
                                Console.WriteLine("{0}: {1}", Attribute.GetName(), Attribute.ToString());
                            }
                        }
                    }
                }
                else if(String.Equals(sCommand, "exec", StringComparison.CurrentCultureIgnoreCase) || String.Equals(sCommand, "execute", StringComparison.CurrentCultureIgnoreCase))
                {
                    if(sArgument.Length == 0)
                    {
                        Console.WriteLine("No argument for command open supplied.");
                    }
                    else
                    {
                        string[] NewCommands = sArgument.Split(new char[] { '|' });
                        Commands.AddRange(NewCommands);
                    }
                }
                else if(String.Equals(sCommand, "cls", StringComparison.CurrentCultureIgnoreCase))
                {
                    try
                    {
                        Console.Clear();
                    }
                    catch
                    {
                    }
                }
                else if(String.Equals(sCommand, "help", StringComparison.CurrentCultureIgnoreCase))
                {
                    Console.WriteLine("Valid commands:");
                    Console.WriteLine();
                    Console.WriteLine("dir <filter>       (Directory list.)");
                    Console.WriteLine("cd <folder>        (Change directroy.)");
                    Console.WriteLine("info <item>        (Item information.)");
                    Console.WriteLine("extract <item>     (Extract item.)");
                    Console.WriteLine("validate <item>    (Validate item.)");
                    Console.WriteLine("defragment [force] (Defragment package.)");
                    Console.WriteLine("find <filter>      (Find item.)");
                    Console.WriteLine("type <file>        (Type a file.)");
                    Console.WriteLine("open <file>        (Open a nested package.)");
                    Console.WriteLine("root               (Go to the root folder.)");
                    Console.WriteLine("status             (Package information.)");
                    Console.WriteLine("execute <cmd>|...  (Execute 1 or more pipe delimited commands.)");
                    Console.WriteLine("cls                (Clear the screen.)");
                    Console.WriteLine("help               (Program help.)");
                    Console.WriteLine("exit               (Quit program.)");
                    Console.WriteLine();
                }
                else if(String.Equals(sCommand, "exit", StringComparison.CurrentCultureIgnoreCase))
                {
                    break;
                }
                else
                {
                    Console.WriteLine("Unkown command: {0}", sCommand);
                }
            }
        }
    
        #endregion
    }
}
