
namespace LuaInterface 
{    
	using System;
	using System.IO;
	using System.Collections;
    using System.Collections.Generic;
	using System.Collections.Specialized;
	using System.Reflection;
    using System.Threading;
    using Lua511;

	/*
	 * Main class of LuaInterface
	 * Object-oriented wrapper to Lua API
	 *
	 * Author: Fabio Mascarenhas
	 * Version: 1.0
	 * 
	 * // steffenj: important changes in Lua class:
	 * - removed all Open*Lib() functions 
	 * - all libs automatically open in the Lua class constructor (just assign nil to unwanted libs)
	 * */
	[CLSCompliant(true)]
    public class Lua : IDisposable
	{

		static string init_luanet =
			"local metatable = {}									\n"+
			"local import_type = luanet.import_type							\n"+
			"local load_assembly = luanet.load_assembly						\n"+
			"											\n"+
			"-- Lookup a .NET identifier component.							\n"+
			"function metatable:__index(key) -- key is e.g. \"Form\"				\n"+
			"    -- Get the fully-qualified name, e.g. \"System.Windows.Forms.Form\"		\n"+
			"    local fqn = ((rawget(self,\".fqn\") and rawget(self,\".fqn\") ..			\n"+
			"		\".\") or \"\") .. key							\n"+
			"											\n"+
			"    -- Try to find either a luanet function or a CLR type				\n"+
			"    local obj = rawget(luanet,key) or import_type(fqn)					\n"+
			"											\n"+
			"    -- If key is neither a luanet function or a CLR type, then it is simply		\n"+
			"    -- an identifier component.							\n"+
			"    if obj == nil then									\n"+
			"		-- It might be an assembly, so we load it too.				\n"+
			"        load_assembly(fqn)								\n"+
			"        obj = { [\".fqn\"] = fqn }							\n"+
			"        setmetatable(obj, metatable)							\n"+
			"    end										\n"+
			"											\n"+
			"    -- Cache this lookup								\n"+
			"    rawset(self, key, obj)								\n"+
			"    return obj										\n"+
			"end											\n"+
			"											\n"+
			"-- A non-type has been called; e.g. foo = System.Foo()					\n"+
			"function metatable:__call(...)								\n"+
			"    error(\"No such type: \" .. rawget(self,\".fqn\"), 2)				\n"+
			"end											\n"+
			"											\n"+
			"-- This is the root of the .NET namespace						\n"+
			"luanet[\".fqn\"] = false								\n"+
			"setmetatable(luanet, metatable)							\n"+
			"											\n"+
			"-- Preload the mscorlib assembly							\n"+
			"luanet.load_assembly(\"mscorlib\")							\n";

		/*readonly */ IntPtr luaState;
		ObjectTranslator translator;

        LuaCSFunction panicCallback, lockCallback, unlockCallback;

        /// <summary>
        /// Used to ensure multiple .net threads all get serialized by this single lock for access to the lua stack/objects
        /// </summary>
        object luaLock = new object();

				internal Lua(bool newThread)
				{
					if (!newThread) init();
				}

				public Lua()
				{
					init();
				}
		void init()
		{
			luaState = LuaDLL.luaL_newstate();	// steffenj: Lua 5.1.1 API change (lua_open is gone)
			//LuaDLL.luaopen_base(luaState);	// steffenj: luaopen_* no longer used
			LuaDLL.luaL_openlibs(luaState);		// steffenj: Lua 5.1.1 API change (luaopen_base is gone, just open all libs right here)
		    LuaDLL.lua_pushstring(luaState, "LUAINTERFACE LOADED");
            LuaDLL.lua_pushboolean(luaState, true);
            LuaDLL.lua_settable(luaState, (int) LuaIndexes.LUA_REGISTRYINDEX);
			LuaDLL.lua_newtable(luaState);
			LuaDLL.lua_setglobal(luaState, "luanet");
            LuaDLL.lua_pushvalue(luaState, (int)LuaIndexes.LUA_GLOBALSINDEX);
			LuaDLL.lua_getglobal(luaState, "luanet");
			LuaDLL.lua_pushstring(luaState, "getmetatable");
			LuaDLL.lua_getglobal(luaState, "getmetatable");
			LuaDLL.lua_settable(luaState, -3);
            LuaDLL.lua_replace(luaState, (int)LuaIndexes.LUA_GLOBALSINDEX);
			translator=new ObjectTranslator(this,luaState);
            LuaDLL.lua_replace(luaState, (int)LuaIndexes.LUA_GLOBALSINDEX);
			LuaDLL.luaL_dostring(luaState, Lua.init_luanet);	// steffenj: lua_dostring renamed to luaL_dostring

            // We need to keep this in a managed reference so the delegate doesn't get garbage collected
            panicCallback = new LuaCSFunction(PanicCallback);
            LuaDLL.lua_atpanic(luaState, panicCallback);

            //LuaDLL.lua_atlock(luaState, lockCallback = new LuaCSFunction(LockCallback));
            //LuaDLL.lua_atunlock(luaState, unlockCallback = new LuaCSFunction(UnlockCallback));
        }

