/*
 * Copyright (c) Contributors, http://opensimulator.org/
 * See CONTRIBUTORS.TXT for a full list of copyright holders.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSimulator Project nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Threading;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Remoting.Lifetime;
using System.Security.Policy;
using System.IO;
using System.Xml;
using System.Text;
using OpenMetaverse;
using OpenSim.Framework;
using OpenSim.Region.ScriptEngine.Interfaces;
using OpenSim.Region.ScriptEngine.Shared;
using OpenSim.Region.ScriptEngine.Shared.Api;
using OpenSim.Region.ScriptEngine.Shared.ScriptBase;
using OpenSim.Region.ScriptEngine.Yengine;
using OpenSim.Region.Framework.Scenes;
using log4net;

using LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat;
using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger;
using LSL_Key = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString;
using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list;
using LSL_Rotation = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Quaternion;
using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString;
using LSL_Vector = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3;

// This class exists in the main app domain
//
namespace OpenSim.Region.ScriptEngine.Yengine
{
    /**
     * @brief Which queue it is in as far as running is concerned,
     *        ie, m_StartQueue, m_YieldQueue, m_SleepQueue, etc.
     * Allowed transitions:
     *   Starts in CONSTRUCT when constructed
     *   CONSTRUCT->ONSTARTQ              : only by thread that constructed and compiled it
     *   IDLE->ONSTARTQ,RESETTING         : by any thread but must have m_QueueLock when transitioning
     *   ONSTARTQ->RUNNING,RESETTING      : only by thread that removed it from m_StartQueue
     *   ONYIELDQ->RUNNING,RESETTING      : only by thread that removed it from m_YieldQueue
     *   ONSLEEPQ->REMDFROMSLPQ           : by any thread but must have m_SleepQueue when transitioning
     *   REMDFROMSLPQ->ONYIELDQ,RESETTING : only by thread that removed it from m_SleepQueue
     *   RUNNING->whatever1               : only by thread that transitioned it to RUNNING
     *                                      whatever1 = IDLE,ONSLEEPQ,ONYIELDQ,ONSTARTQ,SUSPENDED,FINISHED
     *   FINSHED->whatever2               : only by thread that transitioned it to FINISHED
     *                                      whatever2 = IDLE,ONSTARTQ,DISPOSED
     *   SUSPENDED->ONSTARTQ              : by any thread (NOT YET IMPLEMENTED, should be under some kind of lock?)
     *   RESETTING->ONSTARTQ              : only by the thread that transitioned it to RESETTING
     */
    public enum XMRInstState
    {
        CONSTRUCT,     // it is being constructed
        IDLE,          // nothing happening (finished last event and m_EventQueue is empty)
        ONSTARTQ,      // inserted on m_Engine.m_StartQueue
        RUNNING,       // currently being executed by RunOne()
        ONSLEEPQ,      // inserted on m_Engine.m_SleepQueue
        REMDFROMSLPQ,  // removed from m_SleepQueue but not yet on m_YieldQueue
        ONYIELDQ,      // inserted on m_Engine.m_YieldQueue
        FINISHED,      // just finished handling an event
        SUSPENDED,     // m_SuspendCount > 0
        RESETTING,     // being reset via external call
        DISPOSED       // has been disposed
    }

    public partial class XMRInstance: XMRInstAbstract, IDisposable
    {
        /******************************************************************\
         *  This module contains the instance variables for XMRInstance.  *
        \******************************************************************/

        public const int MAXEVENTQUEUE = 64;

        public static readonly DetectParams[] zeroDetectParams = new DetectParams[0];
        public static readonly object[] zeroObjectArray = new object[0];

        public static readonly ILog m_log =
            LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

        public XMRInstance m_NextInst;  // used by XMRInstQueue
        public XMRInstance m_PrevInst;

        // For a given m_Item.AssetID, do we have the compiled object code and where
        // is it?
        public static object m_CompileLock = new object();
        private static Dictionary<string, ScriptObjCode> m_CompiledScriptObjCode = new Dictionary<string, ScriptObjCode>();

        public XMRInstState m_IState;

        public bool m_ForceRecomp = false;
        public SceneObjectPart m_Part = null;
        public uint m_LocalID = 0;
        public TaskInventoryItem m_Item = null;
        public UUID m_ItemID;
        public UUID m_PartUUID;
        private string m_CameFrom;
        private string m_ScriptObjCodeKey;

        private Yengine m_Engine = null;
        private string m_ScriptBasePath;
        private string m_StateFileName;
        public string m_SourceCode;
        public bool m_PostOnRez;
        private DetectParams[] m_DetectParams = null;
        public int m_StartParam = 0;
        public StateSource m_StateSource;
        public string m_DescName;
        private bool[] m_HaveEventHandlers;
        public int m_StackSize;
        public int m_HeapSize;
        private ArrayList m_CompilerErrors;
        private DateTime m_LastRanAt = DateTime.MinValue;
        private string m_RunOnePhase = "hasn't run";
        private string m_CheckRunPhase = "hasn't checked";
        public int m_InstEHEvent = 0;  // number of events dequeued (StartEventHandler called)
        public int m_InstEHSlice = 0;  // number of times handler timesliced (ResumeEx called)
        public double m_CPUTime = 0;  // accumulated CPU time (milliseconds)
        public double m_SliceStart = 0;  // when did current exec start

        // If code needs to have both m_QueueLock and m_RunLock,
        // be sure to lock m_RunLock first then m_QueueLock, as
        // that is the order used in RunOne().
        // These locks are currently separated to allow the script
        // to call API routines that queue events back to the script.
        // If we just had one lock, then the queuing would deadlock.

        // guards m_DetachQuantum, m_EventQueue, m_EventCounts, m_Running, m_Suspended
        public Object m_QueueLock = new Object();

        // true iff allowed to accept new events
        public bool m_Running = true;

        // queue of events that haven't been acted upon yet
        public LinkedList<EventParams> m_EventQueue = new LinkedList<EventParams>();

        // number of events of each code currently in m_EventQueue.
        private int[] m_EventCounts = new int[(int)ScriptEventCode.Size];

        // locked whilst running on the microthread stack (or about to run on it or just ran on it)
        private Object m_RunLock = new Object();

        // script won't step while > 0.  bus-atomic updates only.
        private int m_SuspendCount = 0;

        // don't run any of script until this time
        // or until one of these events are queued
        public DateTime m_SleepUntil = DateTime.MinValue;
        public int m_SleepEventMask1 = 0;
        public int m_SleepEventMask2 = 0;

        private XMRLSL_Api m_XMRLSLApi;

        /*
         * Makes sure migration data version is same on both ends.
         */
        public static byte migrationVersion = 11;

        // Incremented each time script gets reset.
        public int m_ResetCount = 0;

        // Scripts start suspended now. This means that event queues will
        // accept events, but will not actually run them until the core
        // tells it it's OK. This is needed to prevent loss of link messages
        // in complex objects, where no event can be allowed to run until
        // all possible link message receivers' queues are established.
        // Guarded by m_QueueLock.
        public bool m_Suspended = true;

        // We really don't want to save state for a script that hasn't had
        // a chance to run, because it's state will be blank. That would
        // cause attachment state loss.
        public bool m_HasRun = false;

        // When llDie is executed within the attach(NULL_KEY) event of
        // a script being detached to inventory, the DeleteSceneObject call
        // it causes will delete the script instances before their state can
        // be saved. Therefore, the instance needs to know that it's being
        // detached to inventory, rather than to ground.
        // Also, the attach(NULL_KEY) event needs to run with priority, and
        // it also needs to have a limited quantum.
        // If this is nonzero, we're detaching to inventory.
        // Guarded by m_QueueLock.
        private int m_DetachQuantum = 0;

        // Finally, we need to wait until the quantum is done, or the script
        // suspends itself. This should be efficient, so we use an event
        // for it instead of spinning busy.
        // It's born ready, but will be reset when the detach is posted.
        // It will then be set again on suspend/completion
        private ManualResetEvent m_DetachReady = new ManualResetEvent(true);
    }
}