/// <summary>
 ///     This removes the event from the queue and allows it to be fired again
 /// </summary>
 /// <param name="QIS"></param>
 public void EventComplete(QueueItemStruct QIS)
 {
     lock (QIS.ID.ScriptEventLock)
     {
         scriptEvents eventType = (scriptEvents) Enum.Parse(typeof (scriptEvents), QIS.functionName);
         switch (eventType)
         {
             case scriptEvents.timer:
                 QIS.ID.TimerInQueue = false;
                 break;
             case scriptEvents.control:
                 if (QIS.ID.ControlEventsInQueue > 0)
                     QIS.ID.ControlEventsInQueue--;
                 break;
             case scriptEvents.collision:
                 QIS.ID.CollisionInQueue = false;
                 break;
             case scriptEvents.collision_end:
                 QIS.ID.CollisionInQueue = false;
                 break;
             case scriptEvents.moving_end:
                 QIS.ID.MovingInQueue = false;
                 break;
             case scriptEvents.touch:
                 QIS.ID.TouchInQueue = false;
                 break;
             case scriptEvents.touch_end:
                 QIS.ID.TouchInQueue = false;
                 break;
             case scriptEvents.land_collision:
                 QIS.ID.LandCollisionInQueue = false;
                 break;
             case scriptEvents.land_collision_end:
                 QIS.ID.LandCollisionInQueue = false;
                 break;
             case scriptEvents.sensor:
                 QIS.ID.SensorInQueue = false;
                 break;
             case scriptEvents.no_sensor:
                 QIS.ID.NoSensorInQueue = false;
                 break;
             case scriptEvents.at_target:
                 QIS.ID.AtTargetInQueue = false;
                 break;
             case scriptEvents.not_at_target:
                 QIS.ID.NotAtTargetInQueue = false;
                 break;
             case scriptEvents.at_rot_target:
                 QIS.ID.AtRotTargetInQueue = false;
                 break;
             case scriptEvents.not_at_rot_target:
                 QIS.ID.NotAtRotTargetInQueue = false;
                 break;
             case scriptEvents.changed:
                 Changed changed;
                 if (QIS.param[0] is Changed)
                 {
                     changed = (Changed) QIS.param[0];
                 }
                 else
                 {
                     changed = (Changed) (((LSL_Types.LSLInteger) QIS.param[0]).value);
                 }
                 QIS.ID.ChangedInQueue.Remove(changed);
                 break;
         }
     }
 }
        public bool CheckAddEventToQueue(QueueItemStruct itm)
        {
            if (funcsToDrop.Contains(itm.functionName))
                return false;//Drop them, don't enqueue

            lock (m_eventQueueLock)
            {
                if (m_eventQueueTimer == null)//Have it try again in 5ms
                    m_eventQueueTimer = new Timer(EventQueuePoke, null, 5, 5);

                m_eventQueue.Enqueue(itm);
            }
            return false;
        }
        public bool SetEventParams(QueueItemStruct itm)
        {
            if (Suspended || !Running)
                return false; //No suspended scripts...
            if (itm.llDetectParams.Length > 0)
                LastDetectParams = itm.llDetectParams;

            if (itm.functionName == "state_entry" || itm.functionName == "state_exit" || itm.functionName == "on_rez")
                return true;

            long NowTicks = Util.EnvironmentTickCount();

            if (EventDelayTicks != 0)
            {
                if (NowTicks < NextEventTimeTicks)
                    return false;
                NextEventTimeTicks = NowTicks + EventDelayTicks;
            }
            switch (itm.functionName)
            {
                    //Times pulled from http://wiki.secondlife.com/wiki/LSL_Delay
                case "touch": //Limits for 0.1 seconds
                case "touch_start":
                case "touch_end":
                    if (NowTicks < NextEventDelay[itm.functionName])
                        return CheckAddEventToQueue(itm);
                    NextEventDelay[itm.functionName] = NowTicks + (long) (TouchEventDelayTicks*TicksPerMillisecond);
                    break;
                case "timer": //Settable timer limiter
                    if (NowTicks < NextEventDelay[itm.functionName])
                        return CheckAddEventToQueue(itm);
                    NextEventDelay[itm.functionName] = NowTicks + (long) (TimerEventDelayTicks*TicksPerMillisecond);
                    break;
                case "collision":
                case "collision_start":
                case "collision_end":
                case "land_collision":
                case "land_collision_start":
                case "land_collision_end":
                    if (NowTicks < NextEventDelay[itm.functionName])
                        return CheckAddEventToQueue(itm);
                    NextEventDelay[itm.functionName] = NowTicks + (long) (CollisionEventDelayTicks*TicksPerMillisecond);
                    break;
                case "control":
                    if (NowTicks < NextEventDelay[itm.functionName])
                        return CheckAddEventToQueue(itm);
                    NextEventDelay[itm.functionName] = NowTicks + (long) (0.05f*TicksPerMillisecond);
                    break;
                default: //Default is 0.05 seconds for event limiting
                    if (!NextEventDelay.ContainsKey(itm.functionName))
                        break; //If it doesn't exist, we don't limit it
                    if (NowTicks < NextEventDelay[itm.functionName])
                        return CheckAddEventToQueue(itm);
                    NextEventDelay[itm.functionName] = NowTicks + (long) (DefaultEventDelayTicks*TicksPerMillisecond);
                    break;
            }
            //Add the event to the stats
            ScriptScore++;
            m_ScriptEngine.ScriptEPS++;
            return true;
        }
        public bool EventSchProcessQIS(ref QueueItemStruct QIS)
        {
            try
            {
                Exception ex = null;
                EnumeratorInfo Running = QIS.ID.Script.ExecuteEvent(QIS.State,
                                                                    QIS.functionName,
                                                                    QIS.param, QIS.CurrentlyAt, out ex);

                if (ex != null)
                {
                    //Check exceptions, some are ours to deal with, and others are to be logged
                    if (ex.Message.Contains("SelfDeleteException"))
                    {
                        if (QIS.ID.Part != null && QIS.ID.Part.ParentEntity != null)
                        {
                            IBackupModule backup =
                                QIS.ID.Part.ParentEntity.Scene.RequestModuleInterface<IBackupModule>();
                            if (backup != null)
                                backup.DeleteSceneObjects(
                                    new ISceneEntity[1] {QIS.ID.Part.ParentEntity}, true, true);
                        }
                    }
                    else if (ex.Message.Contains("ScriptDeleteException"))
                    {
                        if (QIS.ID.Part != null && QIS.ID.Part.ParentEntity != null)
                            QIS.ID.Part.Inventory.RemoveInventoryItem(QIS.ID.ItemID);
                    }
                        //Log it for the user
                    else if (!(ex.Message.Contains("EventAbortException")) &&
                             !(ex.Message.Contains("MinEventDelayException")))
                        QIS.ID.DisplayUserNotification(ex.ToString(), "executing", false, true);
                    EventManager.EventComplete(QIS);
                    return false;
                }
                else if (Running != null)
                {
                    //Did not finish so requeue it
                    QIS.CurrentlyAt = Running;
                    QIS.RunningNumber++;
                    return true; //Do the return... otherwise we open the queue for this event back up
                }
            }
            catch (Exception ex)
            {
                //Error, tell the user
                QIS.ID.DisplayUserNotification(ex.ToString(), "executing", false, true);
            }
            //Tell the event manager about it so that the events will be removed from the queue
            EventManager.EventComplete(QIS);
            return false;
        }
        public void EventSchExec(QueueItemStruct QIS)
        {
            if (QIS.ID == null || QIS.ID.Script == null)
                return;

            if (!QIS.ID.Running)
            {
                //do only state_entry and on_rez
                if (QIS.functionName != "state_entry"
                    || QIS.functionName != "on_rez")
                {
                    return;
                }
            }

            //Check the versionID so that we can kill events
            if (QIS.functionName != "link_message" &&
                QIS.VersionID != Interlocked.Read(ref QIS.ID.VersionID))
            {
                MainConsole.Instance.WarnFormat("FOUND BAD VERSION ID, OLD {0}, NEW {1}, FUNCTION NAME {2}",
                                                QIS.VersionID,
                                                Interlocked.Read(ref QIS.ID.VersionID), QIS.functionName);
                //return;
            }

            if(MainConsole.Instance.IsTraceEnabled)
                MainConsole.Instance.TraceFormat("[DNE]: Running Event {0} in object {1} in region {2}",
                                           QIS.functionName, QIS.ID.Part.ToString(),
                                           QIS.ID.Part.ParentEntity.Scene.RegionInfo.RegionName);
            if (!EventSchProcessQIS(ref QIS)) //Execute the event
            {
                //All done
                QIS.EventsProcData.State = ScriptEventsState.Idle;
            }
            else
            {
                if (QIS.CurrentlyAt.SleepTo.Ticks != 0)
                {
                    QIS.EventsProcData.TimeCheck = QIS.CurrentlyAt.SleepTo;
                    QIS.EventsProcData.State = ScriptEventsState.Sleep;
                    //If it is greater, we need to check sooner for this one
                    if (NextSleepersTest.Ticks > QIS.CurrentlyAt.SleepTo.Ticks)
                        NextSleepersTest = QIS.CurrentlyAt.SleepTo;
                    lock (SleepingScriptEvents)
                    {
                        SleepingScriptEvents.Enqueue(QIS, QIS.CurrentlyAt.SleepTo.Ticks);
                        SleepingScriptEventCount++;
                    }
                }
                else
                {
                    QIS.EventsProcData.State = ScriptEventsState.Running;
                    this.ScriptEvents.Enqueue(QIS);
                }
            }
        }
        public void eventLoop()
        {
            int numberOfEmptyWork = 0;
            while (!m_ScriptEngine.ConsoleDisabled && !m_ScriptEngine.Disabled &&
                   m_ScriptEngine.Scene.ShouldRunHeartbeat)
            {
                //int numScriptsProcessed = 0;
                int numSleepScriptsProcessed = 0;
                //const int minNumScriptsToProcess = 1;
                //processMoreScripts:
                QueueItemStruct QIS = new QueueItemStruct();
                bool found = false;

                //Check whether it is time, and then do the thread safety piece
                if (Interlocked.CompareExchange(ref m_CheckingSleepers, 1, 0) == 0)
                {
                    lock (SleepingScriptEvents)
                    {
                    restart:
                        if (SleepingScriptEvents.Count > 0)
                        {
                            QIS = SleepingScriptEvents.Dequeue().Value;
                            found = true;
                            if (QIS.RunningNumber > 2 && SleepingScriptEventCount > 0 &&
                                numSleepScriptsProcessed < SleepingScriptEventCount)
                            {
                                QIS.RunningNumber = 1;
                                SleepingScriptEvents.Enqueue(QIS, QIS.EventsProcData.TimeCheck.Ticks);
                                numSleepScriptsProcessed++;found = false;
                                found = false;
                                goto restart;
                            }
                        }
                    }
                    if (found)
                    {
                        if (QIS.EventsProcData.TimeCheck.Ticks < DateTime.Now.Ticks)
                        {
                            DateTime NextTime = DateTime.MaxValue;
                            lock (SleepingScriptEvents)
                            {
                                if (SleepingScriptEvents.Count > 0)
                                    NextTime = SleepingScriptEvents.Peek().Value.EventsProcData.TimeCheck;
                                //Now add in the next sleep time
                                NextSleepersTest = NextTime;

                                //All done
                                Interlocked.Exchange(ref m_CheckingSleepers, 0);
                            }

                            //Execute the event
                            EventSchExec(QIS);
                            lock (SleepingScriptEvents)
                                SleepingScriptEventCount--;
                            //numScriptsProcessed++;
                        }
                        else
                        {
                            lock (SleepingScriptEvents)
                            {
                                NextSleepersTest = QIS.EventsProcData.TimeCheck;
                                SleepingScriptEvents.Enqueue(QIS, QIS.EventsProcData.TimeCheck.Ticks);
                                //All done
                                Interlocked.Exchange(ref m_CheckingSleepers, 0);
                            }
                        }
                    }
                    else //No more left, don't check again
                    {
                        lock (SleepingScriptEvents)
                        {
                            NextSleepersTest = DateTime.MaxValue;
                            //All done
                            Interlocked.Exchange(ref m_CheckingSleepers, 0);
                        }
                    }
                }
                int timeToSleep = 5;
                //If we can, get the next event
                if (Interlocked.CompareExchange(ref m_CheckingEvents, 1, 0) == 0)
                {
                    if (ScriptEvents.TryDequeue(out QIS))
                    {
                        Interlocked.Exchange(ref m_CheckingEvents, 0);
            #if Debug
                        MainConsole.Instance.Warn(QIS.functionName + "," + ScriptEvents.Count);
            #endif
                        EventSchExec(QIS);
                        //numScriptsProcessed++;
                    }
                    else
                        Interlocked.Exchange(ref m_CheckingEvents, 0);
                }
                //Process a bunch each time
                //if (ScriptEventCount > 0 && numScriptsProcessed < minNumScriptsToProcess)
                //    goto processMoreScripts;

                if (ScriptEvents.Count == 0 && NextSleepersTest.Ticks != DateTime.MaxValue.Ticks)
                    timeToSleep = (int) (NextSleepersTest - DateTime.Now).TotalMilliseconds;
                if (timeToSleep < 5)
                    timeToSleep = 5;
                if (timeToSleep > 50)
                    timeToSleep = 50;

                if (SleepingScriptEventCount == 0 && ScriptEvents.Count == 0)
                {
                    numberOfEmptyWork++;
                    if (numberOfEmptyWork > EMPTY_WORK_KILL_THREAD_TIME)
                        //Don't break immediately, otherwise we have to wait to spawn more threads
                    {
                        break; //No more events, end
                    }
                    else if (numberOfEmptyWork > EMPTY_WORK_KILL_THREAD_TIME/20)
                        timeToSleep += 10;
                }
                else if (Interlocked.Read(ref scriptThreadpool.nthreads) >
                         (ScriptEvents.Count + (int) ((SleepingScriptEventCount/2f + 0.5f))) ||
                         Interlocked.Read(ref scriptThreadpool.nthreads) > MaxScriptThreads)
                {
                    numberOfEmptyWork++;
                    if (numberOfEmptyWork > (EMPTY_WORK_KILL_THREAD_TIME/2)) //Don't break immediately
                    {
                        break; //Too many threads, kill some off
                    }
                    else if (numberOfEmptyWork > EMPTY_WORK_KILL_THREAD_TIME/20)
                        timeToSleep += 5;
                }
                else
                    numberOfEmptyWork /= 2; //Cut it down, but don't zero it out, as this may just be one event
            #if Debug
                MainConsole.Instance.Warn ("Sleep: " + timeToSleep);
            #endif
                Interlocked.Increment(ref scriptThreadpool.nSleepingthreads);
                Thread.Sleep(timeToSleep);
                Interlocked.Decrement(ref scriptThreadpool.nSleepingthreads);
            }
        }
        public void AddEventSchQueue(ScriptData ID, string FunctionName, DetectParams[] qParams, EventPriority priority,
                                     params object[] param)
        {
            QueueItemStruct QIS = new QueueItemStruct
            {
                EventsProcData = new ScriptEventsProcData(),
                ID = ID,
                functionName = FunctionName,
                llDetectParams = qParams,
                param = param,
                VersionID = Interlocked.Read(ref ID.VersionID),
                State = ID.State,
                CurrentlyAt = null
            };

            if (ID == null || ID.Script == null || ID.IgnoreNew)
                return;

            if (!ID.SetEventParams(QIS)) // check events delay rules
                return;

            ScriptEvents.Enqueue(QIS);

            long threadCount = Interlocked.Read(ref scriptThreadpool.nthreads);
            if (threadCount == 0 || threadCount < (ScriptEvents.Count + (SleepingScriptEventCount/2))*EventPerformance)
            {
                scriptThreadpool.QueueEvent(eventLoop, 2);
            }
        }
        public bool AddEventSchQIS(QueueItemStruct QIS, EventPriority priority)
        {
            if (QIS.ID == null || QIS.ID.IgnoreNew)
            {
                EventManager.EventComplete(QIS);
                return false;
            }

            if (QIS.ID.Script == null)
            {
                QIS.ID.CheckAddEventToQueue(QIS);
                return false;
            }

            if (!QIS.ID.SetEventParams(QIS)) // check events delay rules
            {
                EventManager.EventComplete(QIS);
                return false;
            }

            QIS.CurrentlyAt = null;

            if (priority == EventPriority.Suspended || priority == EventPriority.Continued)
            {
                lock (SleepingScriptEvents)
                {
                    long time = priority == EventPriority.Suspended
                                    ? DateTime.Now.AddMilliseconds(10).Ticks
                                    : DateTime.Now.Ticks;
                    //Let it sleep for 10ms so that other scripts can process before it, any repeating plugins ought to use this
                    SleepingScriptEvents.Enqueue(QIS, time);
                    SleepingScriptEventCount++;
            #if Debug
                MainConsole.Instance.Warn (ScriptEventCount + ", " + QIS.functionName);
            #endif
                }
            }
            else
                ScriptEvents.Enqueue(QIS);

            long threadCount = Interlocked.Read(ref scriptThreadpool.nthreads);
            if (threadCount == 0 || threadCount < (ScriptEvents.Count + (SleepingScriptEventCount/2))*EventPerformance)
            {
                scriptThreadpool.QueueEvent(eventLoop, 2);
            }
            return true;
        }