        private bool _StatePassed;

    	/*
    	 * CAUTION: LuaInterface.Lua instances can't share the same lua state! 
    	 */
    	public Lua(Int64 luaState)
    	{
    		IntPtr lState = new IntPtr(luaState);
    		LuaDLL.lua_pushstring(lState, "LUAINTERFACE LOADED");
            LuaDLL.lua_gettable(lState, (int)LuaIndexes.LUA_REGISTRYINDEX);
    		
            if(LuaDLL.lua_toboolean(lState,-1)) 
            {
        		LuaDLL.lua_settop(lState,-2);
        		throw new LuaException("There is already a LuaInterface.Lua instance associated with this Lua state");
    		} 
            else 
            {
        		LuaDLL.lua_settop(lState,-2);
        		LuaDLL.lua_pushstring(lState, "LUAINTERFACE LOADED");
        		LuaDLL.lua_pushboolean(lState, true);
                LuaDLL.lua_settable(lState, (int)LuaIndexes.LUA_REGISTRYINDEX);
        		this.luaState=lState;
                LuaDLL.lua_pushvalue(lState, (int)LuaIndexes.LUA_GLOBALSINDEX);
				LuaDLL.lua_getglobal(lState, "luanet");
				LuaDLL.lua_pushstring(lState, "getmetatable");
				LuaDLL.lua_getglobal(lState, "getmetatable");
				LuaDLL.lua_settable(lState, -3);
                LuaDLL.lua_replace(lState, (int)LuaIndexes.LUA_GLOBALSINDEX);
				translator=new ObjectTranslator(this, this.luaState);
                LuaDLL.lua_replace(lState, (int)LuaIndexes.LUA_GLOBALSINDEX);
				LuaDLL.luaL_dostring(lState, Lua.init_luanet);	// steffenj: lua_dostring renamed to luaL_dostring
    		}
                
            _StatePassed = true;
    	}

        /// <summary>
        /// Called for each lua_lock call 
        /// </summary>
        /// <param name="luaState"></param>
        /// Not yet used
        int LockCallback(IntPtr luaState)
        {
            // Monitor.Enter(luaLock);

            return 0;
        }

        /// <summary>
        /// Called for each lua_unlock call 
        /// </summary>
        /// <param name="luaState"></param>
        /// Not yet used
        int UnlockCallback(IntPtr luaState)
        {
            // Monitor.Exit(luaLock);

            return 0;
        }

        public void Close()
        {
            if (_StatePassed)
                return;

            if (luaState != IntPtr.Zero)
                LuaDLL.lua_close(luaState);
            //luaState = IntPtr.Zero; <- suggested by Christopher Cebulski http://luaforge.net/forum/forum.php?thread_id=44593&forum_id=146
        }

        static int PanicCallback(IntPtr luaState)
        {
            // string desc = LuaDLL.lua_tostring(luaState, 1);
            string reason = String.Format("unprotected error in call to Lua API ({0})", LuaDLL.lua_tostring(luaState, -1));

           //        lua_tostring(L, -1);

            throw new LuaException(reason);
        }



        /// <summary>
        /// Assuming we have a Lua error string sitting on the stack, throw a C# exception out to the user's app
        /// </summary>
        /// <exception cref="LuaScriptException">Thrown if the script caused an exception</exception>
        void ThrowExceptionFromError(int oldTop)
        {
            object err = translator.getObject(luaState, -1);
            LuaDLL.lua_settop(luaState, oldTop);

            // A pre-wrapped exception - just rethrow it (stack trace of InnerException will be preserved)
            LuaScriptException luaEx = err as LuaScriptException;
            if (luaEx != null) throw luaEx;

            // A non-wrapped Lua error (best interpreted as a string) - wrap it and throw it
            if (err == null) err = "Unknown Lua Error";
            throw new LuaScriptException(err.ToString(), "");
        }



        /// <summary>
        /// Convert C# exceptions into Lua errors
        /// </summary>
        /// <returns>num of things on stack</returns>
        /// <param name="e">null for no pending exception</param>
        internal int SetPendingException(Exception e)
        {
            Exception caughtExcept = e;

            if (caughtExcept != null)
            {
                translator.throwError(luaState, caughtExcept);
                LuaDLL.lua_pushnil(luaState);

                return 1;
            }
            else
                return 0;
        }

        private bool executing;

