/*
 * CallFrameOps.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 Eagle._Attributes;
using Eagle._Components.Public;
using Eagle._Constants;
using Eagle._Containers.Public;
using Eagle._Interfaces.Public;

namespace Eagle._Components.Private
{
    [ObjectId("72edb8df-b8c4-47ca-b8c9-6a0f463ee97a")]
    internal static class CallFrameOps
    {
        #region Private Constants
        internal static readonly string InfoLevelSubCommand = "level";
        #endregion

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

        #region Checking Methods
        public static long GetLevel(
            ICallFrame frame
            )
        {
            return (frame != null) ? frame.Level : Index.Invalid;
        }

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

        public static bool HasFlags(
            ICallFrame frame,
            CallFrameFlags flags,
            bool all
            )
        {
            return (frame != null) ?
                FlagOps.HasFlags(frame.Flags, flags, all) : false;
        }

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

        public static bool IsAlias(
            ICallFrame frame
            )
        {
            return (frame != null) ?
                FlagOps.HasFlags(frame.Flags,
                    CallFrameFlags.Alias, true) : false;
        }

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

        public static bool IsGlobal(
            ICallFrame frame
            )
        {
            return (frame != null) ?
                FlagOps.HasFlags(frame.Flags,
                    CallFrameFlags.Global, true) : false;
        }

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

        public static bool IsLambda(
            ICallFrame frame
            )
        {
            return (frame != null) ?
                FlagOps.HasFlags(frame.Flags,
                    CallFrameFlags.Lambda, true) : false;
        }

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

        public static bool IsNoFree(
            ICallFrame frame
            )
        {
            return (frame != null) ?
                FlagOps.HasFlags(frame.Flags,
                    CallFrameFlags.NoFree, true) : false;
        }

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

        public static bool IsLocal(
            ICallFrame frame
            )
        {
            return IsProcedure(frame) || IsScope(frame);
        }

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

        public static bool IsProcedure(
            ICallFrame frame
            )
        {
            return (frame != null) ?
                FlagOps.HasFlags(frame.Flags,
                    CallFrameFlags.Procedure, true) : false;
        }

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

        public static bool IsScope(
            ICallFrame frame
            )
        {
            return (frame != null) ?
                FlagOps.HasFlags(frame.Flags,
                    CallFrameFlags.Scope, true) : false;
        }

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

        public static bool IsTracking(
            ICallFrame frame
            )
        {
            return (frame != null) ?
                FlagOps.HasFlags(frame.Flags,
                    CallFrameFlags.Tracking, true) : false;
        }

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

        public static bool IsUseNamespace(
            ICallFrame frame
            )
        {
            return (frame != null) ?
                FlagOps.HasFlags(frame.Flags,
                    CallFrameFlags.UseNamespace, true) : false;
        }

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

        public static bool IsUndefined(
            ICallFrame frame
            )
        {
            return (frame != null) ?
                FlagOps.HasFlags(frame.Flags,
                    CallFrameFlags.Undefined, true) : false;
        }

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

        public static bool IsDownlevel(
            ICallFrame frame
            )
        {
            return (frame != null) ?
                FlagOps.HasFlags(frame.Flags,
                    CallFrameFlags.Downlevel, true) : false;
        }

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

        public static bool IsUplevel(
            ICallFrame frame
            )
        {
            return (frame != null) ?
                FlagOps.HasFlags(frame.Flags,
                    CallFrameFlags.Uplevel, true) : false;
        }

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

        public static bool IsNamespace(
            ICallFrame frame
            )
        {
            return IsNamespace(frame, true);
        }

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

        private static bool IsNamespace(
            ICallFrame frame,
            bool variables
            )
        {
            if (frame == null)
                return false;

            if (!HasFlags(frame, CallFrameFlags.Namespace, true))
                return false;

            if (variables && !IsVariable(frame))
                return false;

            return true;
        }

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

        public static bool IsVariable(
            ICallFrame frame
            )
        {
            if (frame == null)
                return false;

            return frame.IsVariable;
        }

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

        public static bool IsNonGlobalVariable(
            ICallFrame frame
            )
        {
            if (frame == null)
                return false;

            if (!frame.IsVariable)
                return false;

            return !FlagOps.HasFlags(
                frame.Flags, CallFrameFlags.Global, true);
        }

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

        private static bool IsCleanup(
            ICallFrame frame,
            bool variables
            )
        {
            if (variables && !IsVariable(frame))
                return false;

            if (IsGlobal(frame))
                return false;

            if (IsNamespace(frame))
                return false;

            if (IsScope(frame))
                return false;

            return true;
        }
        #endregion

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

        #region Equality Methods
        public static bool IsSame(
            ICallFrame frame1,
            ICallFrame frame2
            )
        {
            return Object.ReferenceEquals(frame1, frame2);
        }

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

        public static bool IsSame(
            Interpreter interpreter,
            ICallFrame frame1,
            ICallFrame frame2,
            string name1,
            string name2
            )
        {
            if (!IsSame(frame1, frame2))
                return false;

            if (String.Compare(name1, name2,
                    StringOps.SystemStringComparisonType) == 0)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        #endregion

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

        #region Traversal Methods
        public static ReturnCode Count(
            CallStack callStack,
            ICallFrame currentFrame,
            CallFrameFlags hasFlags,
            CallFrameFlags notHasFlags,
            bool hasAll,
            bool notHasAll,
            ref int count,
            ref Result error
            )
        {
            //
            // NOTE: If the current call frame is null then we are basically
            //       in the global scope; therefore, there would be zero call
            //       frames of any type between the current scope and down
            //       toward the bottom of the call stack.
            //
            if (currentFrame != null)
            {
                //
                // NOTE: Figure out where we should stop searching.
                //
                ICallFrame stopFrame = currentFrame;

                for (int index = 0; index < callStack.Count; index++)
                {
                    ICallFrame thisFrame = callStack[index];

                    if (((hasFlags == CallFrameFlags.None) ||
                            HasFlags(thisFrame, hasFlags, hasAll)) &&
                        ((notHasFlags == CallFrameFlags.None) ||
                            !HasFlags(thisFrame, notHasFlags, notHasAll)))
                    {
                        count++;
                    }

                    if (IsSame(thisFrame, stopFrame))
                        return ReturnCode.Ok;
                }
            }

            return ReturnCode.Ok;
        }

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

        public static ReturnCode Find(
            CallStack callStack,
            ICallFrame currentFrame,
            bool absolute,
            int level,
            CallFrameFlags hasFlags,
            CallFrameFlags notHasFlags,
            bool hasAll,
            bool notHasAll
            )
        {
            ICallFrame frame = null;

            return Find(callStack, currentFrame, absolute, level,
                hasFlags, notHasFlags, hasAll, notHasAll, ref frame);
        }

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

        public static ReturnCode Find(
            CallStack callStack,
            ICallFrame currentFrame,
            bool absolute,
            int level,
            CallFrameFlags hasFlags,
            CallFrameFlags notHasFlags,
            bool hasAll,
            bool notHasAll,
            ref ICallFrame frame
            )
        {
            Result error = null;

            return Find(callStack, currentFrame, absolute, level, hasFlags,
                notHasFlags, hasAll, notHasAll, ref frame, ref error);
        }

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

        public static ReturnCode Find(
            CallStack callStack,
            ICallFrame currentFrame,
            bool absolute,
            int level,
            CallFrameFlags hasFlags,
            CallFrameFlags notHasFlags,
            bool hasAll,
            bool notHasAll,
            ref ICallFrame frame,
            ref Result error
            )
        {
            //
            // NOTE: Call frame determination logic:
            //
            //       1. absolute level = 0: Always the outermost call frame that
            //                              match the specified type(s).
            //
            //       2. relative level = 0: Always the innermost call frame that
            //                              match the specified type(s).
            //
            //       3. absolute level > 0: Always the Nth call frame inward from
            //                              the global call frame that match the
            //                              specified type(s).
            //
            //       4. relative level > 0: Always the Nth call frame outward from
            //                              the current call frame that match the
            //                              specified type(s).
            //
            //       5. absolute level < 0: Always an error.
            //
            //       6. relative level < 0: Always an error.
            //
            // NOTE: There must be at least one call frame to continue.  Actually,
            //       there must be at least two for the code below to make sense;
            //       however, we do not enforce that rule here.
            //
            if ((callStack != null) && (callStack.Count > 0))
            {
                int startIndex = 0;

                if (absolute || FindIndex(callStack, currentFrame, absolute, level,
                        ref startIndex, ref error) == ReturnCode.Ok)
                {
                    //
                    // NOTE: Starting at the previously determined current call
                    //       frame index, traverse through the call stack N times
                    //       (where only call frames that match against the
                    //       specified type(s) count against N).
                    //
                    int count = 0;

                    for (int index = startIndex;
                        CommonOps.ForCondition(absolute, index, 0, callStack.Count - 1);
                        CommonOps.ForLoop(absolute, ref index))
                    {
                        ICallFrame thisFrame = callStack[index];

                        if (((hasFlags == CallFrameFlags.None) ||
                                HasFlags(thisFrame, hasFlags, hasAll)) &&
                            ((notHasFlags == CallFrameFlags.None) ||
                                !HasFlags(thisFrame, notHasFlags, notHasAll)))
                        {
                            if (count++ == level)
                            {
                                frame = thisFrame;
                                return ReturnCode.Ok;
                            }
                        }
                    }

                    error = "call frame not found";
                }
            }
            else
            {
                error = "invalid call stack";
            }

            return ReturnCode.Error;
        }

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

        #region Dead Code
#if DEAD_CODE
        public static ICallFrame FollowPrevious(
            ICallFrame frame
            )
        {
            if (frame != null)
                while (frame.Previous != null)
                    frame = frame.Previous;

            return frame;
        }
#endif
        #endregion

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

        public static ICallFrame FollowNext(
            ICallFrame frame
            )
        {
            if (frame != null)
                while (frame.Next != null)
                    frame = frame.Next;

            return frame;
        }

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

        public static ReturnCode FindIndex(
            CallStack callStack,
            bool absolute,
            int level,
            CallFrameFlags hasFlags,
            CallFrameFlags notHasFlags,
            bool hasAll,
            bool notHasAll,
            ref int index,
            ref Result error
            )
        {
            if ((callStack != null) && (callStack.Count > 0))
            {
                //
                // NOTE: If we are doing handling an absolute level, start at the
                //       outermost call frame; otherwise, work outwards from the
                //       current call frame until we find its index.
                //
                int startIndex;

                if (absolute)
                    startIndex = 0;
                else
                    startIndex = callStack.Count - 1;

                //
                // NOTE: Starting at the previously determined starting call frame
                //       index, traverse through the call stack N times (where only
                //       call frames that match against the specified type(s) count
                //       against N).
                //
                int count = 0;

                for (int thisIndex = startIndex;
                    CommonOps.ForCondition(absolute, thisIndex, 0, callStack.Count - 1);
                    CommonOps.ForLoop(absolute, ref thisIndex))
                {
                    ICallFrame thisFrame = callStack[thisIndex];

                    if (((hasFlags == CallFrameFlags.None) ||
                            HasFlags(thisFrame, hasFlags, hasAll)) &&
                        ((notHasFlags == CallFrameFlags.None) ||
                            !HasFlags(thisFrame, notHasFlags, notHasAll)))
                    {
                        if (count++ == level)
                        {
                            index = thisIndex;
                            return ReturnCode.Ok;
                        }
                    }
                }

                error = "call frame not found";
            }
            else
            {
                error = "invalid call stack";
            }

            return ReturnCode.Error;
        }

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

        public static ReturnCode FindIndex(
            CallStack callStack,
            ICallFrame frame,
            bool absolute,
            int level,
            ref int index,
            ref Result error
            )
        {
            if ((callStack != null) && (callStack.Count > 0))
            {
                if (frame != null)
                {
                    //
                    // NOTE: If we are doing handling an absolute level, start
                    //       at the outermost call frame; otherwise, work
                    //       outwards from the current call frame until we find
                    //       its index.
                    //
                    int startIndex;

                    if (absolute)
                        startIndex = 0;
                    else
                        startIndex = callStack.Count - 1;

                    //
                    // NOTE: Starting at the previously determined starting call
                    //       frame index, traverse through the call stack N times
                    //       (where only call frames that match against the
                    //       specified type(s) count against N).
                    //
                    for (int thisIndex = startIndex;
                        CommonOps.ForCondition(absolute, thisIndex, 0, callStack.Count - 1);
                        CommonOps.ForLoop(absolute, ref thisIndex))
                    {
                        ICallFrame thisFrame = callStack[thisIndex];

                        if (IsSame(thisFrame, frame))
                        {
                            index = thisIndex;
                            return ReturnCode.Ok;
                        }
                    }

                    error = "call frame not found";
                }
                else
                {
                    error = "invalid call frame";
                }
            }
            else
            {
                error = "invalid call stack";
            }

            return ReturnCode.Error;
        }

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

        public static ReturnCode GetOrFind(
            CallStack callStack,
            ICallFrame globalFrame,
            ICallFrame currentFrame,
            bool absolute,
            int level,
            CallFrameFlags hasFlags,
            CallFrameFlags notHasFlags,
            bool hasAll,
            bool notHasAll,
            ref ICallFrame frame
            )
        {
            Result error = null;

            return GetOrFind(
                callStack, globalFrame, currentFrame, absolute, level,
                hasFlags, notHasFlags, hasAll, notHasAll, ref frame,
                ref error);
        }

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

        public static ReturnCode GetOrFind(
            CallStack callStack,
            ICallFrame globalFrame,
            ICallFrame currentFrame,
            bool absolute,
            int level,
            CallFrameFlags hasFlags,
            CallFrameFlags notHasFlags,
            bool hasAll,
            bool notHasAll,
            ref ICallFrame frame,
            ref Result error
            )
        {
            //
            // NOTE: Are they asking for "Absolute Zero"?
            //
            if (absolute && (level == 0))
            {
                //
                // NOTE: Absolute level 0 is the global call frame.
                //
                frame = globalFrame;
                return ReturnCode.Ok;
            }
            else
            {
                //
                // NOTE: Relative level 0 is the current procedure call
                //       frame.  If there is no current procedure call,
                //       this is an error.
                //
                return Find(
                    callStack, currentFrame, absolute, level, hasFlags,
                    notHasFlags, hasAll, notHasAll, ref frame, ref error);
            }
        }

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

        public static ReturnCode MarkMatching(
            CallStack callStack,
            ICallFrame currentFrame,
            bool absolute,
            int level,
            CallFrameFlags hasFlags,
            CallFrameFlags notHasFlags,
            CallFrameFlags markFlags,
            bool hasAll,
            bool notHasAll,
            bool mark,
            ref Result error
            )
        {
            if ((callStack != null) && (callStack.Count > 0))
            {
                if (currentFrame != null)
                {
                    //
                    // NOTE: If we are doing handling an absolute level, start
                    //       at the outermost call frame; otherwise, work
                    //       outwards from the current call frame until we find
                    //       its index.
                    //
                    int startIndex = 0;

                    if ((absolute && FindIndex(callStack, absolute, level,
                            hasFlags, notHasFlags, hasAll, notHasAll,
                            ref startIndex, ref error) == ReturnCode.Ok) ||
                        (!absolute && FindIndex(callStack, currentFrame, absolute,
                            level, ref startIndex, ref error) == ReturnCode.Ok))
                    {
                        //
                        // NOTE: If we are in absolute mode, we need to start
                        //       just after where the current frame index is.
                        //
                        if (absolute)
                            startIndex++;

                        //
                        // NOTE: Starting at the previously determined current
                        //       call frame index, traverse through the call
                        //       stack N times (where only call frames that
                        //       match against the specified type(s) count
                        //       against N).
                        //
                        int count = 0;

                        //
                        // NOTE: Use the unique Id of the starting call frame
                        //       to keep track of which frames were marked in
                        //       this "round".
                        //
                        string markName = StringList.MakeList(
                            currentFrame.FrameId, currentFrame.Name);

                        for (int index = startIndex;
                            CommonOps.ForCondition(absolute, index, 0, callStack.Count - 1);
                            CommonOps.ForLoop(absolute, ref index))
                        {
                            ICallFrame thisFrame = callStack[index];

                            if (((hasFlags == CallFrameFlags.None) ||
                                    HasFlags(thisFrame, hasFlags, hasAll)) &&
                                ((notHasFlags == CallFrameFlags.None) ||
                                    !HasFlags(thisFrame, notHasFlags, notHasAll)))
                            {
                                ICallFrame markFrame = null;

                                if (mark || (thisFrame.HasMark(markName, ref markFrame) &&
                                        IsSame(markFrame, currentFrame)))
                                {
                                    if (!absolute && (count++ == level))
                                        return ReturnCode.Ok;

                                    thisFrame.SetMark(mark, markFlags, markName,
                                        currentFrame);

                                    if (absolute &&
                                        IsSame(thisFrame, currentFrame))
                                    {
                                        return ReturnCode.Ok;
                                    }
                                }
                            }
                        }

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

            return ReturnCode.Error;
        }

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

        public static ReturnCode Traverse(
            Interpreter interpreter,
            CallStack callStack,
            ICallFrame skipFrame,
            int limit,
            bool all,
            ref CallStack newCallStack,
            ref Result error
            )
        {
            ReturnCode code;

            if (interpreter != null)
            {
                if (callStack != null)
                {
                    int count = 0;

                    if (all)
                    {
                        //
                        // NOTE: Create the new call stack for the caller now
                        //       because this method guarantees it will be valid
                        //       if the method itself returns without error and
                        //       no errors can be generated beyond this point.
                        //
                        if (newCallStack == null)
                            newCallStack = new CallStack();

                        count = callStack.Count;

                        for (int index = 0; index < count; index++)
                        {
                            //
                            // NOTE: Index the call frames backwards.
                            //
                            ICallFrame frame = callStack[((count - 1) - index)];

                            //
                            // HACK: Preserve this wacky logic from the default host.
                            //       Skip over the specified call frame unless we
                            //       have already processed the first call frame.
                            //
                            if ((skipFrame == null) ||
                                (index > 0) ||
                                !IsSame(frame, skipFrame))
                            {
                                if ((limit == 0) || (newCallStack.Count < limit))
                                    newCallStack.Add(frame);
                            }
                        }

                        code = ReturnCode.Ok;
                    }
                    else
                    {
                        CallFrameFlags notHasFlags = CallFrameFlags.None;
                        ICallFrame currentFrame = null;

                        interpreter.GetInfoLevelFlagsAndFrame(
                            null, ref notHasFlags, ref currentFrame);

                        code = Count(callStack, currentFrame,
                            interpreter.GetInfoLevelCallFrameFlags(),
                            notHasFlags, false, false, ref count, ref error);

                        if (code == ReturnCode.Ok)
                        {
                            ICallFrame globalFrame = interpreter.GlobalFrame;

                            //
                            // NOTE: Increase the calculated count because it
                            //       does not include the global call frame.
                            //
                            count++;

                            for (int index = 0; index < count; index++)
                            {
                                ICallFrame frame = null;

                                code = GetOrFind(
                                    callStack, globalFrame, currentFrame,
                                    false, index, CallFrameFlags.Variables,
                                    notHasFlags, false, false, ref frame,
                                    ref error);

                                if (code != ReturnCode.Ok)
                                    break;

                                if (newCallStack == null)
                                    newCallStack = new CallStack();

                                if ((limit == 0) || (newCallStack.Count < limit))
                                    newCallStack.Add(frame);
                            }
                        }
                    }
                }
                else
                {
                    error = "invalid call stack";
                    code = ReturnCode.Error;
                }
            }
            else
            {
                error = "invalid interpreter";
                code = ReturnCode.Error;
            }

            return code;
        }
        #endregion

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

        #region Push / Peek / Pop Methods
        public static ICallFrame Peek(
            CallStack callStack
            )
        {
            return callStack.Peek();
        }

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

        public static ICallFrame Pop(
            CallStack callStack,
            ref ICallFrame currentFrame
            )
        {
            ICallFrame newFrame = callStack.Pop(); // pop current call frame.
            currentFrame = callStack.Peek(); // current frame is now stack top.
            return newFrame; // return popped call frame.
        }

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

        public static void Push(
            CallStack callStack,
            ICallFrame newFrame,
            ref ICallFrame currentFrame
            )
        {
            callStack.Push(newFrame); // push new call frame.
            currentFrame = callStack.Peek(); // current frame is now stack top.
        }
        #endregion

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

        #region Creation / Cleanup Methods
        public static int Cleanup(
            ICallFrame currentFrame,
            ICallFrame frame,
            bool undefined
            )
        {
            int count = _Constants.Count.Invalid;

            if (frame != null)
            {
                VariableDictionary variables = frame.Variables;

                if (variables != null)
                {
                    StringList list = new StringList(variables.Keys);
                    long currentLevel = GetLevel(currentFrame);

                    //
                    // NOTE: Ok, we got this far, reset the count of how many
                    //       variables have been removed.  Caller should check
                    //       if the result is greater than or equal to zero for
                    //       "success".
                    //
                    count = 0;

                    foreach (string name in list)
                    {
                        //
                        // NOTE: Grab the variable, by name, from the call frame
                        //       being "cleaned up".
                        //
                        IVariable variable = variables[name];

                        //
                        // NOTE: We only want to remove variables that match
                        //       certain criteria (below).
                        //
                        bool remove = false;

                        //
                        // NOTE: Is the variable actually valid (i.e. not null)?
                        //
                        if (variable != null)
                        {
                            //
                            // NOTE: Is the variable or link flagged as undefined?
                            //
                            if (EntityOps.IsUndefined(variable))
                            {
                                //
                                // NOTE: Always remove undefined variables for the
                                //       call frame we are "cleaning up".
                                //
                                remove = true;
                            }
                            else if (GetLevel(variable.Frame) > currentLevel)
                            {
                                //
                                // NOTE: Remove variables for the call frame we are
                                //       "cleaning up" only if they are going out of
                                //       scope.  If these frames are equal, we are
                                //       simply cleaning up undefined variables in
                                //       the current call frame without touching any
                                //       of the other variables.
                                //
                                remove = true;
                            }

                            //
                            // BUGFIX: We need to remove the targets of any links that
                            //         are now undefined due to unset or something
                            //         similar.  The link itself will also be removed
                            //         (however, not by this block of code) if it also
                            //         happens to be undefined or going out of scope.
                            //
                            if (EntityOps.IsLink(variable))
                            {
                                //
                                // NOTE: Follow the linked variable(s).
                                //
                                while (variable.Link != null)
                                {
                                    //
                                    // NOTE: Save the link itself because we may need
                                    //       to mark it as undefined (below).
                                    //
                                    IVariable savedVariable = variable; // TEST: Test.

                                    //
                                    // NOTE: Follow the linked variable.
                                    //
                                    variable = variable.Link;

                                    //
                                    // NOTE: Remove undefined variable in the other call
                                    //       frame via the local linked variable we may
                                    //       also be removing (below).
                                    //
                                    if (EntityOps.IsUndefined(variable))
                                    {
                                        //
                                        // NOTE: Unlink the local link variable and the
                                        //       variable in the other call frame
                                        //       (prevents having dangling references
                                        //       to "deleted" variables).
                                        //
                                        savedVariable.Link = null;
                                        savedVariable.LinkIndex = null;

                                        //
                                        // NOTE: Grab the name of the linked variable.
                                        //       If it is null, skip removing this
                                        //       variable (i.e. the variables dictionary
                                        //       for this frame cannot have a null key).
                                        //
                                        string linkName = variable.Name;

                                        if (linkName == null)
                                            continue;

                                        //
                                        // BUGFIX: We cannot remove the linked variable
                                        //         from the linked call frame if it is
                                        //         null or the same as the call frame
                                        //         being cleaned up; otherwise, an
                                        //         exception may be thrown at the top
                                        //         of the loop when fetching variables
                                        //         from the frame being cleaned up if
                                        //         the name of the linked variable
                                        //         occurs later in the list than the
                                        //         name of the link to it.
                                        //
                                        ICallFrame linkFrame = variable.Frame;

                                        if ((linkFrame == null) ||
                                            IsSame(linkFrame, frame))
                                        {
                                            continue;
                                        }

                                        //
                                        // BUGFIX: If the other end of the link is the
                                        //         global call frame or a namespace call
                                        //         frame, we cannot remove the variable
                                        //         because it may have been declared via
                                        //         [variable] and just not set yet (e.g.
                                        //         some script procedure declares the
                                        //         variable and then returns non-zero if
                                        //         it exists).
                                        //
                                        if (!IsCleanup(linkFrame, false))
                                            continue;

                                        //
                                        // NOTE: Grab the variables dictionary from the
                                        //       linked call frame.  If it is null we
                                        //       cannot do anything.  Otherwise, since
                                        //       we are traversing, use the frame
                                        //       reference stored within the variable
                                        //       itself to remove the variable from the
                                        //       other call frame.
                                        //
                                        VariableDictionary linkVariables =
                                            linkFrame.Variables;

                                        if (linkVariables == null)
                                            continue;

                                        linkVariables.Remove(linkName);
                                    }
                                }
                            }
                        }
                        else
                        {
                            //
                            // NOTE: Always remove null variables for the call frame
                            //       we are "cleaning up".
                            //
                            remove = true;
                        }

                        if (remove && variables.Remove(name))
                            count++;
                    }
                }

                if (undefined)
                    frame.Flags |= CallFrameFlags.Undefined;
            }

            return count;
        }

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

        public static ReturnCode Purge(
            Interpreter interpreter,
            ref Result result
            )
        {
            if (interpreter == null)
            {
                result = "invalid interpreter";
                return ReturnCode.Error;
            }

            lock (interpreter.SyncRoot) /* TRANSACTIONAL */
            {
                ICallFrame variableFrame = interpreter.CurrentFrame;

                if (interpreter.GetVariableFrameViaResolvers(
                        LookupFlags.Default, ref variableFrame,
                        ref result) != ReturnCode.Ok)
                {
                    return ReturnCode.Error;
                }

                if (variableFrame != null)
                {
                    int purged = Cleanup(
                        interpreter.CurrentFrame, variableFrame, false);

                    if (purged >= 0)
                    {
                        result = String.Format(
                            "purged {0} undefined variables for call frame \"{1}\"",
                            purged, variableFrame);

                        return ReturnCode.Ok;
                    }
                    else
                    {
                        result = "failed to cleanup call frame";
                    }
                }
                else
                {
                    result = "invalid variable call frame";
                }
            }

            return ReturnCode.Error;
        }
        #endregion
    }
}
