// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
// Copyright (C) 2010  Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include "stdafx.h"

#include "lua_helper.h"
#include "nel/misc/file.h"
#include "nel/misc/path.h"
//#include "interface_manager.h"

extern "C"
{
#include "lualib.h"
}

// to get rid of you_must_not_use_assert___use_nl_assert___read_debug_h_file messages
#include <cassert>
#undef assert
#define assert nlassert
#include <luabind/luabind.hpp>

using namespace std;
using namespace NLMISC;

// ***************************************************************************
const char	*CLuaState::_NELSmallScriptTableName= "NELSmallScriptTable";
uint         CLuaStackChecker::_ExceptionContextCounter = 0;

// ***************************************************************************
void CLuaStackChecker::incrementExceptionContextCounter()
{
	++ _ExceptionContextCounter;
}

// ***************************************************************************
void CLuaStackChecker::decrementExceptionContextCounter()
{
	nlassert(_ExceptionContextCounter > 0);
	-- _ExceptionContextCounter;
}


#ifdef LUA_NEVRAX_VERSION
	ILuaIDEInterface *LuaDebuggerIDE = NULL;
	static bool LuaDebuggerVisible = false;
#endif

#ifdef NL_OS_WINDOWS
	HMODULE		LuaDebuggerModule = 0;
#endif

void luaDebuggerMainLoop()
{
#ifdef LUA_NEVRAX_VERSION
	if (!LuaDebuggerIDE) return;
	if (!LuaDebuggerVisible)
	{
		LuaDebuggerIDE->showDebugger(true);
		LuaDebuggerIDE->expandProjectTree();
		LuaDebuggerIDE->sortFiles();
		LuaDebuggerVisible = true;
	}
	LuaDebuggerIDE->doMainLoop();
#endif
}



static std::allocator<uint8> l_stlAlloc;


static void l_free_func(void *block, int oldSize)
{
	l_stlAlloc.deallocate((uint8 *) block, oldSize);
}

static void *l_realloc_func(void *b, int os, int s)
{
	if (os == s) return b;
	void *newB = l_stlAlloc.allocate(s);
	memcpy(newB, b, std::min(os, s));
	l_free_func(b, os);
	return newB;
}



const int MinGCThreshold = 128; // min value at which garbage collector will be triggered (in kilobytes)
// ***************************************************************************
CLuaState::CLuaState()
{
	_State = NULL;

	#ifdef LUA_NEVRAX_VERSION
		_GCThreshold = MinGCThreshold;
	#endif

	if (!_State)
	{
		#ifdef LUA_NEVRAX_VERSION
			_State = lua_open(l_realloc_func, l_free_func);
		#else
			_State = lua_open();
		#endif
		nlassert(_State);
	}

	// *** Load base libs
	{
		CLuaStackChecker lsc(this);
#if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM >= 501
		luaL_openlibs(_State);
#else
		luaopen_base (_State);
		luaopen_table (_State);
		luaopen_io (_State);
		luaopen_string (_State);
		luaopen_math (_State);
		luaopen_debug (_State);
#endif

		// open are buggy????
		clear();
	}

	// *** Register basics
	CLuaStackChecker lsc(this);

	// do: LUA_REGISTRYINDEX.(lightuserdata*)this.classes= {}
	pushLightUserData((void *) this);
	newTable();
	push("classes");
	newTable(); // registry class
	setTable(-3);
	setTable(LUA_REGISTRYINDEX);

	// add pointer from lua state to this CLuaState object
	// do: LUA_REGISTRYINDEX.(lightuserdata*)_State= this
	pushLightUserData((void *) _State); // NB : as creator of the state,   we make the assumption that
	                       // no one will be using this pointer in the registry (cf. ref manual about registry)
	pushLightUserData((void *) this);
	setTable(LUA_REGISTRYINDEX);

	// Create the Table that contains Function cache for small script execution
	push(_NELSmallScriptTableName);			// 1:TableName
	newTable();								// 1:TableName 2:table
	setTable(LUA_REGISTRYINDEX);			// ...
	_SmallScriptPool= 0;

	// *** luabind init
	luabind::open(_State);
}


