/*
 * ScriptOps.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.IO;
using System.Reflection;
using System.Threading;
using Eagle._Attributes;
using Eagle._Components.Public;
using Eagle._Components.Public.Delegates;
using Eagle._Constants;
using Eagle._Containers.Public;
using Eagle._Interfaces.Public;

namespace Eagle._Components.Private
{
    [ObjectId("81c28526-dd5a-4ba8-b056-b62bbd3b8d90")]
    internal static class ScriptOps
    {
        #region Private Constants
        #region Script Variable Handling
        private static readonly string DefaultVariableValue = null;
        private static readonly string DefaultGetVariableValue = String.Empty;
        private static readonly string DefaultSetVariableValue = null;
        private static readonly string DefaultUnsetVariableValue = null;
        #endregion
        #endregion

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

        #region Private Data
        private static readonly object syncRoot = new object();

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

        #region Variable Name Lists
        private static StringDictionary defaultVariableNames;
        #endregion

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

        #region Variable "Safe" Name / Element Lists
        private static StringDictionary safeVariableNames;
        private static StringDictionary safeTclPlatformElementNames;
        private static StringDictionary safeEaglePlatformElementNames;
        #endregion
        #endregion

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

        #region Location Support Methods
        public static ReturnCode GetAndCheckProcedureLocation(
            Interpreter interpreter,
            IProcedure procedure,
            ref IScriptLocation location,
            ref Result error
            )
        {
            ReturnCode code = GetProcedureLocation(
                interpreter, procedure, ref location, ref error);

            if (code != ReturnCode.Ok)
                return code;

            ProcedureFlags procedureFlags = procedure.Flags;

            if (!FlagOps.HasFlags(
                    procedureFlags, ProcedureFlags.Private, true))
            {
                return ReturnCode.Ok;
            }

            IScriptLocation scriptLocation = null;
            ICallFrame frame = interpreter.ProcedureFrame;

            if (frame != null)
            {
                //
                // NOTE: There is an active procedure, attempt to grab
                //       the location from it.
                //
                IProcedure scriptProcedure = frame.Execute as IProcedure;

                if (scriptProcedure == null)
                {
                    error = "invalid procedure in procedure frame";
                    return ReturnCode.Error;
                }

                scriptLocation = scriptProcedure.Location;
            }
            else
            {
                //
                // NOTE: No active procedure, use script scope.
                //
                code = GetLocation(
                    interpreter, true, ref scriptLocation, ref error);

                if (code != ReturnCode.Ok)
                    return code;
            }

            IScriptLocation procedureLocation = (location != null) &&
                (location.FileName != null) ? location : null;

            if (!ScriptLocation.MatchFileName(
                    interpreter, procedureLocation, scriptLocation, true))
            {
                error = "cannot execute private procedure, " +
                    "script location mismatch";

                return ReturnCode.Error;
            }

            return ReturnCode.Ok;
        }

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

        private static ReturnCode GetProcedureLocation(
            Interpreter interpreter,
            IProcedure procedure,
            ref IScriptLocation location,
            ref Result error
            )
        {
            if (interpreter == null)
            {
                error = "invalid interpreter";
                return ReturnCode.Error;
            }

            if (procedure == null)
            {
                error = "invalid procedure";
                return ReturnCode.Error;
            }

            if (FlagOps.HasFlags(
                    procedure.Flags, ProcedureFlags.ScriptLocation, true))
            {
                return GetLocation(
                    interpreter, false, ref location, ref error);
            }

            location = procedure.Location;
            return ReturnCode.Ok;
        }

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

        public static ReturnCode GetLocation(
            Interpreter interpreter,
            ArgumentList arguments,
            int startIndex,
            ref IScriptLocation location,
            ref Result error
            )
        {
            if (arguments == null)
            {
                error = "invalid argument list";
                return ReturnCode.Error;
            }

            if ((startIndex < 0) || (startIndex >= arguments.Count))
            {
                error = "argument index out of range";
                return ReturnCode.Error;
            }

            Argument firstArgument = arguments[startIndex];
            Argument lastArgument = arguments[arguments.Count - 1];

            if ((firstArgument == null) && (lastArgument == null))
            {
                location = ScriptLocation.Create((IScriptLocation)null);
                return ReturnCode.Ok;
            }

            if (firstArgument != null)
            {
                location = ScriptLocation.Create(interpreter,
                    firstArgument.FileName, firstArgument.StartLine,
                    (lastArgument != null) ? lastArgument.EndLine :
                        firstArgument.EndLine,
                    firstArgument.ViaSource);
            }
            else
            {
                location = ScriptLocation.Create(interpreter,
                    lastArgument.FileName, lastArgument.StartLine,
                    lastArgument.EndLine, lastArgument.ViaSource);
            }

            return ReturnCode.Ok;
        }

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

        //
        // NOTE: FOR [info script] USE ONLY.
        //
        public static ReturnCode GetLocation(
            Interpreter interpreter,
            bool viaSource,
            bool scrub,
            ref string fileName,
            ref Result error
            )
        {
            int currentLine = Parser.UnknownLine;

            return GetLocation(
                interpreter, viaSource, scrub, ref fileName,
                ref currentLine, ref error);
        }

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

        public static ReturnCode GetLocation(
            Interpreter interpreter,
            bool viaSource,
            bool scrub,
            ref string fileName,
            ref int currentLine,
            ref Result error
            )
        {
            if (interpreter != null)
            {
                ReturnCode code;
                IScriptLocation location = null;

                code = GetLocation(
                    interpreter, viaSource, ref location, ref error);

                if (code == ReturnCode.Ok)
                {
                    string scriptFileName = (location != null) ?
                        location.FileName : null;

                    if (scrub && (scriptFileName != null))
                        fileName = PathOps.ScrubPath(
                            GlobalState.GetBasePath(), scriptFileName);
                    else
                        fileName = scriptFileName; /* NOTE: May be null. */

                    currentLine = (location != null) ?
                        location.StartLine : Parser.UnknownLine;

                    return ReturnCode.Ok;
                }
            }
            else
            {
                error = "invalid interpreter";
            }

            return ReturnCode.Error;
        }

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

        private static ReturnCode GetLocation(
            Interpreter interpreter,
            bool viaSource,
            ref IScriptLocation location,
            ref Result error
            )
        {
            if (interpreter != null)
            {
#if !THREADING
                lock (interpreter.SyncRoot) /* TRANSACTIONAL */
#endif
                {
                    //
                    // NOTE: Grab whatever the caller previously manually
                    //       set the current script file name to, if any.
                    //
                    location = interpreter.ScriptLocation;

                    if (location == null)
                    {
                        StackList<IScriptLocation> locations =
                            interpreter.ScriptLocations;

                        if (locations != null)
                        {
                            int count = locations.Count;

                            if (count > 0)
                            {
                                for (int index = count - 1; index >= 0; index--)
                                {
                                    IScriptLocation thisLocation = locations[index];

                                    if (!viaSource || thisLocation.ViaSource)
                                    {
                                        //
                                        // NOTE: Grab the last (most recent) script
                                        //       location from the stack of active
                                        //       script locations that matches the
                                        //       via [source] flag set by the caller.
                                        //
                                        location = thisLocation;
                                        break;
                                    }
                                }
                            }
                        }
                    }

                    return ReturnCode.Ok;
                }
            }
            else
            {
                error = "invalid interpreter";
            }

            return ReturnCode.Error;
        }
        #endregion

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

        #region Application Settings Support Methods
        //
        // WARNING: This method is designed to check the *COMPLETE* list of
        //          system variables that may be set in created interpreters.
        //          If additional variables need to be set during interpreter
        //          creation, they will need to be added here as well.
        //
        private static bool IsDefaultVariableName(
            string name
            )
        {
            lock (syncRoot)
            {
                if (defaultVariableNames == null)
                {
                    defaultVariableNames = new StringDictionary(new string[] {
                        TclVars.ShellArgumentCount,
                        TclVars.ShellArguments,
                        TclVars.ShellArgument0,
                        TclVars.AutoExecutables,
                        TclVars.AutoIndex,
                        TclVars.AutoNoExecute,
                        TclVars.AutoNoLoad,
                        TclVars.AutoOldPath,
                        TclVars.AutoPath,
                        TclVars.AutoSourcePath,
                        Vars.Platform.Name,
                        Vars.Paths,
                        Vars.Tests,
                        TclVars.Environment,
                        TclVars.ErrorCode,
                        TclVars.ErrorInfo,
                        Vars.Null,
                        TclVars.Interactive,
                        TclVars.InteractiveLoops,
                        TclVars.Library,
                        TclVars.LibraryPath,
                        TclVars.NonWordCharacters,
                        TclVars.PatchLevelName,
                        TclVars.PackagePath,
                        TclVars.Platform.Name,
                        TclVars.PrecisionName,
                        TclVars.Prompt1,
                        TclVars.Prompt2,
                        TclVars.RunCommandsFileName,
                        TclVars.RunCommandsResourceName,
                        TclVars.TraceCompile,
                        TclVars.TraceExecute,
                        TclVars.VersionName,
                        TclVars.WordCharacters
                    }, true, false);
                }

                return (name != null) &&
                    defaultVariableNames.ContainsKey(name);
            }
        }

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

        public static ReturnCode LoadSettingsViaFile(
            string fileName,               /* in */
            bool safe,                     /* in */
            ref StringDictionary settings, /* in, out */
            ref Result error               /* out */
            )
        {
            ReturnCode code = ReturnCode.Ok;
            Result result = null;

            try
            {
                using (Interpreter interpreter = Interpreter.Create(
                        null, safe ? CreateFlags.SafeSettingsUse :
                        CreateFlags.SettingsUse, ref result))
                {
                    if (interpreter == null)
                    {
                        error = result;
                        code = ReturnCode.Error;

                        return code;
                    }

                    code = interpreter.EvaluateFile(fileName, ref result);

                    if (code != ReturnCode.Ok)
                    {
                        error = result;
                        return code;
                    }

                    lock (interpreter.SyncRoot) /* TRANSACTIONAL */
                    {
                        ICallFrame frame = interpreter.GlobalFrame;

                        if (frame == null)
                        {
                            error = "invalid call frame";
                            code = ReturnCode.Error;

                            return code;
                        }

                        VariableDictionary variables = frame.Variables;

                        if (variables == null)
                        {
                            error = "call frame does not support variables";
                            code = ReturnCode.Error;

                            return code;
                        }

                        //
                        // NOTE: If the caller specified some settings to be
                        //       loaded, use that list verbatim; otherwise,
                        //       add settings based on the global variables
                        //       now present in the interpreter that were NOT
                        //       put there during the interpreter creation
                        //       process.
                        //
                        if (settings == null)
                            settings = new StringDictionary();

                        if (settings.Count > 0)
                        {
                            foreach (KeyValuePair<string, string> pair in settings)
                            {
                                IVariable variable;

                                if (variables.TryGetValue(pair.Key, out variable) &&
                                    (variable != null))
                                {
                                    //
                                    // NOTE: A setting with this name may or may
                                    //       not already exist in the dictionary
                                    //       provided by the caller; therefore,
                                    //       add or update the setting.
                                    //
                                    ElementDictionary arrayValue = null;

                                    if (EntityOps.IsArray(variable, ref arrayValue))
                                    {
                                        foreach (KeyValuePair<string, object> pair2 in
                                                arrayValue)
                                        {
                                            string key = FormatOps.SettingKey(
                                                variable, arrayValue,  pair2.Key);

                                            settings[key] = StringOps.GetStringFromObject(
                                                pair2.Value);
                                        }
                                    }
                                    else
                                    {
                                        settings[pair.Key] = StringOps.GetStringFromObject(
                                            variable.Value);
                                    }
                                }
                            }
                        }
                        else
                        {
                            foreach (KeyValuePair<string, IVariable> pair in variables)
                            {
                                if (!IsDefaultVariableName(pair.Key))
                                {
                                    IVariable variable = pair.Value;

                                    if (variable != null)
                                    {
                                        //
                                        // NOTE: A setting with this name may or may
                                        //       not already exist in the dictionary
                                        //       provided by the caller; therefore,
                                        //       add or update the setting.
                                        //
                                        ElementDictionary arrayValue = null;

                                        if (EntityOps.IsArray(variable, ref arrayValue))
                                        {
                                            foreach (KeyValuePair<string, object> pair2 in
                                                    arrayValue)
                                            {
                                                string key = FormatOps.SettingKey(
                                                    variable, arrayValue, pair2.Key);

                                                settings[key] = StringOps.GetStringFromObject(
                                                    pair2.Value);
                                            }
                                        }
                                        else
                                        {
                                            settings[pair.Key] = StringOps.GetStringFromObject(
                                                variable.Value);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            catch (Exception e)
            {
                error = e;
                code = ReturnCode.Error;
            }
            finally
            {
                TraceOps.DebugTrace(String.Format(
                    "{0}: LoadSettingsViaFile: fileName = {1}, safe = {2}, " +
                    "settings = {3}, code = {4}, result = {5}, " +
                    "error = {6}{7}", GlobalState.GetCurrentThreadId(),
                    FormatOps.WrapOrNull(fileName), safe,
                    FormatOps.KeysAndValues(settings, true), code,
                    FormatOps.WrapOrNull(result), FormatOps.WrapOrNull(error),
                    Environment.NewLine), typeof(ScriptOps).Name);
            }

            return code;
        }
        #endregion

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

        #region Library Support Methods
        private static ReturnCode GetFile(
            Interpreter interpreter,
            string directory,
            string name,
            ref ScriptFlags flags,
            ref Result result
            )
        {
            //
            // NOTE: Check for the script on disk in the
            //       specified directory.
            //
            string fileName = PathOps.CombinePath(
                null, directory, name);

            if (!String.IsNullOrEmpty(fileName) &&
                File.Exists(fileName))
            {
                flags |= ScriptFlags.File;
                result = fileName;

                return ReturnCode.Ok;
            }
            else if (!FlagOps.HasFlags(flags, ScriptFlags.NoAutoPath, true))
            {
                //
                // NOTE: Check for the script on disk in the
                //       directories listed in the "auto_path".
                //
                StringList autoPathList = GlobalState.GetAutoPathList(
                    interpreter, false);

                foreach (string path in autoPathList)
                {
                    fileName = PathOps.CombinePath(
                        null, path, name);

                    if (!String.IsNullOrEmpty(fileName) &&
                        File.Exists(fileName))
                    {
                        flags |= ScriptFlags.File;
                        result = fileName;

                        return ReturnCode.Ok;
                    }
                }

                result = String.Format(
                    "cannot find a suitable \"{0}\" script in \"{1}\"",
                    name, autoPathList);
            }
            else
            {
                result = String.Format(
                    "cannot find a suitable \"{0}\" script in \"{1}\"",
                    name, fileName);
            }

            return ReturnCode.Error;
        }

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

        public static ReturnCode GetLibrary(
            Interpreter interpreter,
            IEngineHost engineHost,
            string name,
            ref ScriptFlags flags,
            ref Result result
            )
        {
            Result localResult = null;
            ResultList errors = new ResultList();

            //
            // NOTE: Query the primary root directory where the Eagle
            //       core script library files should be found (e.g.
            //       "<dir>\lib\Eagle1.0\init.eagle", etc)?
            //
            string directory = GlobalState.GetLibraryPath(interpreter);

            if (FlagOps.HasFlags(flags, ScriptFlags.PreferFileSystem, true))
            {
                //
                // NOTE: First, look for the script on the native file
                //       system.
                //
                if (GetFile(interpreter, directory, name, ref flags,
                        ref localResult) == ReturnCode.Ok)
                {
                    //
                    // NOTE: Return the value (and flags) provided by
                    //       the file system, verbatim.
                    //
                    result = localResult;

                    return ReturnCode.Ok;
                }
                else
                {
                    errors.Add(localResult);

                    //
                    // NOTE: Next, query the IHost.GetScript method for
                    //       the script.
                    //
                    if (HostOps.GetScript(
                            interpreter, engineHost, name, ref flags,
                            ref localResult) == ReturnCode.Ok)
                    {
                        //
                        // NOTE: Return the value (and flags) provided by
                        //       the host, verbatim.
                        //
                        result = localResult;

                        return ReturnCode.Ok;
                    }
                    else
                    {
                        errors.Add(localResult);
                    }
                }
            }
            else
            {
                //
                // NOTE: First, query the IHost.GetScript method for
                //       the script.
                //
                if (HostOps.GetScript(
                        interpreter, engineHost, name, ref flags,
                        ref localResult) == ReturnCode.Ok)
                {
                    //
                    // NOTE: Return the value (and flags) provided by
                    //       the host, verbatim.
                    //
                    result = localResult;

                    return ReturnCode.Ok;
                }
                else
                {
                    errors.Add(localResult);

                    //
                    // NOTE: Look for the script on the native file
                    //       system.
                    //
                    if (GetFile(interpreter, directory, name,
                            ref flags, ref localResult) == ReturnCode.Ok)
                    {
                        //
                        // NOTE: Return the value (and flags) provided by
                        //       the file system, verbatim.
                        //
                        result = localResult;

                        return ReturnCode.Ok;
                    }
                    else
                    {
                        errors.Add(localResult);
                    }
                }
            }

            result = errors;
            return ReturnCode.Error;
        }

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

        public static ReturnCode GetStartup(
            Interpreter interpreter,
            IEngineHost engineHost,
            string name,
            ref ScriptFlags flags,
            ref Result result,
            ref ResultList errors
            )
        {
            if (errors == null)
                errors = new ResultList();

            Result localResult = null;

            if (HostOps.GetScript(
                    interpreter, engineHost, name, ref flags,
                    ref localResult) == ReturnCode.Ok)
            {
                result = localResult;

                return ReturnCode.Ok;
            }
            else
            {
                errors.Add(localResult);

                if ((interpreter != null) && interpreter.GetVariableValue(
                    VariableFlags.GlobalOnly | VariableFlags.ViaShell,
                    name, ref localResult) == ReturnCode.Ok)
                {
                    if (HostOps.GetScript(
                            interpreter, engineHost, localResult, ref flags,
                            ref localResult) == ReturnCode.Ok)
                    {
                        if (FlagOps.HasFlags(flags, ScriptFlags.File, true) &&
                            !PathOps.IsRemoteUri(localResult))
                        {
                            result = PathOps.ResolveFullPath(interpreter,
                                localResult);
                        }
                        else
                        {
                            result = localResult;
                        }

                        return ReturnCode.Ok;
                    }
                    else
                    {
                        errors.Add(localResult);
                    }
                }
                else
                {
                    errors.Add(localResult);
                }
            }

            return ReturnCode.Error;
        }
        #endregion

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

        #region Ensemble Support Methods
        public static ReturnCode SubCommandFromEnsemble(
            IEnsemble ensemble,         /* in */
            bool strict,                /* in */
            bool noCase,                /* in */
            ref string name,            /* in, out */
            ref Result error            /* out */
            )
        {
            ISubCommand subCommand = null;

            return SubCommandFromEnsemble(
                ensemble, strict, noCase, ref name, ref subCommand, ref error);
        }

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

        private static ReturnCode SubCommandFromEnsemble(
            IEnsemble ensemble,         /* in */
            bool strict,                /* in */
            bool noCase,                /* in */
            ref string name,            /* in, out */
            ref ISubCommand subCommand, /* out */
            ref Result error            /* out */
            )
        {
            //
            // NOTE: *WARNING* Empty sub-command names are allowed, please
            //       do not change this to "!String.IsNullOrEmpty".
            //
            if (name == null)
            {
                error = "invalid sub-command name";
                return ReturnCode.Error;
            }

            if (ensemble == null)
            {
                error = "invalid ensemble";
                return ReturnCode.Error;
            }

            EnsembleDictionary subCommands = ensemble.SubCommands;

            if ((subCommands == null) || (subCommands.Count == 0))
            {
                error = "invalid ensemble";
                return ReturnCode.Error;
            }

            bool exact = false;

            List<KeyValuePair<string, ISubCommand>> matches =
                new List<KeyValuePair<string, ISubCommand>>();

            foreach (KeyValuePair<string, ISubCommand> pair in subCommands)
            {
                if (String.Compare(pair.Key, 0, name, 0, name.Length,
                        noCase ? StringOps.SystemNoCaseStringComparisonType :
                        StringOps.SystemStringComparisonType) == 0)
                {
                    //
                    // NOTE: Was the key valid (this should always succeed).
                    //
                    if (pair.Key != null)
                    {
                        //
                        // NOTE: Did we match the whole string, regardless of
                        //       case?
                        //
                        bool whole = (pair.Key.Length == name.Length);

                        //
                        // NOTE: It was a match; however, was it exact?
                        //
                        if (whole)
                            //
                            // NOTE: Preserve match, it may differ in case.
                            //
                            exact = true;

                        //
                        // NOTE: Was it an exact match or did we match at least
                        //       one character in a partial match?
                        //
                        if (whole || (name.Length > 0))
                        {
                            //
                            // NOTE: Store the exact or partial match in the
                            //       result list.
                            //
                            matches.Add(pair);
                        }
                    }
                }
            }

            //
            // NOTE: If there was an exact match, just use it.
            //
            if (exact)
            {
                //
                // NOTE: No need to change the caller's name variable,
                //       it already contains the correct value; however,
                //       we do need to provide the actual ISubCommand.
                //
                subCommand = matches[0].Value;

                return ReturnCode.Ok;
            }
            else if (matches.Count == 1)
            {
                //
                // NOTE: Normal case, exactly one sub-command matched.
                //
                name = matches[0].Key;
                subCommand = matches[0].Value;

                return ReturnCode.Ok;
            }
            else if (matches.Count > 1)
            {
                error = BadSubCommand(
                    "ambiguous", "option", name, /* ensemble, */ matches,
                    null, null);
            }
            else if (strict)
            {
                error = BadSubCommand(
                    null, "option", name, ensemble, null, null);
            }
            else
            {
                //
                // NOTE: Non-strict mode, leave the original sub-command
                //       unchanged and let the caller deal with it.
                //
                return ReturnCode.Ok;
            }

            return ReturnCode.Error;
        }

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

        public static ReturnCode TryExecuteSubCommandFromEnsemble(
            IEnsemble ensemble,         /* in */
            Interpreter interpreter,    /* in */
            IClientData clientData,     /* in */
            ArgumentList arguments,     /* in */
            bool strict,                /* in */
            bool noCase,                /* in */
            ref string name,            /* in, out */
            ref bool tried,             /* out */
            ref Result result           /* out */
            )
        {
            ISubCommand subCommand = null;

            return TryExecuteSubCommandFromEnsemble(
                ensemble, interpreter, clientData, arguments, strict,
                noCase, ref name, ref subCommand, ref tried, ref result);
        }

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

        private static ReturnCode TryExecuteSubCommandFromEnsemble(
            IEnsemble ensemble,                  /* in */
            Interpreter interpreter,             /* in */
            IClientData clientData,              /* in */
            ArgumentList arguments,              /* in */
            bool strict,                         /* in */
            bool noCase,                         /* in */
            ref string name,                     /* in, out */
            ref ISubCommand subCommand,          /* out */
            ref bool tried,                      /* out */
            ref Result result                    /* out */
            )
        {
            ReturnCode code;

            if (interpreter != null)
            {
                code = SubCommandFromEnsemble(
                    ensemble, strict, noCase, ref name, ref subCommand,
                    ref result);

                if (code == ReturnCode.Ok)
                {
                    if (subCommand != null)
                    {
                        //
                        // NOTE: Indicate that the sub-command has been
                        //       dispatched (i.e. there is no need for
                        //       the caller to handle this sub-command).
                        //       Even if the execution fails, we still
                        //       tried to execute it and the caller
                        //       should not try to handle it.
                        //
                        tried = true;

                        code = interpreter.Execute(
                            name, subCommand, clientData, arguments,
                            ref result);
                    }
                }
            }
            else
            {
                result = "invalid interpreter";
                code = ReturnCode.Error;
            }

            return code;
        }
        #endregion

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

        #region Error Message Support Methods
        public static Exception GetBaseException(
            Exception exception
            )
        {
            if (exception == null)
                return null;

            Exception baseException = exception.GetBaseException();

            if (baseException != null)
                return baseException;

            return exception;
        }

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

        public static Result BadValue(
            string adjective,
            string type,
            string value,
            StringSortedList values,
            string prefix,
            string suffix
            )
        {
            if (values != null)
            {
                return String.Format("{0} {1} \"{2}\": must be {3}{4}",
                    !String.IsNullOrEmpty(adjective) ? adjective : "bad",
                    !String.IsNullOrEmpty(type) ? type : "value", value,
                    GenericOps<string>.ListToEnglish(values, prefix, ", ",
                    !String.IsNullOrEmpty(suffix) ? null : "or "), suffix);
            }

            //
            // FIXME: Fallback here?
            //
            return String.Format(
                "{0} {1} \"{2}\"",
                !String.IsNullOrEmpty(adjective) ? adjective : "bad",
                !String.IsNullOrEmpty(type) ? type : "value", value);
        }

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

        public static Result BadValue(
            string adjective,
            string type,
            string value,
            IEnumerable<string> values,
            string prefix,
            string suffix
            )
        {
            return BadValue(
                adjective, type, value, (values != null) ?
                new StringSortedList(values) : null, prefix, suffix);
        }

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

        public static Result BadSubCommand(
            string adjective,
            string type,
            string subCommand,
            IEnsemble ensemble,
            string prefix,
            string suffix
            )
        {
            if ((ensemble != null) &&
                (ensemble.SubCommands != null))
            {
                return BadValue(
                    !String.IsNullOrEmpty(adjective) ? adjective : "bad",
                    !String.IsNullOrEmpty(type) ? type : "option", subCommand,
                    ensemble.SubCommands.Keys, prefix, suffix);
            }

            //
            // FIXME: Fallback here?
            //
            return String.Format("{0} {1} \"{2}\"",
                !String.IsNullOrEmpty(adjective) ? adjective : "bad",
                !String.IsNullOrEmpty(type) ? type : "option", subCommand);
        }

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

        private static Result BadSubCommand(
            string adjective,
            string type,
            string subCommand,
            IEnumerable<KeyValuePair<string, ISubCommand>> subCommands,
            string prefix,
            string suffix
            )
        {
            return BadValue(adjective, type, subCommand,
                (subCommands != null) ? new StringSortedList(subCommands) :
                null, prefix, suffix);
        }

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

        public static Result WrongNumberOfArguments(
            IIdentifier identifier,
            int count,
            ArgumentList arguments,
            string suffix
            )
        {
            if ((count > 0) &&
                (arguments != null) &&
                (arguments.Count > 0))
            {
                return String.Format(
                    "wrong # args: should be \"{0}{1}{2}\"",
                    ArgumentList.GetRange(arguments, 0, Math.Min(count - 1,
                        arguments.Count - 1)), !String.IsNullOrEmpty(
                    suffix) ? Characters.Space.ToString() : null, suffix);
            }

            //
            // FIXME: Fallback here?
            //
            return "wrong # args";
        }
        #endregion

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

        #region Output Support Methods
        public static ReturnCode WriteViaIExecute(
            Interpreter interpreter,
            string commandName, /* NOTE: Almost always null, for [puts]. */
            string channelId,   /* NOTE: Almost always null, for "stdout". */
            string value,
            ref Result result
            )
        {
            if (interpreter == null)
            {
                result = "invalid interpreter";
                return ReturnCode.Error;
            }

            if (commandName == null)
                commandName = typeof(_Commands.Puts).Name.ToLowerInvariant();

            if (channelId == null)
                channelId = StandardChannel.Output;

            ReturnCode code;
            IExecute execute = null;

            code = interpreter.GetIExecuteViaResolvers(
                interpreter.GetResolveEngineFlags(true), commandName,
                null, LookupFlags.Default, ref execute, ref result);

            if (code != ReturnCode.Ok)
                return code;

            code = Engine.ExternalExecuteWithFrame(
                commandName, execute, interpreter, null, new ArgumentList(
                    commandName, channelId, value), interpreter.EngineFlags,
                interpreter.SubstitutionFlags, interpreter.EngineEventFlags,
                interpreter.ExpressionFlags, ref result);

            return code;
        }
        #endregion

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

        #region Variable Support Methods
        #region Variable Tracing Support Methods
        public static object GetDefaultValue(
            BreakpointType breakpointType
            )
        {
            switch (breakpointType)
            {
                case BreakpointType.BeforeVariableGet:
                    return DefaultGetVariableValue;
                case BreakpointType.BeforeVariableSet:
                    return DefaultSetVariableValue;
                case BreakpointType.BeforeVariableUnset:
                    return DefaultUnsetVariableValue;
            }

            return DefaultVariableValue;
        }

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

        public static bool IsWriteValueTrace(
            BreakpointType breakpointType
            )
        {
            return (breakpointType == BreakpointType.BeforeVariableSet) ||
                (breakpointType == BreakpointType.BeforeVariableReset) ||
                (breakpointType == BreakpointType.BeforeVariableUnset);
        }

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

        public static bool NeedArrayValueForTrace(
            BreakpointType breakpointType,
            bool old
            )
        {
            return old ?
                ((breakpointType == BreakpointType.BeforeVariableReset) ||
                    (breakpointType == BreakpointType.BeforeVariableUnset)) :
                (breakpointType == BreakpointType.BeforeVariableSet);
        }

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

        public static void GatherTraceValues(
            string varIndex,
            object value,
            ElementDictionary arrayValue,
            ref StringList values
            )
        {
            if ((varIndex == null) && (value == null) && (arrayValue == null))
                return;

            if (varIndex != null)
            {
                if (values == null)
                    values = new StringList();

                values.Add(varIndex);
            }

            if (value != null)
            {
                if (values == null)
                    values = new StringList();

                values.Add(StringOps.GetStringFromObject(value));
            }

            if ((arrayValue != null) && (arrayValue.Count > 0))
            {
                if (values == null)
                    values = new StringList();

                values.Add(arrayValue.Keys);

                foreach (KeyValuePair<string, object> pair in arrayValue)
                {
                    if (pair.Value != null)
                        values.Add(StringOps.GetStringFromObject(pair.Value));
                }
            }
        }

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

        public static void AddOldValuesToTraceInfo(
            Interpreter interpreter,
            ITraceInfo traceInfo,
            ElementDictionary oldValues
            )
        {
            if ((traceInfo == null) || (oldValues == null))
                return;

            ElementDictionary localOldValues = traceInfo.OldValues;

            if (localOldValues != null)
            {
                localOldValues.Add(oldValues);
            }
            else
            {
                EventWaitHandle variableEvent = (interpreter != null) ?
                    interpreter.VariableEvent : null;

                localOldValues = new ElementDictionary(variableEvent);
                localOldValues.Add(oldValues);

                traceInfo.OldValues = localOldValues;
            }
        }

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

        public static ITrace NewCoreTrace(
            TraceCallback callback,
            IClientData clientData,
            TraceFlags traceFlags,
            IPlugin plugin,
            ref Result error
            )
        {
            if (callback != null)
            {
                MethodInfo methodInfo = callback.Method;

                if (methodInfo != null)
                {
                    Type type = methodInfo.DeclaringType;

                    if (type != null)
                    {
                        _Traces.Core trace = new _Traces.Core(new TraceData(
                            FormatOps.TraceDelegateName(callback), null, null,
                            clientData, type.FullName, methodInfo.Name,
                            RuntimeOps.DelegateBindingFlags,
                            AttributeOps.GetMethodFlags(methodInfo),
                            traceFlags, plugin, 0));

                        trace.Callback = callback;
                        return trace;
                    }
                    else
                    {
                        error = "invalid trace callback method type";
                    }
                }
                else
                {
                    error = "invalid trace callback method";
                }
            }
            else
            {
                error = "invalid trace callback";
            }

            return null;
        }

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

        public static ITraceInfo NewTraceInfo(
            Interpreter interpreter,
            ITrace trace,
            BreakpointType breakpointType,
            ICallFrame frame,
            IVariable variable,
            string name,
            string index,
            VariableFlags flags,
            object oldValue,
            object newValue,
            ElementDictionary oldValues,
            ElementDictionary newValues,
            StringList list,
            bool force,
            bool cancel,
            bool postProcess,
            ReturnCode returnCode
            )
        {
            //
            // HACK: This method is used to prevent creating a ton of redundant
            //       TraceInfo objects on the heap (i.e. whenever a variable is
            //       read, set, or unset).  Now, there is one TraceInfo object
            //       per-thread and it will be re-used as necessary.
            //
            ITraceInfo traceInfo;

            if (!force && (interpreter != null))
            {
                traceInfo = interpreter.TraceInfo;

                if (traceInfo != null)
                {
                    traceInfo = traceInfo.Update(
                       trace, breakpointType, frame, variable, name, index,
                       flags, oldValue, newValue, oldValues, newValues, list,
                       cancel, postProcess, returnCode);

                    if (traceInfo != null)
                        return traceInfo;
                }
            }

            traceInfo = new TraceInfo(
                trace, breakpointType, frame, variable, name, index,
                flags, oldValue, newValue, oldValues, newValues, list,
                cancel, postProcess, returnCode);

            if (!force && (interpreter != null))
                interpreter.TraceInfo = traceInfo;

            return traceInfo;
        }

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

        //
        // WARNING: Do NOT call this method from "get" operation traces.  This
        //          method is ONLY for use by variable operations that cannot
        //          return a value (e.g. "set", "unset", "reset", "add").
        //
        public static ReturnCode FireTraces(
            IVariable variable,
            BreakpointType breakpointType,
            Interpreter interpreter,
            ITraceInfo traceInfo,
            ref Result error
            )
        {
            Result value = null;

            return FireTraces(variable, breakpointType, interpreter,
                traceInfo, ref value, ref error);
        }

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

        public static ReturnCode FireTraces(
            IVariable variable,
            BreakpointType breakpointType,
            Interpreter interpreter,
            ITraceInfo traceInfo,
            ref Result value,
            ref Result error
            )
        {
            if (variable == null)
            {
                error = "invalid variable";
                return ReturnCode.Error;
            }

            if (traceInfo == null)
            {
                error = "invalid trace";
                return ReturnCode.Error;
            }

            //
            // NOTE: Save the original return code.  We will need it
            //       later to figure out how to process the trace
            //       callback results.
            //
            ReturnCode localCode = traceInfo.ReturnCode;

            //
            // NOTE: Start off with the original old value from the trace
            //       information object as the local result.  This value
            //       may be overwritten via the fired traces if necessary.
            //
            Result localResult = StringOps.GetStringFromObject(
                traceInfo.OldValue);

            //
            // NOTE: Attempt to fire the traces for the variable, if any.
            //
            if (variable.FireTraces(
                    breakpointType, interpreter, traceInfo,
                    ref localResult) == ReturnCode.Ok)
            {
                //
                // HACK: For "get" traces, we need a little bit more magic
                //       here.
                //
                if (breakpointType == BreakpointType.BeforeVariableGet)
                {
                    //
                    // NOTE: Did a trace callback cancel processing of a
                    //       variable operation that was previously regarded
                    //       as unsuccessful?
                    //
                    if ((localCode != ReturnCode.Ok) && traceInfo.Cancel)
                    {
                        //
                        // NOTE: This was a failed "get" operation; however,
                        //       it has been canceled by a trace callback
                        //       (presumably after taking some more meaningful
                        //       action) and is now considered to be successful;
                        //       therefore, place the trace result into the
                        //       OldValue property of the trace object itself,
                        //       if necessary (i.e. it is still null).  Also,
                        //       this relies upon the old value being an actual
                        //       string, not a Result object.
                        //
                        if (traceInfo.OldValue == null)
                        {
                            traceInfo.OldValue = (localResult != null) ?
                                localResult.Value : null;
                        }
                    }
                    else if ((localCode == ReturnCode.Ok) && traceInfo.Cancel)
                    {
                        //
                        // NOTE: This was a successful "get" operation; however,
                        //       it has now been canceled and the OldValue
                        //       property of the trace object will not be used.
                        //       Set the trace result for the caller to grab.
                        //
                        value = localResult;
                    }
                }

                return ReturnCode.Ok;
            }
            else
            {
                //
                // NOTE: Give the caller the error from the trace callback.
                //
                error = localResult;
            }

            return ReturnCode.Error;
        }

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

        #region Linked Variable Support Methods
        public static void GetLinkedVariableMemberValue(
            Interpreter interpreter,
            object value,
            ref object memberValue
            )
        {
            ReturnCode code;
            MemberInfo memberInfo = null;
            Type type = null;
            object @object = null;
            Result error = null;

            code = GetLinkedVariableMemberAndValue(
                BreakpointType.None, null, null, null, value,
                ref memberInfo, ref type, ref @object,
                ref memberValue, ref error);

            if (code != ReturnCode.Ok)
                DebugOps.Complain(interpreter, code, error);
        }

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

        public static ReturnCode GetLinkedVariableMemberAndValue(
            BreakpointType breakpointType,
            IVariable variable,
            string name,
            string index,
            object value,
            ref MemberInfo memberInfo,
            ref Type type,
            ref object @object,
            ref object memberValue,
            ref Result error
            )
        {
            IAnyPair<MemberInfo, object> anyPair =
                value as IAnyPair<MemberInfo, object>;

            if (anyPair == null)
            {
                error = String.Format(
                    "can't {0} {1}: broken link",
                    FormatOps.Breakpoint(breakpointType),
                    FormatOps.ErrorVariableName(
                        variable, null, name, index));

                return ReturnCode.Error;
            }

            memberInfo = anyPair.X;

            if (memberInfo == null)
            {
                error = String.Format(
                    "can't {0} {1}: missing member",
                    FormatOps.Breakpoint(breakpointType),
                    FormatOps.ErrorVariableName(
                        variable, null, name, index));

                return ReturnCode.Error;
            }

            if (memberInfo is FieldInfo)
            {
                FieldInfo fieldInfo = (FieldInfo)memberInfo;

                type = fieldInfo.FieldType;

                if (type == null)
                {
                    error = String.Format(
                        "can't {0} {1}: missing field type",
                        FormatOps.Breakpoint(breakpointType),
                        FormatOps.ErrorVariableName(
                            variable, null, name, index));

                    return ReturnCode.Error;
                }

                @object = anyPair.Y;

                try
                {
                    memberValue = fieldInfo.GetValue(@object); /* throw */
                    return ReturnCode.Ok;
                }
                catch (Exception e)
                {
                    error = e;
                    return ReturnCode.Error;
                }
            }
            else if (memberInfo is PropertyInfo)
            {
                PropertyInfo propertyInfo = (PropertyInfo)memberInfo;

                type = propertyInfo.PropertyType;

                if (type == null)
                {
                    error = String.Format(
                        "can't {0} {1}: missing property type",
                        FormatOps.Breakpoint(breakpointType),
                        FormatOps.ErrorVariableName(
                            variable, null, name, index));

                    return ReturnCode.Error;
                }

                @object = anyPair.Y;

                try
                {
                    //
                    // BUGBUG: Only non-indexed properties are currently
                    //         supported.
                    //
                    memberValue = propertyInfo.GetValue(@object, null); /* throw */
                    return ReturnCode.Ok;
                }
                catch (Exception e)
                {
                    error = e;
                    return ReturnCode.Error;
                }
            }
            else
            {
                error = String.Format(
                    "can't {0} {1}: member must be field or property",
                    FormatOps.Breakpoint(breakpointType),
                    FormatOps.ErrorVariableName(
                        variable, null, name, index));

                return ReturnCode.Error;
            }
        }

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

        public static void GetLinkedVariableArrayValues(
            Interpreter interpreter,
            ElementDictionary arrayValue,
            ref ElementDictionary values
            )
        {
            if (arrayValue == null)
                return;

            EventWaitHandle variableEvent = (interpreter != null) ?
                interpreter.VariableEvent : null;

            ElementDictionary localValues = new ElementDictionary(
                variableEvent);

            foreach (KeyValuePair<string, object> pair in arrayValue)
            {
                object memberValue = null;

                GetLinkedVariableMemberValue(
                    interpreter, pair.Value, ref memberValue);

                if (memberValue == null)
                    continue;

                localValues.Add(pair.Key, memberValue);
            }

            if (localValues.Count > 0)
            {
                if (values == null)
                    values = new ElementDictionary(variableEvent);

                values.Add(localValues);
            }
        }

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

        public static ReturnCode SetLinkedVariableMemberValue(
            BreakpointType breakpointType,
            IVariable variable,
            string name,
            string index,
            MemberInfo memberInfo,
            object @object,
            object memberValue,
            ref Result error
            )
        {
            if (memberInfo == null)
            {
                error = String.Format(
                    "can't {0} {1}: missing member",
                    FormatOps.Breakpoint(breakpointType),
                    FormatOps.ErrorVariableName(
                        variable, null, name, index));

                return ReturnCode.Error;
            }

            if (memberInfo is FieldInfo)
            {
                FieldInfo fieldInfo = (FieldInfo)memberInfo;

                try
                {
                    fieldInfo.SetValue(@object, memberValue); /* throw */
                    return ReturnCode.Ok;
                }
                catch (Exception e)
                {
                    error = e;
                    return ReturnCode.Error;
                }
            }
            else if (memberInfo is PropertyInfo)
            {
                PropertyInfo propertyInfo = (PropertyInfo)memberInfo;

                try
                {
                    //
                    // BUGBUG: Only non-indexed properties are currently supported.
                    //
                    propertyInfo.SetValue(@object, memberValue, null); /* throw */
                    return ReturnCode.Ok;
                }
                catch (Exception e)
                {
                    error = e;
                    return ReturnCode.Error;
                }
            }
            else
            {
                error = String.Format(
                    "can't {0} {1}: member must be field or property",
                    FormatOps.Breakpoint(breakpointType),
                    FormatOps.ErrorVariableName(
                        variable, null, name, index));

                return ReturnCode.Error;
            }
        }
        #endregion
        #endregion

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

        #region Variable Name Support Methods
        public static ReturnCode SplitVariableName(
            Interpreter interpreter,
            VariableFlags flags,
            string name,
            ref string varName,
            ref string varIndex,
            ref Result error
            )
        {
            if (name != null)
            {
                if (name.Length > 0)
                {
                    if (FlagOps.HasFlags(flags, VariableFlags.NoSplit, true))
                    {
                        //
                        // HACK: Skip parsing, use the supplied name verbatim.
                        //
                        varName = name;
                        varIndex = null;

                        return ReturnCode.Ok;
                    }
                    else
                    {
                        string localVarName = null;
                        string localVarIndex = null;

                        if (Parser.SplitVariableName(name, ref localVarName,
                                ref localVarIndex, ref error) == ReturnCode.Ok)
                        {
                            if (localVarIndex != null)
                            {
                                if (!FlagOps.HasFlags(flags,
                                        VariableFlags.NoElement, true))
                                {
                                    varName = localVarName;
                                    varIndex = localVarIndex;

                                    return ReturnCode.Ok;
                                }
                                else
                                {
                                    error = "name refers to an element in an array";
                                }
                            }
                            else
                            {
                                //
                                // BUGFIX: Use the supplied name verbatim.
                                //
                                varName = name;
                                varIndex = null;

                                return ReturnCode.Ok;
                            }
                        }
                    }
                }
                else
                {
                    varName = String.Empty;
                    varIndex = null;

                    return ReturnCode.Ok;
                }
            }
            else
            {
                error = "invalid variable name";
            }

            return ReturnCode.Error;
        }
        #endregion

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

        #region Variable Frame Support Methods
        public static ReturnCode LinkVariable(
            Interpreter interpreter,
            ICallFrame localFrame,
            string localName,
            ICallFrame otherFrame,
            string otherName,
            ref Result error
            )
        {
            if (interpreter == null)
            {
                error = "invalid interpreter";
                return ReturnCode.Error;
            }

            lock (interpreter.SyncRoot) /* TRANSACTIONAL */
            {
                if (localFrame == null)
                {
                    error = "invalid \"local\" call frame";
                    return ReturnCode.Error;
                }

                if (otherFrame == null)
                {
                    error = "invalid \"other\" call frame";
                    return ReturnCode.Error;
                }

                //
                // NOTE: *WARNING* Empty variable names are allowed, please do
                //       not change these to "!String.IsNullOrEmpty".
                //
                if (localName == null)
                {
                    error = "invalid \"local\" variable name";
                    return ReturnCode.Error;
                }

                if (otherName == null)
                {
                    error = "invalid \"other\" variable name";
                    return ReturnCode.Error;
                }

                string localVarName = null;
                string localVarIndex = null;

                if (SplitVariableName(
                        interpreter, VariableFlags.NoElement, localName,
                        ref localVarName, ref localVarIndex,
                        ref error) != ReturnCode.Ok)
                {
                    return ReturnCode.Error;
                }

                string otherVarName = null;
                string otherVarIndex = null;

                //
                // BUGFIX: Allow the other side of the link to be an array
                //         element.
                //
                if (SplitVariableName(
                        interpreter, VariableFlags.None, otherName,
                        ref otherVarName, ref otherVarIndex,
                        ref error) != ReturnCode.Ok)
                {
                    return ReturnCode.Error;
                }

                //
                // NOTE: Is the interpreter running with namespaces enabled?
                //       If so, extra steps must be taken later.
                //
                bool useNamespaces = interpreter.AreNamespacesEnabled();

                //
                // NOTE: *NAMESPACES* Need to make sure the correct frame is
                //       being used if the local frame is marked to use the
                //       associated namespace.
                //
                localFrame = CallFrameOps.FollowNext(localFrame);

                if (useNamespaces && CallFrameOps.IsUseNamespace(localFrame))
                {
                    INamespace localNamespace = NamespaceOps.GetCurrent(
                        interpreter, localFrame);

                    if ((localNamespace != null) &&
                        !NamespaceOps.IsGlobal(interpreter, localNamespace))
                    {
                        localFrame = localNamespace.VariableFrame;
                    }
                }

                string newLocalVarName = useNamespaces ?
                    NamespaceOps.MakeRelativeName(
                        interpreter, localFrame, localVarName) :
                    ScriptOps.MakeVariableName(localVarName);

                //
                // NOTE: *NAMESPACES* Need to make sure the correct frame is
                //       being used if the other frame is marked to use the
                //       associated namespace.
                //
                otherFrame = CallFrameOps.FollowNext(otherFrame);

                if (useNamespaces && CallFrameOps.IsUseNamespace(otherFrame))
                {
                    INamespace otherNamespace = NamespaceOps.GetCurrent(
                        interpreter, otherFrame);

                    if ((otherNamespace != null) &&
                        !NamespaceOps.IsGlobal(interpreter, otherNamespace))
                    {
                        otherFrame = otherNamespace.VariableFrame;
                    }
                }

                string newOtherVarName = useNamespaces ?
                    NamespaceOps.MakeRelativeName(
                        interpreter, otherFrame, otherVarName) :
                    ScriptOps.MakeVariableName(otherVarName);

                if (CallFrameOps.IsSame(
                        interpreter, localFrame, otherFrame, newLocalVarName,
                        newOtherVarName))
                {
                    error = "can't upvar from variable to itself";
                    return ReturnCode.Error;
                }

                //
                // NOTE: After this point, both the local and other variable
                //       names must be stripped of their qualifiers (i.e. if
                //       they were qualified to begin with).
                //
                if (useNamespaces)
                {
                    newLocalVarName = NamespaceOps.TailOnly(newLocalVarName);
                    newOtherVarName = NamespaceOps.TailOnly(newOtherVarName);
                }

                VariableDictionary localVariables = localFrame.Variables;

                if (localVariables == null)
                {
                    error = "local call frame does not support variables";
                    return ReturnCode.Error;
                }

                IVariable localVariable = null;

                if (interpreter.GetVariableViaResolversWithSplit(
                        localFrame, localVarName /* FULL NAME*/,
                        ref localVariable) == ReturnCode.Ok)
                {
                    //
                    // NOTE: If the local variable has been flagged as undefined
                    //       then go ahead and allow them to use it (it was not
                    //       purged?).
                    //
                    if (!EntityOps.IsUndefined(localVariable))
                    {
                        //
                        // BUGFIX: If the local variable is a link then go
                        //         ahead and allow them to use it.  We do
                        //         this for Tcl compatibility, which allows
                        //         for this "re-targeting" of variable links
                        //         to a different variable.
                        //
                        if (!EntityOps.IsLink(localVariable))
                        {
                            error = String.Format(
                                "variable \"{0}\" already exists",
                                localVarName /* FULL NAME */);

                            return ReturnCode.Error;
                        }
                    }
                }

                VariableDictionary otherVariables = otherFrame.Variables;
                IVariable otherVariable = null;

                if (interpreter.GetVariableViaResolversWithSplit(
                        otherFrame, otherVarName /* FULL NAME*/,
                        ref otherVariable) == ReturnCode.Ok)
                {
                    if (EntityOps.IsLink(otherVariable))
                    {
                        otherVariable = EntityOps.FollowLinks(
                            otherVariable, VariableFlags.None);
                    }

                    //
                    // NOTE: Make double sure now that we are not trying to
                    //       create a link to ourselves.
                    //
                    if ((localVariable != null) &&
                        Object.ReferenceEquals(otherVariable, localVariable))
                    {
                        error = "can't upvar from variable to itself";
                        return ReturnCode.Error;
                    }
                }
                else if (otherVariables != null)
                {
                    if (otherVariables.ContainsKey(newOtherVarName))
                    {
                        //
                        // BUGBUG: Really, this can only happen if the variable
                        //         resolver lies to us (i.e. it does not return
                        //         the variable when asked yet if appears to be
                        //         present in the target call frame).
                        //
                        error = String.Format(
                            "other variable \"{0}\" already exists",
                            otherVarName /* FULL NAME */);

                        return ReturnCode.Error;
                    }

                    bool isGlobalCallFrame = interpreter.IsGlobalCallFrame(
                        otherFrame);

                    otherVariable = new Variable(
                        otherFrame, newOtherVarName,
                        interpreter.GetNewVariableFlags(isGlobalCallFrame),
                        interpreter.GetTraces(null),
                        interpreter.VariableEvent);

                    if (isGlobalCallFrame)
                        EntityOps.SetGlobal(otherVariable, true);
                    else
                        EntityOps.SetLocal(otherVariable, true);

                    EntityOps.SetUndefined(otherVariable, true);

                    otherVariables.Add(newOtherVarName, otherVariable);
                }
                else
                {
                    error = "other call frame does not support variables";
                    return ReturnCode.Error;
                }

                EventWaitHandle variableEvent = interpreter.VariableEvent;

                if (localVariable != null)
                {
                    localVariable.Reset(variableEvent);
                    localVariable.Link = otherVariable;
                    localVariable.LinkIndex = otherVarIndex;
                }
                else
                {
                    localVariable = new Variable( /* EXEMPT */
                        localFrame, newLocalVarName, otherVariable,
                        otherVarIndex, variableEvent);
                }

                //
                // NOTE: Make sure to flag the local variable as a link to the
                //       real one.
                //
                EntityOps.SetLink(localVariable, true);

                //
                // NOTE: If we get to this point and the local variable exists
                //       in the call frame, it should be replaced; otherwise,
                //       it should be added.
                //
                localVariables[newLocalVarName] = localVariable;

                //
                // BUGFIX: Mark the variable as "dirty" AFTER the actual
                //         modifications have been completed.
                //
                EntityOps.SetDirty(localVariable, true);

                //
                // NOTE: If we get this far, we have succeeded.
                //
                return ReturnCode.Ok;
            }
        }
        #endregion

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

        #region Variable "Safe" Support Methods
        public static bool IsSafeVariableName(
            string name
            )
        {
            lock (syncRoot)
            {
                //
                // WARNING: This list MUST be kept synchronized with the
                //          variable setup code in the Interpreter.Setup
                //          and Interpreter.SetupPlatform methods.
                //
                if (safeVariableNames == null) /* ONCE */
                {
                    safeVariableNames = new StringDictionary(new string[] {
                        Vars.Null,
                        Vars.Platform.Name,
                        TclVars.Interactive,
                        TclVars.PatchLevelName,
                        TclVars.Platform.Name,
                        TclVars.VersionName
                    }, true, false);
                }

                return (name != null) &&
                    safeVariableNames.ContainsKey(name);
            }
        }

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

        public static bool IsSafeTclPlatformElementName(
            string name
            )
        {
            lock (syncRoot)
            {
                //
                // WARNING: This list MUST be kept synchronized with the
                //          variable setup code in the Interpreter.Setup
                //          and Interpreter.SetupPlatform methods.
                //
                if (safeTclPlatformElementNames == null) /* ONCE */
                {
                    safeTclPlatformElementNames = new StringDictionary(
                        new string[] {
                        TclVars.Platform.ByteOrder,
                        TclVars.Platform.CharacterSize,
                        TclVars.Platform.Debug,
                        TclVars.Platform.DirectorySeparator,
                        TclVars.Platform.Engine,
                        TclVars.Platform.PatchLevel,
                        TclVars.Platform.PathSeparator,
                        TclVars.Platform.PlatformName,
                        TclVars.Platform.PointerSize,
                        TclVars.Platform.Threaded,
                        TclVars.Platform.Unicode,
                        TclVars.Platform.Version,
                        TclVars.Platform.WordSize
                    }, true, false);
                }

                return (name != null) &&
                    safeTclPlatformElementNames.ContainsKey(name);
            }
        }

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

        public static bool IsSafeEaglePlatformElementName(
            string name
            )
        {
            lock (syncRoot)
            {
                //
                // WARNING: This list MUST be kept synchronized with the
                //          variable setup code in the Interpreter.Setup
                //          and Interpreter.SetupPlatform methods.
                //
                if (safeEaglePlatformElementNames == null) /* ONCE */
                {
                    safeEaglePlatformElementNames = new StringDictionary(
                        new string[] {
                        Vars.Platform.Configuration,
                        Vars.Platform.Engine,
                        Vars.Platform.PatchLevel,
                        Vars.Platform.Version
                    }, true, false);
                }

                return (name != null) &&
                    safeEaglePlatformElementNames.ContainsKey(name);
            }
        }
        #endregion
        #endregion

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

        #region IExecute/IVariable Naming Support Methods
        public static string MakeCommandName(
            string name
            )
        {
            return NamespaceOps.TrimLeading(name);
        }

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

        public static string MakeVariableName(
            string name
            )
        {
            return NamespaceOps.TrimLeading(name);
        }
        #endregion

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

        #region Core Command Support Methods
        public static ReturnCode EachLoopCommand(
            IIdentifier identifier,  /* in */
            bool collect,            /* in */
            Interpreter interpreter, /* in */
            IClientData clientData,  /* in */
            ArgumentList arguments,  /* in */
            ref Result result        /* out */
            )
        {
            if (interpreter == null)
            {
                result = "invalid interpreter";
                return ReturnCode.Error;
            }

            if (arguments == null)
            {
                result = "invalid argument list";
                return ReturnCode.Error;
            }

            string commandName = (identifier != null) ?
                identifier.Name : "foreach";

            if ((arguments.Count < 4) || ((arguments.Count % 2) != 0))
            {
                result = String.Format(
                    "wrong # args: should be \"{0} varList list ?varList list ...? script\"",
                    commandName);

                return ReturnCode.Error;
            }

            ReturnCode code = ReturnCode.Ok;
            int numLists = ((arguments.Count - 2) / 2);
            List<StringList> variableLists = new List<StringList>();
            List<StringList> valueLists = new List<StringList>();
            IntList valueIndexes = new IntList();
            StringList resultList = collect ? new StringList() : null;
            int maximumIterations = 0;

            for (int listIndex = 0; listIndex < numLists; listIndex++)
            {
                int argumentIndex = 1 + (listIndex * 2);
                StringList variableList = null;

                code = Parser.SplitList(
                    interpreter, arguments[argumentIndex], 0,
                    Length.Invalid, true, ref variableList,
                    ref result);

                if (code != ReturnCode.Ok)
                    goto done;

                if (variableList.Count < 1)
                {
                    result = String.Format(
                        "{0} varlist is empty",
                        commandName);

                    code = ReturnCode.Error;
                    goto done;
                }

                variableLists.Add(variableList);
                argumentIndex = 2 + (listIndex * 2);

                StringList valueList = null;

                code = Parser.SplitList(
                    interpreter, arguments[argumentIndex], 0,
                    Length.Invalid, true, ref valueList,
                    ref result);

                if (code != ReturnCode.Ok)
                    goto done;

                valueLists.Add(valueList);
                valueIndexes.Add(0);

                int iterations = valueList.Count / variableList.Count;

                if ((valueList.Count % variableList.Count) != 0)
                    iterations++;

                if (iterations > maximumIterations)
                    maximumIterations = iterations;
            }

            string body = arguments[arguments.Count - 1];
            IScriptLocation location = arguments[arguments.Count - 1];

            for (int iteration = 0; iteration < maximumIterations; iteration++)
            {
                for (int listIndex = 0; listIndex < numLists; listIndex++)
                {
                    for (int variableIndex = 0;
                            variableIndex < variableLists[listIndex].Count;
                            variableIndex++)
                    {
                        int valueIndex = valueIndexes[listIndex]++;
                        string value = String.Empty;

                        if (valueIndex < valueLists[listIndex].Count)
                            value = valueLists[listIndex][valueIndex];

                        string variableName =
                            variableLists[listIndex][variableIndex];

                        code = interpreter.SetVariableValue(
                            VariableFlags.None, variableName, value, null,
                            ref result);

                        if (code != ReturnCode.Ok)
                        {
                            Engine.AddErrorInformation(interpreter, result,
                                String.Format(
                                    "{0}    (setting {1} loop variable \"{2}\"",
                                    Environment.NewLine, commandName,
                                    FormatOps.Ellipsis(variableName)));

                            goto done;
                        }
                    }
                }

                code = interpreter.EvaluateScript(body, location, ref result);

                if (code == ReturnCode.Ok)
                {
                    if (collect && (resultList != null))
                        resultList.Add(result);
                }
                else
                {
                    if (code == ReturnCode.Continue)
                    {
                        code = ReturnCode.Ok;
                    }
                    else if (code == ReturnCode.Break)
                    {
                        code = ReturnCode.Ok;
                        break;
                    }
                    else if (code == ReturnCode.Error)
                    {
                        Engine.AddErrorInformation(interpreter, result,
                            String.Format(
                                "{0}    (\"{1}\" body line {2})",
                                Environment.NewLine, commandName,
                                Interpreter.GetErrorLine(interpreter)));

                        break;
                    }
                    else
                    {
                        //
                        // TODO: Can we actually get to this point?
                        //
                        break;
                    }
                }
            }

            //
            // NOTE: Upon success, either set the result to the collected list
            //       elements or clear the result.
            //
            if (code == ReturnCode.Ok)
            {
                if (collect && (resultList != null))
                    result = resultList;
                else
                    Engine.ResetResult(interpreter, ref result);
            }

        done:
            return code;
        }

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

        public static ReturnCode ArrayLoopCommand(
            IIdentifier identifier,  /* in */
            string subCommandName,   /* in */
            bool collect,            /* in */
            Interpreter interpreter, /* in */
            IClientData clientData,  /* in */
            ArgumentList arguments,  /* in */
            ref Result result        /* out */
            )
        {
            if (interpreter == null)
            {
                result = "invalid interpreter";
                return ReturnCode.Error;
            }

            if (arguments == null)
            {
                result = "invalid argument list";
                return ReturnCode.Error;
            }

            string commandName = (identifier != null) ?
                identifier.Name : "array";

            if (subCommandName == null)
                subCommandName = "foreach";

            if ((arguments.Count < 5) || (((arguments.Count - 1) % 2) != 0))
            {
                result = String.Format(
                    "wrong # args: should be \"{0} {1} varList arrayVarName " +
                    "?varList arrayVarName ...? script\"",
                    commandName, subCommandName);

                return ReturnCode.Error;
            }

            ReturnCode code = ReturnCode.Ok;
            int numLists = ((arguments.Count - 3) / 2);
            List<StringList> variableLists = new List<StringList>();
            List<IEnumerator> valueLists = new List<IEnumerator>();
            StringList resultList = collect ? new StringList() : null;
            int maximumIterations = 0;

            for (int listIndex = 0; listIndex < numLists; listIndex++)
            {
                int argumentIndex = 2 + (listIndex * 2);
                StringList variableList = null;

                code = Parser.SplitList(
                    interpreter, arguments[argumentIndex], 0,
                    Length.Invalid, true, ref variableList,
                    ref result);

                if (code != ReturnCode.Ok)
                    goto done;

                if (variableList.Count < 1)
                {
                    result = String.Format(
                        "{0} {1} varlist is empty",
                        commandName, subCommandName);

                    code = ReturnCode.Error;
                    goto done;
                }

                variableLists.Add(variableList);
                argumentIndex = 3 + (listIndex * 2);

                VariableFlags variableFlags = VariableFlags.NoElement |
                    VariableFlags.NoLinkIndex | VariableFlags.Defined |
                    VariableFlags.NonVirtual;

                IVariable variable = null;

                code = interpreter.GetVariableViaResolversWithSplit(
                    arguments[argumentIndex], ref variableFlags,
                    ref variable, ref result);

                if (code != ReturnCode.Ok)
                    goto done;

                if (EntityOps.IsLink(variable))
                    variable = EntityOps.FollowLinks(variable, variableFlags);

                if (EntityOps.IsUndefined(variable) ||
                    !EntityOps.IsArray(variable))
                {
                    result = String.Format(
                        "\"{0}\" isn't an array",
                        arguments[argumentIndex]);

                    code = ReturnCode.Error;
                    goto done;
                }

                ICollection valueList;

                if (interpreter.IsEnvironmentVariable(variable))
                {
                    IDictionary environment =
                        Environment.GetEnvironmentVariables();

                    if (environment == null)
                    {
                        result = "environment variables unavailable";
                        code = ReturnCode.Error;
                        goto done;
                    }

                    valueList = environment.Keys;
                }
                else if (interpreter.IsTestsVariable(variable))
                {
                    StringDictionary tests = interpreter.GetAllTestInformation(
                        false, ref result);

                    if (tests == null)
                    {
                        code = ReturnCode.Error;
                        goto done;
                    }

                    valueList = tests.Keys;
                }
                else
                {
#if DATA
                    DatabaseVariable databaseVariable = null;

                    if (interpreter.IsDatabaseVariable(variable, ref databaseVariable))
                    {
                        ObjectDictionary database = databaseVariable.GetList(
                            interpreter, true, false, ref result);

                        if (database == null)
                        {
                            code = ReturnCode.Error;
                            goto done;
                        }

                        valueList = database.Keys;
                    }
                    else
#endif
                    {
                        valueList = variable.ArrayValue.Keys;
                    }
                }

                valueLists.Add(valueList.GetEnumerator());

                int iterations = valueList.Count / variableList.Count;

                if ((valueList.Count % variableList.Count) != 0)
                    iterations++;

                if (iterations > maximumIterations)
                    maximumIterations = iterations;
            }

            string body = arguments[arguments.Count - 1];
            IScriptLocation location = arguments[arguments.Count - 1];

            for (int iteration = 0; iteration < maximumIterations; iteration++)
            {
                for (int listIndex = 0; listIndex < numLists; listIndex++)
                {
                    for (int variableIndex = 0;
                            variableIndex < variableLists[listIndex].Count;
                            variableIndex++)
                    {
                        IEnumerator valueList = valueLists[listIndex];
                        object value = null;

                        if (valueList != null)
                        {
                            if (valueList.MoveNext())
                                value = valueList.Current;
                            else
                                valueLists[listIndex] = null;
                        }

                        string variableName =
                            variableLists[listIndex][variableIndex];

                        code = interpreter.SetVariableValue(
                            VariableFlags.None, variableName,
                            StringOps.GetStringFromObject(value),
                            null, ref result);

                        if (code != ReturnCode.Ok)
                        {
                            Engine.AddErrorInformation(interpreter, result,
                                String.Format(
                                    "{0}    (setting {1} {2} loop variable \"{3}\"",
                                    Environment.NewLine, commandName, subCommandName,
                                    FormatOps.Ellipsis(variableName)));

                            goto done;
                        }
                    }
                }

                code = interpreter.EvaluateScript(body, location, ref result);

                if (code == ReturnCode.Ok)
                {
                    if (collect && (resultList != null))
                        resultList.Add(result);
                }
                else
                {
                    if (code == ReturnCode.Continue)
                    {
                        code = ReturnCode.Ok;
                    }
                    else if (code == ReturnCode.Break)
                    {
                        code = ReturnCode.Ok;
                        break;
                    }
                    else if (code == ReturnCode.Error)
                    {
                        Engine.AddErrorInformation(interpreter, result,
                            String.Format(
                                "{0}    (\"{1} {2}\" body line {3})",
                                Environment.NewLine, commandName,
                                subCommandName,
                                Interpreter.GetErrorLine(interpreter)));

                        break;
                    }
                    else
                    {
                        //
                        // TODO: Can we actually get to this point?
                        //
                        break;
                    }
                }
            }

            //
            // NOTE: Upon success, either set the result to the collected list
            //       elements or clear the result.
            //
            if (code == ReturnCode.Ok)
            {
                if (collect && (resultList != null))
                    result = resultList;
                else
                    Engine.ResetResult(interpreter, ref result);
            }

        done:
            return code;
        }
        #endregion
    }
}
