
#include "EABaseTypesLua/EABaseTypesLua.h"

#include "EABaseTypes/iencoder.h"
#include "EABaseTypes/functioncallwrappers.h"
#include "EABaseTypes/autorefuserdata.h"

#include "EABaseTypesPlus/path.h"
#include "EABaseTypesPlus/ifilehandler.h"
#include "EABaseTypesPlus/vrefcountedica.h"

#include "EABaseTypesJson/jsonloader.h"

#include "EABaseTypesLua/datautillua.h"
#include "EABaseTypesLua/stackcheck.h"
#include "EABaseTypesLua/luafunction.h"
#include "EABaseTypesLua/function_userdata.h"
#include "EABaseTypesLua/luaencoder.h"

#include "eaassert/eaassert.h"

#include "eastdc/eamemory.h"

#include "coreallocator/icoreallocatormacros.h"

#include "loader.h"

#if !defined(EA_FINAL)
#include "luaprofiler.h"
#endif

#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
#include "lgc.h"

namespace EA
{
	namespace Lua
	{
		static const char *FileResourceReader(lua_State *L, void *data, size_t *size);

		struct FileResourceContext
		{
			EA::Types::IFileResourceRef	mFileResource;
			bool				mRead;

			explicit FileResourceContext(EA::Types::IFileResourceIn fileResource):
				mFileResource(fileResource),
				mRead(false)
			{
			}
		};

		class LuaFileHandleCallback : public EA::Types::VRefCountedICA<EA::Types::IFileHandlerCallback>
		{
		public:
			LuaFileHandleCallback(EA::Allocator::ICoreAllocator* allocator, 
								  lua_State* L, 
								  const char* filename, 
								  int closureRef)
				: EA::Types::VRefCountedICA<EA::Types::IFileHandlerCallback>(allocator)
				, mL(L)
				, mFileName(GetFactory(L), filename)
				, mClosureRef(closureRef)
			{
				
			}

			//LINT Boolean within 'if' always evaluates to False. We are just trying to recover gracefully from the assert
			//lint -efunc(774, EA::Lua::LuaFileHandleCallback::~LuaFileHandleCallback)
			//lint -efunc(948, EA::Lua::LuaFileHandleCallback::~LuaFileHandleCallback)
			virtual ~LuaFileHandleCallback() 
			{
				lua_State* L = mL;
//FIFASTART - dstreat - May 9, 2014 - Allow deletion with existing references.  The refs are cleaned up below.
				//EA_ASSERT(mClosureRef == LUA_REFNIL);
//FIFAEND
				if(mClosureRef != LUA_REFNIL)
				{
					// remove our table for lua callbacks from the registry
					// this will cause it to be collected
					luaL_unref(L, LUA_REGISTRYINDEX, mClosureRef);
					mClosureRef = LUA_REFNIL;
				}
				mL = NULL;
			}

			//lint -efunc(64, EA::Lua::LuaFileHandleCallback::LoadComplete)
			virtual void LoadComplete(EA::Types::IFileResourceIn fileResource)
			{
				lua_State* L = mL;
				
				EA::Lua::StackCheck s(L);

				//push the function
				lua_rawgeti(L, LUA_REGISTRYINDEX, mClosureRef);

				int numArgs = 0;

				if(fileResource != NULL)
				{
					FileResourceContext context(fileResource);

					// prepend an @ to the fileName so that LUA knows that this chunk comes from a file (as opposed to a string literal or something)
					EA::RawString chunkName(mAllocator);
					chunkName.sprintf("@%s", mFileName.c_str());

					int result = lua_load(L, FileResourceReader, &context, chunkName.c_str(), nullptr);

					++numArgs;

					//push any arguments
					if(result != 0)
					{
						//push nil and move it below the error message returned by lua_load
						lua_pushnil(L);
						lua_insert(L, -2);
						
						++numArgs;
					}
				}
				else
				{
					lua_pushnil(L);

					EA::RawString error(mAllocator);
					error.sprintf("Could not find '%s'", mFileName.c_str());
					lua_pushstring(L, error.c_str());

					numArgs += 2;
				}

				//call the function
				Call(L, numArgs, 0);

				// remove our table for lua callbacks from the registry
				// this will cause it to be collected
				if(mClosureRef != LUA_REFNIL)
				{
					luaL_unref(L, LUA_REGISTRYINDEX, mClosureRef);
					mClosureRef = LUA_REFNIL;
				}
			}