        /// <summary>
        /// True while a script is being executed
        /// </summary>
        public bool IsExecuting { get { return executing; } }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="chunk"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        public LuaFunction LoadString(string chunk, string name)
        {
            int oldTop = LuaDLL.lua_gettop(luaState);

            executing = true;
            try
            {
                if (LuaDLL.luaL_loadbuffer(luaState, chunk, name) != 0)
                    ThrowExceptionFromError(oldTop);
            }
            finally { executing = false; }

            LuaFunction result = translator.getFunction(luaState, -1);
            translator.popValues(luaState, oldTop);
            
            return result;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public LuaFunction LoadFile(string fileName)
        {
            int oldTop = LuaDLL.lua_gettop(luaState);
            if (LuaDLL.luaL_loadfile(luaState, fileName) != 0)
                ThrowExceptionFromError(oldTop);

            LuaFunction result = translator.getFunction(luaState, -1);
            translator.popValues(luaState, oldTop);

            return result;
        }


		/*
		 * Excutes a Lua chunk and returns all the chunk's return
		 * values in an array
		 */
		public object[] DoString(string chunk) 
		{
			int oldTop=LuaDLL.lua_gettop(luaState);
            if (LuaDLL.luaL_loadbuffer(luaState, chunk, "chunk") == 0)
            {
                executing = true;
                try
                {
                    if (LuaDLL.lua_pcall(luaState, 0, -1, 0) == 0)
                        return translator.popValues(luaState, oldTop);
                    else
                        ThrowExceptionFromError(oldTop);
                }
                finally { executing = false; }
            }
            else
                ThrowExceptionFromError(oldTop);

            return null;            // Never reached - keeps compiler happy
		}

        /// <summary>
        /// Executes a Lua chnk and returns all the chunk's return values in an array.
        /// </summary>
        /// <param name="chunk">Chunk to execute</param>
        /// <param name="chunkName">Name to associate with the chunk</param>
        /// <returns></returns>
        public object[] DoString(string chunk, string chunkName)
        {
            int oldTop = LuaDLL.lua_gettop(luaState);
            executing = true;
            if (LuaDLL.luaL_loadbuffer(luaState, chunk, chunkName) == 0)
            {
                try
                {
                    if (LuaDLL.lua_pcall(luaState, 0, -1, 0) == 0)
                        return translator.popValues(luaState, oldTop);
                    else
                        ThrowExceptionFromError(oldTop);
                }
                finally { executing = false; }
            }
            else
                ThrowExceptionFromError(oldTop);

            return null;            // Never reached - keeps compiler happy
        }

		/*
		 * Excutes a Lua file and returns all the chunk's return
		 * values in an array
		 */
		public object[] DoFile(string fileName) 
		{
			int oldTop=LuaDLL.lua_gettop(luaState);
			if(LuaDLL.luaL_loadfile(luaState,fileName)==0) 
			{
			    executing = true;
                try
                {
                    if (LuaDLL.lua_pcall(luaState, 0, -1, 0) == 0)
                        return translator.popValues(luaState, oldTop);
                    else
                        ThrowExceptionFromError(oldTop);
                }
                finally { executing = false; }
			} 
			else
                ThrowExceptionFromError(oldTop);

            return null;            // Never reached - keeps compiler happy
		}


		/*
		 * Indexer for global variables from the LuaInterpreter
		 * Supports navigation of tables by using . operator
		 */
		public object this[string fullPath]
		{
			get 
			{
				object returnValue=null;
				int oldTop=LuaDLL.lua_gettop(luaState);
				string[] path=fullPath.Split(new char[] { '.' });
				LuaDLL.lua_getglobal(luaState,path[0]);
				returnValue=translator.getObject(luaState,-1);
				if(path.Length>1) 
				{
					string[] remainingPath=new string[path.Length-1];
					Array.Copy(path,1,remainingPath,0,path.Length-1);
					returnValue=getObject(remainingPath);
				}
				LuaDLL.lua_settop(luaState,oldTop);
				return returnValue;
			}
			set 
			{
				int oldTop=LuaDLL.lua_gettop(luaState);
				string[] path=fullPath.Split(new char[] { '.' });
				if(path.Length==1) 
				{
					translator.push(luaState,value);
					LuaDLL.lua_setglobal(luaState,fullPath);
				} 
				else 
				{
					LuaDLL.lua_getglobal(luaState,path[0]);
					string[] remainingPath=new string[path.Length-1];
					Array.Copy(path,1,remainingPath,0,path.Length-1);
					setObject(remainingPath,value);
				}
				LuaDLL.lua_settop(luaState,oldTop);

                // Globals auto-complete
                if (value == null)
                {
                    // Remove now obsolete entries
                    globals.Remove(fullPath);
                }
                else
                {
                    // Add new entries
                    if (!globals.Contains(fullPath))
                        registerGlobal(fullPath, value.GetType(), 0);
                }
            }
		}

        #region Globals auto-complete
        private readonly List<string> globals = new List<string>();
        private bool globalsSorted;

        /// <summary>
        /// An alphabetically sorted list of all globals (objects, methods, etc.) externally added to this Lua instance
        /// </summary>
        /// <remarks>Members of globals are also listed. The formatting is optimized for text input auto-completion.</remarks>
        public IEnumerable<string> Globals
        {
            get
            {
                // Only sort list when necessary
                if (!globalsSorted)
                {
                    globals.Sort();
                    globalsSorted = true;
                }

                return globals;
            }
        }

        /// <summary>
        /// Adds an entry to <see cref="globals"/> (recursivley handles 2 levels of members)
        /// </summary>
        /// <param name="path">The index accessor path ot the entry</param>
        /// <param name="type">The type of the entry</param>
        /// <param name="recursionCounter">How deep have we gone with recursion?</param>
        private void registerGlobal(string path, Type type, int recursionCounter)
        {
            // If the type is a global method, list it directly
            if (type == typeof(LuaCSFunction))
            {
                // Format for easy method invocation
                globals.Add(path + "(");
            }
            // If the type is a class or an interface and recursion hasn't been running too long, list the members
            else if ((type.IsClass || type.IsInterface) && type != typeof(string) && recursionCounter < 2)
            {
                #region Methods
                foreach (MethodInfo method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance))
                {
                    if (
                        // Check that the LuaHideAttribute and LuaGlobalAttribute were not applied
                        (method.GetCustomAttributes(typeof(LuaHideAttribute), false).Length == 0) &&
                        (method.GetCustomAttributes(typeof(LuaGlobalAttribute), false).Length == 0) &&
                        // Exclude some generic .NET methods that wouldn't be very usefull in Lua
                        method.Name != "GetType" && method.Name != "GetHashCode" && method.Name != "Equals" &&
                        method.Name != "ToString" && method.Name != "Clone" && method.Name != "Dispose" &&
                        method.Name != "GetEnumerator" && method.Name != "CopyTo" &&
                        !method.Name.StartsWith("get_", StringComparison.Ordinal) &&
                        !method.Name.StartsWith("set_", StringComparison.Ordinal) &&
                        !method.Name.StartsWith("add_", StringComparison.Ordinal) &&
                        !method.Name.StartsWith("remove_", StringComparison.Ordinal))
                    {
                        // Format for easy method invocation
                        string command = path + ":" + method.Name + "(";
                        if (method.GetParameters().Length == 0) command += ")";
                        globals.Add(command);
                    }
                }
                #endregion

                #region Fields
                foreach (FieldInfo field in type.GetFields(BindingFlags.Public | BindingFlags.Instance))
                {
                    if (
                        // Check that the LuaHideAttribute and LuaGlobalAttribute were not applied
                        (field.GetCustomAttributes(typeof(LuaHideAttribute), false).Length == 0) &&
                        (field.GetCustomAttributes(typeof(LuaGlobalAttribute), false).Length == 0))
                    {
                        // Go into recursion for members
                        registerGlobal(path + "." + field.Name, field.FieldType, recursionCounter + 1);
                    }
                }
                #endregion

                #region Properties
                foreach (PropertyInfo property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
                {
                    if (
                        // Check that the LuaHideAttribute and LuaGlobalAttribute were not applied
                        (property.GetCustomAttributes(typeof(LuaHideAttribute), false).Length == 0) &&
                        (property.GetCustomAttributes(typeof(LuaGlobalAttribute), false).Length == 0)
                        // Exclude some generic .NET properties that wouldn't be very usefull in Lua
                        && property.Name != "Item")
                    {
                        // Go into recursion for members
                        registerGlobal(path + "." + property.Name, property.PropertyType, recursionCounter + 1);
                    }
                }
                #endregion
            }
            // Otherwise simply add the element to the list
            else globals.Add(path);

            // List will need to be sorted on next access
            globalsSorted = false;
        }
        #endregion



				public int Resume(int narg)
				{
					int top = LuaDLL.lua_gettop(luaState);
					int ret = LuaDLL.lua_resume(luaState, narg);
					if (ret == 1 /*LUA_YIELD*/)
						return 1; //yielded
					if (ret == 0) 
						return 0; //normal termination
					//error. throw exception with error message (TBD - debug api to get call stack)
					ThrowExceptionFromError(top);
					return ret;
				}
				public void Yield(int nresults)
				{
					LuaDLL.lua_yield(luaState, nresults);
				}

				public Lua NewThread()
				{
					var lua = new Lua(true);
					lua.translator = translator;
					lua.luaState = LuaDLL.lua_newthread(luaState);
					return lua;
				}

		/*
		 * Navigates a table in the top of the stack, returning
		 * the value of the specified field
		 */
		internal object getObject(string[] remainingPath) 
		{
			object returnValue=null;
			for(int i=0;i<remainingPath.Length;i++) 
			{
				LuaDLL.lua_pushstring(luaState,remainingPath[i]);
				LuaDLL.lua_gettable(luaState,-2);
				returnValue=translator.getObject(luaState,-1);
				if(returnValue==null) break;	
			}
			return returnValue;    
		}
		/*
		 * Gets a numeric global variable
		 */
		public double GetNumber(string fullPath) 
		{
			return (double)this[fullPath];
		}
		/*
		 * Gets a string global variable
		 */
		public string GetString(string fullPath) 
		{
			return (string)this[fullPath];
		}
		/*
		 * Gets a table global variable
		 */
		public LuaTable GetTable(string fullPath) 
		{
			return (LuaTable)this[fullPath];
		}
		/*
		 * Gets a table global variable as an object implementing
		 * the interfaceType interface
		 */
		public object GetTable(Type interfaceType, string fullPath) 
		{
			return CodeGeneration.Instance.GetClassInstance(interfaceType,GetTable(fullPath));
		}
		/*
		 * Gets a function global variable
		 */
		public LuaFunction GetFunction(string fullPath) 
		{
            object obj=this[fullPath];
			return (obj is LuaCSFunction ? new LuaFunction((LuaCSFunction)obj,this) : (LuaFunction)obj);
		}
		/*
		 * Gets a function global variable as a delegate of
		 * type delegateType
		 */
		public Delegate GetFunction(Type delegateType,string fullPath) 
		{
			return CodeGeneration.Instance.GetDelegate(delegateType,GetFunction(fullPath));
		}
		/*
		 * Calls the object as a function with the provided arguments,
		 * returning the function's returned values inside an array
		 */
		internal object[] callFunction(object function,object[] args) 
		{
            return callFunction(function, args, null);
		}


		/*
		 * Calls the object as a function with the provided arguments and
		 * casting returned values to the types in returnTypes before returning
		 * them in an array
		 */
		internal object[] callFunction(object function,object[] args,Type[] returnTypes) 
		{
			int nArgs=0;
			int oldTop=LuaDLL.lua_gettop(luaState);
			if(!LuaDLL.lua_checkstack(luaState,args.Length+6))
                throw new LuaException("Lua stack overflow");
			translator.push(luaState,function);
			if(args!=null) 
			{
				nArgs=args.Length;
				for(int i=0;i<args.Length;i++) 
				{
					translator.push(luaState,args[i]);
				}
            }
            executing = true;
            try
            {
                int error = LuaDLL.lua_pcall(luaState, nArgs, -1, 0);
                if (error != 0)
                    ThrowExceptionFromError(oldTop);
            }
            finally { executing = false; }

            if(returnTypes != null)
			    return translator.popValues(luaState,oldTop,returnTypes);
            else
                return translator.popValues(luaState, oldTop);
		}
		/*
		 * Navigates a table to set the value of one of its fields
		 */
		internal void setObject(string[] remainingPath, object val) 
		{
			for(int i=0; i<remainingPath.Length-1;i++) 
			{
				LuaDLL.lua_pushstring(luaState,remainingPath[i]);
				LuaDLL.lua_gettable(luaState,-2);
			}
			LuaDLL.lua_pushstring(luaState,remainingPath[remainingPath.Length-1]);
			translator.push(luaState,val);
			LuaDLL.lua_settable(luaState,-3);
		}

		//zero 24-mar-2012 added
		public LuaTable NewTable()
		{
			NewTable("_TEMP_BIZHAWK_RULES_");
			return GetTable("_TEMP_BIZHAWK_RULES_");
		}

		/*
		 * Creates a new table as a global variable or as a field
		 * inside an existing table
		 */
		public void NewTable(string fullPath) 
		{
			string[] path=fullPath.Split(new char[] { '.' });
			int oldTop=LuaDLL.lua_gettop(luaState);
			if(path.Length==1) 
			{
				LuaDLL.lua_newtable(luaState);
				LuaDLL.lua_setglobal(luaState,fullPath);
			} 
			else 
			{
				LuaDLL.lua_getglobal(luaState,path[0]);
				for(int i=1; i<path.Length-1;i++) 
				{
					LuaDLL.lua_pushstring(luaState,path[i]);
					LuaDLL.lua_gettable(luaState,-2);
				}
				LuaDLL.lua_pushstring(luaState,path[path.Length-1]);
				LuaDLL.lua_newtable(luaState);
				LuaDLL.lua_settable(luaState,-3);
			}
			LuaDLL.lua_settop(luaState,oldTop);
		}

		public ListDictionary GetTableDict(LuaTable table)
		{
			ListDictionary dict = new ListDictionary();

			int oldTop = LuaDLL.lua_gettop(luaState);
			translator.push(luaState, table);
			LuaDLL.lua_pushnil(luaState);
			while (LuaDLL.lua_next(luaState, -2) != 0) 
			{
				dict[translator.getObject(luaState, -2)] = translator.getObject(luaState, -1);
				LuaDLL.lua_settop(luaState, -2);
			}
			LuaDLL.lua_settop(luaState, oldTop);

			return dict;
		}

		/*
		 * Lets go of a previously allocated reference to a table, function
		 * or userdata
		 */

      #region lua debug functions

      /// <summary>
      /// lua hook calback delegate
      /// </summary>
      /// <author>Reinhard Ostermeier</author>
      private LuaHookFunction hookCallback = null;

	    /// <summary>
      /// Activates the debug hook
      /// </summary>
      /// <param name="mask">Mask</param>
      /// <param name="count">Count</param>
      /// <returns>see lua docs. -1 if hook is already set</returns>
      /// <author>Reinhard Ostermeier</author>
      public int SetDebugHook(EventMasks mask, int count)
      {
         if (hookCallback == null)
         {
            hookCallback = new LuaHookFunction(DebugHookCallback);
            return LuaDLL.lua_sethook(luaState, hookCallback, (int)mask, count);
         }
         return -1;
      }

      /// <summary>
      /// Removes the debug hook
      /// </summary>
      /// <returns>see lua docs</returns>
      /// <author>Reinhard Ostermeier</author>
      public int RemoveDebugHook()
      {
         hookCallback = null;
         return LuaDLL.lua_sethook(luaState, null, 0, 0);
      }

      /// <summary>
      /// Gets the hook mask.
      /// </summary>
      /// <returns>hook mask</returns>
      /// <author>Reinhard Ostermeier</author>
      public EventMasks GetHookMask()
      {
         return (EventMasks)LuaDLL.lua_gethookmask(luaState);
      }

      /// <summary>
      /// Gets the hook count
      /// </summary>
      /// <returns>see lua docs</returns>
      /// <author>Reinhard Ostermeier</author>
      public int GetHookCount()
      {
         return LuaDLL.lua_gethookcount(luaState);
      }

      /// <summary>
      /// Gets the stack entry on a given level
      /// </summary>
      /// <param name="level">level</param>
      /// <param name="luaDebug">lua debug structure</param>
      /// <returns>Returns true if level was allowed, false if level was invalid.</returns>
      /// <author>Reinhard Ostermeier</author>
      public bool GetStack(int level, out LuaDebug luaDebug)
      {
         luaDebug = new LuaDebug();
         IntPtr ld = System.Runtime.InteropServices.Marshal.AllocHGlobal(System.Runtime.InteropServices.Marshal.SizeOf(luaDebug));
         System.Runtime.InteropServices.Marshal.StructureToPtr(luaDebug, ld, false);
         try
         {
            return LuaDLL.lua_getstack(luaState, level, ld) != 0;
         }
         finally
         {
            luaDebug = (LuaDebug)System.Runtime.InteropServices.Marshal.PtrToStructure(ld, typeof(LuaDebug));
            System.Runtime.InteropServices.Marshal.FreeHGlobal(ld);
         }
      }

      /// <summary>
      /// Gets info (see lua docs)
      /// </summary>
      /// <param name="what">what (see lua docs)</param>
      /// <param name="luaDebug">lua debug structure</param>
      /// <returns>see lua docs</returns>
      /// <author>Reinhard Ostermeier</author>
      public int GetInfo(String what, ref LuaDebug luaDebug)
      {
         IntPtr ld = System.Runtime.InteropServices.Marshal.AllocHGlobal(System.Runtime.InteropServices.Marshal.SizeOf(luaDebug));
         System.Runtime.InteropServices.Marshal.StructureToPtr(luaDebug, ld, false);
         try
         {
            return LuaDLL.lua_getinfo(luaState, what, ld);
         }
         finally
         {
            luaDebug = (LuaDebug)System.Runtime.InteropServices.Marshal.PtrToStructure(ld, typeof(LuaDebug));
            System.Runtime.InteropServices.Marshal.FreeHGlobal(ld);
         }
      }

      /// <summary>
      /// Gets local (see lua docs)
      /// </summary>
      /// <param name="luaDebug">lua debug structure</param>
      /// <param name="n">see lua docs</param>
      /// <returns>see lua docs</returns>
      /// <author>Reinhard Ostermeier</author>
      public String GetLocal(LuaDebug luaDebug, int n)
      {
         IntPtr ld = System.Runtime.InteropServices.Marshal.AllocHGlobal(System.Runtime.InteropServices.Marshal.SizeOf(luaDebug));
         System.Runtime.InteropServices.Marshal.StructureToPtr(luaDebug, ld, false);
         try
         {
            return LuaDLL.lua_getlocal(luaState, ld, n);
         }
         finally
         {
            System.Runtime.InteropServices.Marshal.FreeHGlobal(ld);
         }
      }

      /// <summary>
      /// Sets local (see lua docs)
      /// </summary>
      /// <param name="luaDebug">lua debug structure</param>
      /// <param name="n">see lua docs</param>
      /// <returns>see lua docs</returns>
      /// <author>Reinhard Ostermeier</author>
      public String SetLocal(LuaDebug luaDebug, int n)
      {
         IntPtr ld = System.Runtime.InteropServices.Marshal.AllocHGlobal(System.Runtime.InteropServices.Marshal.SizeOf(luaDebug));
         System.Runtime.InteropServices.Marshal.StructureToPtr(luaDebug, ld, false);
         try
         {
            return LuaDLL.lua_setlocal(luaState, ld, n);
         }
         finally
         {
            System.Runtime.InteropServices.Marshal.FreeHGlobal(ld);
         }
      }

      /// <summary>
      /// Gets up value (see lua docs)
      /// </summary>
      /// <param name="funcindex">see lua docs</param>
      /// <param name="n">see lua docs</param>
      /// <returns>see lua docs</returns>
      /// <author>Reinhard Ostermeier</author>
      public String GetUpValue(int funcindex, int n)
      {
         return LuaDLL.lua_getupvalue(luaState, funcindex, n);
      }

      /// <summary>
      /// Sets up value (see lua docs)
      /// </summary>
      /// <param name="funcindex">see lua docs</param>
      /// <param name="n">see lua docs</param>
      /// <returns>see lua docs</returns>
      /// <author>Reinhard Ostermeier</author>
      public String SetUpValue(int funcindex, int n)
      {
         return LuaDLL.lua_setupvalue(luaState, funcindex, n);
      }

      /// <summary>
      /// Delegate that is called on lua hook callback
      /// </summary>
      /// <param name="luaState">lua state</param>
      /// <param name="luaDebug">Pointer to LuaDebug (lua_debug) structure</param>
      /// <author>Reinhard Ostermeier</author>
      private void DebugHookCallback(IntPtr luaState, IntPtr luaDebug)
      {
         try
         {
            LuaDebug ld = (LuaDebug)System.Runtime.InteropServices.Marshal.PtrToStructure(luaDebug, typeof(LuaDebug));
            EventHandler<DebugHookEventArgs> temp = DebugHook;
            if (temp != null)
            {
               temp(this, new DebugHookEventArgs(ld));
            }
         }
         catch (Exception ex)
         {
            OnHookException(new HookExceptionEventArgs(ex));
         }
      }

      /// <summary>
      /// Event that is raised when an exception occures during a hook call.
      /// </summary>
      /// <author>Reinhard Ostermeier</author>
      public event EventHandler<HookExceptionEventArgs> HookException;
      private void OnHookException(HookExceptionEventArgs e)
      {
         EventHandler<HookExceptionEventArgs> temp = HookException;
         if (temp != null)
         {
            temp(this, e);
         }
      }

      /// <summary>
      /// Event when lua hook callback is called
      /// </summary>
      /// <remarks>
      /// Is only raised if SetDebugHook is called before.
      /// </remarks>
      /// <author>Reinhard Ostermeier</author>
      public event EventHandler<DebugHookEventArgs> DebugHook;

      /// <summary>
      /// Pops a value from the lua stack.
      /// </summary>
      /// <returns>Returns the top value from the lua stack.</returns>
      /// <author>Reinhard Ostermeier</author>
      public object Pop()
      {
         int top = Lua511.LuaDLL.lua_gettop(luaState);
         return translator.popValues(luaState, top - 1)[0];
      }

      /// <summary>
      /// Pushes a value onto the lua stack.
      /// </summary>
      /// <param name="value">Value to push.</param>
      /// <author>Reinhard Ostermeier</author>
      public void Push(object value)
      {
         translator.push(luaState, value);
      }

      #endregion

			List<int> scheduledDisposes = new List<int>();

		internal void ScheduleDispose(int reference)
		{	
			//TODO - theres a race condition here, see comment elsewhere
			lock (scheduledDisposes)
				scheduledDisposes.Add(reference);
		}

		public void RunScheduledDisposes()
		{
			//TODO - theres a race condition here, in case GC happens during this method
			lock (scheduledDisposes)
			{
				foreach (var item in scheduledDisposes)
					dispose(item);
				scheduledDisposes.Clear();
			}
		}

		internal void dispose(int reference) 
		{
            if (luaState != IntPtr.Zero) //Fix submitted by Qingrui Li
                LuaDLL.lua_unref(luaState,reference);
		}
		/*
		 * Gets a field of the table corresponding to the provided reference
		 * using rawget (do not use metatables)
		 */
		internal object rawGetObject(int reference,string field) 
		{
			int oldTop=LuaDLL.lua_gettop(luaState);
			LuaDLL.lua_getref(luaState,reference);
			LuaDLL.lua_pushstring(luaState,field);
			LuaDLL.lua_rawget(luaState,-2);
			object obj=translator.getObject(luaState,-1);
			LuaDLL.lua_settop(luaState,oldTop);
			return obj;
		}
		/*
		 * Gets a field of the table or userdata corresponding to the provided reference
		 */
		internal object getObject(int reference,string field) 
		{
			int oldTop=LuaDLL.lua_gettop(luaState);
			LuaDLL.lua_getref(luaState,reference);
			object returnValue=getObject(field.Split(new char[] {'.'}));
			LuaDLL.lua_settop(luaState,oldTop);
			return returnValue;
		}
		/*
		 * Gets a numeric field of the table or userdata corresponding the the provided reference
		 */
		internal object getObject(int reference,object field) 
		{
			int oldTop=LuaDLL.lua_gettop(luaState);
			LuaDLL.lua_getref(luaState,reference);
			translator.push(luaState,field);
			LuaDLL.lua_gettable(luaState,-2);
			object returnValue=translator.getObject(luaState,-1);
			LuaDLL.lua_settop(luaState,oldTop);
			return returnValue;
		}
		/*
		 * Sets a field of the table or userdata corresponding the the provided reference
		 * to the provided value
		 */
		internal void setObject(int reference, string field, object val) 
		{
			int oldTop=LuaDLL.lua_gettop(luaState);
			LuaDLL.lua_getref(luaState,reference);
			setObject(field.Split(new char[] {'.'}),val);
			LuaDLL.lua_settop(luaState,oldTop);
		}
		/*
		 * Sets a numeric field of the table or userdata corresponding the the provided reference
		 * to the provided value
		 */
		internal void setObject(int reference, object field, object val) 
		{
			int oldTop=LuaDLL.lua_gettop(luaState);
			LuaDLL.lua_getref(luaState,reference);
			translator.push(luaState,field);
			translator.push(luaState,val);
			LuaDLL.lua_settable(luaState,-3);
			LuaDLL.lua_settop(luaState,oldTop);
		}

		/*
		 * Registers an object's method as a Lua function (global or table field)
		 * The method may have any signature
		 */
        public LuaFunction RegisterFunction(string path, object target, MethodBase function /*MethodInfo function*/)  //CP: Fix for struct constructor by Alexander Kappner (link: http://luaforge.net/forum/forum.php?thread_id=2859&forum_id=145)
		{
            // We leave nothing on the stack when we are done
            int oldTop = LuaDLL.lua_gettop(luaState);
            
			LuaMethodWrapper wrapper=new LuaMethodWrapper(translator,target,function.DeclaringType,function);
			translator.push(luaState,new LuaCSFunction(wrapper.call));

			this[path]=translator.getObject(luaState,-1);
            LuaFunction f = GetFunction(path);

            LuaDLL.lua_settop(luaState, oldTop);

            return f;
		}


		/*
		 * Compares the two values referenced by ref1 and ref2 for equality
		 */
		internal bool compareRef(int ref1, int ref2) 
		{
			int top=LuaDLL.lua_gettop(luaState);
			LuaDLL.lua_getref(luaState,ref1);
			LuaDLL.lua_getref(luaState,ref2);
            int equal=LuaDLL.lua_equal(luaState,-1,-2);
			LuaDLL.lua_settop(luaState,top);
			return (equal!=0);
		}
        
        internal void pushCSFunction(LuaCSFunction function)
        {
            translator.pushFunction(luaState,function);
        }

        #region IDisposable Members

        public virtual void Dispose()
        {
            if (translator != null)
            {
                translator.pendingEvents.Dispose();
                translator = null;
            }
        
            this.Close();
            System.GC.Collect();
            System.GC.WaitForPendingFinalizers();
        }

        #endregion
   }