// ***************************************************************************
CLuaStackRestorer::CLuaStackRestorer(CLuaState *state, int finalSize) : _State(state), _FinalSize(finalSize)
{
}

// ***************************************************************************
CLuaStackRestorer::~CLuaStackRestorer()
{
	nlassert(_State);
	_State->setTop(_FinalSize);
}

#ifdef NL_OS_WINDOWS
	static int NoOpReportHook( int /* reportType */, char * /* message */, int * /* returnValue */ )
	{
		return TRUE;
	}
#endif


// ***************************************************************************
CLuaState::~CLuaState()
{
	nlassert(_State);

	#ifdef LUA_NEVRAX_VERSION
		if (!LuaDebuggerIDE)
	#else
		if (1)
	#endif
	{
		lua_close(_State);
	}
	else
	{
		#ifdef LUA_NEVRAX_VERSION
			LuaDebuggerIDE->stopDebug(); // this will also close the lua state
			LuaDebuggerIDE = NULL;
			LuaDebuggerVisible = false;
			#ifdef NL_OS_WINDOWS
				nlassert(LuaDebuggerModule)
				_CrtSetReportHook(NoOpReportHook); // prevent dump of memory leaks at this point
				//::FreeLibrary(LuaDebuggerModule); // don't free the library now (seems that it destroy, the main window, causing
													// a crash when the app window is destroyed for real...
													// -> FreeLibrary will be called when the application is closed
				LuaDebuggerModule = 0;
			#endif
		#endif
	}

	// Clear Small Script Cache
	_SmallScriptPool= 0;
	_SmallScriptCache.clear();
}

// ***************************************************************************
CLuaState *CLuaState::fromStatePointer(lua_State *state)
{
	nlassert(state);
	int initialStackSize = lua_gettop(state);
	lua_checkstack(state,   initialStackSize + 2);
	lua_pushlightuserdata(state,   (void *) state);
	lua_gettable(state,   LUA_REGISTRYINDEX);
	if (!lua_islightuserdata(state,   -1))
	{
		lua_pop(state,   1);
		return NULL;
	}
	CLuaState *ls = (CLuaState *) lua_touserdata(state,   -1);
	lua_pop(state,   1);
	nlassert(initialStackSize == lua_gettop(state));
	return ls;
}

// ***************************************************************************
struct CLuaReader
{
	const std::string *Str;
	bool               Done;
};

void CLuaState::loadScript(const std::string &code,   const std::string &dbgSrc)
{
	if (code.empty()) return;
	struct CHelper
	{
		static const char *luaChunkReaderFromString(lua_State * /* L */,   void *ud,   size_t *sz)
		{
			CLuaReader *rd = (CLuaReader *) ud;
			if (!rd->Done)
			{
				rd->Done = true;
				*sz = rd->Str->size();
				return rd->Str->c_str();
			}
			else
			{
				*sz = 0;
				return NULL;
			}
		}
	};
	CLuaReader rd;
	rd.Str = &code;
	rd.Done = false;

	int result = lua_load(_State,   CHelper::luaChunkReaderFromString,   (void *) &rd,   dbgSrc.c_str());
	if (result !=0)
	{
		// pop the error code
		string	err= toString();
		pop();
		// throw error
		throw ELuaParseError(err);
	}
}

// ***************************************************************************
void CLuaState::executeScriptInternal(const std::string &code,   const std::string &dbgSrc, int numRet)
{
	CLuaStackChecker lsc(this, numRet);

	// load the script
	loadScript(code,   dbgSrc);

	// execute
	if (pcall(0,   numRet) != 0)
	{
		// pop the error code
		string	err= toString();
		pop();
		// throw error
		throw ELuaExecuteError(err);
	}
}

// ***************************************************************************
void CLuaState::executeScript(const std::string &code, int numRet)
{
	// run the script,   with dbgSrc==script
	executeScriptInternal(code,   code, numRet);
}