		private:

			lua_State*						mL;
			EA::String						mFileName;
			int								mClosureRef;
		};

		static void *LuaICoreAllocator(void *ud, void *ptr, size_t oldSize, size_t newSize)
		{
			EA::Allocator::ICoreAllocator *allocator = static_cast<EA::Allocator::ICoreAllocator *>(ud);

			void *newPtr = NULL;

			// A request of zero bytes is a request to free the given pointer.
			if (newSize == 0)
			{
				if (ptr != NULL)
				{
					allocator->Free(ptr);
				}
			}
			else if(newSize != oldSize)
			{
				newPtr = allocator->Alloc(newSize, "LUA", EA::Allocator::MEM_PERM, 8);

				if(ptr != NULL)
				{
					size_t size = (oldSize < newSize) ? oldSize : newSize;
					EA::StdC::Memcpy(newPtr, ptr, size);
					allocator->Free(ptr);
				}
			}
			else
			{
				newPtr = ptr;
			}

			return newPtr;
		}

#if !defined(EA_FINAL)

		struct debugalloc_userdata
		{
			lua_State*						mL;
			EA::Allocator::ICoreAllocator	*mAllocator;
		};

		static void *LuaDebugICoreAllocator(void *ud, void *ptr, size_t oldSize, size_t newSize)
		{
			debugalloc_userdata* userdata = static_cast<debugalloc_userdata*>(ud);

			EA::Allocator::ICoreAllocator *allocator = userdata->mAllocator;

			void *newPtr = NULL;

			// A request of zero bytes is a request to free the given pointer.
			if (newSize == 0)
			{
				if (ptr != NULL)
				{
					allocator->Free(ptr);
				}
			}
			else if(newSize != oldSize)
			{
				const char *name = "LUA";

#if defined(EA_DEBUG)
				lua_State* L = userdata->mL;

				lua_Debug ar;

				EA::RawString allocName(allocator);

				if(0 != lua_getstack(L, 0, &ar))
				{
					// Live, from New York!
					lua_getinfo(L, "Snl", &ar);

					allocName.sprintf("%s!%s line %d", ar.short_src, ar.name != NULL ? ar.name : "<anonymous>", ar.currentline);

					name = allocName.c_str();
				}
#endif

				newPtr = allocator->Alloc(newSize, name, EA::Allocator::MEM_PERM, 16);

				if(ptr != NULL)
				{
					size_t size = (oldSize < newSize) ? oldSize : newSize;
					EA::StdC::Memcpy(newPtr, ptr, size);
					allocator->Free(ptr);
				}
			}
			else
			{
				newPtr = ptr;
			}

			return newPtr;
		}

		static int debugalloc_gc(lua_State* L)
		{
			debugalloc_userdata* userdata = static_cast<debugalloc_userdata*>(luaL_checkudata(L, 1, "DebugAlloc"));
			luaL_argcheck(L, userdata, 1, "DebugAlloc expected");

			// switch back to the regular alloc hook
			lua_setallocf(L, LuaICoreAllocator, userdata->mAllocator);

			return 0;
		}

		static const luaL_reg debugalloc_methods[] =
		{
			{"__gc",			debugalloc_gc},
			{NULL,				NULL}
		};

		static char sDebugAllocRegistryKey = 'k';

		static void InitDebugAlloc(lua_State* L, EA::Allocator::ICoreAllocator *allocator)
		{
			lua_pushlightuserdata(L, &sDebugAllocRegistryKey);

			debugalloc_userdata* userdata = 
				static_cast<debugalloc_userdata*>(lua_newuserdata(L, sizeof(debugalloc_userdata)));

			userdata->mL = L;
			userdata->mAllocator = allocator;

			// create a new metatable
			luaL_newmetatable(L, "DebugAlloc");
			// metatable.__index = metatable
			lua_pushliteral(L, "__index");
			lua_pushvalue(L, -2);
			lua_rawset(L, -3);
			// register methods
			luaL_openlib(L, NULL, debugalloc_methods, 0);

			// set the DebugAlloc metatable as the metatable of our new userdata
			// this pops the metatable
			lua_setmetatable(L, -2);

			// put the DebugAlloc userdata into the registry
			lua_settable(L, LUA_REGISTRYINDEX);

			lua_setallocf(L, LuaDebugICoreAllocator, userdata);
		}
#endif

