//-------------------------------------------------------------------------------------------------
// <copyright file="LqrSMACLuaInstance.cpp" company="LiquidRobotics">
//  Copyright (c) Liquid Robotics Corporation, confidential and proprietary.  All rights reserved. 
// </copyright>
//
// <summary>
//	Implementation of LuaInstance, a wrapper for an instance of the lua interpreter.
//	It provides a thin wrapper for each API that is provided.
// </summary>
//
// <owner>Mike Cookson</owner>
//-------------------------------------------------------------------------------------------------

#include "LqrSMACLuaInstance.h"
#include <sstream>
#include <stdexcept>

using namespace std;

namespace LiquidRobotics {
namespace SMACFramework {

LuaInstance::StackStateHolder::StackStateHolder(LuaInstance& _lua)
: lua(_lua)
, initialized(false)
, top(lua.gettop())
{
	initialized = true;
}

LuaInstance::StackStateHolder::~StackStateHolder()
{
	if (initialized)
		lua.settop(top);
}

void LuaInstance::CheckState()
{
	if (!L)		throw runtime_error("LuaInstance: state is NULL");
}

LuaInstance::LuaInstance()
: L(NULL)
, isThread(false)
{
	L = luaL_newstate();

	if (L)
		luaL_openlibs(L);
	else
		throw runtime_error("LuaInstance: luaL_newstate() returned NULL");
}

LuaInstance::LuaInstance(const string& fileName)
: L(NULL)
, isThread(false)
{
	L = luaL_newstate();

	if (L)
	{
		luaL_openlibs(L);

		const int dofileStatus = L_dofile(fileName);

		if (dofileStatus)
		{
			stringstream msg;
			msg << "LuaInstance: dofile(" << fileName << ") returned " << dofileStatus;
			throw runtime_error(msg.str());
		}
	}
	else
		throw runtime_error("LuaInstance: luaL_newstate() returned NULL");
}


LuaInstance::LuaInstance(lua_State* thread)
: L(thread)
, isThread(true)
{
	if (!L)
		throw invalid_argument("LuaInstance: cannot instantiate with a NULL pointer");
}

LuaInstance::~LuaInstance()
{
	if (L)
	{
		if (!isThread)
			lua_close(L);

		L = NULL;
	}
}

int LuaInstance::L_dofile(const string& fileName, bool throwOnError)
{
	CheckState();
	int result = L_loadfile(fileName, throwOnError);

	if (!result)
		result = pcall(0,0,0,throwOnError);

	return result;
	//return luaL_dofile(L, fileName.c_str());
}

int LuaInstance::L_loadfile(const string& fileName, bool throwOnError)
{
	int result = luaL_loadfile(L, fileName.c_str());

	if (result && throwOnError)
	{
		stringstream msg;
		bool getErrString = true;

		switch(result)
		{
		case LUA_ERRRUN:	msg << "lua runtime error";															break;
		case LUA_ERRSYNTAX:	msg << "lua syntax error";															break;
		case LUA_ERRMEM:	msg << "lua memory error";									getErrString = false;	break;
		case LUA_ERRERR:	msg << "lua generic error";															break;
		default:			msg << "unexpected result from pcall (" << result << ")";	getErrString = false;	break;
		}

		if (getErrString && isstring(-1))
			msg << ": " << *tolstring(-1);

		throw runtime_error(msg.str());
	}

	return result;
}

void* LuaInstance::L_checkudata(int narg, const string& tname)	{ CheckState(); return luaL_checkudata(L, narg, tname.c_str()); }
void LuaInstance::L_getmetatable(const string& registeredName)	{ CheckState(); luaL_getmetatable(L, registeredName.c_str()); }
int LuaInstance::L_newmetatable(const string& registeredName)	{ CheckState(); return luaL_newmetatable(L, registeredName.c_str()); }

int LuaInstance::gettop()										{ CheckState(); return lua_gettop(L); }
void LuaInstance::settop(int idx)								{ CheckState(); lua_settop(L, idx); }
void LuaInstance::pushvalue(int idx)							{ CheckState(); lua_pushvalue(L, idx); }
void LuaInstance::remove(int idx)								{ CheckState(); lua_remove(L, idx); }
void LuaInstance::insert(int idx)								{ CheckState(); lua_insert(L, idx); }
void LuaInstance::replace(int idx)								{ CheckState(); lua_replace(L, idx); }
int LuaInstance::checkstack(int sz)								{ CheckState(); return lua_checkstack(L, sz); }
int LuaInstance::isnumber(int idx)								{ CheckState(); return lua_isnumber(L, idx); }
int LuaInstance::isstring(int idx)								{ CheckState(); return lua_isstring(L, idx); }
int LuaInstance::iscfunction(int idx)							{ CheckState(); return lua_iscfunction(L, idx); }
int LuaInstance::isuserdata(int idx)							{ CheckState(); return lua_isuserdata(L, idx); }
int LuaInstance::type(int idx)									{ CheckState(); return lua_type(L, idx); }
const char* LuaInstance::_typename(int tp)						{ CheckState(); return lua_typename(L, tp); }
int LuaInstance::equal(int idx1, int idx2)						{ CheckState(); return lua_equal(L, idx1, idx2); }
int LuaInstance::rawequal(int idx1, int idx2)					{ CheckState(); return lua_rawequal(L, idx1, idx2); }
int LuaInstance::lessthan(int idx1, int idx2)					{ CheckState(); return lua_lessthan(L, idx1, idx2); }
LuaInstance::Number LuaInstance::tonumber(int idx)				{ CheckState(); return lua_tonumber(L, idx); }
LuaInstance::Integer LuaInstance::tointeger(int idx)			{ CheckState(); return lua_tointeger(L, idx); }
int LuaInstance::toboolean(int idx)								{ CheckState(); return lua_toboolean(L, idx); }

LuaInstance::StringPtr LuaInstance::tolstring(int idx)
{
	CheckState();
	StringPtr result;
	size_t len;
	const char* str = lua_tolstring(L, idx, &len);

	if (str)
		result.reset(new string(str, len));

	return result;
}

size_t LuaInstance::objlen(int idx)								{ CheckState(); return lua_objlen(L, idx); }
LuaInstance::CFunction LuaInstance::tocfunction(int idx)		{ CheckState(); return lua_tocfunction(L, idx); }
void* LuaInstance::touserdata(int idx)							{ CheckState(); return lua_touserdata(L, idx); }

LuaInstance::Ptr LuaInstance::tothread(int idx)
{
	CheckState();
	Ptr result;
	lua_State* ptr = lua_tothread(L, idx);

	if (ptr)
		result.reset(new LuaInstance(ptr));

	return result;
}

const void* LuaInstance::topointer(int idx)						{ CheckState(); return lua_topointer(L, idx); }
void LuaInstance::pushnil()										{ CheckState(); lua_pushnil(L); }
void LuaInstance::pushnumber(Number n)							{ CheckState(); lua_pushnumber(L, n); }
void LuaInstance::pushinteger(Integer n)						{ CheckState(); lua_pushinteger(L, n); }
void LuaInstance::pushlstring(const string& s)					{ pushlstring(s.data(), s.length()); }
void LuaInstance::pushlstring(const char *s, size_t l)			{ CheckState(); lua_pushlstring(L, s, l); }
void LuaInstance::pushstring(const char* s)						{ CheckState(); lua_pushstring(L, s); }

LuaInstance::StringPtr LuaInstance::pushvfstring(const char* fmt, va_list argp)
{
	StringPtr result;
	const char* ptr = lua_pushvfstring(L, fmt, argp);

	if (ptr)
		result.reset(new string(ptr));

	return result;
}

LuaInstance::StringPtr LuaInstance::pushfstring(const char* fmt, ...)
{
	va_list argp;
	va_start(argp, fmt);
	return pushvfstring(fmt, argp);
}

void LuaInstance::pushcclosure(lua_CFunction fn, int n)			{ CheckState(); lua_pushcclosure(L, fn, n); }
void LuaInstance::pushboolean(int b)							{ CheckState(); lua_pushboolean(L, b); }
void LuaInstance::pushlightuserdata(void *p)					{ CheckState(); lua_pushlightuserdata(L, p); }
int LuaInstance::pushthread(LuaInstance& thd)					{ CheckState(); return lua_pushthread(thd.L); }
void LuaInstance::gettable(int idx)								{ CheckState(); lua_gettable(L, idx); }
void LuaInstance::getfield(int idx, const char *k)				{ CheckState(); lua_getfield(L, idx, k); }
void LuaInstance::rawget(int idx)								{ CheckState(); lua_rawget(L, idx); }
void LuaInstance::rawgeti(int idx, int n)						{ CheckState(); lua_rawgeti(L, idx, n); }
void LuaInstance::createtable(int narr, int nrec)				{ CheckState(); lua_createtable(L, narr, nrec); }
void* LuaInstance::newuserdata(size_t sz)						{ CheckState(); return lua_newuserdata(L, sz); }
int LuaInstance::getmetatable(int objindex)						{ CheckState(); return lua_getmetatable(L, objindex); }
#if LUA_VERSION_NUM < 502
void LuaInstance::getfenv(int idx)								{ CheckState(); lua_getfenv(L, idx); }
#else
void LuaInstance::getuservalue(int idx)							{ CheckState(); lua_getuservalue(L, idx); }
#endif
void LuaInstance::settable(int idx)								{ CheckState(); lua_settable(L, idx); }
void LuaInstance::setfield(int idx, const char *k)				{ CheckState(); lua_setfield(L, idx, k); }
void LuaInstance::rawset(int idx)								{ CheckState(); lua_rawset(L, idx); }
void LuaInstance::rawseti(int idx, int n)						{ CheckState(); lua_rawseti(L, idx, n); }
int LuaInstance::setmetatable(int objindex)						{ CheckState(); return lua_setmetatable(L, objindex); }
#if LUA_VERSION_NUM < 502
int LuaInstance::setfenv(int idx)								{ CheckState(); return lua_setfenv(L, idx); }
#else
void LuaInstance::setuservalue(int idx)							{ CheckState(); lua_setuservalue(L, idx); }
#endif
void LuaInstance::call(int nargs, int nresults)					{ CheckState(); lua_call(L, nargs, nresults); }

int LuaInstance::pcall(int nargs, int nresults, int errfunc, bool throwOnError)
{
	CheckState();

	const int result = lua_pcall(L, nargs, nresults, errfunc);

	if (throwOnError && (result != 0))
	{
		stringstream msg;
		bool getErrString = true;

		switch(result)
		{
		case LUA_ERRRUN:	msg << "lua runtime error";															break;
		case LUA_ERRSYNTAX:	msg << "lua syntax error";															break;
		case LUA_ERRMEM:	msg << "lua memory error";									getErrString = false;	break;
		case LUA_ERRERR:	msg << "lua generic error";															break;
		default:			msg << "unexpected result from pcall (" << result << ")";	getErrString = false;	break;
		}

		if (getErrString && isstring(-1))
			msg << ": " << *tolstring(-1);

		throw runtime_error(msg.str());
	}

	return 0;
}

int LuaInstance::cpcall(lua_CFunction func, void *ud)			{ CheckState(); return lua_cpcall(L, func, ud); }
#if LUA_VERSION_NUM < 502
int LuaInstance::load(Reader reader, void *dt, const char *chunkname)
{ CheckState(); return lua_load(L, reader, dt, chunkname); }
#else
int LuaInstance::load(Reader reader, void* data, const char* source, const char* mode)
{ CheckState(); return lua_load(L, reader, data, source, mode); }
#endif

int LuaInstance::dump(Writer writer, void *data)				{ CheckState(); return lua_dump(L, writer, data); }
int LuaInstance::yield(int nresults)							{ CheckState(); return lua_yield(L, nresults); }
#if LUA_VERSION_NUM < 502
int LuaInstance::resume(int narg)								{ CheckState(); return lua_resume(L, narg); }
#else
int LuaInstance::resume(int narg)								{ CheckState(); return lua_resume(L, NULL, narg); }
int LuaInstance::resume(const LuaInstance& from, int narg)	{ CheckState(); return lua_resume(L, from.L, narg); }
#endif
int LuaInstance::status()										{ CheckState(); return lua_status(L); }
int LuaInstance::gc(int what, int data)							{ CheckState(); return lua_gc(L, what, data); }
int LuaInstance::error()										{ CheckState(); return lua_error(L); }
int LuaInstance::next(int idx)									{ CheckState(); return lua_next(L, idx); }
void LuaInstance::concat(int n)									{ CheckState(); lua_concat(L, n); }
lua_Alloc LuaInstance::getallocf(void **ud)						{ CheckState(); return lua_getallocf(L, ud); }
void LuaInstance::setallocf(lua_Alloc f, void *ud)				{ CheckState(); lua_setallocf(L, f, ud); }
void LuaInstance::pop(int n)									{ CheckState(); lua_pop(L, n); }
void LuaInstance::newtable()									{ CheckState(); lua_newtable(L); }
void LuaInstance::_register(const char* name, CFunction fn)		{ CheckState(); lua_register(L, name, fn); }
void LuaInstance::pushcfunction(CFunction fn)					{ CheckState(); lua_pushcfunction(L, fn); }
size_t LuaInstance::strlen(int idx)								{ CheckState(); return lua_strlen(L, idx); }
int LuaInstance::isfunction(int idx)							{ CheckState(); return lua_isfunction(L, idx); }
int LuaInstance::istable(int idx)								{ CheckState(); return lua_istable(L, idx); }
int LuaInstance::islightuserdata(int idx)						{ CheckState(); return lua_islightuserdata(L, idx); }
int LuaInstance::isnil(int idx)									{ CheckState(); return lua_isnil(L, idx); }
int LuaInstance::isboolean(int idx)								{ CheckState(); return lua_isboolean(L, idx); }
int LuaInstance::isthread(int idx)								{ CheckState(); return lua_isthread(L, idx); }
int LuaInstance::isnone(int idx)								{ CheckState(); return lua_isnone(L, idx); }
int LuaInstance::isnoneornil(int idx)							{ CheckState(); return lua_isnoneornil(L, idx); }
//void LuaInstance::pushliteral(const char* s)					{ CheckState(); lua_pushliteral(L, s); }
void LuaInstance::setglobal(const char* name)					{ CheckState(); lua_setglobal(L, name); }
void LuaInstance::getglobal(const char* name)					{ CheckState(); lua_getglobal(L, name); }
int LuaInstance::clearhook(lua_Hook hook)						{ CheckState(); return lua_sethook(L, hook, 0, 0); }
int LuaInstance::sethook(lua_Hook hook, int mask, int count)	{ CheckState(); return lua_sethook(L, hook, mask, count); }
int LuaInstance::getinfo(const string& what, lua_Debug& ar)		{ CheckState(); return lua_getinfo(L, what.c_str(), &ar); }

}
}