// ***************************************************************************
bool CLuaState::executeScriptNoThrow(const std::string &code, int numRet)
{
	try
	{
		executeScript(code, numRet);
	}
	catch (ELuaError &e)
	{
		nlwarning(e.what());
		return false;
	}
	return true;
}

// ***************************************************************************
bool CLuaState::executeFile(const std::string &pathName)
{
	CIFile	inputFile;
	if(!inputFile.open(pathName))
		return false;

	#ifdef LUA_NEVRAX_VERSION
		if (LuaDebuggerIDE)
		{
			std::string path = NLMISC::CPath::getCurrentPath() + "/" + pathName.c_str();
			path = CPath::standardizeDosPath(path);
			LuaDebuggerIDE->addFile(path.c_str());
		}
	#endif

	// load the script text
	string	script;
	/*
	while(!inputFile.eof())
	{
		char	tmpBuff[5000];
		inputFile.getline(tmpBuff,   5000);
		script+= tmpBuff;
		script+= "\n";
	}
	*/
	script.resize(NLMISC::CFile::getFileSize(pathName));
	inputFile.serialBuffer((uint8 *) &script[0],  (uint)script.size());


	// execute the script text,   with dbgSrc==filename (use @ for lua internal purpose)
	executeScriptInternal(script,   string("@") + NLMISC::CFile::getFilename(pathName));

	return true;
}

// ***************************************************************************
void CLuaState::executeSmallScript(const std::string &script)
{
	if (script.empty()) return;
	// *** if the small script has not already been called before,   parse it now
	TSmallScriptCache::iterator it= _SmallScriptCache.find(script);
	if(it==_SmallScriptCache.end())
	{
		CLuaStackChecker lsc(this);

		// add it to a function
		loadScript(script,   script);

		// Assign the method to the NEL table: NELSmallScriptTable[_SmallScriptPool]= function
		push(_NELSmallScriptTableName);		// 1:function 2:NelTableName
		getTable(LUA_REGISTRYINDEX);		// 1:function 2:NelTable
		insert(-2);							// 1:NelTable 2:function
		rawSetI(-2,   _SmallScriptPool);		// 1:NelTable
		pop();

		// bkup in cache map
		it= _SmallScriptCache.insert(make_pair(script,   _SmallScriptPool)).first;

		// next allocated
		_SmallScriptPool++;
	}

	// *** Execute the function associated to the script
	CLuaStackChecker lsc(this);
	push(_NELSmallScriptTableName);		// 1:NelTableName
	getTable(LUA_REGISTRYINDEX);		// 1:NelTable
	// get the function at the given index in the "NELSmallScriptTable" table
	rawGetI(-1,   it->second);			// 1:NelTable 2:function

	// execute
	if (pcall(0,   0) != 0)
	{
		// Stack: 1: NelTable 2:errorcode
		// pop the error code,   and clear stack
		string	err= toString();
		pop();			// 1:NelTable
		pop();			// ....
		// throw error
		throw ELuaExecuteError(err);
	}
	else
	{
		// Stack: 1:NelTable
		pop();			// ....
	}
}

// ***************************************************************************
void		CLuaState::registerFunc(const char *name,   lua_CFunction function)
{
	lua_register(_State,   name,   function);
}

// ***************************************************************************
void		CLuaState::pushCClosure(lua_CFunction function,   int n)
{
	nlassert(function);
	nlassert(getTop() >= n);
	lua_pushcclosure(_State,   function,   n);
}