		//PcLint turns this into LINT_FAIL_MESSAGE, which makes the rest of the method unreachable
		//lint -efunc(527, EA::Lua::Panic)
		static int Panic(lua_State *L)
		{
			EA_FAIL_MESSAGE(lua_tostring(L, -1));

			lua_settop(L, 0);
			return 0;
		}

		//PcLint thinks L can be const - not so, this function must match a prototype in LUA
		//lint -efunc(818, EA::Lua::FileResourceReader)
		//lint -efunc(904, EA::Lua::FileResourceReader)
		static const char *FileResourceReader(lua_State *L, void *data, size_t *size)
		{
			FileResourceContext *context = static_cast<FileResourceContext *>(data);
			
			if(!context->mRead)
			{
				context->mRead = true;

				*size = context->mFileResource->GetSize();

				return static_cast<const char *>(context->mFileResource->GetData());
			}
			else
			{
				*size = 0;

				return NULL;
			}
		}

		static char sFileHandlerRegistryKey = 'k';

		//lint -efunc(64, EA::Lua::LoadFileFileResource)
		//lint -efunc(904, EA::Lua::LoadFileFileResource)
		static int LoadFileFileResource(lua_State *L)
		{
			lua_pushlightuserdata(L, &sFileHandlerRegistryKey);
			lua_rawget(L, LUA_REGISTRYINDEX);

			EA::Types::IFileHandlerRef fileHandler = ToBaseType(L, -1)->As<EA::Types::IFileHandlerRef>();

			lua_pop(L, 1);

			// get the allocator
			EA::Allocator::ICoreAllocator* allocator = GetAllocator(L);

			// get the filename
			const char* fileName = lua_tostring(L, 1);

			EA::Types::IFileResourceRef fileResource = fileHandler->Load(allocator, fileName);

			if(fileResource != NULL)
			{
				FileResourceContext context(fileResource);

				// prepend an @ to the fileName so that LUA knows that this chunk comes from a file (as opposed to a string literal or something)
				EA::RawString chunkName(allocator);
				chunkName.sprintf("@%s", fileName);

				int result = lua_load(L, FileResourceReader, &context, chunkName.c_str(), nullptr);

				if(result != 0)
				{
					lua_pushnil(L);
					lua_insert(L, -2);

					return 2;
				}
				else
				{
					// lua_load should push a function onto the stack representing the chunk we loaded
					return 1;
				}
			}
			else
			{
				return luaL_error(L, "Could not find '%s'", fileName);
			}
		}

		//LINT Custodial pointer 'callback' has not been freed or returned. The object takes ownership of itself
		//lint -efunc(429, EA::Lua::AsyncLoadFileFileResource)
		static int AsyncLoadFileFileResource(lua_State* L)
		{
			lua_pushlightuserdata(L, &sFileHandlerRegistryKey);
			lua_rawget(L, LUA_REGISTRYINDEX);

			EA::Types::IFileHandlerRef fileHandler = ToBaseType(L, -1)->As<EA::Types::IFileHandlerRef>();

			lua_pop(L, 1);

			//dupe the closure on the stack
			lua_pushvalue(L, 1);

			//put the closure in the registry
			int ref = luaL_ref(L, LUA_REGISTRYINDEX);

			// get the allocator
			EA::Allocator::ICoreAllocator* allocator = GetAllocator(L);

			// get the filename
			const char* fileName = lua_tostring(L, 2);
			EA::Types::IFileHandlerCallbackRef callback = CORE_NEW(allocator, "LuaFileHandleCallback", EA::Allocator::MEM_TEMP) LuaFileHandleCallback(allocator, L, fileName, ref);
			fileHandler->Load(callback, allocator, fileName);
			return 0;
		}

		static int LoadFile(lua_State *L)
		{
			return LoadFileFileResource(L);
		}

		static int LoadFileAsync(lua_State* L)
		{
			return AsyncLoadFileFileResource(L);
		}

		static int DoFile(lua_State *L)
		{
			int numArgs = lua_gettop(L);

			lua_getglobal(L, "loadfile");
			lua_pushvalue(L, 1);
			Call(L, 1, LUA_MULTRET);
			
			int numResults = lua_gettop(L) - numArgs;

			lua_getglobal(L, "assert");
			lua_insert(L, -(numResults + 1));

			Call(L, numResults, LUA_MULTRET);

			numArgs = lua_gettop(L) - 1; // don't count the function which is already on the stack!

			Call(L, 0, LUA_MULTRET);

			return lua_gettop(L) - numArgs;
		}

