/** @file ux_luafunction.cpp
 *  Implementation of lua implementation of EA::Types::Function interface
 * 	@headerfile ux_luafunction.h "ux_lua/ux_luafunction.h"
 *	@author Nick Beatty (nbeatty@ea.com)
 *
 *	UX
 *	(c) 2011 Electronic Arts Inc. 
 */

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

#include "EABaseTypes/callermap.h"
#include "EABaseTypes/factory.h"
#include "EABaseTypes/iencoder.h"

#include "eaassert/eaassert.h"

#include "coreallocator/icoreallocatormacros.h"

namespace EA
{
	namespace Lua
	{
		LuaFunction::LuaFunction(EA::Types::Factory* factory, lua_State* L, int index)
			: EA::Types::Function(factory)
			, mL(L)
			, mRef(LUA_REFNIL)
		{
			// index should point to a function
			luaL_checktype(L, index, LUA_TFUNCTION);

			//dupe the function on the stack
			lua_pushvalue(L, index);

			//put the function in the registry
			mRef = luaL_ref(L, LUA_REGISTRYINDEX);

#if !defined(EA_FINAL)
			EA::String location(mFactory);

			lua_Debug ar;
			lua_pushvalue(L, index);
			lua_getinfo(L, ">Sn", &ar);

			location.sprintf("LUA function from %s!%s line %d", ar.short_src, ar.name != NULL ? ar.name : "<anonymous>", ar.linedefined);

			SetDebugInfo(mFactory->String(location));
#endif
		}

		LuaFunction::~LuaFunction()
		{
			lua_State* L = mL;
			EA::Lua::StackCheck s(L);

			// remove our function from the lua registry
			if(mRef != LUA_REFNIL)
			{
				luaL_unref(L, LUA_REGISTRYINDEX, mRef);
				mRef = LUA_REFNIL;
			}
			mL = NULL;
		}

		/* virtual */ EA::Types::BaseTypeReturn LuaFunction::Clone()
		{
			//commented out code would be a actual deep copy, however for now we don't think we want/need to do this
			//so just add a reference and return this
			//push the function
			//lua_rawgeti(L, LUA_REGISTRYINDEX, mRef);
			//LuaFunction* pClone = new LuaFunction(L, 1);
			//lua_pop(L, 1);
			//return pClone;

			AddRef();
			return this;
		}

		/* virtual */ void LuaFunction::Call(unsigned int id, 
											 void* decoderParams,
											 void* encoder,
											 EA::Types::DecodeFunction defaultDecode,
											 EA::Types::IEncoder* defaultEncoder)
		{
			EABASETYPES_PROFILE_BEGIN(0, "LuaFunction_Decode");

			CheckData(decoderParams, defaultDecode);
			
			const eastl::pair<EA::Types::DecodeFunction, EA::Types::DecodeFunction>* decode = GetFactory()->GetCallerMap()->Get(Lua::CALLER_TYPE, id);

			// save the lua_State locally so that LuaFunctions can be destroyed re-entrantly
			lua_State* L = mL;
			int ref = mRef;

			int top = lua_gettop(L);

			lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
			
			LuaEncoder argumentEncoder(L);
			
			if(decode != NULL)
			{
				decode->first(&argumentEncoder, decoderParams);
			}
			else
			{
				EA::Types::IEncoderImpl<LuaEncoder> defaultArgumentEncoder(&argumentEncoder);
				defaultDecode(&defaultArgumentEncoder, decoderParams);
			}

			EABASETYPES_PROFILE_END();
			EA::Lua::Call(L, lua_gettop(L) - top - 1, LUA_MULTRET);
			EABASETYPES_PROFILE_BEGIN(0, "LuaFunction_Encode");

			int numResults = lua_gettop(L) - top;
			
			LuaDecoderParams resultDecoderParams(L, static_cast<unsigned int>(numResults));
			
			if(decode != NULL)
			{
				decode->second(encoder, &resultDecoderParams);
			}
			else
			{
				LuaDecoder<EA::Types::IEncoder>::Decode(defaultEncoder, &resultDecoderParams);
			}

			lua_pop(L, numResults);

			EABASETYPES_PROFILE_END();
		}

		//lint -efunc(931, UX::Lua::LuaFunction::Equals)
		//lint -efunc(904, UX::Lua::LuaFunction::Equals)
		/* virtual */ bool LuaFunction::Equals(EA::Types::FunctionIn pCompare)
		{
			lua_State* L = mL;
			EA::Lua::StackCheck s(L);

			if(pCompare->GetFunctionType() != GetFunctionType())
			{
				return false;
			}
			
			LuaFunction* tmp = static_cast<LuaFunction*>(pCompare.GetUnsafePointer());
			if(tmp->GetLuaState() != mL)
			{
				return false;
			}

			//push the function
			lua_rawgeti(L, LUA_REGISTRYINDEX, mRef);
			lua_rawgeti(L, LUA_REGISTRYINDEX, tmp->GetRef());
			bool retVal = (lua_equal(L, -1, -2) == 1);
			lua_pop(L, 2);
	
			return retVal;
		}

	}
}
