/*
 * This file is part of NLua.
 * 
 * Copyright (c) 2013 Vinicius Jarina (viniciusjarina@gmail.com)
 * Copyright (C) 2003-2005 Fabio Mascarenhas de Queiroz.
 * Copyright (C) 2009 Joshua Simmons <simmons.44@gmail.com>
 * Copyright (C) 2012 Megax <http://megax.yeahunter.hu/>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
using System;
using System.IO;
using NLua.Extensions;

namespace NLua
{
	#if USE_KOPILUA
	using LuaCore = KopiLua.Lua;
	#else
	using LuaCore = KeraLua.Lua;
	#endif

	public class LuaLib
	{
		// steffenj: BEGIN additional Lua API functions new in Lua 5.1
		public static int lua_gc (LuaCore.lua_State luaState, GCOptions what, int data)
		{
			return LuaCore.lua_gc (luaState, (int)what, data);
		}

		public static string lua_typename (LuaCore.lua_State luaState, LuaTypes type)
		{
			return LuaCore.lua_typename (luaState, (int)type).ToString ();
		}

		public static string luaL_typename (LuaCore.lua_State luaState, int stackPos)
		{
			return lua_typename (luaState, lua_type (luaState, stackPos));
		}

		public static void luaL_error (LuaCore.lua_State luaState, string message)
		{
			LuaCore.luaL_error (luaState, message);
		}

		public static void luaL_where (LuaCore.lua_State luaState, int level)
		{
			LuaCore.luaL_where (luaState, level);
		}

		// steffenj: BEGIN Lua 5.1.1 API change (lua_open replaced by luaL_newstate)
		public static LuaCore.lua_State luaL_newstate ()
		{
			return LuaCore.luaL_newstate ();
		}

		// steffenj: BEGIN Lua 5.1.1 API change (new function luaL_openlibs)
		public static void luaL_openlibs (LuaCore.lua_State luaState)
		{
			LuaCore.luaL_openlibs (luaState);
		}

		// steffenj: END Lua 5.1.1 API change (lua_strlen is now lua_objlen)
		// steffenj: BEGIN Lua 5.1.1 API change (lua_dostring is now a macro luaL_dostring)
		public static int luaL_loadstring (LuaCore.lua_State luaState, string chunk)
		{
			return LuaCore.luaL_loadstring (luaState, chunk);
		}

		public static int luaL_loadstring (LuaCore.lua_State luaState, byte[] chunk)
		{
			return LuaCore.luaL_loadstring (luaState, chunk);
		}

		public static int luaL_dostring (LuaCore.lua_State luaState, string chunk)
		{
			int result = luaL_loadstring (luaState, chunk);
			if (result != 0)
				return result;

			return lua_pcall (luaState, 0, -1, 0);
		}

		public static int luaL_dostring (LuaCore.lua_State luaState, byte[] chunk)
		{
			int result = luaL_loadstring (luaState, chunk);
			if (result != 0)
				return result;
			
			return lua_pcall (luaState, 0, -1, 0);
		}
		
		/// <summary>DEPRECATED - use luaL_dostring(LuaCore.lua_State luaState, string chunk) instead!</summary>
		public static int lua_dostring (LuaCore.lua_State luaState, string chunk)
		{
			return luaL_dostring (luaState, chunk);
		}

		// steffenj: END Lua 5.1.1 API change (lua_dostring is now a macro luaL_dostring)
		// steffenj: BEGIN Lua 5.1.1 API change (lua_newtable is gone, lua_createtable is new)
		public static void lua_createtable (LuaCore.lua_State luaState, int narr, int nrec)
		{
			LuaCore.lua_createtable (luaState, narr, nrec);
		}

		public static void lua_newtable (LuaCore.lua_State luaState)
		{
			lua_createtable (luaState, 0, 0);
		}

		// steffenj: END Lua 5.1.1 API change (lua_newtable is gone, lua_createtable is new)
		// steffenj: BEGIN Lua 5.1.1 API change (lua_dofile now in LuaLib as luaL_dofile macro)
		public static int luaL_dofile (LuaCore.lua_State luaState, string fileName)
		{
			int result = LuaCore.luaL_loadfile (luaState, fileName);
			if (result != 0)
				return result;

			return LuaCore.lua_pcall (luaState, 0, -1, 0);
		}

		// steffenj: END Lua 5.1.1 API change (lua_dofile now in LuaLib as luaL_dofile)
		public static void lua_getglobal (LuaCore.lua_State luaState, string name)
		{
			lua_pushstring (luaState, name);
			LuaCore.lua_gettable (luaState, (int)LuaIndexes.Globals);
		}

		public static void lua_setglobal (LuaCore.lua_State luaState, string name)
		{
			lua_pushstring (luaState, name);
			lua_insert (luaState, -2);
			lua_settable (luaState, (int)LuaIndexes.Globals);
		}

		public static void lua_settop (LuaCore.lua_State luaState, int newTop)
		{
			LuaCore.lua_settop (luaState, newTop);
		}

		public static void lua_pop (LuaCore.lua_State luaState, int amount)
		{
			lua_settop (luaState, -(amount) - 1);
		}

		public static void lua_insert (LuaCore.lua_State luaState, int newTop)
		{
			LuaCore.lua_insert (luaState, newTop);
		}

		public static void lua_remove (LuaCore.lua_State luaState, int index)
		{
			LuaCore.lua_remove (luaState, index);
		}

		public static void lua_gettable (LuaCore.lua_State luaState, int index)
		{
			LuaCore.lua_gettable (luaState, index);
		}

		public static void lua_rawget (LuaCore.lua_State luaState, int index)
		{
			LuaCore.lua_rawget (luaState, index);
		}

		public static void lua_settable (LuaCore.lua_State luaState, int index)
		{
			LuaCore.lua_settable (luaState, index);
		}

		public static void lua_rawset (LuaCore.lua_State luaState, int index)
		{
			LuaCore.lua_rawset (luaState, index);
		}

		public static void lua_setmetatable (LuaCore.lua_State luaState, int objIndex)
		{
			LuaCore.lua_setmetatable (luaState, objIndex);
		}

		public static int lua_getmetatable (LuaCore.lua_State luaState, int objIndex)
		{
			return LuaCore.lua_getmetatable (luaState, objIndex);
		}

		public static int lua_equal (LuaCore.lua_State luaState, int index1, int index2)
		{
			return LuaCore.lua_equal (luaState, index1, index2);
		}

		public static void lua_pushvalue (LuaCore.lua_State luaState, int index)
		{
			LuaCore.lua_pushvalue (luaState, index);
		}

		public static void lua_replace (LuaCore.lua_State luaState, int index)
		{
			LuaCore.lua_replace (luaState, index);
		}

		public static int lua_gettop (LuaCore.lua_State luaState)
		{
			return LuaCore.lua_gettop (luaState);
		}

		public static LuaTypes lua_type (LuaCore.lua_State luaState, int index)
		{
			return (LuaTypes)LuaCore.lua_type (luaState, index);
		}

		public static bool lua_isnil (LuaCore.lua_State luaState, int index)
		{
			return lua_type (luaState, index) == LuaTypes.Nil;
		}

		public static bool lua_isnumber (LuaCore.lua_State luaState, int index)
		{
			return lua_type (luaState, index) == LuaTypes.Number;
		}

		public static bool lua_isboolean (LuaCore.lua_State luaState, int index)
		{
			return lua_type (luaState, index) == LuaTypes.Boolean;
		}

		public static int luaL_ref (LuaCore.lua_State luaState, int registryIndex)
		{
			return LuaCore.luaL_ref (luaState, registryIndex);
		}

		public static int lua_ref (LuaCore.lua_State luaState, int lockRef)
		{
			return lockRef != 0 ? luaL_ref (luaState, (int)LuaIndexes.Registry) : 0;
		}

		public static void lua_rawgeti (LuaCore.lua_State luaState, int tableIndex, int index)
		{
			LuaCore.lua_rawgeti (luaState, tableIndex, index);
		}

		public static void lua_rawseti (LuaCore.lua_State luaState, int tableIndex, int index)
		{
			LuaCore.lua_rawseti (luaState, tableIndex, index);
		}

		public static object lua_newuserdata (LuaCore.lua_State luaState, int size)
		{
			return LuaCore.lua_newuserdata (luaState, (uint)size);
		}

		public static object lua_touserdata (LuaCore.lua_State luaState, int index)
		{
			return LuaCore.lua_touserdata (luaState, index);
		}

		public static void lua_getref (LuaCore.lua_State luaState, int reference)
		{
			lua_rawgeti (luaState, (int)LuaIndexes.Registry, reference);
		}

		public static void lua_unref (LuaCore.lua_State luaState, int reference)
		{
			LuaCore.luaL_unref (luaState, (int)LuaIndexes.Registry, reference);
		}

		public static bool lua_isstring (LuaCore.lua_State luaState, int index)
		{
			return LuaCore.lua_isstring (luaState, index) != 0;
		}

		public static bool lua_iscfunction (LuaCore.lua_State luaState, int index)
		{
			return LuaCore.lua_iscfunction (luaState, index);
		}

		public static void lua_pushnil (LuaCore.lua_State luaState)
		{
			LuaCore.lua_pushnil (luaState);
		}

		public static void lua_call (LuaCore.lua_State luaState, int nArgs, int nResults)
		{
			LuaCore.lua_call (luaState, nArgs, nResults);
		}

		public static void lua_pushstdcallcfunction (LuaCore.lua_State luaState, LuaCore.lua_CFunction function)
		{
			LuaCore.lua_pushcfunction (luaState, function);
		}

		public static int lua_pcall (LuaCore.lua_State luaState, int nArgs, int nResults, int errfunc)
		{
			return LuaCore.lua_pcall (luaState, nArgs, nResults, errfunc);
		}

		public static LuaCore.lua_CFunction lua_tocfunction (LuaCore.lua_State luaState, int index)
		{
			return LuaCore.lua_tocfunction (luaState, index);
		}

		public static double lua_tonumber (LuaCore.lua_State luaState, int index)
		{
			return LuaCore.lua_tonumber (luaState, index);
		}

		public static bool lua_toboolean (LuaCore.lua_State luaState, int index)
		{
			return LuaCore.lua_toboolean (luaState, index) != 0;
		}

		public static string lua_tostring (LuaCore.lua_State luaState, int index)
		{
#if true
			// FIXME use the same format string as lua i.e. LUA_NUMBER_FMT
			var t = lua_type (luaState, index);

			if (t == LuaTypes.Number)
				return string.Format ("{0}", lua_tonumber (luaState, index));
			else if (t == LuaTypes.String) {
				uint strlen;
				// Changed 2013-05-18 by Dirk Weltz
				// Changed because binary chunks, which are also transfered as strings
				// get corrupted by conversion to strings because of the encoding.
				// So we use the ToString method with string length, so it could be checked,
				// if string is a binary chunk and if, could transfered to string without
				// encoding.
				return LuaCore.lua_tolstring (luaState, index, out strlen).ToString ((int)strlen);
			} else if (t == LuaTypes.Nil)
				return null;			// treat lua nulls to as C# nulls
			else
				return "0";	// Because luaV_tostring does this
#else
			size_t strlen;

			// Note!  This method will _change_ the representation of the object on the stack to a string.
			// We do not want this behavior so we do the conversion ourselves
			const char *str = LuaCore.lua_tolstring(luaState, index, &strlen);
			if (str)
				return Marshal::PtrToStringAnsi(IntPtr((char *) str), strlen);
			else
				return nullptr;			// treat lua nulls to as C# nulls
#endif
		}

		public static void lua_atpanic (LuaCore.lua_State luaState, LuaCore.lua_CFunction panicf)
		{
			LuaCore.lua_atpanic (luaState, (LuaCore.lua_CFunction)panicf);
		}


		public static void lua_pushnumber (LuaCore.lua_State luaState, double number)
		{
			LuaCore.lua_pushnumber (luaState, number);
		}

		public static void lua_pushboolean (LuaCore.lua_State luaState, bool value)
		{
			LuaCore.lua_pushboolean (luaState, value ? 1 : 0);
		}

		public static void lua_pushstring (LuaCore.lua_State luaState, string str)
		{
			LuaCore.lua_pushstring (luaState, str);
		}

		public static int luaL_newmetatable (LuaCore.lua_State luaState, string meta)
		{
			return LuaCore.luaL_newmetatable (luaState, meta);
		}

		// steffenj: BEGIN Lua 5.1.1 API change (luaL_getmetatable is now a macro using lua_getfield)
		public static void lua_getfield (LuaCore.lua_State luaState, int stackPos, string meta)
		{
			LuaCore.lua_getfield (luaState, stackPos, meta);
		}

		public static void luaL_getmetatable (LuaCore.lua_State luaState, string meta)
		{
			lua_getfield (luaState, (int)LuaIndexes.Registry, meta);
		}

		public static object luaL_checkudata (LuaCore.lua_State luaState, int stackPos, string meta)
		{
			return LuaCore.luaL_checkudata (luaState, stackPos, meta);
		}

		public static bool luaL_getmetafield (LuaCore.lua_State luaState, int stackPos, string field)
		{
			return LuaCore.luaL_getmetafield (luaState, stackPos, field) != 0;
		}

		public static int luaL_loadbuffer (LuaCore.lua_State luaState, string buff, string name)
		{
			return LuaCore.luaL_loadbuffer (luaState, buff, (uint)buff.Length, name);
		}

		public static int luaL_loadbuffer (LuaCore.lua_State luaState, byte [] buff, string name)
		{
			return LuaCore.luaL_loadbuffer (luaState, buff, (uint)buff.Length, name);
		}

		public static int luaL_loadfile (LuaCore.lua_State luaState, string filename)
		{
			return LuaCore.luaL_loadfile (luaState, filename);
		}

		public static bool luaL_checkmetatable (LuaCore.lua_State luaState, int index)
		{
			return LuaCore.luaL_checkmetatable (luaState, index);
		}

		public static int luanet_tonetobject (LuaCore.lua_State luaState, int index)
		{
			return LuaCore.luanet_tonetobject (luaState, index);
		}

		public static void luanet_newudata (LuaCore.lua_State luaState, int val)
		{
			LuaCore.luanet_newudata (luaState, val);
		}

		public static int luanet_rawnetobj (LuaCore.lua_State luaState, int obj)
		{
			return LuaCore.luanet_rawnetobj (luaState, obj);
		}

		public static int luanet_checkudata (LuaCore.lua_State luaState, int ud, string tname)
		{
			return LuaCore.luanet_checkudata (luaState, ud, tname);
		}

		public static void lua_error (LuaCore.lua_State luaState)
		{
			LuaCore.lua_error (luaState);
		}

		public static bool lua_checkstack (LuaCore.lua_State luaState, int extra)
		{
			return LuaCore.lua_checkstack (luaState, extra) != 0;
		}

		public static int lua_next (LuaCore.lua_State luaState, int index)
		{
			return LuaCore.lua_next (luaState, index);
		}

		public static void lua_pushlightuserdata (LuaCore.lua_State luaState, LuaCore.LuaTag udata)
		{
			LuaCore.lua_pushlightuserdata (luaState, udata.Tag);
		}

		public static LuaCore.LuaTag luanet_gettag ()
		{
			return LuaCore.luanet_gettag ();
		}

	}
}