		//lint -efunc(730, EA::Lua::FileExists)
		static int FileExists(lua_State* L)
		{
			lua_pushlightuserdata(L, &sFileHandlerRegistryKey);
			lua_rawget(L, LUA_REGISTRYINDEX);

			EA::Types::IFileHandlerRef fileHandler = ToBaseType(L, -1)->As<EA::Types::IFileHandlerRef>();

			lua_pop(L, 1);

			// get the allocator
			EA::Allocator::ICoreAllocator* allocator = GetAllocator(L);

			bool exists = fileHandler->Exists(allocator, lua_tostring(L, -1));

			lua_pushboolean(L, exists);

			return 1;
		}
		
		static int GetPath(lua_State* L)
		{
			const char* p = lua_tostring(L, -1);

			EA::RawString result(GetAllocator(L));
			EA::Types::Path::GetPath(result, p);

			lua_pushstring(L, result.c_str());

			return 1;
		}

		static int AppendPath(lua_State* L)
		{
			EA::Allocator::ICoreAllocator* allocator = GetAllocator(L);

			EA::RawString p1(allocator, lua_tostring(L, 1));
			const char* p2 = lua_tostring(L, 2);

			EA::RawString result(GetAllocator(L));
			EA::Types::Path::AppendRelative(result, p1, p2);

			lua_pushstring(L, result.c_str());

			return 1;
		}

		static int AppendPathRaw(lua_State* L)
		{
			EA::Allocator::ICoreAllocator* allocator = GetAllocator(L);

			EA::RawString p1(allocator, lua_tostring(L, 1));
			const char* p2 = lua_tostring(L, 2);

			EA::RawString result(GetAllocator(L));
			EA::Types::Path::AppendRaw(result, p1, p2);

			lua_pushstring(L, result.c_str());

			return 1;
		}

		static int IsAbsolutePath(lua_State* L)
		{
			const char* p = lua_tostring(L, 1);

			lua_pushboolean(L, EA::Types::Path::IsAbsolute(p));

			return 1;
		}

		static char sPrintRegistryKey = 'k';

		static EA::Types::FunctionReturn GetPrintFunction(lua_State* L)
		{
			// get the print channel
			lua_pushlightuserdata(L, &sPrintRegistryKey);
			lua_rawget(L, LUA_REGISTRYINDEX);

			EA::Types::FunctionRef print = ToBaseType(L, -1)->AsFunction();

			lua_pop(L, 1);

			return print;
		}
		
		//PcLint thinks L can be const - not so, this function must match a prototype in LUA
		//lint -efunc(818, EA::Lua::Print)
		//lint -efunc(904, EA::Lua::Print)
		static int Print(lua_State *L)
		{
			int numArgs = lua_gettop(L);  // number of arguments

			if(numArgs > 0)
			{
				EA::Types::FunctionRef print = GetPrintFunction(L);

				if(print != NULL)
				{
					EA::RawString message(GetAllocator(L));

					lua_getglobal(L, "tostring");
					for (int i = 1; i <= numArgs; ++i) 
					{
						lua_pushvalue(L, -1);  // tostring function 
						lua_pushvalue(L, i);   // value to print
						lua_call(L, 1, 1);
						const char *s = lua_tostring(L, -1);  // get result
						if (s == NULL)
						{
							return luaL_error(L, LUA_QL("tostring") " must return a string to "	LUA_QL("print"));
						}
						if (i > 1) 
						{
							message.append("\t");
						}
						message.append_sprintf("%s", s);
						lua_pop(L, 1);  // pop result
					}

					print->Call<void>(message);
				}
			}

			return 0;
		}
		
		static char sErrorRegistryKey = 'k';

		static EA::Types::FunctionReturn GetErrorFunction(lua_State* L)
		{
			// get the print channel
			lua_pushlightuserdata(L, &sErrorRegistryKey);
			lua_rawget(L, LUA_REGISTRYINDEX);

			EA::Types::FunctionRef error = ToBaseType(L, -1)->AsFunction();

			lua_pop(L, 1);

			return error;
		}

