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

namespace Eagle._Components.Public
{
    [ObjectId("ac231a31-e777-41e3-89de-e74cb4092467")]
    public class EventManager :
#if ISOLATED_INTERPRETERS || ISOLATED_PLUGINS
        ScriptMarshalByRefObject,
#endif
        IEventManager, IHaveInterpreter, IDisposable
    {
        #region Private Constants
        //
        // NOTE: All values are in milliseconds unless otherwise noted.
        //
        internal static readonly int DefaultSleepTime = 0;
        internal static readonly int MinimumSleepTime = 50;

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

        internal static readonly int MinimumEventTime = 1;
        internal static readonly int MinimumIdleWaitTime = 1000;
        #endregion

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

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

        private int maximumCount;
        private int maximumIdleCount;
        private DateTime lastNow;
        private EventQueue events;
        private EventQueue idleEvents;
        private EventWaitHandle emptyEvent;
        private EventWaitHandle enqueueEvent;
        private EventWaitHandle[] userEvents;
        private int enabled;
        private int levels;
        private DateTimeNowCallback nowCallback;
        #endregion

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

        #region Private Constructors
        private EventManager()
        {
            maximumCount = 0;
            maximumIdleCount = 0;
            lastNow = DateTime.MinValue;
            events = new EventQueue();
            idleEvents = new EventQueue();
            emptyEvent = ThreadOps.CreateEvent(true);
            enqueueEvent = ThreadOps.CreateEvent(true);
            userEvents = null;
            enabled = 1; // FIXME: PRI 6: Make this an argument?
        }
        #endregion

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

        #region Public Constructors
        public EventManager(
            Interpreter interpreter
            )
            : this()
        {
            this.interpreter = interpreter;
        }
        #endregion

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

        #region Private Static Methods
        #region Event Formatting Methods
        private static string EventToList(
            int index,
            IEvent @event
            )
        {
            if (@event == null)
                return null;

            StringPairList result = new StringPairList();

            result.Add("dateTime",
                FormatOps.Iso8601FullDateTime(@event.DateTime));

            result.Add("index", index.ToString());

            Guid id = @event.Id;

            if (!id.Equals(Guid.Empty))
                result.Add("id", @event.Id.ToString());

            result.Add("priority", @event.Priority.ToString());
            result.Add("flags", @event.Flags.ToString());
            result.Add("name", @event.Name);

            if (IsScriptEvent(@event))
            {
                IClientData clientData = @event.ClientData;

                IScript script = (clientData != null) ?
                    clientData.Data as IScript : null;

                result.Add("script",
                    (script != null) ? script.Text : null);
            }
            else
            {
                result.Add("callback",
                    FormatOps.DelegateName(@event.Callback));

                IClientData clientData = @event.ClientData;

                result.Add("clientData", (clientData != null) ?
                    clientData.ToString() : String.Empty);
            }

            return result.ToString();
        }
        #endregion

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

        #region Event Checking Methods
        private static bool IsEventPriorityReady(
            IEvent @event,
            EventPriority priority
            )
        {
            if (@event != null)
            {
                EventPriority eventPriority = GetReadyEventPriority(
                    @event.Flags, @event.Priority);

                //
                // NOTE: This code assumes that lower numbers indicate higher
                //       relative priority.
                //
                return eventPriority <= priority;
            }

            return false;
        }

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

        private static bool IsEventDateTimeReady(
            IEvent @event,
            DateTime dateTime
            )
        {
            if (@event != null)
            {
                DateTime eventDateTime = @event.DateTime;

                return (eventDateTime == DateTime.MinValue) ||
                    (dateTime >= eventDateTime);
            }

            return false;
        }

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

        private static bool AreEventFlagsReady(
            IEvent @event,
            EventFlags eventFlags,
            bool notHas,
            bool all
            )
        {
            if (@event != null)
            {
                EventFlags localEventFlags = @event.Flags;

                return (!notHas == FlagOps.HasFlags(
                    localEventFlags, eventFlags, all));
            }
            else
            {
                return false;
            }
        }

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

        private static bool IsIdleEvent(
            EventFlags eventFlags
            )
        {
            return FlagOps.HasFlags(eventFlags, EventFlags.Idle, true);
        }

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

        private static bool IsIdleEvent(
            IEvent @event
            )
        {
            return (@event != null) ? IsIdleEvent(@event.Flags) : false;
        }

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

        internal static bool IsScriptEvent(
            IEvent @event
            )
        {
            return (@event != null) ?
                (@event.Callback == ScriptEventCallback) : false;
        }

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

        private static bool MatchEventName(
            IEvent @event,
            string pattern
            )
        {
            return (@event != null) ? String.Compare(@event.Name,
                pattern, StringOps.SystemStringComparisonType) == 0 : false;
        }

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

        private static bool MatchScriptText(
            IScript script,
            string pattern
            )
        {
            return (script != null) ? String.Compare(script.Text,
                pattern, StringOps.UserStringComparisonType) == 0 : false;
        }

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

        private static bool ShouldDequeueIdleEvent(
            EventFlags eventFlags,
            int nonIdleCount
            )
        {
            if (FlagOps.HasFlags(eventFlags, EventFlags.NoIdle, true))
                return false;

            if ((nonIdleCount != 0) &&
                FlagOps.HasFlags(eventFlags, EventFlags.IdleIfEmpty, true))
            {
                return false;
            }

            return true;
        }

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

        private static EventPriority GetAutomaticEventPriority(
            EventFlags eventFlags,
            EventPriority priority
            )
        {
            //
            // NOTE: If the caller specified any priority that is not
            //       "Automatic", simply return it verbatim.
            //
            if (priority != EventPriority.Automatic)
                return priority;

            //
            // NOTE: If the caller specified the "Idle" event flag then the
            //       priority for this event is "Idle".  This must be checked
            //       first because all idle events currently also include the
            //       "After" flag.
            //
            if (FlagOps.HasFlags(eventFlags, EventFlags.Idle, true))
            {
                priority = EventPriority.Idle;
            }
            //
            // NOTE: Otherwise, if the caller specified the "After" event flag
            //       then the priority for this event is "After".
            //
            else if (FlagOps.HasFlags(eventFlags, EventFlags.After, true))
            {
                priority = EventPriority.After;
            }
            //
            // NOTE: Otherwise, if the caller specified the "Immediate" event
            //       flag then the priority for this event is "Immediate".
            //
            else if (FlagOps.HasFlags(eventFlags, EventFlags.Immediate, true))
            {
                priority = EventPriority.Immediate;
            }
            //
            // NOTE: Otherwise, just return the "Normal" priority.
            //
            else
            {
                priority = EventPriority.Normal;
            }

            return priority;
        }

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

        private static EventPriority GetReadyEventPriority(
            EventFlags eventFlags,
            EventPriority priority
            )
        {
            //
            // NOTE: This code assumes that lower numbers indicate higher
            //       relative priority.  If the caller is accepting a event
            //       priorities "Immediate" and [relatively] lower and this
            //       event has an "Immediate" priority flag then this event
            //       should be considered to be of "Immediate" priority.
            //
            if ((priority >= EventPriority.Immediate) &&
                FlagOps.HasFlags(eventFlags, EventFlags.Immediate, true))
            {
                return EventPriority.Immediate;
            }
            //
            // NOTE: This code assumes that lower numbers indicate higher
            //       relative priority.  Otherwise, if the caller is accepting
            //       a event priorities "After" and [relatively] lower and this
            //       event has an "After" priority flag then this event should
            //       be considered to be of "After" priority.
            //
            else if ((priority >= EventPriority.After) &&
                FlagOps.HasFlags(eventFlags, EventFlags.After, true))
            {
                return EventPriority.After;
            }
            //
            // NOTE: This code assumes that lower numbers indicate higher
            //       relative priority.  Otherwise, if the caller is accepting
            //       a event priorities "Idle" and [relatively] lower and this
            //       event has an "Idle" priority flag then this event should
            //       be considered to be of "Idle" priority.
            //
            else if ((priority >= EventPriority.Idle) &&
                FlagOps.HasFlags(eventFlags, EventFlags.Idle, true))
            {
                return EventPriority.Idle;
            }

            //
            // NOTE: Just return the "Normal" priority.
            //
            return EventPriority.Normal;
        }
        #endregion

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

        #region ParameterizedThreadStart Methods
        internal static void QueueEventThreadStart(
            object obj
            )
        {
            try
            {
                IAnyPair<Interpreter, IScript> pair = obj as
                    IAnyPair<Interpreter, IScript>;

                if (pair != null)
                {
                    Interpreter interpreter = pair.X;
                    IScript script = pair.Y;

                    if ((interpreter != null) && (script != null))
                    {
                        IEventManager eventManager = interpreter.EventManager;

                        if (EventOps.ManagerIsOk(eventManager))
                        {
                            ReturnCode code;
                            Result result = null;

                            code = eventManager.QueueEvent(
                                script.Name, DateTime.MinValue,
                                ScriptEventCallback, new ClientData(script),
                                interpreter.QueueEventFlags,
                                EventPriority.QueueEvent, ref result);

                            IInteractiveHost interactiveHost = interpreter.Host;

                            if (interactiveHost != null)
                            {
                                if (code == ReturnCode.Ok)
                                    interactiveHost.WriteLine("event queued");
                                else
                                    interactiveHost.WriteLine(String.Format(
                                        "failed to queue event{0}{1}: {2}",
                                        Environment.NewLine, code.ToString(),
                                        result));
                            }
                        }
                    }
                }
            }
            catch
            {
                //
                // TODO: Add logging support.  Not much else we can really do.
                //
            }
        }

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

        internal static void ServiceEventsThreadStart(
            object obj
            )
        {
            try
            {
                Interpreter interpreter = obj as Interpreter;

                if (interpreter != null)
                {
                    IEventManager eventManager = interpreter.EventManager;

                    if (EventOps.ManagerIsOk(eventManager))
                    {
                        ReturnCode code;
                        Result result = null;

                        //
                        // NOTE: Keep servicing events for this interpreter until
                        //       an error occurs.
                        //
                        code = eventManager.ServiceEvents(
                            interpreter.ServiceEventFlags, EventPriority.Service,
                            0, false, true, false, false, ref result);

                        IInteractiveHost interactiveHost = interpreter.Host;

                        if (interactiveHost != null)
                        {
                            if (code == ReturnCode.Ok)
                                interactiveHost.WriteLine("events serviced");
                            else
                                interactiveHost.WriteLine(String.Format(
                                    "failed to service events{0}{1}: {2}",
                                    Environment.NewLine, code.ToString(),
                                    result));
                        }
                    }
                }
            }
            catch
            {
                //
                // TODO: Add logging support.  Not much else we can really do.
                //
            }
        }
        #endregion

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

        #region ExecuteCallback Methods
        private static ReturnCode ScriptEventCallback(
            Interpreter interpreter,
            IClientData clientData,
            ref Result result
            )
        {
            if (interpreter != null)
            {
                //
                // NOTE: We must have a clientData object since it contains the
                //       script to evaluate.
                //
                if (clientData != null)
                {
                    //
                    // NOTE: We expect the clientData to contain a script (string).
                    //
                    IScript script = clientData.Data as IScript;

                    if (script != null)
                    {
                        lock (script.SyncRoot)
                        {
                            //
                            // NOTE: Grab all the necessary flags from the script object
                            //       now because they will not change inside the loop.
                            //
                            EngineMode engineMode = script.EngineMode;
                            EngineFlags engineFlags = script.EngineFlags;
                            SubstitutionFlags substitutionFlags = script.SubstitutionFlags;
                            ExpressionFlags expressionFlags = script.ExpressionFlags;

                            //
                            // BUGFIX: For the vast majority of cases, we must prefer the
                            //         dequeue-related event flags (i.e. After, Immediate,
                            //         etc) from the interpreter engine flags field over
                            //         those same flags from the script itself; otherwise,
                            //         events will not be processed in the correct order
                            //         by the [vwait] machinery (which this callback method
                            //         is technically a party of).
                            //
                            EventFlags eventFlags = interpreter.CombineEngineEventFlags(
                                script.EventFlags);

                            //
                            // NOTE: Is debug mode enabled for this script event?
                            //
                            bool eventDebug = FlagOps.HasFlags(eventFlags, EventFlags.Debug,
                                true);

                            //
                            // NOTE: The initial result is Ok (i.e. if the are no scripts,
                            //       that will be the final result as well).
                            //
                            ReturnCode code = ReturnCode.Ok;

                            //
                            // NOTE: Process each section of the provided script.
                            //       Currently, there can be only one.
                            //
                            foreach (string text in script)
                            {
                                //
                                // NOTE: If debug mode is enabled for this event, emit the
                                //       necessary diagnostic message(s) now.
                                //
                                if (eventDebug)
                                {
                                    TraceOps.DebugTrace(String.Format(
                                        "{0}: ScriptEventCallback: received script, " +
                                        "interpreter = {1}, engineMode = {2}, " +
                                        "engineFlags = {3}, substitutionFlags = {4}, " +
                                        "expressionFlags = {5}, eventFlags = {6}, " +
                                        "text = {7}, code = {8}, result = {9}{10}",
                                        GlobalState.GetCurrentThreadId(),
                                        FormatOps.WrapOrNull(interpreter),
                                        FormatOps.WrapOrNull(engineMode),
                                        FormatOps.WrapOrNull(engineFlags),
                                        FormatOps.WrapOrNull(substitutionFlags),
                                        FormatOps.WrapOrNull(expressionFlags),
                                        FormatOps.WrapOrNull(eventFlags),
                                        FormatOps.WrapOrNull(true, true, text),
                                        code, FormatOps.WrapOrNull(true, true, result),
                                        Environment.NewLine), typeof(EventManager).Name);
                                }

                                switch (engineMode)
                                {
                                    case EngineMode.None:
                                        {
                                            //
                                            // NOTE: Do nothing.
                                            //
                                            code = ReturnCode.Ok;

                                            break;
                                        }
                                    case EngineMode.EvaluateExpression:
                                        {
                                            code = Engine.EvaluateExpression(
                                                interpreter, text, engineFlags,
                                                substitutionFlags, eventFlags,
                                                expressionFlags, ref result);

                                            break;
                                        }
                                    case EngineMode.EvaluateScript:
                                        {
                                            code = Engine.EvaluateScript(
                                                interpreter, text, engineFlags,
                                                substitutionFlags, eventFlags,
                                                expressionFlags, ref result);

                                            break;
                                        }
                                    case EngineMode.EvaluateFile:
                                        {
                                            code = Engine.EvaluateFile(
                                                interpreter, text, engineFlags,
                                                substitutionFlags, eventFlags,
                                                expressionFlags, ref result);

                                            break;
                                        }
                                    case EngineMode.SubstituteString:
                                        {
                                            code = Engine.SubstituteString(
                                                interpreter, text, engineFlags,
                                                substitutionFlags, eventFlags,
                                                expressionFlags, ref result);

                                            break;
                                        }
                                    case EngineMode.SubstituteFile:
                                        {
                                            code = Engine.SubstituteFile(
                                                interpreter, text, engineFlags,
                                                substitutionFlags, eventFlags,
                                                expressionFlags, ref result);

                                            break;
                                        }
                                    default:
                                        {
                                            result = String.Format(
                                                "invalid engine mode {0}",
                                                engineMode);

                                            code = ReturnCode.Error;
                                            break;
                                        }
                                }

                                //
                                // NOTE: If debug mode is enabled for this event, emit the
                                //       necessary diagnostic message(s) now.
                                //
                                if (eventDebug)
                                {
                                    TraceOps.DebugTrace(String.Format(
                                        "{0}: ScriptEventCallback: completed script, " +
                                        "interpreter = {1}, engineMode = {2}, " +
                                        "engineFlags = {3}, substitutionFlags = {4}, " +
                                        "expressionFlags = {5}, eventFlags = {6}, " +
                                        "text = {7}, code = {8}, result = {9}{10}",
                                        GlobalState.GetCurrentThreadId(),
                                        FormatOps.WrapOrNull(interpreter),
                                        FormatOps.WrapOrNull(engineMode),
                                        FormatOps.WrapOrNull(engineFlags),
                                        FormatOps.WrapOrNull(substitutionFlags),
                                        FormatOps.WrapOrNull(expressionFlags),
                                        FormatOps.WrapOrNull(eventFlags),
                                        FormatOps.WrapOrNull(true, true, text),
                                        code, FormatOps.WrapOrNull(true, true, result),
                                        Environment.NewLine), typeof(EventManager).Name);
                                }

                                if (code != ReturnCode.Ok)
                                {
                                    if (code == ReturnCode.Error)
                                        Engine.AddErrorInformation(interpreter, result,
                                            String.Format("{0}    (\"after\" script line {1})",
                                                Environment.NewLine, Interpreter.GetErrorLine(interpreter)));

                                    break;
                                }
                            }

                            return code;
                        }
                    }
                    else
                    {
                        result = "event clientData is not a script";
                    }
                }
                else
                {
                    result = "invalid event clientData";
                }
            }
            else
            {
                result = "invalid interpreter";
            }

            return ReturnCode.Error;
        }
        #endregion

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

        #region Background Error Dispatcher
        internal static ReturnCode HandleBackgroundError(
            Interpreter interpreter,
            ReturnCode code,
            Result result
            )
        {
            bool bgError = false;

            return HandleBackgroundError(
                interpreter, code, result, ref bgError);
        }

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

        private static ReturnCode HandleBackgroundError(
            Interpreter interpreter,
            ReturnCode code,
            Result result,
            ref bool bgError
            )
        {
            if (interpreter != null)
            {
                //
                // BUGFIX: Acquire the interpreter lock here; however, do
                //         not use the property because the interpreter
                //         may be disposed at this point.
                //
                lock (interpreter.InternalSyncRoot) /* TRANSACTIONAL */
                {
                    //
                    // BUGFIX: Do not try to handle any background errors
                    //         with a deleted or disposed interpreter.
                    //
                    if (!Interpreter.IsDeletedOrDisposed(interpreter))
                    {
                        string backgroundError = interpreter.BackgroundError;

                        //
                        // NOTE: If there is an invalid background error handler set,
                        //       ignore the error.
                        //
                        if (!String.IsNullOrEmpty(backgroundError))
                        {
                            //
                            // NOTE: Must hold lock while processing the error.
                            //
                            // TODO: Need to more carefully analyze this lock usage at
                            //       some point.
                            //
                            lock (interpreter.SyncRoot) /* TRANSACTIONAL */
                            {
                                //
                                // NOTE: We do not yet know if the background error
                                //       handler can actually be resolved.
                                //
                                bool haveBgError = false;

                                //
                                // NOTE: Construct the arguments for this error (i.e.
                                //       "bgerror <message>").
                                //
                                ArgumentList bgArguments =
                                    new ArgumentList(backgroundError, result);

                                //
                                // NOTE: Attempt to lookup the background error handler
                                //       via the current command resolvers for the
                                //       interpreter.  If this lookup fails the default
                                //       background error processing will be used.
                                //
                                ReturnCode resolveCode;
                                Result resolveError = null;
                                IExecute bgExecute = null;

                                resolveCode = interpreter.GetIExecuteViaResolvers(
                                    interpreter.GetResolveEngineFlags(true), backgroundError,
                                    bgArguments, LookupFlags.Default, ref bgExecute,
                                    ref resolveError);

                                if (resolveCode == ReturnCode.Ok)
                                {
                                    //
                                    // NOTE: We actually found a background error handler.
                                    //
                                    haveBgError = true;

                                    //
                                    // NOTE: Create a new call frame for the background error
                                    //       handler and push it.
                                    //
                                    ICallFrame frame = interpreter.NewTrackingCallFrame(
                                        StringList.MakeList("bgerror", backgroundError),
                                        CallFrameFlags.BackgroundError);

                                    interpreter.PushAutomaticCallFrame(frame);

                                    //
                                    // NOTE: Save the current engine flags and then enable
                                    //       the external execution flags.
                                    //
                                    EngineFlags savedEngineFlags = interpreter.BeginExternalExecution();

                                    //
                                    // NOTE: Evaluate the script and check the result to see if
                                    //       the background error handler failed or canceled
                                    //       further background error handling for this invocation
                                    //       of ProcessEvents.
                                    //
                                    ReturnCode bgCode;
                                    Result bgResult = null;

                                    bgCode = interpreter.Execute(
                                        backgroundError, bgExecute, null, bgArguments, ref bgResult);

                                    //
                                    // NOTE: Restore the saved engine flags, masking off the
                                    //       external execution flags as necessary.
                                    //
                                    /* IGNORED */
                                    interpreter.EndAndCleanupExternalExecution(
                                        savedEngineFlags);

                                    //
                                    // NOTE: Pop the original call frame that we pushed above
                                    //       and any intervening scope call frames that may be
                                    //       leftover (i.e. they were not explicitly closed).
                                    //
                                    /* IGNORED */
                                    interpreter.PopCallFrame(CallFrameFlags.Scope, true);

                                    //
                                    // NOTE: Now we handle the return code for the background
                                    //       error handler.
                                    //
                                    if (bgCode == ReturnCode.Break)
                                    {
                                        //
                                        // NOTE: A return code of "Break" indicates that we
                                        //       should not call the background error handler
                                        //       until the next time ProcessEvents is invoked.
                                        //
                                        bgError = false;
                                    }
                                    else if (bgCode != ReturnCode.Ok)
                                    {
                                        //
                                        // NOTE: Any other non-"Ok" return code is an error an
                                        //       gets reported to the standard error channel of
                                        //       the host, if any.
                                        //
                                        Result bgReport = String.Concat(
                                            "bgerror failed to handle background error.",
                                            Environment.NewLine,
                                            String.Format(
                                                "    Original error: {0}",
                                                ResultOps.Format(code, result)),
                                            Environment.NewLine,
                                            String.Format(
                                                "    Error in bgerror: {0}",
                                                ResultOps.Format(bgCode, bgResult,
                                                    Interpreter.GetErrorLine(interpreter))),
                                            Environment.NewLine);

                                        DebugOps.Complain(interpreter, bgCode, bgReport);
                                    }
                                }

                                //
                                // NOTE: If there is no background error handler setup
                                //       just write the errorInfo to the error channel
                                //       (if possible).
                                //
                                if (!haveBgError)
                                {
                                    Result bgReport = String.Concat(
                                        "background error.",
                                        Environment.NewLine,
                                        String.Format(
                                            "    Original error: {0}",
                                            ResultOps.Format(code, result)),
                                        Environment.NewLine,
                                        String.Format(
                                            "    Resolver error: {0}",
                                            ResultOps.Format(resolveCode, resolveError)),
                                        Environment.NewLine);

                                    DebugOps.Complain(interpreter, ReturnCode.Error, bgReport);
                                }
                            }
                        }

                        return ReturnCode.Ok;
                    }
                    else
                    {
                        DebugOps.Complain(interpreter, ReturnCode.Error,
                            "cannot handle background error, interpreter disposed");
                    }
                }
            }
            else
            {
                DebugOps.Complain(interpreter /* null */, ReturnCode.Error,
                    "cannot handle background error, invalid interpreter");
            }

            return ReturnCode.Error;
        }
        #endregion
        #endregion

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

        #region Private Methods
        private bool IsEnabled()
        {
            return enabled > 0;
        }

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

        private bool IsActive()
        {
            return levels > 0;
        }

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

        private int EnterLevel()
        {
            return Interlocked.Increment(ref levels);
        }

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

        private int ExitLevel()
        {
            return Interlocked.Decrement(ref levels);
        }

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

        private DateTime GetNow()
        {
            //
            // HACK: Make sure that time moves forward by some minimal amount
            //       because the resolution of the system clock is limited to
            //       10 milliseconds (i.e. if you call it faster than that, it
            //       may not increment (?)).
            //
            DateTime result;

            lock (syncRoot)
            {
                result = (nowCallback != null) ?
                    nowCallback() : TimeOps.GetUtcNow();

                TimeSpan timeSpan = result.Subtract(lastNow);
                double milliseconds = timeSpan.TotalMilliseconds;

                if (milliseconds < MinimumEventTime)
                    result = result.AddMilliseconds(MinimumEventTime);

                lastNow = result;
            }

            return result;
        }

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

        private int GetEventCount()
        {
            lock (syncRoot)
            {
                return ((events != null) ? events.Count : 0) +
                    ((idleEvents != null) ? idleEvents.Count : 0);
            }
        }

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

        private bool IsIdleEventQueue(
            EventQueue eventQueue
            )
        {
            lock (syncRoot)
            {
                return Object.ReferenceEquals(eventQueue, idleEvents);
            }
        }

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

        private ReturnCode DequeueAnyReadyEvent(
            DateTime dateTime,
            EventFlags eventFlags,
            EventPriority priority,
            bool strict,
            ref IEvent @event
            )
        {
            Result error = null;

            return DequeueAnyReadyEvent(
                dateTime, eventFlags, priority, strict, ref @event, ref error);
        }

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

        private ReturnCode DequeueAnyReadyEvent(
            EventQueue eventQueue,
            DateTime dateTime,
            EventFlags eventFlags,
            EventPriority priority,
            bool strict,
            ref IEvent @event,
            ref Result error
            )
        {
            lock (syncRoot)
            {
                if (eventQueue != null)
                {
                    //
                    // NOTE: Are we dealing with idle events now?
                    //
                    bool idle = IsIdleEventQueue(eventQueue);

                    if (eventQueue.Count > 0)
                    {
                        priority = GetAutomaticEventPriority(eventFlags, priority);

                        for (int index = 0; index < eventQueue.Count; index++)
                        {
                            //
                            // NOTE: Grab the Nth event from the specified
                            //       event queue.
                            //
                            IEvent localEvent = eventQueue[index];

                            //
                            // NOTE: The events in the queue should never be
                            //       null; however, if there are null events,
                            //       just skip over them.
                            //
                            if (localEvent == null)
                                continue;

                            //
                            // HACK: Events are sorted in highest priority
                            //       order first; therefore, if the priority of
                            //       this event is not high enough, none of
                            //       them will be and we can bail out early.
                            //
                            if (!IsEventPriorityReady(localEvent, priority))
                                break;

                            //
                            // HACK: Events are sorted soonest first; therefire,
                            //       if the time for this event has not arrived
                            //       yet then none of the events after this are
                            //       ready yet either.
                            //
                            if (!IsEventDateTimeReady(localEvent, dateTime))
                                break;

                            //
                            // NOTE: See if the event flags for this event match
                            //       the ones we are looking for.
                            //
                            if (AreEventFlagsReady(localEvent, eventFlags &
                                    EventFlags.DequeueMask, false, false))
                            {
                                @event = localEvent;
                                eventQueue.RemoveAt(index);

                                if (CheckForEmptyQueue())
                                    SignalEmptyQueue();

#if NOTIFY
                                if (interpreter != null)
                                    /* IGNORED */
                                    interpreter.CheckNotifications(
                                        (idle ? NotifyType.Idle : NotifyType.None) |
                                            NotifyType.Event, NotifyFlags.Dequeued,
                                        new ObjectTriplet(dateTime, eventFlags, @event),
                                        interpreter, null, null, null, ref error);
#endif

                                return ReturnCode.Ok;
                            }
                        }

                        error = "no events are ready";
                    }
                    else
                    {
                        if (strict)
                        {
                            error = "no events";
                        }
                        else
                        {
                            @event = null;
                            return ReturnCode.Ok;
                        }
                    }
                }
                else
                {
                    if (strict)
                    {
                        error = "not accepting events";
                    }
                    else
                    {
                        @event = null;
                        return ReturnCode.Ok;
                    }
                }
            }

            return ReturnCode.Error;
        }

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

        private bool CheckForEmptyQueue()
        {
            return GetEventCount() == 0;
        }

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

        private void SignalEmptyQueue()
        {
            ReturnCode code;
            Result error = null;

            code = SignalEmptyQueue(ref error);

            if (code != ReturnCode.Ok)
            {
                Interpreter interpreter;

                lock (syncRoot)
                {
                    interpreter = this.interpreter;
                }

                DebugOps.Complain(interpreter, code, error);
            }
        }

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

        private ReturnCode SignalEmptyQueue(
            ref Result error
            )
        {
            EventWaitHandle emptyEvent;

            lock (syncRoot)
            {
                emptyEvent = this.emptyEvent;
            }

            if (emptyEvent != null)
            {
                if (ThreadOps.SetEvent(emptyEvent))
                {
                    return ReturnCode.Ok;
                }
                else
                {
                    error = "failed to signal empty queue";
                }
            }
            else
            {
                error = "cannot signal empty queue";
            }

            return ReturnCode.Error;
        }

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

        private bool CheckForEventEnqueued()
        {
            return GetEventCount() > 0;
        }

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

        private void SignalEventEnqueued()
        {
            ReturnCode code;
            Result error = null;

            code = SignalEventEnqueued(ref error);

            if (code != ReturnCode.Ok)
            {
                Interpreter interpreter;

                lock (syncRoot)
                {
                    interpreter = this.interpreter;
                }

                DebugOps.Complain(interpreter, code, error);
            }
        }

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

        private ReturnCode SignalEventEnqueued(
            ref Result error
            )
        {
            EventWaitHandle enqueueEvent;

            lock (syncRoot)
            {
                enqueueEvent = this.enqueueEvent;
            }

            if (enqueueEvent != null)
            {
                if (ThreadOps.SetEvent(enqueueEvent))
                {
                    return ReturnCode.Ok;
                }
                else
                {
                    error = "failed to signal event enqueued";
                }
            }
            else
            {
                error = "cannot signal event enqueued";
            }

            return ReturnCode.Error;
        }
        #endregion

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

        #region IGetInterpreter / ISetInterpreter Members
        private Interpreter interpreter;
        public Interpreter Interpreter
        {
            get
            {
                CheckDisposed();

                lock (syncRoot)
                {
                    return interpreter;
                }
            }
            set
            {
                CheckDisposed();

                lock (syncRoot)
                {
                    interpreter = value;
                }
            }
        }
        #endregion

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

        #region IEventManager Members
        //
        // NOTE: Some people may wonder why the IEventManager interface exposes
        //       this property at all, since doing so is contrary to the best
        //       practices normally used when implementing the IDisposable
        //       interface [and "pattern"].  The reasoning is that the event
        //       manager properties and methods are called from various places
        //       where asynchronous event processing may have occurred and the
        //       event manager itself may end up being disposed out from under
        //       the running code.  Therefore, this property can be used to
        //       check for this problem from the few critical places in the
        //       code where this kind of safety check is required.
        //
        public bool Disposed
        {
            get
            {
                //
                // NOTE: Obviously, this would be pointless.
                //
                // CheckDisposed();

                lock (syncRoot)
                {
                    return disposed;
                }
            }
        }

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

        public int EventCount
        {
            get
            {
                CheckDisposed();

                lock (syncRoot)
                {
                    return (events != null) ? events.Count : 0;
                }
            }
        }

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

        public int IdleEventCount
        {
            get
            {
                CheckDisposed();

                lock (syncRoot)
                {
                    return (idleEvents != null) ? idleEvents.Count : 0;
                }
            }
        }

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

        public int TotalEventCount
        {
            get
            {
                CheckDisposed();

                return GetEventCount();
            }
        }

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

        public int MaximumEventCount
        {
            get
            {
                CheckDisposed();

                lock (syncRoot)
                {
                    return maximumCount;
                }
            }
        }

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

        public int MaximumIdleEventCount
        {
            get
            {
                CheckDisposed();

                lock (syncRoot)
                {
                    return maximumIdleCount;
                }
            }
        }

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

        public EventWaitHandle EmptyEvent
        {
            get { CheckDisposed(); lock (syncRoot) { return emptyEvent; } }
        }

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

        public EventWaitHandle EnqueueEvent
        {
            get { CheckDisposed(); lock (syncRoot) { return enqueueEvent; } }
        }

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

        public EventWaitHandle[] UserEvents
        {
            get
            {
                CheckDisposed();

                lock (syncRoot)
                {
                    if (userEvents == null)
                        return null;

                    int length = userEvents.Length;
                    EventWaitHandle[] result = new EventWaitHandle[length];

                    Array.Copy(userEvents, result, length); /* throw */

                    return result;
                }
            }
            set
            {
                CheckDisposed();

                lock (syncRoot)
                {
                    if (value != null)
                    {
                        int length = value.Length;
                        EventWaitHandle[] result = new EventWaitHandle[length];

                        Array.Copy(value, result, length); /* throw */

                        //
                        // NOTE: Replace the user events.  Do not dispose of
                        //       them because we do not own them.
                        //
                        userEvents = result;
                    }
                    else
                    {
                        //
                        // NOTE: Clear out the user events.  Do not dispose
                        //       of them because we do not own them.
                        //
                        userEvents = null;
                    }
                }
            }
        }

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

        public bool Enabled
        {
            get
            {
                CheckDisposed();

                return IsEnabled();
            }
            set
            {
                CheckDisposed();

                if (value)
                    Interlocked.Increment(ref enabled);
                else
                    Interlocked.Decrement(ref enabled);
            }
        }

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

        public bool Active
        {
            get
            {
                CheckDisposed();

                return IsActive();
            }
            set
            {
                CheckDisposed();

                if (value)
                    EnterLevel();
                else
                    ExitLevel();
            }
        }

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

        public DateTimeNowCallback NowCallback
        {
            get { CheckDisposed(); lock (syncRoot) { return nowCallback; } }
            set { CheckDisposed(); lock (syncRoot) { nowCallback = value; } }
        }

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

        public ReturnCode Dump(
            ref Result result
            )
        {
            CheckDisposed();

            lock (syncRoot)
            {
                if ((events != null) || (idleEvents != null))
                {
                    StringList list = new StringList();

                    if (events != null)
                        for (int index = 0; index < events.Count; index++)
                            list.Add(EventToList(index, events[index]));

                    if (idleEvents != null)
                        for (int index = 0; index < idleEvents.Count; index++)
                            list.Add(EventToList(index, idleEvents[index]));

                    result = list.ToString();
                    return ReturnCode.Ok;
                }
                else
                {
                    result = "not accepting events";
                }
            }

            return ReturnCode.Error;
        }

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

        public ReturnCode ClearEvents(
            ref Result error
            )
        {
            CheckDisposed();

            lock (syncRoot)
            {
                if ((events != null) || (idleEvents != null))
                {
                    if (events != null)
                    {
                        events.Clear();

#if NOTIFY
                        if (interpreter != null)
                            /* IGNORED */
                            interpreter.CheckNotifications(
                                NotifyType.Event, NotifyFlags.Cleared,
                                null, interpreter,
                                null, null, null, ref error);
#endif
                    }

                    if (idleEvents != null)
                    {
                        idleEvents.Clear();

#if NOTIFY
                        if (interpreter != null)
                            /* IGNORED */
                            interpreter.CheckNotifications(
                                NotifyType.Idle | NotifyType.Event,
                                NotifyFlags.Cleared,
                                null, interpreter,
                                null, null, null, ref error);
#endif
                    }

                    return ReturnCode.Ok;
                }
                else
                {
                    error = "not accepting events";
                }
            }

            return ReturnCode.Error;
        }

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

        public ReturnCode PeekEvent(
            bool strict,
            ref IEvent @event,
            ref Result error
            )
        {
            CheckDisposed();

            lock (syncRoot)
            {
                if ((events != null) || (idleEvents != null))
                {
                    if ((events != null) && (events.Count > 0))
                    {
                        @event = events.Peek();
                        return ReturnCode.Ok;
                    }
                    else if ((idleEvents != null) && (idleEvents.Count > 0))
                    {
                        @event = idleEvents.Peek();
                        return ReturnCode.Ok;
                    }
                    else
                    {
                        if (strict)
                        {
                            error = "no events";
                        }
                        else
                        {
                            @event = null;
                            return ReturnCode.Ok;
                        }
                    }
                }
                else
                {
                    if (strict)
                    {
                        error = "not accepting events";
                    }
                    else
                    {
                        @event = null;
                        return ReturnCode.Ok;
                    }
                }
            }

            return ReturnCode.Error;
        }

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

        public ReturnCode GetEvent(
            string name,
            ref IEvent @event,
            ref Result error
            )
        {
            CheckDisposed();

            lock (syncRoot)
            {
                if ((events != null) || (idleEvents != null))
                {
                    if (events != null)
                    {
                        //
                        // NOTE: Search normal events by event "id"...
                        //
                        for (int index = 0; index < events.Count; index++)
                        {
                            IEvent localEvent = events[index];

                            if (localEvent != null)
                            {
                                if (MatchEventName(localEvent, name))
                                {
                                    @event = localEvent;
                                    return ReturnCode.Ok;
                                }
                            }
                        }
                    }

                    if (idleEvents != null)
                    {
                        //
                        // NOTE: Search idle events by event "id"...
                        //
                        for (int index = 0; index < idleEvents.Count; index++)
                        {
                            IEvent localEvent = idleEvents[index];

                            if (localEvent != null)
                            {
                                if (MatchEventName(localEvent, name))
                                {
                                    @event = localEvent;
                                    return ReturnCode.Ok;
                                }
                            }
                        }
                    }

                    error = String.Format(
                        "event \"{0}\" doesn't exist",
                        name);
                }
                else
                {
                    error = "not accepting events";
                }
            }

            return ReturnCode.Error;
        }

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

        public ReturnCode DiscardEvent(
            bool strict,
            ref Result error
            )
        {
            CheckDisposed();

            lock (syncRoot)
            {
                if ((events != null) || (idleEvents != null))
                {
                    if ((events != null) && (events.Count > 0))
                    {
                        IEvent @event = events.Dequeue();

                        if (CheckForEmptyQueue())
                            SignalEmptyQueue();

#if NOTIFY
                        if (interpreter != null)
                            /* IGNORED */
                            interpreter.CheckNotifications(
                                NotifyType.Event, NotifyFlags.Discarded,
                                @event, interpreter,
                                null, null, null);
#endif

                        return ReturnCode.Ok;
                    }
                    else if ((idleEvents != null) && (idleEvents.Count > 0))
                    {
                        IEvent @event = idleEvents.Dequeue();

                        if (CheckForEmptyQueue())
                            SignalEmptyQueue();

#if NOTIFY
                        if (interpreter != null)
                            /* IGNORED */
                            interpreter.CheckNotifications(
                                NotifyType.Idle | NotifyType.Event,
                                NotifyFlags.Discarded,
                                @event, interpreter,
                                null, null, null);
#endif

                        return ReturnCode.Ok;
                    }
                    else
                    {
                        if (strict)
                            error = "no events";
                        else
                            return ReturnCode.Ok;
                    }
                }
                else
                {
                    if (strict)
                        error = "not accepting events";
                    else
                        return ReturnCode.Ok;
                }
            }

            return ReturnCode.Error;
        }

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

        public ReturnCode DequeueEvent(
            bool strict,
            ref IEvent @event,
            ref Result error
            )
        {
            CheckDisposed();

            lock (syncRoot)
            {
                if ((events != null) || (idleEvents != null))
                {
                    if ((events != null) && (events.Count > 0))
                    {
                        @event = events.Dequeue();

                        if (CheckForEmptyQueue())
                            SignalEmptyQueue();

#if NOTIFY
                        if (interpreter != null)
                            /* IGNORED */
                            interpreter.CheckNotifications(
                                NotifyType.Event, NotifyFlags.Dequeued,
                                new ObjectPair(strict, @event), interpreter,
                                null, null, null, ref error);
#endif

                        return ReturnCode.Ok;
                    }
                    else if ((idleEvents != null) && (idleEvents.Count > 0))
                    {
                        @event = idleEvents.Dequeue();

                        if (CheckForEmptyQueue())
                            SignalEmptyQueue();

#if NOTIFY
                        if (interpreter != null)
                            /* IGNORED */
                            interpreter.CheckNotifications(
                                NotifyType.Idle | NotifyType.Event,
                                NotifyFlags.Dequeued,
                                new ObjectPair(strict, @event), interpreter,
                                null, null, null, ref error);
#endif

                        return ReturnCode.Ok;
                    }
                    else
                    {
                        if (strict)
                        {
                            error = "no events";
                        }
                        else
                        {
                            @event = null;
                            return ReturnCode.Ok;
                        }
                    }
                }
                else
                {
                    if (strict)
                    {
                        error = "not accepting events";
                    }
                    else
                    {
                        @event = null;
                        return ReturnCode.Ok;
                    }
                }
            }

            return ReturnCode.Error;
        }

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

        public ReturnCode QueueEvent(
            string name,
            DateTime dateTime,
            EventCallback callback,
            IClientData clientData,
            EventFlags eventFlags,
            EventPriority priority,
            ref Result error
            )
        {
            CheckDisposed();

            IEvent @event = null;

            return QueueEvent(name, dateTime, callback, clientData,
                eventFlags, priority, ref @event, ref error);
        }

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

        public ReturnCode QueueEvent(
            string name,
            DateTime dateTime,
            EventCallback callback,
            IClientData clientData,
            EventFlags eventFlags,
            EventPriority priority,
            ref IEvent @event,
            ref Result error
            )
        {
            CheckDisposed();

            lock (syncRoot)
            {
                bool idle = IsIdleEvent(eventFlags);

                if ((!idle && (events != null)) || (idle && (idleEvents != null)))
                {
                    EventPriority oldPriority = priority;

                    priority = GetAutomaticEventPriority(eventFlags, priority);

                    IEvent localEvent = new Event(
                        new object(), null, EventType.Callback, eventFlags |
                            EventFlags.Queued | EventFlags.UnknownThread |
                            EventFlags.Internal, priority,
                        interpreter, name, dateTime, callback, clientData);

                    IAnyTriplet<EventPriority, DateTime, long> key =
                        new AnyTriplet<EventPriority, DateTime, long>(
                            priority, dateTime, GlobalState.NextId(interpreter));

                    if (!idle)
                    {
                        events.Enqueue(key, localEvent);

                        if (events.Count > maximumCount)
                        {
                            TraceOps.DebugTrace(String.Format(
                                "{0}: QueueEvent: maximum event count exceeded, " +
                                "interpreter = {1}, dateTime = {2}, callback = {3}, " +
                                "clientData = {4}, eventFlags = {5}, oldPriority = {6}, " +
                                "priority = {7}, count = {8}, maximumCount = {9}{10}",
                                GlobalState.GetCurrentThreadId(),
                                FormatOps.WrapOrNull(interpreter),
                                FormatOps.WrapOrNull(dateTime),
                                FormatOps.WrapOrNull(FormatOps.DelegateName(callback)),
                                FormatOps.WrapOrNull(clientData),
                                FormatOps.WrapOrNull(eventFlags),
                                FormatOps.WrapOrNull(oldPriority),
                                FormatOps.WrapOrNull(priority), events.Count,
                                maximumCount, Environment.NewLine),
                                typeof(EventManager).Name);

                            maximumCount = events.Count;
                        }
                    }
                    else
                    {
                        idleEvents.Enqueue(key, localEvent);

                        if (idleEvents.Count > maximumIdleCount)
                        {
                            TraceOps.DebugTrace(String.Format(
                                "{0}: QueueEvent: maximum idle event count exceeded, " +
                                "interpreter = {1}, dateTime = {2}, callback = {3}, " +
                                "clientData = {4}, eventFlags = {5}, oldPriority = {6}, " +
                                "priority = {7}, idleCount = {8}, maximumIdleCount = {9}{10}",
                                GlobalState.GetCurrentThreadId(),
                                FormatOps.WrapOrNull(interpreter),
                                FormatOps.WrapOrNull(dateTime),
                                FormatOps.WrapOrNull(FormatOps.DelegateName(callback)),
                                FormatOps.WrapOrNull(clientData),
                                FormatOps.WrapOrNull(eventFlags),
                                FormatOps.WrapOrNull(oldPriority),
                                FormatOps.WrapOrNull(priority), idleEvents.Count,
                                maximumIdleCount, Environment.NewLine),
                                typeof(EventManager).Name);

                            maximumIdleCount = idleEvents.Count;
                        }
                    }

                    SignalEventEnqueued();

#if NOTIFY
                    if (interpreter != null)
                        /* IGNORED */
                        interpreter.CheckNotifications(
                            (idle ? NotifyType.Idle : NotifyType.None) |
                                NotifyType.Event, NotifyFlags.Queued,
                            new ObjectTriplet(dateTime, eventFlags, localEvent),
                            interpreter, clientData, null, null, ref error);
#endif

                    @event = localEvent;
                    return ReturnCode.Ok;
                }
                else
                {
                    error = "not accepting events";
                }
            }

            return ReturnCode.Error;
        }

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

        public ReturnCode QueueScript(
            DateTime dateTime,
            string text,
            EngineFlags engineFlags,
            SubstitutionFlags substitutionFlags,
            EventFlags eventFlags,
            ExpressionFlags expressionFlags,
            EventPriority priority,
            ref Result error
            )
        {
            CheckDisposed();

            IEvent @event = null;

            return QueueScript(
                dateTime, text, engineFlags, substitutionFlags, eventFlags,
                expressionFlags, priority, ref @event, ref error);
        }

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

        public ReturnCode QueueScript(
            DateTime dateTime,
            string text,
            EngineFlags engineFlags,
            SubstitutionFlags substitutionFlags,
            EventFlags eventFlags,
            ExpressionFlags expressionFlags,
            EventPriority priority,
            ref IEvent @event,
            ref Result error
            )
        {
            CheckDisposed();

            string name = FormatOps.Id(ScriptTypes.Queue, null,
                GlobalState.NextId(interpreter));

            IScript script = Script.Create(
                name, null, null, ScriptTypes.Queue, text, dateTime,
                EngineMode.EvaluateScript, ScriptFlags.None, engineFlags,
                substitutionFlags, eventFlags, expressionFlags,
                ClientData.Empty);

            return QueueEvent(name, dateTime, ScriptEventCallback,
                new ClientData(script), eventFlags, priority, ref @event,
                ref error);
        }

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

        public ReturnCode QueueScript(
            string name,
            DateTime dateTime,
            IScript script,
            EventFlags eventFlags,
            EventPriority priority,
            ref Result error
            )
        {
            CheckDisposed();

            IEvent @event = null;

            return QueueScript(name, dateTime, script, eventFlags, priority,
                ref @event, ref error);
        }

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

        public ReturnCode QueueScript(
            string name,
            DateTime dateTime,
            IScript script,
            EventFlags eventFlags,
            EventPriority priority,
            ref IEvent @event,
            ref Result error
            )
        {
            CheckDisposed();

            return QueueEvent(name, dateTime, ScriptEventCallback,
                new ClientData(script), eventFlags, priority, ref @event,
                ref error);
        }

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

        public ReturnCode DequeueAnyReadyEvent(
            DateTime dateTime,
            EventFlags eventFlags,
            EventPriority priority,
            bool strict,
            ref IEvent @event,
            ref Result error
            )
        {
            CheckDisposed();

            lock (syncRoot)
            {
                if ((events != null) || (idleEvents != null))
                {
                    int count = GetEventCount();

                    if (count > 0)
                    {
                        if (DequeueAnyReadyEvent(
                                events, dateTime, eventFlags, priority, true,
                                ref @event, ref error) == ReturnCode.Ok)
                        {
                            return ReturnCode.Ok;
                        }

                        int nonIdlecount = (events != null) ? events.Count : 0;

                        if (ShouldDequeueIdleEvent(eventFlags, nonIdlecount))
                        {
                            if (DequeueAnyReadyEvent(
                                    idleEvents, dateTime, eventFlags, priority,
                                    true, ref @event, ref error) == ReturnCode.Ok)
                            {
                                return ReturnCode.Ok;
                            }
                        }
                    }
                    else
                    {
                        if (strict)
                        {
                            error = "no events";
                        }
                        else
                        {
                            @event = null;
                            return ReturnCode.Ok;
                        }
                    }
                }
                else
                {
                    if (strict)
                    {
                        error = "not accepting events";
                    }
                    else
                    {
                        @event = null;
                        return ReturnCode.Ok;
                    }
                }
            }

            return ReturnCode.Error;
        }

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

        public ReturnCode ListEvents(
            string pattern,
            bool noCase,
            ref StringList list,
            ref Result error
            )
        {
            CheckDisposed();

            lock (syncRoot)
            {
                if ((events != null) || (idleEvents != null))
                {
                    StringList localList = new StringList();

                    if (events != null)
                    {
                        if (GenericOps<string>.FilterList(
                            new StringList(events.Values), localList,
                            Index.Invalid, Index.Invalid, ToStringFlags.None,
                            pattern, noCase, ref error) != ReturnCode.Ok)
                        {
                            return ReturnCode.Error;
                        }
                    }

                    if (idleEvents != null)
                    {
                        if (GenericOps<string>.FilterList(
                            new StringList(idleEvents.Values), localList,
                            Index.Invalid, Index.Invalid, ToStringFlags.None,
                            pattern, noCase, ref error) != ReturnCode.Ok)
                        {
                            return ReturnCode.Error;
                        }
                    }

                    list = localList;
                    return ReturnCode.Ok;
                }
                else
                {
                    error = "not accepting events";
                }
            }

            return ReturnCode.Error;
        }

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

        public ReturnCode CancelEvents(
            string nameOrScript,
            bool strict,
            bool all,
            ref Result error
            )
        {
            CheckDisposed();

            lock (syncRoot)
            {
                if ((events != null) || (idleEvents != null))
                {
                    EventQueue[] eventQueues = new EventQueue[] {
                        events, idleEvents
                    };

                    //
                    // NOTE: Have we actually removed any events yet?  If so,
                    //       how many?
                    //
                    int count = 0;

                    //
                    // NOTE: First, search all events by their "id"...
                    //
                    foreach (EventQueue eventQueue in eventQueues)
                    {
                        //
                        // NOTE: Just skip over null event queues.
                        //
                        if (eventQueue == null)
                            continue;

                        //
                        // NOTE: Are we dealing with idle events now?
                        //
                        bool idle = IsIdleEventQueue(eventQueue);

                        for (int index = eventQueue.Count - 1; index >= 0; index--)
                        {
                            IEvent @event = eventQueue[index];

                            if (@event == null)
                                continue;

                            if (MatchEventName(@event, nameOrScript))
                            {
                                eventQueue.RemoveAt(index);

#if NOTIFY
                                if (interpreter != null)
                                    /* IGNORED */
                                    interpreter.CheckNotifications(
                                        (idle ? NotifyType.Idle : NotifyType.None) |
                                            NotifyType.Event, NotifyFlags.Canceled,
                                        new ObjectPair(nameOrScript, @event),
                                        interpreter, null, null, null, ref error);
#endif

                                if (all)
                                {
                                    count++;
                                }
                                else
                                {
                                    if (CheckForEmptyQueue())
                                        SignalEmptyQueue();

                                    return ReturnCode.Ok;
                                }
                            }
                        }
                    }

                    //
                    // NOTE: Finally, search all events by their script text...
                    //
                    foreach (EventQueue eventQueue in eventQueues)
                    {
                        //
                        // NOTE: Just skip over null event queues.
                        //
                        if (eventQueue == null)
                            continue;

                        //
                        // NOTE: Are we dealing with idle events now?
                        //
                        bool idle = IsIdleEventQueue(eventQueue);

                        for (int index = eventQueue.Count - 1; index >= 0; index--)
                        {
                            IEvent @event = eventQueue[index];

                            if (@event == null)
                                continue;

                            if (!IsScriptEvent(@event))
                                continue;

                            IClientData clientData = @event.ClientData;

                            if (clientData == null)
                                continue;

                            IScript script = clientData.Data as IScript;

                            if (script == null)
                                continue;

                            lock (script.SyncRoot)
                            {
                                if (MatchScriptText(script, nameOrScript))
                                {
                                    eventQueue.RemoveAt(index);

#if NOTIFY
                                    if (interpreter != null)
                                        /* IGNORED */
                                        interpreter.CheckNotifications(
                                            (idle ? NotifyType.Idle : NotifyType.None) |
                                                NotifyType.Event, NotifyFlags.Canceled,
                                            new ObjectPair(nameOrScript, @event),
                                            interpreter, null, null, null, ref error);
#endif

                                    if (all)
                                    {
                                        count++;
                                    }
                                    else
                                    {
                                        if (CheckForEmptyQueue())
                                            SignalEmptyQueue();

                                        return ReturnCode.Ok;
                                    }
                                }
                            }
                        }
                    }

                    //
                    // NOTE: In strict mode, if we matched zero events, this is
                    //       a failure; otherwise, success.
                    //
                    if (!strict || (count > 0))
                    {
                        //
                        // NOTE: Only signal an empty queue if we actually
                        //       removed something.
                        //
                        if ((count > 0) && CheckForEmptyQueue())
                            SignalEmptyQueue();

                        return ReturnCode.Ok;
                    }
                    else
                    {
                        error = String.Format(
                            "event \"{0}\" doesn't exist",
                            nameOrScript);
                    }
                }
                else
                {
                    error = "not accepting events";
                }
            }

            return ReturnCode.Error;
        }

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

        public int GetSleepTime()
        {
            CheckDisposed();

            int result = DefaultSleepTime;

            if (interpreter != null)
                result = interpreter.SleepTime;

            return result;
        }

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

        public int GetMinimumSleepTime()
        {
            CheckDisposed();

            int result = GetSleepTime();

            if (result < MinimumSleepTime)
                result = MinimumSleepTime;

            return result;
        }

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

        public void Sleep(
            bool minimum
            )
        {
            CheckDisposed();

            HostOps.Sleep(interpreter, minimum ?
                GetMinimumSleepTime() :
                GetSleepTime());
        }

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

        public void Yield()
        {
            CheckDisposed();

            HostOps.Yield(interpreter);
        }

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

        public ReturnCode ProcessEvents(
            EventFlags eventFlags,
            EventPriority priority,
            int limit,
            bool stopOnError,
            bool errorOnEmpty,
            ref Result result
            )
        {
            CheckDisposed();

            ReturnCode code = ReturnCode.Ok;

            if (IsEnabled())
            {
                EnterLevel();

                try
                {
                    //
                    // NOTE: Probe for an available event in the queue.
                    //
                    bool bgError = true;
                    IEvent @event = null;
                    int count = 0;
                    ResultList errors = null;

                    while (IsEnabled() &&
                        (DequeueAnyReadyEvent(GetNow(), eventFlags, priority,
                             false, ref @event) == ReturnCode.Ok) &&
                        (@event != null) && ((limit <= 0) || (count++ < limit)))
                    {
                        //
                        // NOTE: If a callback delegate was provided, invoke it
                        //       now.
                        //
                        if (@event.Callback != null)
                        {
                            Result localResult = null;

                            try
                            {
                                //
                                // NOTE: Invoke the callback delegate for this
                                //       event.
                                //
                                code = @event.Callback(
                                    @event.Interpreter, @event.ClientData,
                                    ref localResult);

                                //
                                // NOTE: Set the code and result for this event
                                //       object and signal it as ready.
                                //
                                Result setError = null;

                                //
                                // HACK: The error line is unknown here because
                                //       we do not even know if a script was
                                //       evaluated.
                                //
                                if (!@event.SetResult(
                                        true, code, localResult, 0, ref setError))
                                {
                                    DebugOps.Complain(@event.Interpreter,
                                        ReturnCode.Error, setError);
                                }

                                //
                                // NOTE: Now, handle the callback return code.
                                //
                                if (code == ReturnCode.Return)
                                {
                                    //
                                    // NOTE: Skip process all further events
                                    //       and return an overall success.
                                    //
                                    code = ReturnCode.Ok;
                                    break;
                                }
                                if (code == ReturnCode.Continue)
                                {
                                    //
                                    // NOTE: Skip background error processing
                                    //       and re-checking of the interpreter
                                    //       event processing flag.
                                    //
                                    continue;
                                }
                                if (code != ReturnCode.Ok)
                                {
                                    //
                                    // NOTE: This is considered to be an error,
                                    //       save it.
                                    //
                                    if (errors == null)
                                        errors = new ResultList();

                                    errors.Add(localResult);

                                    //
                                    // NOTE: Ok, now check if we need to stop
                                    //       processing further events.
                                    //
                                    if (code == ReturnCode.Break)
                                    {
                                        //
                                        // NOTE: Skip background error
                                        //       processing and all further
                                        //       events and then return an
                                        //       overall failure.
                                        //
                                        code = ReturnCode.Error;
                                        break;
                                    }
                                }
                            }
                            catch (Exception e)
                            {
                                localResult = e;

                                if (errors == null)
                                    errors = new ResultList();

                                errors.Add(localResult);
                                code = ReturnCode.Error;
                            }

                            //
                            // NOTE: Was there an error and should be handle it
                            //       via the background error handler?
                            //
                            if ((code != ReturnCode.Ok) && bgError)
                            {
                                Interpreter interpreter = @event.Interpreter;

                                //
                                // NOTE: If there is no interpreter context, do
                                //       nothing.
                                //
                                if (interpreter != null)
                                {
                                    /* IGNORED */
                                    HandleBackgroundError(interpreter, code,
                                        localResult, ref bgError);
                                }
                            }

                            //
                            // NOTE: Did we hit an error?
                            //
                            if (code != ReturnCode.Ok)
                            {
                                Interpreter interpreter = @event.Interpreter;

                                //
                                // NOTE: Are we stopping upon hitting an error?
                                //       If so, stop now.  Also, always stop if
                                //       the interpreter has been disposed.
                                //
                                if (stopOnError ||
                                    Interpreter.IsDeletedOrDisposed(interpreter))
                                {
                                    //
                                    // NOTE: Stop on the first error.
                                    //
                                    break;
                                }
                                else
                                {
                                    TraceOps.DebugTrace(String.Format(
                                        "{0}: ProcessEvents: error ignored, " +
                                        "interpreter = {1}, eventFlags = {2}, " +
                                        "limit = {3}, stopOnError = {4}, " +
                                        "errorOnEmpty = {5}, code = {6}, " +
                                        "localResult = {7}, result = {8}{9}",
                                        GlobalState.GetCurrentThreadId(),
                                        FormatOps.WrapOrNull(interpreter),
                                        FormatOps.WrapOrNull(eventFlags),
                                        limit, stopOnError, errorOnEmpty, code,
                                        FormatOps.WrapOrNull(true, true, localResult),
                                        FormatOps.WrapOrNull(true, true, result),
                                        Environment.NewLine),
                                        typeof(EventManager).Name);

                                    //
                                    // NOTE: Ignore the error and keep going.
                                    //
                                    code = ReturnCode.Ok;
                                }
                            }
                        }
                    }

                    if (errorOnEmpty)
                    {
                        if (errors == null)
                            errors = new ResultList();

                        errors.Add(String.Format(
                            "no more events are ready, processed: {0}",
                            count));

                        code = ReturnCode.Error;
                    }

                    if (errors != null)
                        result = errors;
                }
                finally
                {
                    ExitLevel();
                }
            }

            return code;
        }

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

        public ReturnCode DoOneEvent(
            EventFlags eventFlags,
            EventPriority priority,
            int limit, /* NOTE: Pass zero for ALL. */
            bool stopOnError,
            bool errorOnEmpty,
            bool userInterface,
            ref Result result
            )
        {
            CheckDisposed();

            //
            // NOTE: Attempt to process some [or all] pending events [possibly]
            //       stopping if an error is encountered.
            //
            ReturnCode code = ProcessEvents(
                eventFlags, priority, limit, stopOnError, errorOnEmpty,
                ref result);

#if NATIVE && WINDOWS && WINFORMS
            //
            // NOTE: If necessary, process all Windows messages from the queue.
            //
            if ((code == ReturnCode.Ok) && userInterface)
                code = WindowOps.ProcessEvents(interpreter, ref result);
#endif

            return code;
        }

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

        public ReturnCode ServiceEvents(
            EventFlags eventFlags,
            EventPriority priority,
            int limit,
            bool noCancel,
            bool stopOnError,
            bool errorOnEmpty,
            bool userInterface,
            ref Result result
            )
        {
            CheckDisposed();

            ReturnCode code;

            //
            // NOTE: Keep processing asynchronous events until the interpreter
            //       is no longer valid.
            //
            while ((code = Interpreter.EventReady(
                    interpreter, noCancel, ref result)) == ReturnCode.Ok)
            {
                //
                // NOTE: Attempt to process some [or all] pending events [possibly]
                //       stopping if an error is encountered.
                //
                code = DoOneEvent(eventFlags, priority, limit, stopOnError,
                    errorOnEmpty, userInterface, ref result);

                //
                // NOTE: If we encountered an error processing events, break out
                //       of the loop and return the error code and result to the
                //       caller.
                //
                if (code != ReturnCode.Ok)
                    break;

                //
                // NOTE: We always yield to other running threads.  This also gives
                //       them an opportunity to cancel the script in progress on
                //       this thread and/or update the variable we are waiting for.
                //
                // TODO: This default is way too fast.  We may want to introduce a
                //       slight delay (half a second?) here.
                //
                HostOps.Sleep(interpreter, GetMinimumSleepTime());
            }

            return code;
        }

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

        public ReturnCode WaitForEmptyQueue(
            int milliseconds,
            ref Result error
            )
        {
            CheckDisposed();

            EventWaitHandle emptyEvent;

            lock (syncRoot)
            {
                emptyEvent = this.emptyEvent;
            }

            if (emptyEvent != null)
            {
                //
                // BUGFIX: Do not try waiting for an empty queue if it is
                //         already empty.  However, we must at least try to
                //         wait [without blocking this thread]; otherwise, if
                //         the event is currently signaled will still be
                //         signaled when the next caller attempts to wait.
                //
                if (ThreadOps.WaitEvent(emptyEvent, 0))
                {
#if DEBUG && VERBOSE
                    TraceOps.DebugTrace(String.Format(
                        "{0}: WaitForEmptyQueue: queue was already emptied{1}",
                        GlobalState.GetCurrentThreadId(), Environment.NewLine),
                        typeof(EventManager).Name);
#endif

                    return ReturnCode.Ok;
                }
                else if (CheckForEmptyQueue())
                {
#if DEBUG && VERBOSE
                    TraceOps.DebugTrace(String.Format(
                        "{0}: WaitForEmptyQueue: queue is already empty{1}",
                        GlobalState.GetCurrentThreadId(), Environment.NewLine),
                        typeof(EventManager).Name);
#endif

                    return ReturnCode.Ok;
                }
                else if (ThreadOps.WaitEvent(emptyEvent, milliseconds))
                {
#if DEBUG && VERBOSE
                    TraceOps.DebugTrace(String.Format(
                        "{0}: WaitForEmptyQueue: queue emptied after waiting{1}",
                        GlobalState.GetCurrentThreadId(), Environment.NewLine),
                        typeof(EventManager).Name);
#endif

                    return ReturnCode.Ok;
                }
                else if (CheckForEmptyQueue())
                {
#if DEBUG && VERBOSE
                    TraceOps.DebugTrace(String.Format(
                        "{0}: WaitForEmptyQueue: queue emptied after timeout{1}",
                        GlobalState.GetCurrentThreadId(), Environment.NewLine),
                        typeof(EventManager).Name);
#endif

                    return ReturnCode.Ok;
                }
                else
                {
#if DEBUG && VERBOSE
                    TraceOps.DebugTrace(String.Format(
                        "{0}: WaitForEmptyQueue: timeout, {1} milliseconds{2}",
                        GlobalState.GetCurrentThreadId(), milliseconds,
                        Environment.NewLine), typeof(EventManager).Name);
#endif

                    error = "failed to wait for empty queue";
                }
            }
            else
            {
                error = "cannot wait for empty queue";
            }

            return ReturnCode.Error;
        }

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

        public ReturnCode WaitForEventEnqueued(
            int milliseconds,
            ref Result error
            )
        {
            CheckDisposed();

            EventWaitHandle enqueueEvent;

            lock (syncRoot)
            {
                enqueueEvent = this.enqueueEvent;
            }

            if (enqueueEvent != null)
            {
                if (ThreadOps.WaitEvent(enqueueEvent, 0))
                {
#if DEBUG && VERBOSE
                    TraceOps.DebugTrace(String.Format(
                        "{0}: WaitForEventEnqueued: event was already enqueued{1}",
                        GlobalState.GetCurrentThreadId(), Environment.NewLine),
                        typeof(EventManager).Name);
#endif

                    return ReturnCode.Ok;
                }
                else if (CheckForEventEnqueued())
                {
#if DEBUG && VERBOSE
                    TraceOps.DebugTrace(String.Format(
                        "{0}: WaitForEventEnqueued: event is already enqueued{1}",
                        GlobalState.GetCurrentThreadId(), Environment.NewLine),
                        typeof(EventManager).Name);
#endif

                    return ReturnCode.Ok;
                }
                else if (ThreadOps.WaitEvent(enqueueEvent, milliseconds))
                {
#if DEBUG && VERBOSE
                    TraceOps.DebugTrace(String.Format(
                        "{0}: WaitForEventEnqueued: event enqueued after waiting{1}",
                        GlobalState.GetCurrentThreadId(), Environment.NewLine),
                        typeof(EventManager).Name);
#endif

                    return ReturnCode.Ok;
                }
                else if (CheckForEventEnqueued())
                {
#if DEBUG && VERBOSE
                    TraceOps.DebugTrace(String.Format(
                        "{0}: WaitForEventEnqueued: event enqueued after timeout{1}",
                        GlobalState.GetCurrentThreadId(), Environment.NewLine),
                        typeof(EventManager).Name);
#endif

                    return ReturnCode.Ok;
                }
                else
                {
#if DEBUG && VERBOSE
                    TraceOps.DebugTrace(String.Format(
                        "{0}: WaitForEventEnqueued: timeout, {1} milliseconds{2}",
                        GlobalState.GetCurrentThreadId(), milliseconds,
                        Environment.NewLine), typeof(EventManager).Name);
#endif

                    error = "failed to wait for event enqueued";
                }
            }
            else
            {
                error = "cannot wait for event enqueued";
            }

            return ReturnCode.Error;
        }
        #endregion

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

        #region IDisposable "Pattern" Members
        private bool disposed;
        private void CheckDisposed() /* throw */
        {
#if THROW_ON_DISPOSED
            if (disposed && Engine.IsThrowOnDisposed(interpreter, true))
                throw new InterpreterDisposedException(typeof(EventManager));
#endif
        }

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

        private /* protected virtual */ void Dispose(
            bool disposing
            )
        {
            TraceOps.DebugTrace(String.Format(
                "{0}: Dispose: called, disposing = {1}, disposed = {2}{3}",
                GlobalState.GetCurrentThreadId(), disposing, disposed,
                Environment.NewLine), typeof(EventManager).Name);

            if (!disposed)
            {
                if (disposing)
                {
                    ////////////////////////////////////
                    // dispose managed resources here...
                    ////////////////////////////////////

                    lock (syncRoot)
                    {
                        lastNow = DateTime.MinValue;

                        Interlocked.Exchange(ref maximumCount, 0);
                        Interlocked.Exchange(ref maximumIdleCount, 0);
                        Interlocked.Exchange(ref enabled, 0);

                        if (events != null)
                        {
                            events.Clear();
                            events = null;
                        }

                        if (idleEvents != null)
                        {
                            idleEvents.Clear();
                            idleEvents = null;
                        }

                        //
                        // NOTE: Close the queue events (that we own).
                        //
                        ThreadOps.CloseEvent(ref emptyEvent);
                        ThreadOps.CloseEvent(ref enqueueEvent);

                        //
                        // NOTE: Clear out the user events array (i.e. do not
                        //       dispose it as we do not own it).
                        //
                        if (userEvents != null)
                            userEvents = null;

                        //
                        // NOTE: Clear out the parent interpreter (i.e. do not
                        //       dispose it as we do not own it).
                        //
                        if (interpreter != null)
                            interpreter = null; /* NOT OWNED, DO NOT DISPOSE. */
                    }
                }

                //////////////////////////////////////
                // release unmanaged resources here...
                //////////////////////////////////////

                disposed = true;
            }
        }
        #endregion

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

        #region IDisposable Members
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        #endregion

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

        #region Destructor
        ~EventManager()
        {
            Dispose(false);
        }
        #endregion
    }
}