   /// <summary>
   /// Event codes for lua hook function
   /// </summary>
   /// <remarks>
   /// Do not change any of the values because they must match the lua values
   /// </remarks>
   /// <author>Reinhard Ostermeier</author>
   public enum EventCodes
   {
      LUA_HOOKCALL = 0,
      LUA_HOOKRET = 1,
      LUA_HOOKLINE = 2,
      LUA_HOOKCOUNT = 3,
      LUA_HOOKTAILRET = 4,
   }

   /// <summary>
   /// Event masks for lua hook callback
   /// </summary>
   /// <remarks>
   /// Do not change any of the values because they must match the lua values
   /// </remarks>
   /// <author>Reinhard Ostermeier</author>
   [Flags]
   public enum EventMasks
   {
      LUA_MASKCALL = (1 << EventCodes.LUA_HOOKCALL),
      LUA_MASKRET = (1 << EventCodes.LUA_HOOKRET),
      LUA_MASKLINE = (1 << EventCodes.LUA_HOOKLINE),
      LUA_MASKCOUNT = (1 << EventCodes.LUA_HOOKCOUNT),
      LUA_MASKALL = Int32.MaxValue,
   }

   /// <summary>
   /// Structure for lua debug information
   /// </summary>
   /// <remarks>
   /// Do not change this struct because it must match the lua structure lua_debug
   /// </remarks>
   /// <author>Reinhard Ostermeier</author>
   [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
   public struct LuaDebug
   {
      public EventCodes eventCode;
      [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPStr)]
      public String name;
      [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPStr)]
      public String namewhat;
      [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPStr)]
      public String what;
      [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPStr)]
      public String source;
      public int currentline;
      public int nups;
      public int linedefined;
      public int lastlinedefined;
      [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 60/*LUA_IDSIZE*/)]
      public String shortsrc;
      public int i_ci;
   }

   /// <summary>
   /// Event args for hook callback event
   /// </summary>
   /// <author>Reinhard Ostermeier</author>
   public class DebugHookEventArgs : EventArgs
   {
      private readonly LuaDebug luaDebug;

      public DebugHookEventArgs(LuaDebug luaDebug)
      {
         this.luaDebug = luaDebug;
      }

      public LuaDebug LuaDebug
      {
         get { return luaDebug; }
      }
   }

   public class HookExceptionEventArgs : EventArgs
   {
      private readonly Exception m_Exception;
      public Exception Exception
      {
         get { return m_Exception; }
      }

      public HookExceptionEventArgs(Exception ex)
      {
         m_Exception = ex;
      }
   }
}