		//PcLint removes the code that uses 'msg'
		//lint -efunc(550, EA::Lua::Assert)
		//PcLint doesn't know about EA_FINAL
		//lint -efunc(553, EA::Lua::Assert)
		//PcLint thinks L can be const - not so, this function must match a prototype in LUA
		//lint -efunc(818, EA::Lua::Assert)
		static int Assert(lua_State *L)
		{
			int numArgs = lua_gettop(L);  // number of arguments
			if (numArgs > 0)
			{
				if (!lua_toboolean(L, 1))
				{
					const char *msg = "";
					
					if(numArgs > 1)
					{
						msg = lua_tostring(L, 2);
					}

					EA::RawString message(GetAllocator(L));
					message.sprintf("assert failed: %s\n", msg);
					message.append(GetBacktrace(L, 1));

					EA::Types::FunctionRef error = GetErrorFunction(L);
				
					if(error != NULL)
					{
						error->Call<void>(message);
					}
					else
					{
						EA_FAIL_MESSAGE(message.c_str());
					}
				}
			}

			return 1;
		}

		void PushFunction(lua_State* L, EA::Types::FunctionIn value)
		{
			EA::Types::Function* function = value.GetUnsafePointer();

			EA::Type::TypeID type = function->GetFunctionType();

			if(type == EA::Type::TypeOf<LuaFunction>() && static_cast<LuaFunction*>(function)->GetLuaState() == L)
			{
				lua_rawgeti(L, LUA_REGISTRYINDEX, static_cast<LuaFunction*>(function)->GetRef());
			}
			else
			{
				// create and push the userdata
				EA::Lua::function_userdata* userdata = static_cast<EA::Lua::function_userdata*>(lua_newuserdata(L, sizeof(EA::Lua::function_userdata)));

				// push the Function metatable
				luaL_getmetatable(L, "Function");

				// set the Function metatable as the metatable of our new userdata
				// this pops the metatable
				lua_setmetatable(L, -2);
				function->AddRef(); // must use a raw pointer here because userdata is not initialized - it's like malloc()
				userdata->mFunc = function;
			}
		}

		void PushUserData(lua_State* L, EA::Types::UserDataIn value)
		{
			EA::Types::UserData* uData = value.GetUnsafePointer();

			// create and push the userdata
			EA::Lua::userdata_userdata* userdata = static_cast<EA::Lua::userdata_userdata*>(lua_newuserdata(L, sizeof(EA::Lua::userdata_userdata)));

			// push the UserData metatable
			luaL_getmetatable(L, "UserData");

			// set the UserData metatable as the metatable of our new userdata
			// this pops the metatable
			lua_setmetatable(L, -2);
			uData->AddRef(); // must use a raw pointer here because userdata is not initialized - it's like malloc()
			userdata->mUData = uData;
		}

		EA::Types::FunctionReturn CreateFunction(lua_State* L, int index)
		{
			EA::Types::Factory* factory = GetFactory(L);

			return new(factory) EA::Lua::LuaFunction(factory, L, index);
		}

		static void AppendTailCalls(EA::RawString& backtrace, int numTailCalls)
		{
			if(numTailCalls == 1)
			{
				backtrace.append("(tail call)\n");
			}
			else
			{
				backtrace.append_sprintf("(%d tail calls)\n", numTailCalls);
			}
		}

		EA::RawString GetBacktrace(lua_State* L, int level)
		{
			EA::RawString backtrace(GetAllocator(L));
			int numTailCalls = 0;

			lua_Debug debug;
			while(lua_getstack(L, level++, &debug) != 0)
			{
				// It's Saturday Night!
				lua_getinfo(L, "Snl", &debug);

				if(0 == strcmp(debug.short_src, "(tail call)"))
				{
					numTailCalls++;
					continue;
				}
				else
				{
					if(numTailCalls > 0)
					{
						AppendTailCalls(backtrace, numTailCalls);

						numTailCalls = 0;
					}
				}

				backtrace.append_sprintf("%s!%s line %d\n", debug.short_src, debug.name != NULL ? debug.name : "<anonymous>", debug.currentline);
			}

			if(numTailCalls > 0)
			{
				AppendTailCalls(backtrace, numTailCalls);
			}

			return backtrace;
		}

		//PcLint thinks L can be const. This is a lie.
		//lint -efunc(818, EA::Lua::PrintBacktrace)
		void PrintBacktrace(lua_State* L, int level)
		{
			EA::Types::FunctionRef print = GetPrintFunction(L);

			if(print != NULL)
			{
				print->Call<void>(GetBacktrace(L, level));
			}
		}

