/*
 * PathOps.cs --
 *
 * Copyright (c) 2007-2012 by Joe Mistachkin.  All rights reserved.
 *
 * See the file "license.terms" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * RCS: @(#) $Id: $
 */

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;

#if NATIVE
using System.Security;
#endif

#if !NET_40
using System.Security.Permissions;
#endif

using System.Text;

#if WEB
using System.Web;
#endif

using Eagle._Attributes;
using Eagle._Components.Public;
using Eagle._Constants;
using Eagle._Containers.Private;
using Eagle._Containers.Public;

namespace Eagle._Components.Private
{
#if NATIVE
#if NET_40
    [SecurityCritical()]
#else
    [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
#endif
#endif
    [ObjectId("1e358ef6-1b8f-49ac-a152-0ffece56f5af")]
    internal static class PathOps
    {
#if NATIVE
        ///////////////////////////////////////////////////////////////////////////////////////////////
        // Required Native APIs used via P/Invoke
        ///////////////////////////////////////////////////////////////////////////////////////////////

        #region Unsafe Native Methods Class
        [SuppressUnmanagedCodeSecurity()]
        [ObjectId("73db358b-e0ef-42b5-9de5-46362ad86e91")]
        internal static class UnsafeNativeMethods
        {
#if WINDOWS
            [StructLayout(LayoutKind.Sequential)]
            [ObjectId("41367b38-86f2-41c3-b7ee-4b3374372039")]
            internal struct FILETIME
            {
                public uint dwLowDateTime;
                public uint dwHighDateTime;
            }

            ///////////////////////////////////////////////////////////////////////////////////////////

            [StructLayout(LayoutKind.Sequential)]
            [ObjectId("bb894e4a-17d1-4f0e-aae1-6f878ad05f2c")]
            internal struct BY_HANDLE_FILE_INFORMATION
            {
                public FileFlagsAndAttributes dwFileAttributes;
                public FILETIME ftCreationTime;
                public FILETIME ftLastAccessTime;
                public FILETIME ftLastWriteTime;
                public uint dwVolumeSerialNumber;
                public uint nFileSizeHigh;
                public uint nFileSizeLow;
                public uint nNumberOfLinks;
                public uint nFileIndexHigh;
                public uint nFileIndexLow;
            }

            ///////////////////////////////////////////////////////////////////////////////////////////

            internal const uint FSCTL_GET_OBJECT_ID = 0x9009c;
            internal const uint FSCTL_CREATE_OR_GET_OBJECT_ID = 0x900c0;

            [StructLayout(LayoutKind.Sequential)]
            [ObjectId("71d21cdf-5626-4197-9c5c-428e4717dc80")]
            internal struct FILE_OBJECTID_BUFFER
            {
                [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
                public byte[] ObjectId;
                [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
                public byte[] BirthVolumeId;
                [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
                public byte[] BirthObjectId;
                [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
                public byte[] DomainId;
            }

            ///////////////////////////////////////////////////////////////////////////////////////////

            [DllImport(DllName.Kernel32,
                CallingConvention = CallingConvention.Winapi,
                CharSet = CharSet.Auto, BestFitMapping = false,
                ThrowOnUnmappableChar = true, SetLastError = true)]
            internal static extern IntPtr CreateFile(
                string fileName,
                FileAccessMask desiredAccess,
                FileShareMode shareMode,
                IntPtr securityAttributes,
                FileCreationDisposition creationDisposition,
                FileFlagsAndAttributes flagsAndAttributes,
                IntPtr templateFile
            );

            ///////////////////////////////////////////////////////////////////////////////////////////

            [DllImport(DllName.Kernel32,
                CallingConvention = CallingConvention.Winapi,
                SetLastError = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool DeviceIoControl(
                IntPtr device, uint ioControlCode, IntPtr inBuffer,
                uint inBufferSize, IntPtr outBuffer, uint outBufferSize,
                ref uint bytesReturned, IntPtr overlapped
            );

            ///////////////////////////////////////////////////////////////////////////////////////////

            [DllImport(DllName.Kernel32,
                CallingConvention = CallingConvention.Winapi,
                SetLastError = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool GetFileInformationByHandle(
                IntPtr file,
                ref BY_HANDLE_FILE_INFORMATION fileInformation
            );
#endif
        }
        #endregion
#endif

        ///////////////////////////////////////////////////////////////////////////////////////////////

        #region Private Constants
        internal static readonly bool NoCase =
            PlatformOps.IsWindowsOperatingSystem() ?
                true : PlatformOps.IsUnixOperatingSystem() ? false : true;

        ///////////////////////////////////////////////////////////////////////////////////////////////

        //
        // WINDOWS: File names are not case-sensitive.
        //
        // UNIX: File names are case-sensitive.
        //
        internal static readonly StringComparison ComparisonType =
            PlatformOps.IsWindowsOperatingSystem() ?
                StringOps.UserNoCaseStringComparisonType :
                PlatformOps.IsUnixOperatingSystem() ?
                    StringOps.UserStringComparisonType :
                    StringOps.SystemStringComparisonType;

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static readonly char[] PathWildcardChars = {
            Characters.Asterisk,
            Characters.QuestionMark
        };

        ///////////////////////////////////////////////////////////////////////////////////////////////

        internal static readonly char[] DirectoryChars = {
            Path.DirectorySeparatorChar,
            Path.AltDirectorySeparatorChar
        };

        private static readonly CharList DirectoryCharsList =
            new CharList(DirectoryChars);

        ///////////////////////////////////////////////////////////////////////////////////////////////

        internal static readonly string CurrentDirectory = _Path.Current;
        internal static readonly string ParentDirectory = _Path.Parent;

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static readonly char NonNativeDirectorySeparatorChar =
            PlatformOps.IsWindowsOperatingSystem() ?
                Path.AltDirectorySeparatorChar :
                Path.DirectorySeparatorChar;

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static readonly char NativeDirectorySeparatorChar =
            PlatformOps.IsWindowsOperatingSystem() ?
                Path.DirectorySeparatorChar :
                Path.AltDirectorySeparatorChar;
        #endregion

        ///////////////////////////////////////////////////////////////////////////////////////////////

#if NATIVE && WINDOWS
        private static void InitializeFileInformation(
            out UnsafeNativeMethods.BY_HANDLE_FILE_INFORMATION fileInformation
            )
        {
            fileInformation.dwFileAttributes =
                FileFlagsAndAttributes.FILE_ATTRIBUTE_NONE;

            fileInformation.ftCreationTime = new UnsafeNativeMethods.FILETIME();
            fileInformation.ftLastAccessTime = new UnsafeNativeMethods.FILETIME();
            fileInformation.ftLastWriteTime = new UnsafeNativeMethods.FILETIME();

            fileInformation.dwVolumeSerialNumber = 0;

            fileInformation.nFileSizeHigh = 0;
            fileInformation.nFileSizeLow = 0;

            fileInformation.nNumberOfLinks = 0;

            fileInformation.nFileIndexHigh = 0;
            fileInformation.nFileIndexLow = 0;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static ReturnCode GetPathInformation(
            string fileName,
            bool directory,
            ref UnsafeNativeMethods.BY_HANDLE_FILE_INFORMATION fileInformation
            )
        {
            Result error = null;

            return GetPathInformation(
                fileName, directory, ref fileInformation, ref error);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static ReturnCode GetPathInformation(
            string fileName,
            bool directory,
            ref UnsafeNativeMethods.BY_HANDLE_FILE_INFORMATION fileInformation,
            ref Result error
            )
        {
            if (PlatformOps.IsWindowsOperatingSystem())
            {
                IntPtr handle = IntPtr.Zero;

                if (!String.IsNullOrEmpty(fileName))
                {
                    try
                    {
                        FileFlagsAndAttributes fileFlagsAndAttributes =
                            FileFlagsAndAttributes.FILE_ATTRIBUTE_NONE;

                        if (directory)
                            fileFlagsAndAttributes |=
                                FileFlagsAndAttributes.FILE_FLAG_BACKUP_SEMANTICS;

                        handle = UnsafeNativeMethods.CreateFile(
                            fileName, FileAccessMask.FILE_NONE,
                            FileShareMode.FILE_SHARE_NONE, IntPtr.Zero,
                            FileCreationDisposition.OPEN_EXISTING,
                            fileFlagsAndAttributes, IntPtr.Zero);

                        if (NativeOps.IsValidHandle(handle))
                        {
                            if (UnsafeNativeMethods.GetFileInformationByHandle(
                                    handle, ref fileInformation))
                            {
                                return ReturnCode.Ok;
                            }
                        }

                        error = NativeOps.GetErrorMessage();
                    }
                    catch (Exception e)
                    {
                        error = e;
                    }
                    finally
                    {
                        if (NativeOps.IsValidHandle(handle))
                        {
                            NativeOps.UnsafeNativeMethods.CloseHandle(handle);
                            handle = IntPtr.Zero;
                        }
                    }
                }
                else
                {
                    error = "invalid file name";
                }
            }
            else
            {
                error = "not supported on this operating system";
            }

            return ReturnCode.Error;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static ReturnCode GetPathInformation(
            string path,
            bool directory,
            ref StringList list,
            ref Result error
            )
        {
            if (PlatformOps.IsWindowsOperatingSystem())
            {
                UnsafeNativeMethods.BY_HANDLE_FILE_INFORMATION fileInformation;

                InitializeFileInformation(out fileInformation);

                if (GetPathInformation(
                        path, directory, ref fileInformation,
                        ref error) == ReturnCode.Ok)
                {
                    if (list == null)
                        list = new StringList();

                    list.Add(
                        "name", path,
                        "directory", directory.ToString(),
                        "attributes",
                        fileInformation.dwFileAttributes.ToString(),
                        "created",
                        ConversionOps.ToULong(
                            fileInformation.ftCreationTime.dwLowDateTime,
                            fileInformation.ftCreationTime.dwHighDateTime).ToString(),
                        "accessed",
                        ConversionOps.ToULong(
                            fileInformation.ftLastAccessTime.dwLowDateTime,
                            fileInformation.ftLastAccessTime.dwHighDateTime).ToString(),
                        "modified",
                        ConversionOps.ToULong(
                            fileInformation.ftLastWriteTime.dwLowDateTime,
                            fileInformation.ftLastAccessTime.dwHighDateTime).ToString(),
                        "vsn",
                        fileInformation.dwVolumeSerialNumber.ToString(),
                        "size",
                        ConversionOps.ToULong(
                            fileInformation.nFileSizeLow,
                            fileInformation.nFileSizeHigh).ToString(),
                        "index",
                        ConversionOps.ToULong(
                            fileInformation.nFileIndexLow,
                            fileInformation.nFileIndexHigh).ToString(),
                        "links",
                        fileInformation.nNumberOfLinks.ToString());

                    return ReturnCode.Ok;
                }
            }
            else
            {
                error = "not supported on this operating system";
            }

            return ReturnCode.Error;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static ReturnCode GetObjectId(
            string fileName,
            bool directory,
            bool create,
            ref UnsafeNativeMethods.FILE_OBJECTID_BUFFER fileObjectId,
            ref Result error
            )
        {
            if (PlatformOps.IsWindowsOperatingSystem())
            {
                IntPtr handle = IntPtr.Zero;

                if (!String.IsNullOrEmpty(fileName))
                {
                    try
                    {
                        FileFlagsAndAttributes fileFlagsAndAttributes =
                            FileFlagsAndAttributes.FILE_ATTRIBUTE_NONE;

                        if (directory)
                            fileFlagsAndAttributes |=
                                FileFlagsAndAttributes.FILE_FLAG_BACKUP_SEMANTICS;

                        handle = UnsafeNativeMethods.CreateFile(fileName,
                            FileAccessMask.FILE_NONE,
                            FileShareMode.FILE_SHARE_READ_WRITE, IntPtr.Zero,
                            FileCreationDisposition.OPEN_EXISTING,
                            fileFlagsAndAttributes, IntPtr.Zero);

                        if (NativeOps.IsValidHandle(handle))
                        {
                            IntPtr outBuffer = IntPtr.Zero;

                            try
                            {
                                int outBufferSize = Marshal.SizeOf(typeof(
                                    UnsafeNativeMethods.FILE_OBJECTID_BUFFER));

                                outBuffer = Marshal.AllocCoTaskMem(
                                    outBufferSize);

                                if (outBuffer != IntPtr.Zero)
                                {
                                    uint bytesReturned = 0;

                                    if (UnsafeNativeMethods.DeviceIoControl(
                                            handle, create ?
                                                UnsafeNativeMethods.FSCTL_CREATE_OR_GET_OBJECT_ID :
                                                UnsafeNativeMethods.FSCTL_GET_OBJECT_ID,
                                            IntPtr.Zero, 0, outBuffer, (uint)outBufferSize,
                                            ref bytesReturned, IntPtr.Zero))
                                    {
                                        fileObjectId = (UnsafeNativeMethods.FILE_OBJECTID_BUFFER)
                                            Marshal.PtrToStructure(outBuffer,
                                                typeof(UnsafeNativeMethods.FILE_OBJECTID_BUFFER));

                                        return ReturnCode.Ok;
                                    }
                                }
                                else
                                {
                                    error = "out of memory";
                                }
                            }
                            finally
                            {
                                if (outBuffer != IntPtr.Zero)
                                {
                                    Marshal.FreeCoTaskMem(outBuffer);
                                    outBuffer = IntPtr.Zero;
                                }
                            }
                        }

                        error = NativeOps.GetErrorMessage();
                    }
                    catch (Exception e)
                    {
                        error = e;
                    }
                    finally
                    {
                        if (NativeOps.IsValidHandle(handle))
                        {
                            NativeOps.UnsafeNativeMethods.CloseHandle(handle);
                            handle = IntPtr.Zero;
                        }
                    }
                }
                else
                {
                    error = "invalid file name";
                }
            }
            else
            {
                error = "not supported on this operating system";
            }

            return ReturnCode.Error;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static ReturnCode GetObjectId(
            string path,
            bool directory,
            bool create,
            ref StringList list,
            ref Result error
            )
        {
            if (PlatformOps.IsWindowsOperatingSystem())
            {
                UnsafeNativeMethods.FILE_OBJECTID_BUFFER fileObjectId =
                    new UnsafeNativeMethods.FILE_OBJECTID_BUFFER();

                if (GetObjectId(
                        path, directory, create,
                        ref fileObjectId,
                        ref error) == ReturnCode.Ok)
                {
                    if (list == null)
                        list = new StringList();

                    list.Add(
                        "name", path,
                        "directory",
                        directory.ToString(),
                        "create",
                        create.ToString(),
                        "objectId",
                        ArrayOps.ToHexadecimalString(fileObjectId.ObjectId),
                        "birthVolumeId",
                        ArrayOps.ToHexadecimalString(fileObjectId.BirthVolumeId),
                        "birthObjectId",
                        ArrayOps.ToHexadecimalString(fileObjectId.BirthObjectId),
                        "domainId",
                        ArrayOps.ToHexadecimalString(fileObjectId.DomainId));

                    return ReturnCode.Ok;
                }
            }
            else
            {
                error = "not supported on this operating system";
            }

            return ReturnCode.Error;
        }
#endif
        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static bool HasPathWildcard(
            string value
            )
        {
            return (value != null) && (PathWildcardChars != null) &&
                (value.IndexOfAny(PathWildcardChars) != Index.Invalid);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static string CleanPath(
            string path,
            bool full,
            string invalidChar
            )
        {
            string result = path;

            if (!String.IsNullOrEmpty(result))
            {
                //
                // NOTE: Just remove any surrounding double quotes.
                //
                result = result.Trim(Characters.QuotationMark);

                if (full)
                {
                    //
                    // NOTE: Full cleaning required, remove all
                    //       invalid path characters.
                    //
                    StringBuilder builder =
                        StringOps.NewStringBuilder(result);

                    foreach (char character in Path.GetInvalidPathChars())
                    {
                        if (!String.IsNullOrEmpty(invalidChar))
                            builder.Replace(character, invalidChar[0]);
                        else
                            builder.Replace(character.ToString(), null);
                    }

                    result = builder.ToString();
                }

            }

            return result;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

#if WEB && !MONO && (NET_20_SP2 || NET_40)
        //
        // HACK: Using the "HttpRuntime.UsingIntegratedPipeline" property when
        //       running on Mono seems to cause serious problems (I guess they
        //       cannot just return false).  Apparently, even referring to this
        //       method causes Mono to crash; therefore, it has been moved
        //       to a method by itself (which seems to get around the problem).
        //
        private static bool HttpRuntimeUsingIntegratedPipeline()
        {
            return HttpRuntime.UsingIntegratedPipeline;
        }
#endif

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static string GetBinaryPath(
            bool full
            )
        {
            string result = null;

            try
            {
#if WEB
                //
                // NOTE: Are we running in a web application?
                //
                HttpContext current = HttpContext.Current;

                if (current != null)
                {
                    TraceOps.DebugTrace(String.Format(
                        "{0}: GetBinaryPath: found HTTP context{1}",
                        GlobalState.GetCurrentThreadId(), Environment.NewLine),
                        typeof(PathOps).Name);

                    string path;

#if !MONO && (NET_20_SP2 || NET_40)
                    //
                    // HACK: Using the "HttpRuntime.UsingIntegratedPipeline" on Mono seems
                    //       to cause serious problems (I guess they cannot just return
                    //       false).  Apparently, even referring to this method causes Mono
                    //       to crash; therefore, it has been moved to a method by itself
                    //       (which seems to get around the problem).
                    //
                    if (!CommonOps.Runtime.IsMono() && HttpRuntimeUsingIntegratedPipeline())
                    {
                        TraceOps.DebugTrace(String.Format(
                            "{0}: GetBinaryPath: detected IIS integrated-pipeline mode{1}",
                            GlobalState.GetCurrentThreadId(), Environment.NewLine),
                            typeof(PathOps).Name);

                        //
                        // NOTE: Get the root of the web application (for use
                        //       in IIS7+ integrated mode).
                        //
                        path = HttpRuntime.AppDomainAppVirtualPath;
                    }
                    else
#endif
                    {
                        TraceOps.DebugTrace(String.Format(
                            "{0}: GetBinaryPath: detected IIS classic mode{1}",
                            GlobalState.GetCurrentThreadId(), Environment.NewLine),
                            typeof(PathOps).Name);

                        //
                        // NOTE: Get the root of the web application.
                        //
                        path = current.Request.ApplicationPath;
                    }

                    //
                    // NOTE: Map the application path to the local file system
                    //       path and append the "bin" folder, which should
                    //       always be there according to MSDN.
                    //
                    result = CombinePath(null,
                        current.Server.MapPath(path), TclVars.BinPath);
                }
                else
#endif
                {
                    TraceOps.DebugTrace(String.Format(
                        "{0}: GetBinaryPath: no HTTP context{1}",
                        GlobalState.GetCurrentThreadId(), Environment.NewLine),
                        typeof(PathOps).Name);

                    //
                    // NOTE: Use the base directory of the current application
                    //       domain.
                    //
                    AppDomain appDomain = AppDomain.CurrentDomain;

                    if (AppDomainOps.IsDefault(appDomain))
                    {
                        TraceOps.DebugTrace(String.Format(
                            "{0}: GetBinaryPath: default application domain{1}",
                            GlobalState.GetCurrentThreadId(), Environment.NewLine),
                            typeof(PathOps).Name);

                        result = appDomain.BaseDirectory;
                    }
                    else
                    {
                        //
                        // HACK: This is an isolated AppDomain.  There is
                        //       [probably?] no entry assembly available and
                        //       the AppDomain base directory is not reliable
                        //       for the purpose of loading packages; therefore,
                        //       just use the directory of the current (Eagle)
                        //       assembly.
                        //
                        TraceOps.DebugTrace(String.Format(
                            "{0}: GetBinaryPath: non-default application domain{1}",
                            GlobalState.GetCurrentThreadId(), Environment.NewLine),
                            typeof(PathOps).Name);

                        result = GlobalState.GetAssemblyPath();
                    }
                }

                //
                // NOTE: Remove trailing directory separator characters, if
                //       necessary.
                //
                if (result != null)
                    result = TrimEndOfPath(result, null);

                //
                // NOTE: Fully resolve to an absolute path if we were requested
                //       to do so.
                //
                if (full)
                    result = Path.GetFullPath(result);
            }
            catch (Exception e)
            {
                TraceOps.DebugTrace(String.Format(
                    "{0}: {1}{2}",
                    GlobalState.GetCurrentThreadId(),
                    e, Environment.NewLine),
                    typeof(PathOps).Name);
            }

            TraceOps.DebugTrace(String.Format(
                "{0}: GetBinaryPath: result = {1}{2}",
                GlobalState.GetCurrentThreadId(),
                FormatOps.WrapOrNull(result), Environment.NewLine),
                typeof(PathOps).Name);

            return result;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static string GetProcessMainModuleFileName(
            Process process,
            bool full
            )
        {
            try
            {
                if (process != null)
                {
                    ProcessModule module = process.MainModule;

                    if (module != null)
                    {
                        return full ?
                            Path.GetFullPath(module.FileName) :
                            module.FileName;
                    }
                }
            }
            catch
            {
                // do nothing.
            }

            return null;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static string GetExecutableName()
        {
            return GetExecutableName(Process.GetCurrentProcess(), true);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static string GetExecutableName(
            Process process,
            bool full
            )
        {
            //
            // NOTE: We need the name of the Eagle executable, not the Mono
            //       executable; however, if we are in a non-default AppDomain,
            //       the entry assembly may be totally meaningless.
            //
            if (!CommonOps.Runtime.IsMono())
            {
                return GetProcessMainModuleFileName(process, full);
            }
            else
            {
                string location = GlobalState.GetEntryAssemblyLocation();

                return (location != null) ?
                    location : GetProcessMainModuleFileName(process, full);
            }
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static string GetBasePath(
            Assembly assembly, /* OPTIONAL: May be null. */
            string path
            )
        {
            string result = path;

            //
            // NOTE: Garbage in, garbage out.
            //
            if (String.IsNullOrEmpty(result))
                return result;

            //
            // NOTE: If we are running in the build tasks directory (i.e.
            //       during the build process), go up one level.
            //
            if (IsEqualFileName(
                    Path.GetFileName(result), _Path.BuildTasks))
            {
                result = Path.GetDirectoryName(result);
            }

            //
            // BUGBUG: This will not always do the right thing because it is
            //         unconditional.  Need to make this check smarter.
            //
            // NOTE: Go up one level to get to the parent directory of "bin"
            //       and "lib".  We do not want to do this if the path
            //       represents the root directory of the drive.
            //
            if (!IsEqualFileName(Path.GetPathRoot(result), result))
            {
                result = Path.GetDirectoryName(result);
            }

            //
            // NOTE: Get the current build configuration for this assembly.
            //
            string configuration =
                AttributeOps.GetAssemblyConfiguration(assembly);

            //
            // NOTE: Get the name of the directory at this level.
            //
            string directory = Path.GetFileName(result);

            //
            // NOTE: If it looks like we are running from the build directory
            //       for this configuration, go up another level to compensate.
            //       If the assembly configuration is null or empty, skip this
            //       step.
            //
            if (/* DebugOps.IsAttached() || */ ((directory != null) &&
                !String.IsNullOrEmpty(configuration) &&
                directory.StartsWith(configuration, ComparisonType)))
            {
                result = Path.GetDirectoryName(result);
            }

            //
            // NOTE: We want the parent of the outer "bin" directory (which
            //       will only be in the result string at this point if we are
            //       running from the build directory), if any.
            //
            if (/* DebugOps.IsAttached() || */
                IsEqualFileName(Path.GetFileName(result), TclVars.BinPath))
            {
                result = Path.GetDirectoryName(result);
            }

            return result;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static string TrimEndOfPath(
            string path,
            char? separator
            )
        {
            //
            // NOTE: If the original path string is null or empty, just return
            //       it as we cannot do anything else meaningful with it.
            //
            if (String.IsNullOrEmpty(path))
                return path;

            //
            // BUGFIX: Whatever the only character may be, we cannot reasonably
            //         be expected to remove it (i.e. even if it is a directory
            //         separator).
            //
            int length = path.Length;

            if (length == 1)
                return path;

            //
            // NOTE: If the last character is not a directory separator then
            //       there is no trimming to be done.
            //
            if (!IsDirectoryChar(path[length - 1]))
                return path;

            //
            // NOTE: Figure out the suffix, if any, we may need to append to
            //       the result.
            //
            string suffix = String.Empty;

            if (separator != null)
                suffix = separator.ToString();

            //
            // NOTE: Trim all trailing directory separator characters from the
            //       end of the path string and append the separator character
            //       provided by the caller.
            //
            return path.TrimEnd(DirectoryChars) + suffix;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static string CombinePath(
            bool? unix,
            IList list
            )
        {
            StringBuilder builder = StringOps.NewStringBuilder();

            if (list != null)
            {
                char separator;

                if (unix != null)
                {
                    separator = (bool)unix ?
                        Path.AltDirectorySeparatorChar :
                        Path.DirectorySeparatorChar;
                }
                else
                {
                    separator = NativeDirectorySeparatorChar;
                    GetFirstDirectorySeparator(list, ref separator);
                }

                for (int index = 0; index < list.Count; index++)
                {
                    string path = StringOps.GetStringFromObject(
                        list[index]);

                    if (!String.IsNullOrEmpty(path))
                    {
                        //
                        // HACK: Remove surrounding whitespace.
                        //
                        string trimPath = path.Trim();

                        if (trimPath.Length > 0)
                        {
                            //
                            // NOTE: Have we already handled the first part
                            //       of the path?
                            //
                            if (builder.Length > 0)
                            {
                                builder.Append(separator);
                                builder.Append(trimPath.Trim(DirectoryChars));
                            }
                            else
                            {
                                //
                                // BUGFIX: *MONO* Do not trim any separator
                                //         characters from the start of the
                                //         string.
                                //
                                builder.Append(TrimEndOfPath(trimPath, null));
                            }
                        }
                    }
                }
            }

            return builder.ToString();
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static StringList GetPathList(
            IEnumerable<string> names
            )
        {
            StringList list = new StringList();

            if (names != null)
            {
                foreach (string name in names)
                {
                    string path = CommonOps.Environment.GetVariable(name);

                    if (!String.IsNullOrEmpty(path))
                        list.AddRange(path.Split(Path.PathSeparator));
                }
            }

            return list;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static string CombinePath(
            bool? unix,
            params string[] paths
            )
        {
            return CombinePath(unix, (IList)paths);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static bool IsDirectoryChar(
            char character
            )
        {
            return DirectoryCharsList.Contains(character);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static int GetHashCode(
            string path
            )
        {
            if (path != null)
            {
                string newPath = GetNativePath(path);

                if (NoCase)
                    newPath = newPath.ToLower();

                return newPath.GetHashCode();
            }

            return 0;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static bool HasDirectory(
            string path
            )
        {
            int index = Index.Invalid;

            return StartsWithDirectory(path, ref index);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static bool StartsWithDirectory(
            string path,
            ref int index
            )
        {
            if (String.IsNullOrEmpty(path))
                return false;

            if (DirectoryChars == null)
                return false;

            index = path.IndexOfAny(DirectoryChars);
            return (index != Index.Invalid);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static bool EndsWithDirectory(
            string path,
            ref int index
            )
        {
            if (String.IsNullOrEmpty(path))
                return false;

            if (DirectoryChars == null)
                return false;

            index = path.LastIndexOfAny(DirectoryChars);
            return (index != Index.Invalid);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static bool GetMappedPaths(
            Interpreter interpreter,
            string path,
            ref StringList locations
            )
        {
            //
            // NOTE: This method requires a valid interpreter context.
            //
            if (interpreter == null)
                return false;

            //
            // NOTE: If the interpreter is either disposed or it has not [yet]
            //       fully completed the PreSetup() method, we cannot use it.
            //       In that case, just return null.
            //
            if (interpreter.Disposed || !interpreter.InternalPreSetup)
                return false;

            //
            // NOTE: *SECURITY* Currently, all path mappings are always ignored
            //       for safe interpreters.
            //
            if (interpreter.IsSafe())
                return false;

            //
            // NOTE: Forbid any attempt to use a null or empty path string.
            //
            if (String.IsNullOrEmpty(path))
                return false;

            try
            {
                StringList list = new StringList();

                foreach (string index in new string[] {
                        path, Path.GetDirectoryName(path),
                        Path.GetFileName(path) })
                {
                    if (index == null)
                        continue;

                    Result value = null;

                    if (interpreter.GetVariableValue2(
                            VariableFlags.GlobalOnly, Vars.Paths,
                            index, ref value) == ReturnCode.Ok)
                    {
                        if (String.IsNullOrEmpty(value))
                            continue;

                        list.Add(value);
                    }
                }

                if (list.Count > 0)
                {
                    if (locations == null)
                        locations = new StringList();

                    locations.Add(list);
                }

                return true;
            }
            catch
            {
                // do nothing.
            }

            return false;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static bool GetAutoSourcePaths(
            Interpreter interpreter,
            ref StringList locations
            )
        {
            //
            // NOTE: This method requires a valid interpreter context.
            //
            if (interpreter == null)
                return false;

            //
            // NOTE: If the interpreter is either disposed or it has not [yet]
            //       fully completed the PreSetup() method, we cannot use it.
            //       In that case, just return null.
            //
            if (interpreter.Disposed || !interpreter.InternalPreSetup)
                return false;

            //
            // NOTE: *SECURITY* Currently, the auto-source-path is always
            //       ignored for safe interpreters.
            //
            if (interpreter.IsSafe())
                return false;

            try
            {
                Result value = null;

                if (interpreter.GetVariableValue(
                        VariableFlags.GlobalOnly, TclVars.AutoSourcePath,
                        ref value) == ReturnCode.Ok)
                {
                    StringList list = null;

                    if (!String.IsNullOrEmpty(value) && Parser.SplitList(
                            interpreter, value, 0, Length.Invalid,
                            false, ref list) == ReturnCode.Ok)
                    {
                        if (list.Count > 0)
                        {
                            if (locations == null)
                                locations = new StringList();

                            locations.Add(list);
                        }

                        return true;
                    }
                }
            }
            catch
            {
                // do nothing.
            }

            return false;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static string SearchForFile(
            Interpreter interpreter, /* optional interpreter context to use. */
            string path,             /* [qualified?] file name to search for. */
            FileSearchFlags flags    /* flags that control search behavior. */
            )
        {
            bool mapped = FlagOps.HasFlags(flags, FileSearchFlags.Mapped, true);

            bool autoSourcePath = FlagOps.HasFlags(flags,
                FileSearchFlags.AutoSourcePath, true);

            bool current = FlagOps.HasFlags(flags, FileSearchFlags.Current, true);
            bool user = FlagOps.HasFlags(flags, FileSearchFlags.User, true);

            bool application = FlagOps.HasFlags(flags,
                FileSearchFlags.Application, true);

            bool vendor = FlagOps.HasFlags(flags, FileSearchFlags.Vendor, true);
            bool strict = FlagOps.HasFlags(flags, FileSearchFlags.Strict, true);
            bool unix = FlagOps.HasFlags(flags, FileSearchFlags.Unix, true);

            bool directoryLocation = FlagOps.HasFlags(flags,
                FileSearchFlags.DirectoryLocation, true);

            bool fileLocation = FlagOps.HasFlags(flags,
                FileSearchFlags.FileLocation, true);

            try
            {
                if (!String.IsNullOrEmpty(path))
                {
                    if (mapped || autoSourcePath || current || user || application)
                    {
                        StringList locations = new StringList();

                        if (mapped)
                            /* IGNORED */
                            GetMappedPaths(interpreter, path, ref locations);

                        if (autoSourcePath)
                            /* IGNORED */
                            GetAutoSourcePaths(interpreter, ref locations);

                        if (current)
                            locations.Add(Directory.GetCurrentDirectory());

                        if (user)
                        {
                            locations.Add(GetHomeDirectory());

                            locations.Add(Environment.GetFolderPath(
                                Environment.SpecialFolder.MyDocuments));

#if NET_40
                            locations.Add(Environment.GetFolderPath(
                                Environment.SpecialFolder.UserProfile));

                            locations.Add(Environment.GetFolderPath(
                                Environment.SpecialFolder.CommonDocuments));
#else
                            locations.Add(GetUserProfileDirectory());
#endif
                        }

                        if (application)
                        {
                            locations.Add(Environment.GetFolderPath(
                                Environment.SpecialFolder.LocalApplicationData));

                            locations.Add(Environment.GetFolderPath(
                                Environment.SpecialFolder.ApplicationData));

                            locations.Add(Environment.GetFolderPath(
                                Environment.SpecialFolder.CommonApplicationData));

                            locations.Add(GlobalState.GetBinaryPath());
                            locations.Add(GlobalState.GetAssemblyPath());
                        }

                        if (user || application)
                            locations.Add(GetUserProfileDirectory());

                        string vendorPath = vendor ? GetVendorPath() : null;

                        foreach (string location in locations)
                        {
                            //
                            // NOTE: Skip locations that are null or an empty
                            //       string.
                            //
                            if (String.IsNullOrEmpty(location))
                                continue;

                            //
                            // NOTE: If the location entry is actually a file,
                            //       return it now if we are allowed to do so.
                            //
                            if (fileLocation && File.Exists(location))
                                return location;

                            //
                            // NOTE: If the location entry is not allowed to be
                            //       a directory -OR- the directory does not
                            //       exist, skip this location entry.
                            //
                            if (!directoryLocation ||
                                !Directory.Exists(location))
                            {
                                continue;
                            }

                            string fileNameOnly = Path.GetFileName(path);
                            string fileName;

                            if (!String.IsNullOrEmpty(vendorPath))
                            {
                                fileName = CombinePath(unix, location,
                                    vendorPath, fileNameOnly);

                                if (File.Exists(fileName))
                                    return fileName;
                            }

                            fileName = CombinePath(unix, location,
                                fileNameOnly);

                            if (File.Exists(fileName))
                                return fileName;
                        }
                    }
                }
            }
            catch
            {
                // do nothing.
            }

            //
            // NOTE: At this point, nothing was found.
            //
            if (strict)
            {
                //
                // NOTE: If we get here, we found nothing and that is
                //       considered an error (in strict mode).
                //
                return null;
            }
            else
            {
                //
                // NOTE: Otherwise, just return whatever input value
                //       we received.
                //
                return path;
            }
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static string GetUserDirectory(
            bool strict
            )
        {
            string[] directories = {
                GetHomeDirectory(),
                GetUserProfileDirectory()
            };

            foreach (string directory in directories)
            {
                if (!String.IsNullOrEmpty(directory) &&
                    Directory.Exists(directory))
                {
                    return directory;
                }
            }

            //
            // NOTE: If we get here, we found nothing and that is
            //       considered an error.
            //
            return strict ? null : GetHomeDirectory();
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static string GetUserProfileDirectory()
        {
            return GlobalState.GetEnvironmentVariable(
                EnvVars.UserProfile, true, false);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static string GetVendorPath()
        {
            //
            // NOTE: Return the vendor path "offset" to be appended to the
            //       search directory when looking for files.
            //
            return GlobalState.GetEnvironmentVariable(
                EnvVars.VendorPath, true, false);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static string GetHomeDirectory()
        {
            return GlobalState.GetEnvironmentVariable(
                EnvVars.Home, true, false);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static void SetHomeDirectory(
            string value
            )
        {
            GlobalState.SetEnvironmentVariable(
                EnvVars.Home, value, true);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static string ScrubPath(
            string basePath,
            string path
            )
        {
            if (!String.IsNullOrEmpty(path))
            {
                //
                // WINDOWS: File names are not case-sensitive.
                //
                if (!String.IsNullOrEmpty(basePath) &&
                    path.StartsWith(basePath, ComparisonType))
                {
                    //
                    // NOTE: Replace the base path with a "token".
                    //
                    return Vars.BaseDirectory +
                        path.Substring(basePath.Length);
                }

                return Path.GetFileName(path);
            }

            return path;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static bool IsUri(
            string value
            )
        {
            return IsUri(value, UriKind.Absolute);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static bool IsUri(
            string value,
            UriKind uriKind
            )
        {
            if (!String.IsNullOrEmpty(value))
            {
                Uri uri = null;
                UriKind localUriKind = UriKind.RelativeOrAbsolute;

                if (TryCreateUri(value, ref uri, ref localUriKind))
                    return (uriKind == UriKind.RelativeOrAbsolute) || (localUriKind == uriKind);
            }

            return false;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static bool TryCreateUri(
            string value,
            ref Uri uri,
            ref UriKind uriKind
            )
        {
            if (Uri.TryCreate(value, UriKind.Absolute, out uri))
            {
                uriKind = UriKind.Absolute;

                return true;
            }
            else if (Uri.TryCreate(value, UriKind.Relative, out uri))
            {
                uriKind = UriKind.Relative;

                return true;
            }

            return false;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static bool IsHttpsUriScheme(
            Uri uri
            )
        {
            return (uri != null) &&
                (String.Compare(uri.Scheme, Uri.UriSchemeHttps,
                    StringOps.SystemNoCaseStringComparisonType) == 0);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static bool IsFileUriScheme(
            Uri uri
            )
        {
            return (uri != null) &&
                (String.Compare(uri.Scheme, Uri.UriSchemeFile,
                    StringOps.SystemNoCaseStringComparisonType) == 0);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static bool IsRemoteUri(
            string value
            )
        {
            Uri uri = null;

            return IsRemoteUri(value, ref uri);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static bool IsRemoteUri(
            string value,
            ref Uri uri
            )
        {
            uri = null;

            if (!String.IsNullOrEmpty(value))
            {
                //
                // WARNING: *SECURITY* The "UriKind" value here must be
                //          "Absolute", please do not change it.
                //
                if (Uri.TryCreate(value, UriKind.Absolute, out uri))
                    return !IsFileUriScheme(uri);
            }

            return false;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static string GetDirectoryName(
            string path
            )
        {
            string result = path;

            try
            {
                if (!String.IsNullOrEmpty(result))
                {
                    if (IsRemoteUri(result))
                    {
                        //
                        // HACK: This is a horrible hack.
                        //
                        result = GetUnixPath(Path.GetDirectoryName(result));
                    }
                    else
                    {
                        result = Path.GetDirectoryName(result);
                    }
                }

                return result;
            }
            catch
            {
                // do nothing.
            }

            return null;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static string GetDirectoryNameRaw(
            string path
            )
        {
            string directory;
            string fileName; /* NOT USED */

            SplitPathRaw(path, out directory, out fileName);

            return directory;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static void SplitPathRaw(
            string path,
            out string directory,
            out string fileName
            )
        {
            directory = null;
            fileName = null;

            if (String.IsNullOrEmpty(path))
                return;

            int index = path.LastIndexOfAny(DirectoryChars);

            if (index != Index.Invalid)
            {
                directory = path.Substring(0, index);
                fileName = path.Substring(index + 1);
            }
            else
            {
                fileName = path;
            }
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static bool GetFirstDirectorySeparator(
            string path,
            ref char separator
            )
        {
            if (!String.IsNullOrEmpty(path))
            {
                int[] indexes = {
                    path.IndexOf(Path.DirectorySeparatorChar),
                    path.IndexOf(Path.AltDirectorySeparatorChar)
                };

                int minimumIndex = Index.Invalid;

                foreach (int index in indexes)
                {
                    if (index == Index.Invalid)
                        continue;

                    if ((minimumIndex == Index.Invalid) ||
                        (index < minimumIndex))
                    {
                        minimumIndex = index;
                    }
                }

                if (minimumIndex != Index.Invalid)
                {
                    separator = path[minimumIndex];
                    return true;
                }
            }

            return false;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static char GetFirstDirectorySeparator(
            string path
            )
        {
            char separator = NativeDirectorySeparatorChar;

            /* IGNORED */
            GetFirstDirectorySeparator(path, ref separator);

            return separator;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static void GetFirstDirectorySeparator(
            IList list,
            ref char separator
            )
        {
            if (list != null)
            {
                for (int index = 0; index < list.Count; index++)
                {
                    string path = StringOps.GetStringFromObject(list[index]);

                    if (!String.IsNullOrEmpty(path) &&
                        GetFirstDirectorySeparator(path, ref separator))
                    {
                        break;
                    }
                }
            }
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static string MakeRelativePath(
            string path,
            bool separator /* NOTE: Also remove trailing separator? */
            )
        {
            if (String.IsNullOrEmpty(path))
                return path;

            if (path.Length <= 2) // NOTE: Do NOT return empty string.
                return path;

            if (!StringOps.CharIsAsciiAlpha(path[0]) ||
                (path[1] != Characters.Colon))
            {
                return path;
            }

            return separator ?
                path.Substring(2).TrimStart(DirectoryChars) :
                path.Substring(2);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static string AppendSeparator(
            string path
            )
        {
            if (String.IsNullOrEmpty(path))
                return path;

            if ((DirectoryChars == null) || (DirectoryChars.Length == 0))
                return path;

            foreach (char character in DirectoryChars)
            {
                int index = path.IndexOf(character);

                if (index != Index.Invalid)
                    return path + character;
            }

            return path + DirectoryChars[0];
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

#if APPDOMAINS || ISOLATED_INTERPRETERS || ISOLATED_PLUGINS
        public static bool IsUnderPath(
            Interpreter interpreter,
            string path1,
            string path2
            )
        {
            string newPath1;

            if (!String.IsNullOrEmpty(path1))
                newPath1 = ResolveFullPath(interpreter, path1);
            else
                newPath1 = GetNativePath(path1);

            string newPath2;

            if (!String.IsNullOrEmpty(path2))
                newPath2 = ResolveFullPath(interpreter, path2);
            else
                newPath2 = GetNativePath(path2);

            return String.Compare(newPath1, 0, newPath2, 0,
                (newPath2 != null) ? newPath2.Length : 0,
                ComparisonType) == 0;
        }
#endif

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static int CompareFileNames(
            string path1,
            string path2
            )
        {
            return String.Compare(
                GetNativePath(path1), GetNativePath(path2), ComparisonType);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static bool IsEqualFileName(
            string path1,
            string path2
            )
        {
            return String.Equals(
                GetNativePath(path1), GetNativePath(path2), ComparisonType);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static bool IsEqualFileName(
            string path1,
            string path2,
            int length
            )
        {
            return String.Compare(
                GetNativePath(path1), 0, GetNativePath(path2), 0, length,
                ComparisonType) == 0;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static bool IsSameFile(
            Interpreter interpreter,
            string path1,
            string path2
            )
        {
#if NATIVE && WINDOWS
            UnsafeNativeMethods.BY_HANDLE_FILE_INFORMATION fileInformation1;
            UnsafeNativeMethods.BY_HANDLE_FILE_INFORMATION fileInformation2;

            InitializeFileInformation(out fileInformation1);
            InitializeFileInformation(out fileInformation2);

            if ((GetPathInformation(path1, Directory.Exists(path1),
                    ref fileInformation1) == ReturnCode.Ok) &&
                (GetPathInformation(path2, Directory.Exists(path2),
                    ref fileInformation2) == ReturnCode.Ok))
            {
                return (fileInformation1.dwVolumeSerialNumber == fileInformation2.dwVolumeSerialNumber) &&
                    (fileInformation1.nFileIndexHigh == fileInformation2.nFileIndexHigh) &&
                    (fileInformation1.nFileIndexLow == fileInformation2.nFileIndexLow);
            }
            else
#endif
            {
                string newPath1;

                if (!String.IsNullOrEmpty(path1))
                    newPath1 = ResolveFullPath(interpreter, path1);
                else
                    newPath1 = path1;

                string newPath2;

                if (!String.IsNullOrEmpty(path2))
                    newPath2 = ResolveFullPath(interpreter, path2);
                else
                    newPath2 = path2;

                //
                // NOTE: If the normalized path strings are the same (or they
                //       are both null or empty string) then we match.
                //
                return IsEqualFileName(newPath1, newPath2);
            }
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static bool IsJustTilde(
            string path
            )
        {
            if (!String.IsNullOrEmpty(path))
            {
                if (path.Length == 1)
                {
                    return (path[0] == Characters.Tilde);
                }
                else
                {
                    string trimPath = TrimEndOfPath(path, null);

                    return ((trimPath.Length == 1) &&
                            (trimPath[0] == Characters.Tilde));
                }
            }
            else
            {
                return false;
            }
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static bool PathExists(
            string path
            )
        {
            //
            // NOTE: Does the specified path exist as either an
            //       existing directory or a file?
            //
            return Directory.Exists(path) || File.Exists(path);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static string TranslatePath(
            string path,
            PathTranslationType translationType
            )
        {
            switch (translationType)
            {
                case PathTranslationType.Unix:
                    return GetUnixPath(path);
                case PathTranslationType.Windows:
                    return GetWindowsPath(path);
                case PathTranslationType.Native:
                    return GetNativePath(path);
                default:
                    return path;
            }
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static string GetNativePath(
            string path
            )
        {
            if (PlatformOps.IsWindowsOperatingSystem())
                return GetWindowsPath(path);
            else
                return GetUnixPath(path);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static string GetWindowsPath(
            string path
            )
        {
            string result = path;

            if (!String.IsNullOrEmpty(result))
                result = result.Replace(
                    Path.AltDirectorySeparatorChar,
                    Path.DirectorySeparatorChar);

            return result;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static string GetUnixPath(
            string path
            )
        {
            string result = path;

            if (!String.IsNullOrEmpty(result))
            {
                result = result.Replace(
                    Path.DirectorySeparatorChar,
                    Path.AltDirectorySeparatorChar);
            }

            return result;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static bool ValidatePathAsFile(
            string path,
            bool rooted,
            bool exists
            )
        {
            return !String.IsNullOrEmpty(path) &&
                (!rooted || Path.IsPathRooted(path)) &&
                (!exists || File.Exists(path));
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static bool ValidatePathAsDirectory(
            string path,
            bool rooted,
            bool exists
            )
        {
            return !String.IsNullOrEmpty(path) &&
                (!rooted || Path.IsPathRooted(path)) &&
                (!exists || Directory.Exists(path));
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static string TildeSubstitution(
            Interpreter interpreter,
            string path,
            bool noSearch,
            bool strict
            )
        {
            string result = path;

            if (!String.IsNullOrEmpty(result))
            {
                //
                // NOTE: Unixism: convert leading tilde in the path to the
                //       "home" directory of the current user.
                //
                // NOTE: Also, search for files in the "home" directory in the
                //       various user/application profile directories as well.
                //
                if ((result.Length >= 2) && (result[0] == Characters.Tilde) &&
                    IsDirectoryChar(result[1]))
                {
                    //
                    // HACK: Attempt to search for the file in the standard
                    //       user/application profile locations; failing that,
                    //       just return the file name as though it existed in
                    //       the user directory.
                    //
                    if (!noSearch)
                    {
                        result = SearchForFile(interpreter,
                            result, FileSearchFlags.StandardAndStrict);
                    }

                    if (noSearch || (result == null))
                    {
                        result = CombinePath(
                            null, GetUserDirectory(false), path.Substring(1));
                    }
                }
                else if ((result.Length == 1) &&
                    (result[0] == Characters.Tilde))
                {
                    result = GetUserDirectory(false);
                }
                else if (strict &&
                    (result.Length >= 2) && (result[0] == Characters.Tilde))
                {
                    //
                    // NOTE: This is probably something like "~~" or "~foo", which
                    //       we do not support.
                    //
                    return null;
                }
            }

            return result;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static string BaseDirectorySubstitution(
            Interpreter interpreter,
            string path
            )
        {
            string result = path;

            if (!String.IsNullOrEmpty(result) &&
                result.StartsWith(Vars.BaseDirectory, ComparisonType))
            {
                result = GlobalState.GetBasePath() +
                    result.Substring(Vars.BaseDirectory.Length);
            }

            return result;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static string EnvironmentSubstitution(
            Interpreter interpreter, /* NOT USED */
            string path
            )
        {
            string result = path;

            if (!String.IsNullOrEmpty(result))
                result = CommonOps.Environment.ExpandVariables(result);

            return result;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static string SubstituteOrResolvePath(
            Interpreter interpreter,
            string path,
            bool resolve,
            ref bool remoteUri
            )
        {
            //
            // NOTE: Start with their original path value.
            //
            string result = path;

            //
            // NOTE: Did they pass null or an empty string?
            //
            bool isNullOrEmpty = String.IsNullOrEmpty(result);

            //
            // NOTE: Is this file name a remote URI?
            //
            remoteUri = !isNullOrEmpty ? IsRemoteUri(result) : false;

            //
            // NOTE: If they passed null or an empty string, there is no need
            //       to do anything else.
            //
            if (!isNullOrEmpty)
            {
                //
                // NOTE: Replace the base directory "token" with the actual
                //       base directory.
                //
                result = BaseDirectorySubstitution(interpreter, result);

                //
                // NOTE: Always perform environment substitution (even on
                //       remote URIs).
                //
                result = EnvironmentSubstitution(interpreter, result);

                //
                // NOTE: Only perform leading tilde substitution if the file
                //       name is local.
                //
                if (!remoteUri)
                {
                    //
                    // NOTE: Either resolve the path (skipping the environment
                    //       variables since they have already been done) -OR-
                    //       just perform tilde substitution on it.
                    //
                    result = resolve ?
                        ResolvePathNoEnvironment(interpreter, result) :
                        TildeSubstitution(interpreter, result, false, false);

                    //
                    // NOTE: When we are not fully resolving the file name, for
                    //       file names that do not represent a remote URI, we
                    //       normalize all directory separators in the result
                    //       to the native one for this operating system.
                    //
                    if (!resolve && !String.IsNullOrEmpty(result))
                    {
                        result = result.Replace(
                            NonNativeDirectorySeparatorChar,
                            NativeDirectorySeparatorChar);
                    }
                }
            }

            TraceOps.DebugTrace(String.Format(
                "{0}: SubstituteOrResolvePath: interpreter = {1}, " +
                "path = {2}, resolve = {3}, remoteUri = {4}, " +
                "result = {5}{6}", GlobalState.GetCurrentThreadId(),
                FormatOps.WrapOrNull(interpreter), FormatOps.WrapOrNull(path),
                resolve, remoteUri, FormatOps.WrapOrNull(result),
                Environment.NewLine), typeof(PathOps).Name);

            return result;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static string ResolvePathNoEnvironment(
            Interpreter interpreter,
            string path
            )
        {
            return NormalizePath(
                interpreter, null, path, false, false, true, false, false);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static string ResolvePath(
            Interpreter interpreter,
            string path
            )
        {
            return NormalizePath(
                interpreter, null, path, false, true, true, false, false);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static string ResolveFullPath(
            Interpreter interpreter,
            string path
            )
        {
            return NormalizePath(
                interpreter, null, path, false, true, true, true, false);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static string NormalizePath(
            Interpreter interpreter,
            string rootPath,
            string path,
            bool unix,
            bool environment,
            bool tilde,
            bool full,
            bool noCase
            )
        {
            string newPath = null;
            Result error = null;

            if (NormalizePath(
                    interpreter, rootPath, path, unix, environment, tilde,
                    full, noCase, ref newPath, ref error) == ReturnCode.Ok)
            {
                return newPath;
            }

            return null;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static ReturnCode NormalizePath(
            Interpreter interpreter,
            string rootPath,
            string path,
            bool unix,
            bool environment,
            bool tilde,
            bool full,
            bool noCase,
            ref string newPath,
            ref Result error
            )
        {
            try
            {
                //
                // FIXME: I do not like this function.  It is too complex
                //        and tries to deal with too many corner cases.
                //        Also, some of the places where this function is
                //        called should probably be doing something simpler
                //        instead.
                //
                newPath = path;

                if (!String.IsNullOrEmpty(newPath))
                {
                    //
                    // NOTE: Perform environment substitution on the path?
                    //
                    if (environment)
                        newPath = EnvironmentSubstitution(interpreter, newPath);

                    //
                    // NOTE: Normalize implies clean.
                    //
                    newPath = CleanPath(newPath, false, null);

                    //
                    // NOTE: Collapse any extra trailing directory separator
                    //       characters into exactly one.
                    //
                    newPath = TrimEndOfPath(newPath, Path.DirectorySeparatorChar);

                    //
                    // NOTE: Perform leading tilde substitution?
                    //
                    if (tilde)
                        newPath = TildeSubstitution(interpreter, newPath, false, false);

                    //
                    // NOTE: Only resolve the full path if it will make sense
                    //       (we do not always want to to resolve relative to
                    //       the current directory).
                    //
                    if (!String.IsNullOrEmpty(newPath))
                    {
                        if (Path.IsPathRooted(newPath))
                        {
                            newPath = Path.GetFullPath(newPath); /* throw */
                        }
                        else if (full)
                        {
                            //
                            // NOTE: In this case, we fully resolve the entire
                            //       path, relative to the specified root path.
                            //
                            if (rootPath == null)
                                rootPath = Directory.GetCurrentDirectory();

                            if (!String.IsNullOrEmpty(newPath))
                                newPath = Path.GetFullPath(CombinePath(
                                    unix, rootPath, newPath)); /* throw */
                            else
                                newPath = rootPath;
                        }
                    }

                    //
                    // NOTE: When on Unix, use forward slashes; otherwise
                    //       (Windows), use backslashes.
                    //
                    newPath = unix ? GetUnixPath(newPath) : GetWindowsPath(newPath);

                    //
                    // NOTE: Does the result need to be normalized to lower
                    //       case?
                    //
                    if (noCase && !String.IsNullOrEmpty(newPath))
                        //
                        // NOTE: From the MSDN documentation at:
                        //
                        //       "ms-help://MS.NETDEVFX.v20.en/cpref7/html/
                        //              M_System_String_ToLowerInvariant.htm"
                        //
                        //       Security Considerations
                        //
                        //       If you need the lowercase or uppercase version of an
                        //       operating system identifier, such as a file name,
                        //       named pipe, or registry key, use the ToLowerInvariant
                        //       or ToUpperInvariant methods.
                        //
                        newPath = newPath.ToLowerInvariant();

                    //
                    // BUGFIX: Do not remove trailing slashes from the root path.
                    //
                    if (!IsEqualFileName(newPath, Path.GetPathRoot(newPath)))
                        //
                        // BUGFIX: Remove any trailing slashes.
                        //
                        newPath = TrimEndOfPath(newPath, null);
                }

                //
                // NOTE: If we get to this point, we have succeeded; however,
                //       this does not necessarily mean that we have a valid
                //       path in the result.
                //
                TraceOps.DebugTrace(String.Format(
                    "{0}: NormalizePath: interpreter = {1}, rootPath = {2}, " +
                    "path = {3}, unix = {4}, environment = {5}, tilde = {6}, " +
                    "full = {7}, noCase = {8}, newPath = {9}, code = {10}, " +
                    "error = {11}{12}", GlobalState.GetCurrentThreadId(),
                    FormatOps.WrapOrNull(interpreter), FormatOps.WrapOrNull(rootPath),
                    FormatOps.WrapOrNull(path), unix, environment, tilde, full,
                    noCase, FormatOps.WrapOrNull(newPath), ReturnCode.Ok,
                    FormatOps.WrapOrNull(true, true, error), Environment.NewLine),
                    typeof(PathOps).Name);

                return ReturnCode.Ok;
            }
            catch (Exception e)
            {
                //
                // NOTE: We encountered some kind of error while
                //       translating the path, return null to
                //       signal the error to the caller.
                //
                error = e;
            }

            TraceOps.DebugTrace(String.Format(
                "{0}: NormalizePath: interpreter = {1}, rootPath = {2}, " +
                "path = {3}, unix = {4}, environment = {5}, tilde = {6}, " +
                "full = {7}, noCase = {8}, newPath = {9}, code = {10}, " +
                "error = {11}{12}", GlobalState.GetCurrentThreadId(),
                FormatOps.WrapOrNull(interpreter), FormatOps.WrapOrNull(rootPath),
                FormatOps.WrapOrNull(path), unix, environment, tilde, full,
                noCase, FormatOps.WrapOrNull(newPath), ReturnCode.Error,
                FormatOps.WrapOrNull(true, true, error), Environment.NewLine),
                typeof(PathOps).Name);

            return ReturnCode.Error;
        }
    }
}