// ***************************************************************************
void CLuaState::push(TLuaWrappedFunction function)
{
	struct CForwarder
	{
		static int callFunc(lua_State *ls)
		{
			nlassert(ls);
			TLuaWrappedFunction func   = (TLuaWrappedFunction) lua_touserdata(ls,   lua_upvalueindex(1));
			CLuaState           *state = (CLuaState *) lua_touserdata(ls,   lua_upvalueindex(2));
			nlassert(func);
			nlassert(state);
			// get real function pointer from the values in the closure
			int numResults;
			int initialStackSize = state->getTop();
			try
			{
				// call the actual function
				numResults =  func(*state);
			}
			catch(const std::exception &e)
			{
				// restore stack to its initial size
				state->setTop(initialStackSize);
				lua_pushstring(ls,   e.what());
				// TODO : see if this is safe to call lua error there" ... (it does a long jump)
				lua_error(ls);
			}
			return numResults;
		}
	};
	pushLightUserData((void *) function);
	pushLightUserData((void *) this);
	pushCClosure(CForwarder::callFunc,   2);
}

// ***************************************************************************
// Wrapped function
void CLuaState::registerFunc(const char *name,   TLuaWrappedFunction function)
{
	nlassert(function);
	CLuaStackChecker lsc(this);
	push(name);
	push(function);
	setTable(LUA_GLOBALSINDEX);
}


// ***************************************************************************
bool			CLuaState::getTableBooleanValue(const char *name,    bool defaultValue)
{
	nlassert(name);
	push(name);
	getTable(-2);
	if (isNil())
	{
		pop();
		return defaultValue;
	}
	bool result = toBoolean(-1);
	pop();
	return result;
}

// ***************************************************************************
double		CLuaState::getTableNumberValue(const char *name,    double defaultValue)
{
	nlassert(name);
	push(name);
	getTable(-2);
	if (isNil())
	{
		pop();
		return defaultValue;
	}
	double result = toNumber(-1);
	pop();
	return result;
}

// ***************************************************************************
const char   *CLuaState::getTableStringValue(const char *name,   const char *defaultValue)
{
	nlassert(name);
	push(name);
	getTable(-2);
	if (isNil())
	{
		pop();
		return defaultValue;
	}
	const char *result = toString(-1);
	pop();
	return result;
}

// ***************************************************************************
void		CLuaState::getStackContext(string &ret,   uint stackLevel)
{
	nlassert(_State);
	ret.clear();
	lua_Debug	dbg;
	if(lua_getstack (_State,   stackLevel,   &dbg))
	{
		if(lua_getinfo(_State,   "lS",   &dbg))
		{
			ret= NLMISC::toString("%s:%d:",   dbg.short_src,   dbg.currentline);
		}
	}
}

// ***************************************************************************
int CLuaState::pcallByName(const char *functionName,   int nargs,   int nresults,   int funcTableIndex /*=LUA_GLOBALSINDEX*/,  int errfunc /*= 0*/)
{
	int initialStackSize = getTop();
	nlassert(functionName);
	nlassert(isTable(funcTableIndex));
	pushValue(funcTableIndex);
	push(functionName);
	getTable(-2);
	remove(-2); // get rid of the table
	nlassert(getTop() >= nargs); // not enough arguments on the stack
	// insert function before its arguments
	insert(- 1 - nargs);
	int result =  pcall(nargs,  nresults,  errfunc);
	int currSize = getTop();
	if (result == 0)
	{
		nlassert(currSize == initialStackSize - nargs + nresults);
	}
	else
	{
		// errors,  the stack contains a single string
		if (errfunc == 0)
		{
			nlassert(currSize == initialStackSize - nargs + 1);
		}
		// else if there's an error handler,  can't know the size of stack
	}
	return result;
}

// ***************************************************************************
void CLuaState::dumpStack()
{
	nlinfo("LUA STACK CONTENT (size = %d)", getTop());
	nlinfo("=================");
	CLuaStackChecker lsc(this);
	for(int k = 1; k <= getTop(); ++k)
	{
		pushValue(k);
		std::string value = toString(-1) ? toString(-1) : "?";
		nlinfo("Stack entry %d : type = %s, value = %s", k, getTypename(type(-1)), value.c_str());
		pop();
	}
}

// ***************************************************************************
void CLuaState::getStackAsString(std::string &dest)
{
	dest = NLMISC::toString("Stack size = %d\n", getTop());
	CLuaStackChecker lsc(this);
	for(int k = 1; k <= getTop(); ++k)
	{
		pushValue(k);
		std::string value = toString(-1) ? toString(-1) : "?";
		dest += NLMISC::toString("Stack entry %d : type = %s, value = %s\n", k, getTypename(type(-1)), value.c_str());
		pop();
	}
}