		static int function_call(lua_State* L)
		{
			function_userdata* userdata = static_cast<function_userdata*>(luaL_checkudata(L, 1, "Function"));
			luaL_argcheck(L, userdata, 1, "Function expected");

			lua_remove(L, 1);

			unsigned int numArguments = static_cast<unsigned int>(lua_gettop(L));

			LuaDecoderParams decoderParams(L, numArguments);
			LuaEncoder encoder(L);
			
			EA::Types::IEncoderImpl<LuaEncoder> defaultEncoder(&encoder);
			
			EABASETYPES_PROFILE_END();

			userdata->mFunc->Call(CALLER_TYPE, 
								  &decoderParams, 
								  &encoder, 
								  LuaDecoder<EA::Types::IEncoder>::Decode,
								  &defaultEncoder);

			EABASETYPES_PROFILE_BEGIN(0, "EABaseTypesLua");

			while(numArguments-- > 0)
			{
				lua_remove(L, 1);
			}

			return lua_gettop(L);
		}

		static int function_eq(lua_State* L)
		{
			EA::Lua::StackCheck s(L, 1);
			function_userdata* userdata = static_cast<function_userdata*>(luaL_checkudata(L, 1, "Function"));
			luaL_argcheck(L, userdata, 1, "Function expected");

			function_userdata* rhs = static_cast<function_userdata*>(luaL_checkudata(L, 2, "Function"));
			
			lua_pushboolean(L, rhs != NULL && userdata->mFunc->Equals(EA::Types::FunctionIn(rhs->mFunc)));

			return 1;
		}

		static int function_gc(lua_State* L)
		{
			EA::Lua::StackCheck s(L);
			function_userdata* userdata = static_cast<function_userdata*>(luaL_checkudata(L, 1, "Function"));
			luaL_argcheck(L, userdata, 1, "Function expected");

			EA_ASSERT(userdata != NULL);
			userdata->mFunc->Release();
			return 0;
		}

		static const luaL_reg function_methods[] =
		{
			{"__call",			function_call},
			{"__eq",			function_eq},
			{"__gc",			function_gc},
			{NULL,				NULL}
		};

		static int userdata_eq(lua_State* L)
		{
			EA::Lua::StackCheck s(L, 1);
			userdata_userdata* userdata = static_cast<userdata_userdata*>(luaL_checkudata(L, 1, "UserData"));
			luaL_argcheck(L, userdata, 1, "UserData expected");

			userdata_userdata* rhs = static_cast<userdata_userdata*>(luaL_checkudata(L, 2, "UserData"));

			lua_pushboolean(L, rhs != NULL && userdata->mUData->Equals(EA::Types::UserDataIn(rhs->mUData)));

			return 1;
		}

		static int userdata_gc(lua_State* L)
		{
			EA::Lua::StackCheck s(L);
			userdata_userdata* userdata = static_cast<userdata_userdata*>(luaL_checkudata(L, 1, "UserData"));
			luaL_argcheck(L, userdata, 1, "UserData expected");

			EA_ASSERT(userdata != NULL);
			userdata->mUData->Release();
			return 0;
		}

		static const luaL_reg userdata_methods[] =
		{
			{"__eq",			userdata_eq},
			{"__gc",			userdata_gc},
			{NULL,				NULL}
		};

// FIFA_BEGIN | SENDBACK | cburns | 23-04-2015 | don't build unused local function
#if !defined(EA_FINAL) && 0
		static void AssertHook(int condition)
		{
			EA_ASSERT(condition);
		}
#endif
// FIFA_END

		//PcLint doesn't like boolean arguments to functions, ever
		//lint -efunc(730, EA::Lua::AddDefines)
		//lint -efunc(838, EA::Lua::AddDefines)
		static void AddDefines(lua_State* L)
		{
			// it seems like there should be a better way to do this, but I can't find one
			// the problem is that if something is not defined we can't use it as a value
			// and we can't put preprocessor directives inside macros
			{
				bool def = false;
#if defined(EA_FINAL)
				def = true;
#endif
				lua_pushboolean(L, def);
				lua_setglobal(L, "EA_FINAL");
			}
			{
				bool def = false;
#if defined(EA_PROFILE)
				def = true;
#endif
				lua_pushboolean(L, def);
				lua_setglobal(L, "EA_PROFILE");
			}
			{
				bool def = false;
#if defined(EA_RELEASE)
				def = true;
#endif
				lua_pushboolean(L, def);
				lua_setglobal(L, "EA_RELEASE");
			}
			{
				bool def = false;
#if defined(EA_DEBUG)
				def = true;
#endif
				lua_pushboolean(L, def);
				lua_setglobal(L, "EA_DEBUG");
			}
#if defined(EA_PLATFORM_NAME)
			{
				EA::RawString platform(GetAllocator(L), "EA_PLATFORM_");
				int pos = platform.length();
				platform.append(EA_PLATFORM_NAME);
				platform.make_upper();
				//Replace spaces in platform names such as "Windows Phone" and "PS3 SPU"
				while ((pos = platform.find(' ', pos)) > -1)
				{
					platform.replace(pos, 1, "_");
				};
				lua_pushboolean(L, true);
				lua_setglobal(L, platform.c_str());
			}
#endif
		}

