/*
 * 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.IO;
using System.Text;
using OpenMetaverse;
using OpenSim.Region.ScriptEngine.Shared;
using OpenSim.Region.ScriptEngine.Shared.Api;
using OpenSim.Region.Framework.Scenes;

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
{
    public partial class XMRInstance
    {

        private bool m_disposed;
        // In case Dispose() doesn't get called, we want to be sure to clean
        // up.  This makes sure we decrement m_CompiledScriptRefCount.
        ~XMRInstance()
        {
            Dispose(false);
        }

        /**
         * @brief Clean up stuff.
         *        We specifically leave m_DescName intact for 'xmr ls' command.
         */
        public void Dispose()
        {
            Dispose(true);
            //GC.SuppressFinalize(this);
        }

        public void Dispose(bool fromdispose)
        {
            if (m_disposed)
                return;

            // Tell script stop executing next time it calls CheckRun().
            suspendOnCheckRunHold = true;

            // Don't send us any more events.
            lock (m_RunLock)
            {
                if(m_Part != null)
                {
                    AsyncCommandManager.RemoveScript(m_Engine, m_LocalID, m_ItemID);
                    m_Part = null;
                }
            }

             // Let script methods get garbage collected if no one else is using
             // them.
            DecObjCodeRefCount();
            m_disposed = true;
        }

        private void DecObjCodeRefCount()
        {
            if(m_ObjCode != null)
            {
                lock(m_CompileLock)
                {
                    if(m_CompiledScriptObjCode.TryGetValue(m_ScriptObjCodeKey, out ScriptObjCode objCode) &&
                        (objCode == m_ObjCode) &&
                        (--objCode.refCount == 0))
                    {
                        m_CompiledScriptObjCode.Remove(m_ScriptObjCodeKey);
                    }
                }
                m_ObjCode = null;
            }
        }

        public void Verbose(string format, params object[] args)
        {
            if(m_Engine.m_Verbose)
                m_log.DebugFormat(format, args);
        }

        // Called by 'xmr top' console command
        // to dump this script's state to console
        //  Sacha 
        public void RunTestTop()
        {
            if(m_InstEHSlice > 0)
            {
                Console.WriteLine(m_DescName);
                Console.WriteLine("    m_LocalID       = " + m_LocalID);
                Console.WriteLine("    m_ItemID        = " + m_ItemID);
                Console.WriteLine("    m_Item.AssetID  = " + m_Item.AssetID);
                Console.WriteLine("    m_StartParam    = " + m_StartParam);
                Console.WriteLine("    m_PostOnRez     = " + m_PostOnRez);
                Console.WriteLine("    m_StateSource   = " + m_StateSource);
                Console.WriteLine("    m_SuspendCount  = " + m_SuspendCount);
                Console.WriteLine("    m_SleepUntil    = " + m_SleepUntil);
                Console.WriteLine("    m_IState        = " + m_IState.ToString());
                Console.WriteLine("    m_StateCode     = " + GetStateName(stateCode));
                Console.WriteLine("    eventCode       = " + eventCode.ToString());
                Console.WriteLine("    m_LastRanAt     = " + m_LastRanAt.ToString());
                Console.WriteLine("    heapUsed/Limit  = " + xmrHeapUsed() + "/" + heapLimit);
                Console.WriteLine("    m_InstEHEvent   = " + m_InstEHEvent.ToString());
                Console.WriteLine("    m_InstEHSlice   = " + m_InstEHSlice.ToString());
            }
        }

        // Called by 'xmr ls' console command
        // to dump this script's state to console
        public string RunTestLs(bool flagFull)
        {
            if(flagFull)
            {
                StringBuilder sb = new();
                sb.AppendLine(m_DescName);
                sb.AppendLine("    m_LocalID            = " + m_LocalID);
                sb.AppendLine("    m_ItemID             = " + m_ItemID + "  (.state file)");
                sb.AppendLine("    m_Item.AssetID       = " + m_Item.AssetID);
                sb.AppendLine("    m_Part.WorldPosition = " + m_Part.GetWorldPosition());
                sb.AppendLine("    m_ScriptObjCodeKey   = " + m_ScriptObjCodeKey + "  (source text)");
                sb.AppendLine("    m_StartParam         = " + m_StartParam);
                sb.AppendLine("    m_PostOnRez          = " + m_PostOnRez);
                sb.AppendLine("    m_StateSource        = " + m_StateSource);
                sb.AppendLine("    m_SuspendCount       = " + m_SuspendCount);
                sb.AppendLine("    m_SleepUntil         = " + m_SleepUntil);
                sb.AppendLine("    m_SleepEvMask1       = 0x" + m_SleepEventMask1.ToString("X"));
                sb.AppendLine("    m_SleepEvMask2       = 0x" + m_SleepEventMask2.ToString("X"));
                sb.AppendLine("    m_IState             = " + m_IState.ToString());
                sb.AppendLine("    m_StateCode          = " + GetStateName(stateCode));
                sb.AppendLine("    eventCode            = " + eventCode.ToString());
                sb.AppendLine("    m_LastRanAt          = " + m_LastRanAt.ToString());
                //sb.AppendLine("    m_RunOnePhase        = " + m_RunOnePhase);
                sb.AppendLine("    suspOnCkRunHold      = " + suspendOnCheckRunHold);
                sb.AppendLine("    suspOnCkRunTemp      = " + suspendOnCheckRunTemp);
                //sb.AppendLine("    m_CheckRunPhase      = " + m_CheckRunPhase);
                sb.AppendLine("    heapUsed/Limit       = " + xmrHeapUsed() + "/" + heapLimit);
                sb.AppendLine("    m_InstEHEvent        = " + m_InstEHEvent.ToString());
                sb.AppendLine("    m_InstEHSlice        = " + m_InstEHSlice.ToString());
                sb.AppendLine("    m_CPUTime            = " + m_CPUTime);
                sb.AppendLine("    callMode             = " + callMode);
                lock(m_QueueLock)
                {
                    sb.AppendLine("    m_Running            = " + m_Running);
                    foreach(EventParams evt in m_EventQueue)
                    {
                        sb.AppendLine("        evt.EventName        = " + evt.EventName);
                    }
                }
                return sb.ToString();
            }
            else
            {
                return String.Format("{0} {1} {2} {3} {4} {5}",
                        m_ItemID,
                        m_CPUTime.ToString("F3").PadLeft(9),
                        m_InstEHEvent.ToString().PadLeft(9),
                        m_IState.ToString().PadRight(10),
                        m_Part.GetWorldPosition().ToString().PadRight(32),
                        m_DescName);
            }
        }

        /**
         * @brief For a given stateCode, get a mask of the low 32 event codes
         *        that the state has handlers defined for.
         */
        public ulong GetStateEventFlags(int state)
        {
            if((state < 0) ||
                (state >= m_ObjCode.scriptEventHandlerTable.GetLength(0)))
            {
                return 0;
            }

            ulong flags = 0;
            for(int i = 0; i <(int)ScriptEventCode.Size; i++)
            {
                if(m_ObjCode.scriptEventHandlerTable[state, i] != null)
                {
                    flags |= 1ul << i;
                }
            }
            return flags;
        }

        /**
         * @brief Get the .state file name.
         */
        public static string GetStateFileName(string scriptBasePath, UUID itemID)
        {
            return GetScriptFileName(scriptBasePath, itemID.ToString() + ".state");
        }

        public string GetScriptFileName(string filename)
        {
            return GetScriptFileName(m_ScriptBasePath, filename);
        }

        public string GetScriptILFileName(string filename)
        {
            string path = Path.Combine(m_ScriptBasePath, "DebugIL");
            Directory.CreateDirectory(path);
            return Path.Combine(path, filename);
        }

        public static string GetScriptFileName(string scriptBasePath, string filename)
        {
             // Get old path, ie, all files lumped in a single huge directory.
            string oldPath = Path.Combine(scriptBasePath, filename);

             // Get new path, ie, files split up based on first 2 chars of name.
             //           string subdir = filename.Substring (0, 2);
             //           filename = filename.Substring (2);
            string subdir = filename[..1];
            filename = filename[1..];
            scriptBasePath = Path.Combine(scriptBasePath, subdir);
            Directory.CreateDirectory(scriptBasePath);
            string newPath = Path.Combine(scriptBasePath, filename);

             // If file exists only in old location, move to new location.
             // If file exists in both locations, delete old location.
            if(File.Exists(oldPath))
            {
                if(File.Exists(newPath))
                {
                    File.Delete(oldPath);
                }
                else
                {
                    File.Move(oldPath, newPath);
                }
            }

             // Always return new location.
            return newPath;
        }

        /**
         * @brief Decode state code (int) to state name (string).
         */
        public string GetStateName(int stateCode)
        {
            try
            {
                return m_ObjCode.stateNames[stateCode];
            }
            catch
            {
                return stateCode.ToString();
            }
        }

        /**
         * @brief various gets & sets.
         */
        public int StartParam
        {
            get
            {
                return m_StartParam;
            }
            set
            {
                m_StartParam = value;
            }
        }

        public double MinEventDelay
        {
            get
            {
                return m_minEventDelay;
            }
            set
            {
                if (value > 0.001)
                    m_minEventDelay = value;
                else
                    m_minEventDelay = 0.0;

                m_nextEventTime = 0.0; // reset it
            }
        }


        public SceneObjectPart SceneObject
        {
            get
            {
                return m_Part;
            }
        }

        public DetectParams[] DetectParams
        {
            get
            {
                return m_DetectParams;
            }
            set
            {
                m_DetectParams = value;
            }
        }

        public UUID ItemID
        {
            get
            {
                return m_ItemID;
            }
        }

        public UUID AssetID
        {
            get
            {
                return m_Item.AssetID;
            }
        }

        public bool Running
        {
            get
            {
                return m_Running;
            }
            set
            {
                lock(m_QueueLock)
                {
                    m_Running = value;
                    if(value)
                    {
                        if (m_IState == XMRInstState.SUSPENDED && m_SuspendCount == 0)
                        {
                            if(eventCode != ScriptEventCode.None)
                            {
                                m_IState = XMRInstState.ONYIELDQ;
                                m_Engine.QueueToYield(this);
                            }
                            else if ((m_EventQueue != null) && (m_EventQueue.First != null))
                            {
                                m_IState = XMRInstState.ONSTARTQ;
                                m_Engine.QueueToStart(this);
                            }
                            else
                                m_IState = XMRInstState.IDLE;
                        }
                        //else if(m_SuspendCount != 0)
                        //    m_IState = XMRInstState.IDLE;
                    }
                    else
                    {
                        if (m_IState == XMRInstState.ONSLEEPQ)
                        {
                            m_Engine.RemoveFromSleep(this);
                            m_IState = XMRInstState.SUSPENDED;
                        }
                        else if (m_IState == XMRInstState.IDLE)
                            m_IState = XMRInstState.SUSPENDED;

                        EmptyEventQueues();
                    }
                }
            }
        }

        /**
         * @brief Empty out the event queues.
         *        Assumes caller has the m_QueueLock locked.
         */
        public void EmptyEventQueues()
        {
            m_EventQueue.Clear();
            for(int i = m_EventCounts.Length; --i >= 0;)
                m_EventCounts[i] = 0;
        }

        /**
         * @brief Convert an LSL vector to an Openmetaverse vector.
         */
        public static OpenMetaverse.Vector3 LSLVec2OMVec(LSL_Vector lslVec)
        {
            return new OpenMetaverse.Vector3((float)lslVec.x, (float)lslVec.y, (float)lslVec.z);
        }

        /**
         * @brief Extract an integer from an element of an LSL_List.
         */
        public static int ListInt(object element)
        {
            if (element is LSL_Integer linteger)
                return linteger.value;
            return (int)element;
        }

        /**
         * @brief Extract a string from an element of an LSL_List.
         */
        public static string ListStr(object element)
        {
            if(element is LSL_String ls)
                return ls.m_string;
            return (string)element;
        }
    }
}