//================================================================================
CLuaStackChecker::~CLuaStackChecker()
{
	nlassert(_State);
	if (!_ExceptionContextCounter)
	{
		int currSize = _State->getTop();
		if (currSize != _FinalWantedSize)
		{
			static volatile bool assertWanted = true;
			if (assertWanted)
			{
				nlwarning("Lua stack size error : expected size is %d, current size is %d", _FinalWantedSize, currSize);
				_State->dumpStack();
				nlassert(0);
			}
		}
	}
	else
	{
		// this object dtor was called because an exception was thrown, so let the exception
		// propagate (the stack must be broken, but because of the exception, not because of code error)
		_State->setTop(_FinalWantedSize);
	}
}

// ***************************************************************************
void	ELuaWrappedFunctionException::init(CLuaState *ls, const std::string &reason)
{
	// Print first Lua Stack Context
/*
	CInterfaceManager	*pIM= CInterfaceManager::getInstance();
	if(ls)
	{
		ls->getStackContext(_Reason, 1);		// 1 because 0 is the current C function => return 1 for script called
		// enclose with cool colors
		pIM->formatLuaStackContext(_Reason);
	}
*/
	// Append the reason
	_Reason+= reason;
}

// ***************************************************************************
ELuaWrappedFunctionException::ELuaWrappedFunctionException(CLuaState *luaState)
{
	init(luaState, "");
}

// ***************************************************************************
ELuaWrappedFunctionException::ELuaWrappedFunctionException(CLuaState *luaState, const std::string &reason)
{
	init(luaState, reason);
}

// ***************************************************************************
ELuaWrappedFunctionException::ELuaWrappedFunctionException(CLuaState *luaState, const char *format, ...)
{
	//H_AUTO(Lua_ELuaWrappedFunctionException_ELuaWrappedFunctionException)
	std::string	reason;
	NLMISC_CONVERT_VARGS (reason, format, NLMISC::MaxCStringSize);
	init(luaState, reason);
}

//================================================================================
void         CLuaState::newTable()
{
	nlverify( lua_checkstack(_State, 1) );
	lua_newtable(_State);
}

//================================================================================
int          CLuaState::getGCCount()
{
	return lua_getgccount(_State);
}

//================================================================================
int          CLuaState::getGCThreshold()
{
	//H_AUTO(Lua_CLuaState_getGCThreshold)
#ifdef LUA_NEVRAX_VERSION
	return _GCThreshold;
#else
#	if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM >= 501
	return lua_gc(_State, LUA_GCCOUNT, 0);
#	else
	return lua_getgcthreshold(_State);
#	endif
#endif
}

//================================================================================
void          CLuaState::setGCThreshold(int kb)
{
	//H_AUTO(Lua_CLuaState_setGCThreshold)
#ifdef LUA_NEVRAX_VERSION
	_GCThreshold = kb;
	handleGC();
#else
#	if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM >= 501
	lua_gc(_State, LUA_GCCOLLECT, kb);
#	else
	lua_setgcthreshold(_State, kb);
#	endif
#endif
}

//================================================================================
void CLuaState::handleGC()
{
	//H_AUTO(Lua_CLuaState_handleGC)
	#ifdef LUA_NEVRAX_VERSION
		// must handle gc manually with the refcounted version
		int gcCount = getGCCount();
		if (gcCount >= _GCThreshold)
		{
			nlwarning("Triggering GC : memory in use = %d kb, current threshold = %d kb", gcCount, _GCThreshold);
			lua_setgcthreshold(_State, 0);
			gcCount = getGCCount();
			_GCThreshold = std::max(MinGCThreshold, gcCount * 2);
			nlwarning("After GC : memory in use = %d kb, threshold = %d kb", gcCount, _GCThreshold);
		}
	#endif
}