		static char sAllocatorRegistryKey = 'k';
		static char sFactoryRegistryKey = 'k';

		//PcLint doesn't like LUA's macros doing C-like things
		//lint -efunc(1924, EA::Lua::NewState)
		lua_State *NewState(EA::Allocator::ICoreAllocator* luaAllocator,
							EA::Allocator::ICoreAllocator* allocator, 
							EA::Types::Factory* factory,
							EA::Types::IFileHandlerIn fileHandler,
							EA::Types::JsonLoader* jsonLoader,
							EA::Types::FunctionIn print,
							EA::Types::FunctionIn error)
		{
#if !defined(EA_FINAL)
// 			lua_AssertHook assertHook = lua_getasserthook();
// 			if(assertHook == NULL)
// 			{
// 				lua_setasserthook(AssertHook);
// 			}
#endif

			lua_State *L = lua_newstate(LuaICoreAllocator, luaAllocator);

			EA::Lua::StackCheck s(L);

#if !defined(EA_FINAL)
			InitDebugAlloc(L, luaAllocator);

			lua_pop(L, luaopen_profiler(L));
#endif

			lua_pushlightuserdata(L, &sAllocatorRegistryKey);
			lua_pushlightuserdata(L, allocator);
			lua_settable(L, LUA_REGISTRYINDEX);

			lua_pushlightuserdata(L, &sFactoryRegistryKey);
			lua_pushlightuserdata(L, factory);
			lua_settable(L, LUA_REGISTRYINDEX);

			lua_atpanic(L, Panic);
			
			suspendnurserygc(G(L));

			luaL_requiref(L, "_G", luaopen_base, 1);
			lua_pop(L, 1);  /* remove lib */

			luaL_requiref(L, LUA_OSLIBNAME, luaopen_os, 1);
			lua_pop(L, 1);  /* remove lib */

#if !defined(EA_FINAL)
			luaL_requiref(L, LUA_DBLIBNAME, luaopen_debug, 1);
			lua_pop(L, 1);  /* remove lib */
#endif

			luaL_requiref(L, LUA_TABLIBNAME, luaopen_table, 1);
			lua_pop(L, 1);  /* remove lib */

			luaL_requiref(L, LUA_STRLIBNAME, luaopen_string, 1);
			lua_pop(L, 1);  /* remove lib */

			luaL_requiref(L, LUA_MATHLIBNAME, luaopen_math, 1);
			lua_pop(L, 1);  /* remove lib */

			resumenurserygc(G(L));

			AddDefines(L);

			// create a new metatable
			luaL_newmetatable(L, "Function");
			// metatable.__index = metatable
			lua_pushliteral(L, "__index");
			lua_pushvalue(L, -2);
			lua_rawset(L, -3);
			// register methods
			luaL_openlib(L, NULL, function_methods, 0);
			// pop the metatable 
			lua_pop(L, 1);

			// create a new metatable
			luaL_newmetatable(L, "UserData");
			// metatable.__index = metatable
			lua_pushliteral(L, "__index");
			lua_pushvalue(L, -2);
			lua_rawset(L, -3);
			// register methods
			luaL_openlib(L, NULL, userdata_methods, 0);
			// pop the metatable 
			lua_pop(L, 1);
			
			lua_pushcfunction(L, LoadFile);
			lua_setglobal(L, "loadfile");

			lua_pushcfunction(L, LoadFileAsync);
			lua_setglobal(L, "loadfileasync");

			lua_pushcfunction(L, DoFile);
			lua_setglobal(L, "dofile");

			lua_pushcfunction(L, FileExists);
			lua_setglobal(L, "fileexists");

			lua_pushcfunction(L, GetPath);
			lua_setglobal(L, "getpath");

			lua_pushcfunction(L, AppendPath);
			lua_setglobal(L, "appendpath");

			lua_pushcfunction(L, AppendPathRaw);
			lua_setglobal(L, "appendpathraw");

			lua_pushcfunction(L, IsAbsolutePath);
			lua_setglobal(L, "isabsolutepath");

			lua_pushlightuserdata(L, &sPrintRegistryKey);
			PushBaseType(L, print);
			lua_settable(L, LUA_REGISTRYINDEX);

			lua_pushlightuserdata(L, &sErrorRegistryKey);
			PushBaseType(L, error);
			lua_settable(L, LUA_REGISTRYINDEX);
			
			lua_pushcfunction(L, Print);
			lua_setglobal(L, "print");

			lua_pushcfunction(L, Assert);
			lua_setglobal(L, "assert");

			lua_pushlightuserdata(L, &sFileHandlerRegistryKey);
			PushBaseType(L, factory->BaseType(fileHandler));
			lua_settable(L, LUA_REGISTRYINDEX);

			EA::Types::ObjectRef json = jsonLoader->ToObject();
			PushBaseType(L, json);
			lua_setglobal(L, "json");

			if(0 != luaL_loadstring(L, LOADER))
			{
				EA_FAIL_MESSAGE(lua_tostring(L, -1));
			}

			lua_call(L, 0, 1);
			lua_setglobal(L, "requireasync");

			return L;
		}

		EA::Allocator::ICoreAllocator* GetAllocator(lua_State* L)
		{
			// get the allocator light user data from the registry with our key
			lua_pushlightuserdata(L, &sAllocatorRegistryKey);
			lua_rawget(L, LUA_REGISTRYINDEX);

			EA::Allocator::ICoreAllocator* allocator = static_cast<EA::Allocator::ICoreAllocator*>(lua_touserdata(L, -1));
			
			// pop the light user data
			lua_pop(L, 1);

			return allocator;
		}

		EA::Types::Factory* GetFactory(lua_State* L)
		{
			// get the allocator light user data from the registry with our key
			lua_pushlightuserdata(L, &sFactoryRegistryKey);
			lua_rawget(L, LUA_REGISTRYINDEX);

			EA::Types::Factory* factory = static_cast<EA::Types::Factory*>(lua_touserdata(L, -1));
			
			// pop the light user data
			lua_pop(L, 1);

			return factory;
		}

		int	ErrorFunction(lua_State* L)
		{
			EA::RawString error(GetAllocator(L), lua_tostring(L, -1));

			error.append("\nbacktrace:\n");
			error.append(GetBacktrace(L, 1));

			lua_pushstring(L, error.c_str());

			return 1;
		}

		//PcLint thinks variables only mentioned in macros are unused as we've told it to ignore the macros
		//lint -efunc(529, EA::Lua::Call)
		void Call(lua_State* L, int nargs, int nresults)
		{
			EABASETYPES_PROFILE_AUTO(a, 0, "EABaseTypesLua");

			// push and insert the error function below the arguments and the function to call
			lua_pushcfunction(L, ErrorFunction);

			int index = abs_index(L, -(nargs + 2));

			lua_insert(L, index);
			
			int result = lua_pcall(L, nargs, nresults, index);

			// remove the error function
			lua_remove(L, index);

			if(result != 0)
			{
				EA::RawString message(GetAllocator(L));

				switch(result)
				{
					case LUA_ERRRUN:
						message.append("LUA runtime error\n");
						break;
					case LUA_ERRMEM:
						message.append("LUA memory error\n");
						break;
					case LUA_ERRERR:
						message.append("LUA error handler error\n");
						break;
					default:
						message.append_sprintf("LUA unknown error: %d\n", result);
						break;
				}

				message.append(lua_tostring(L, -1));

				EA::Types::FunctionRef error = GetErrorFunction(L);

				if(error != NULL)
				{
					error->Call<void>(message);
				}
				else
				{
					EA_FAIL_MESSAGE(message.c_str());
				}

				// pop the error message
				lua_pop(L, 1);

				// push nils for any results we're explicitly expecting
				if(nresults != LUA_MULTRET)
				{
					for(int i = 0; i < nresults; i++)
					{
						lua_pushnil(L);
					}
				}
			}
		}
	}
}
