// This file is part of ZetaGlest <https://github.com/ZetaGlest>
//
// Copyright (C) 2018  The ZetaGlest team
//
// ZetaGlest is a fork of MegaGlest <https://megaglest.org>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>

#include "lua_script.h"

#include <stdexcept>
#include "conversion.h"
#include "util.h"
#include "platform_util.h"
#include "leak_dumper.h"

using namespace std;
using namespace Shared::Util;

namespace Shared {
	namespace Lua {

		//
		// This class wraps streflop for LuaScript. We need to toggle the data type
		// for streflop to use when calling into LUA as streflop may corrupt some
		// numeric values passed from Lua otherwise
		//
		class Lua_STREFLOP_Wrapper {
		public:
			Lua_STREFLOP_Wrapper() {
#ifdef USE_STREFLOP
				streflop_init<streflop::Double>();
#endif
			}
			~Lua_STREFLOP_Wrapper() {
#ifdef USE_STREFLOP
				streflop_init<streflop::Simple>();
#endif
			}
		};

		// =====================================================
		//	class LuaScript
		// =====================================================

		bool LuaScript::disableSandbox = false;
		bool LuaScript::debugModeEnabled = false;

		LuaScript::LuaScript() {
			Lua_STREFLOP_Wrapper streflopWrapper;

			currentLuaFunction = "";
			currentLuaFunctionIsValid = false;
			sandboxWrapperFunctionName = "";
			sandboxCode = "";
			luaState = luaL_newstate();

			luaL_openlibs(luaState);

			if (luaState == NULL) {
				throw game_runtime_error("Can not allocate lua state");
			}

			argumentCount = -1;

			if (disableSandbox == false) {
				lua_getglobal(luaState, "os");
				lua_pushnil(luaState);
				lua_setfield(luaState, -2, "execute");
				lua_pushnil(luaState);
				lua_setfield(luaState, -2, "rename");
				lua_pushnil(luaState);
				lua_setfield(luaState, -2, "remove");
				lua_pushnil(luaState);
				lua_setfield(luaState, -2, "exit");

				lua_getglobal(luaState, "io");
				lua_pushnil(luaState);
				lua_setfield(luaState, -2, "open");
				lua_pushnil(luaState);
				lua_setfield(luaState, -2, "close");
				lua_pushnil(luaState);
				lua_setfield(luaState, -2, "write");
				lua_pushnil(luaState);
				lua_setfield(luaState, -2, "read");
				lua_pushnil(luaState);
				lua_setfield(luaState, -2, "flush");

				lua_pushnil(luaState);
				lua_setglobal(luaState, "loadfile");
				lua_pushnil(luaState);
				lua_setglobal(luaState, "dofile");
				lua_pushnil(luaState);
				lua_setglobal(luaState, "getfenv");
				lua_pushnil(luaState);
				lua_setglobal(luaState, "getmetatable");
				lua_pushnil(luaState);
				lua_setglobal(luaState, "load");
				lua_pushnil(luaState);
				lua_setglobal(luaState, "loadfile");
				lua_pushnil(luaState);
				lua_setglobal(luaState, "loadstring");
				lua_pushnil(luaState);
				lua_setglobal(luaState, "rawequal");
				lua_pushnil(luaState);
				lua_setglobal(luaState, "rawget");
				lua_pushnil(luaState);
				lua_setglobal(luaState, "rawset");
				lua_pushnil(luaState);
				lua_setglobal(luaState, "setfenv");
				lua_pushnil(luaState);
				lua_setglobal(luaState, "setmetatable");


				lua_pop(luaState, 1);
			}
		}

		void LuaScript::DumpGlobals() {
			LuaHandle *L = luaState;
			// push the first key (nil = beginning of table)
#if LUA_VERSION_NUM <= 501
			lua_pushnil(L);
#endif
			// lua_next will:
			// 1 - pop the key
			// 2 - push the next key
			// 3 - push the value at that key
			// ... so the key will be at index -2 and the value at index -1
#if LUA_VERSION_NUM > 501
			lua_pushglobaltable(L);
			lua_pushnil(L);
			while (lua_next(L, -2) != 0) {
#else
			while (lua_next(L, LUA_GLOBALSINDEX) != 0) {
#endif
				// get type of key and value
				int key_type = lua_type(L, -2);
				int value_type = lua_type(L, -1);

				// support only string keys
				// globals aren't likely to have a non-string key, but just to be certain ...
				if (key_type != LUA_TSTRING) {
					lua_pop(L, 1); // pop the value so that the top contains the key for the next iteration
					continue;
				}

				// support only number, boolean and string values
				if (value_type != LUA_TNUMBER &&
					value_type != LUA_TBOOLEAN &&
					value_type != LUA_TSTRING) {
					lua_pop(L, 1); // again, pop the value before going to the next loop iteration
					continue;
				}

				// get the key as a string
				string key_string = lua_tostring(L, -2); // no copy required - we already know this is a string

				// do not support variables that start with '_'
				// lua has some predefined values like _VERSION. They all start with underscore

				if (!key_string.size()) { // this again is highly unlikely, but still ...
					lua_pop(L, 1);
					continue;
				}
				if (key_string[0] == '_') {
					lua_pop(L, 1);
					continue;
				}

				string value_string;

				// convert the value to a string. This depends on its type
				switch (value_type) {
					case LUA_TSTRING:
					case LUA_TNUMBER:
						// numbers can be converted to strings

						// get the value as a string (this requires a copy because traversing tables
						// uses the top of the stack as an index. If conversion from a number to string
						// happens, the top of the stack will be altered and the table index will become invalid)
						lua_pushvalue(L, -1);
						value_string = lua_tostring(L, -1);
						lua_pop(L, 1);
						break;
					case LUA_TBOOLEAN:
						value_string = lua_toboolean(L, -1) == 0 ? "false" : "true";
						break;
				}

				// enclose the value in "" if it is a string
				if (value_type == LUA_TSTRING) {
					value_string = "\"" + value_string + "\"";
				}

				// resulting line. Somehow save this and when you need to restore it, just
				// call luaL_dostring with that line.
				//SaveLine(key_string + " = " + value_string);		// Pop the value so the index remains on top of the stack for the next iteration
				printf("Found global LUA var: %s = %s\n", key_string.c_str(), value_string.c_str());

				lua_pop(L, 1);
			}
#if LUA_VERSION_NUM > 501
			lua_pop(L, 1);
#endif

			}

		void LuaScript::saveGame(XmlNode *rootNode) {
			std::map<string, string> mapTagReplacements;

			//try{
			LuaHandle *L = luaState;
			// push the first key (nil = beginning of table)
#if LUA_VERSION_NUM <= 501
			lua_pushnil(L);
#endif

			// lua_next will:
			// 1 - pop the key
			// 2 - push the next key
			// 3 - push the value at that key
			// ... so the key will be at index -2 and the value at index -1

#if LUA_VERSION_NUM > 501
			lua_pushglobaltable(L);
			lua_pushnil(L);
			while (lua_next(L, -2) != 0) {
#else
			while (lua_next(L, LUA_GLOBALSINDEX) != 0) {
#endif
				// get type of key and value
				int key_type = lua_type(L, -2);
				int value_type = lua_type(L, -1);

				if (LuaScript::debugModeEnabled == true) printf("LUA save key_type = %d, value_type = %d for var [%s]\n", key_type, value_type, lua_tostring(L, -2));

				// support only string keys
				// globals aren't likely to have a non-string key, but just to be certain ...
				if (key_type != LUA_TSTRING) {
					lua_pop(L, 1); // pop the value so that the top contains the key for the next iteration
					continue;
				}

				// support only number, boolean and string values
				if (value_type != LUA_TNUMBER &&
					value_type != LUA_TBOOLEAN &&
					value_type != LUA_TSTRING &&
					value_type != LUA_TTABLE) {
					lua_pop(L, 1); // again, pop the value before going to the next loop iteration
					continue;
				}

				// get the key as a string
				string key_string = lua_tostring(L, -2); // no copy required - we already know this is a string

				if (LuaScript::debugModeEnabled == true) printf("key_string [%s]\n", key_string.c_str());
				// do not support variables that start with '_'
				// lua has some predefined values like _VERSION. They all start with underscore

				if (!key_string.size()) { // this again is highly unlikely, but still ...
					lua_pop(L, 1);
					continue;
				}
				if (key_string[0] == '_') {
					lua_pop(L, 1);
					continue;
				}

				bool skipTable = false;
				// The first pair is the tables key type,value
				// The second pair is the tables value type,value
				vector<pair<pair<int, string>, pair<int, string> > > tableList;
				string value_string;

				// convert the value to a string. This depends on its type
				switch (value_type) {
					case LUA_TSTRING:
					case LUA_TNUMBER:
						// numbers can be converted to strings

						// get the value as a string (this requires a copy because traversing tables
						// uses the top of the stack as an index. If conversion from a number to string
						// happens, the top of the stack will be altered and the table index will become invalid)
						lua_pushvalue(L, -1);
						value_string = lua_tostring(L, -1);
						lua_pop(L, 1);
						break;
					case LUA_TBOOLEAN:
						value_string = lua_toboolean(L, -1) == 0 ? "false" : "true";
						break;
					case LUA_TTABLE:
					{

						int tableItemCount = 0;
						if (LuaScript::debugModeEnabled == true) printf("================ LUA TABLE DETECTED - START =================\n");
						for (lua_pushnil(L); lua_next(L, -2);) {
							tableItemCount++;

							if (LuaScript::debugModeEnabled == true) printf("LUA TABLE loop A\n");

							int tableKeyType = lua_type(L, -2);
							int tableValueType = lua_type(L, -1);

							if (LuaScript::debugModeEnabled == true) printf("LUA TABLE loop item type [%s] key: %d value type: %d\n", lua_typename(L, tableValueType), tableKeyType, tableValueType);

							switch (tableValueType) {
								case LUA_TSTRING:
								case LUA_TNUMBER:
								case LUA_TBOOLEAN:
									break;
								default:
									skipTable = true;
									break;
							}
							if (skipTable == false) {
								// Stack: value, key, table
								std::string value = "";
								if (!lua_isnil(L, -1)) {
									if (LuaScript::debugModeEnabled == true) printf("LUA TABLE loop B\n");

									lua_pushvalue(L, -1);

									if (LuaScript::debugModeEnabled == true) printf("LUA TABLE loop C\n");

									if (tableValueType == LUA_TBOOLEAN) {
										value = lua_toboolean(L, -1) == 0 ? "false" : "true";
									} else {
										value = lua_tostring(L, -1);
									}

									if (LuaScript::debugModeEnabled == true) printf("LUA TABLE loop D\n");

									lua_pop(L, 1);
								}
								lua_pop(L, 1);

								if (LuaScript::debugModeEnabled == true) printf("LUA TABLE value [%s]\n", value.c_str());

								// Stack: key, table
								lua_pushvalue(L, -1);

								// Stack: key, key, table
								std::string key = lua_tostring(L, -1);
								lua_pop(L, 1);

								// Stack: key, table
								//std :: cout << key << "" << value << "\ n";
								if (LuaScript::debugModeEnabled == true) printf("[%s] [%s]\n", key.c_str(), value.c_str());

								if (value_string != "") {
									value_string += "|||";
								}
								char szBuf[8096] = "";
								snprintf(szBuf, 8096, "[%s] [%s]", key.c_str(), value.c_str());
								//value_string += szBuf;
								//vector<pair<pair<int,string>, pair<int,string>> > tableList;
								tableList.push_back(make_pair(make_pair(tableKeyType, key), make_pair(tableValueType, value)));
							} else {
								if (LuaScript::debugModeEnabled == true) printf("***WARNING*** SKIPPING LUA TABLE because it has an unsupported embedded type: %d [%s]\n", tableValueType, lua_typename(L, tableValueType));

								lua_pop(L, 1);
							}
						}
						if (tableItemCount < 1) {
							if (LuaScript::debugModeEnabled == true) printf("Skipping lua table tableItemCount = %d\n", tableItemCount);

							skipTable = true;
						}
						if (LuaScript::debugModeEnabled == true) printf("---------------------------- LUA TABLE DETECTED - END ----------------------------\n");
					}
					break;
				}

				// enclose the value in "" if it is a string
				//if (value_type == LUA_TSTRING) {
					//value_string = "\"" + value_string + "\"";
				//}

				if (skipTable == true) {
					if (LuaScript::debugModeEnabled == true) printf("#2 SKIPPING TABLE\n");
				} else {
					//vector<pair<pair<int,string>, pair<int,string>> > tableList;
					if (tableList.empty() == false) {
						XmlNode *luaScriptNode = rootNode->addChild("LuaScript");
						luaScriptNode->addAttribute("variable", key_string, mapTagReplacements);
						luaScriptNode->addAttribute("value_type", intToStr(value_type), mapTagReplacements);

						for (unsigned int i = 0; i < tableList.size(); ++i) {
							pair<pair<int, string>, pair<int, string> > &item = tableList[i];

							XmlNode *luaScriptTableNode = luaScriptNode->addChild("Table");
							luaScriptTableNode->addAttribute("key_type", intToStr(item.first.first), mapTagReplacements);
							luaScriptTableNode->addAttribute("key", item.first.second, mapTagReplacements);
							luaScriptTableNode->addAttribute("value", item.second.second, mapTagReplacements);
							luaScriptTableNode->addAttribute("value_type", intToStr(item.second.first), mapTagReplacements);
						}
					} else {
						// resulting line. Somehow save this and when you need to restore it, just
						// call luaL_dostring with that line.
						//SaveLine(key_string + " = " + value_string);		// Pop the value so the index remains on top of the stack for the next iteration
						//printf("Found global LUA var: %s = %s\n",key_string.c_str(),value_string.c_str());
						XmlNode *luaScriptNode = rootNode->addChild("LuaScript");
						luaScriptNode->addAttribute("variable", key_string, mapTagReplacements);
						luaScriptNode->addAttribute("value", value_string, mapTagReplacements);
						luaScriptNode->addAttribute("value_type", intToStr(value_type), mapTagReplacements);
					}
				}
				lua_pop(L, 1);
			}

#if LUA_VERSION_NUM > 501
			lua_pop(L, 1);
#endif

			//}
			//catch(const exception &ex) {
			//	abort();
			//}
			}

		void LuaScript::loadGame(const XmlNode *rootNode) {
			if (LuaScript::debugModeEnabled) printf("START [%s::%s] Line: %d\n", extractFileFromDirectoryPath(__FILE__).c_str(), __FUNCTION__, __LINE__);

			vector<XmlNode *> luaScriptNodeList = rootNode->getChildList("LuaScript");

			if (LuaScript::debugModeEnabled) printf("luaScriptNodeList.size(): %d\n", (int) luaScriptNodeList.size());

			for (unsigned int i = 0; i < luaScriptNodeList.size(); ++i) {
				XmlNode *node = luaScriptNodeList[i];

				string variable = node->getAttribute("variable")->getValue();
				int value_type = node->getAttribute("value_type")->getIntValue();

				if (LuaScript::debugModeEnabled) printf("  index #: %u [%s] [%d]\n", i, variable.c_str(), value_type);

				switch (value_type) {
					case LUA_TSTRING:
						if (LuaScript::debugModeEnabled) printf("  value [%s]\n", node->getAttribute("value")->getValue().c_str());
						lua_pushstring(luaState, node->getAttribute("value")->getValue().c_str());
						break;
					case LUA_TNUMBER:
						if (LuaScript::debugModeEnabled) printf("  value [%s]\n", node->getAttribute("value")->getValue().c_str());
						lua_pushnumber(luaState, node->getAttribute("value")->getFloatValue());
						break;
					case LUA_TBOOLEAN:
						if (LuaScript::debugModeEnabled) printf("  value [%s]\n", node->getAttribute("value")->getValue().c_str());
						lua_pushboolean(luaState, node->getAttribute("value")->getBoolValue());
						break;
					case LUA_TTABLE:
					{
						if (LuaScript::debugModeEnabled == true) printf("================ LUA TABLE DETECTED - START =================\n");

						lua_newtable(luaState);    /* We will pass a table */
						vector<XmlNode *> luaScriptTableNode = node->getChildList("Table");

						if (LuaScript::debugModeEnabled) printf("luaScriptTableNode.size(): %d\n", (int) luaScriptTableNode.size());

						for (unsigned int j = 0; j < luaScriptTableNode.size(); ++j) {
							XmlNode *nodeTable = luaScriptTableNode[j];

							int key_type = nodeTable->getAttribute("key_type")->getIntValue();
							if (LuaScript::debugModeEnabled == true) printf("Table item key_type: %d [%s]\n", key_type, lua_typename(luaState, key_type));

							switch (key_type) {
								case LUA_TSTRING:
									if (LuaScript::debugModeEnabled) printf("  table item key [%s]\n", nodeTable->getAttribute("key")->getValue().c_str());
									lua_pushstring(luaState, nodeTable->getAttribute("key")->getValue().c_str());
									break;
								case LUA_TNUMBER:
									if (LuaScript::debugModeEnabled) printf("  table item key [%s]\n", nodeTable->getAttribute("key")->getValue().c_str());
									lua_pushnumber(luaState, nodeTable->getAttribute("key")->getFloatValue());
									break;
								case LUA_TBOOLEAN:
									if (LuaScript::debugModeEnabled) printf("  table item key [%s]\n", nodeTable->getAttribute("key")->getValue().c_str());
									lua_pushboolean(luaState, nodeTable->getAttribute("key")->getBoolValue());
									break;
							}

							int value_type = nodeTable->getAttribute("value_type")->getIntValue();
							if (LuaScript::debugModeEnabled == true) printf("Table item value_type: %d [%s]\n", value_type, lua_typename(luaState, value_type));

							switch (value_type) {
								case LUA_TSTRING:
									if (LuaScript::debugModeEnabled) printf("  table item value [%s]\n", nodeTable->getAttribute("value")->getValue().c_str());
									lua_pushstring(luaState, nodeTable->getAttribute("value")->getValue().c_str());
									break;
								case LUA_TNUMBER:
									if (LuaScript::debugModeEnabled) printf("  table item value [%s]\n", nodeTable->getAttribute("value")->getValue().c_str());
									lua_pushnumber(luaState, nodeTable->getAttribute("value")->getFloatValue());
									break;
								case LUA_TBOOLEAN:
									if (LuaScript::debugModeEnabled) printf("  table item value [%s]\n", nodeTable->getAttribute("value")->getValue().c_str());
									lua_pushboolean(luaState, nodeTable->getAttribute("value")->getBoolValue());
									break;
							}

							lua_rawset(luaState, -3);      /* Stores the pair in the table */
						}
						if (LuaScript::debugModeEnabled == true) printf("----------------- LUA TABLE DETECTED - END -----------------\n");
					}
					break;

				}

				lua_setglobal(luaState, variable.c_str());
			}
		}

		LuaScript::~LuaScript() {
			Lua_STREFLOP_Wrapper streflopWrapper;

			//	LuaInterface.LuaTable luatab;
			//
			//	            luatab = lua.GetTable("_G");
			//
			//
			//	            foreach (DictionaryEntry d in luatab)
			//
			//	            {
			//
			//	                Console.WriteLine("{0} -> {1}", d.Key, d.Value);
			//
			//	            }

				//DumpGlobals();

			lua_close(luaState);
		}

		void LuaScript::loadCode(string code, string name) {
			Lua_STREFLOP_Wrapper streflopWrapper;

			//printf("Code [%s]\nName [%s]\n",code.c_str(),name.c_str());

			int errorCode = luaL_loadbuffer(luaState, code.c_str(), code.length(), name.c_str());
			if (errorCode != 0) {
				printf("=========================================================\n");
				printf("Error loading lua code: %s\n", errorToString(errorCode).c_str());
				printf("Function name [%s]\ncode:\n%s\n", name.c_str(), code.c_str());
				printf("=========================================================\n");

				throw game_runtime_error("Error loading lua code: " + errorToString(errorCode), true);
			}

			if (SystemFlags::getSystemSettingType(SystemFlags::debugLUA).enabled) SystemFlags::OutputDebug(SystemFlags::debugLUA, "In [%s::%s Line: %d] name [%s], errorCode = %d\n", __FILE__, __FUNCTION__, __LINE__, name.c_str(), errorCode);

			//run code
			errorCode = lua_pcall(luaState, 0, 0, 0);
			if (errorCode != 0) {
				printf("=========================================================\n");
				printf("Error calling lua pcall: %s\n", errorToString(errorCode).c_str());
				printf("=========================================================\n");

				throw game_runtime_error("Error initializing lua: " + errorToString(errorCode), true);
			}

			//const char *errMsg = lua_tostring(luaState, -1);

			//printf("END of call to Name [%s]\n",name.c_str());

			if (SystemFlags::getSystemSettingType(SystemFlags::debugLUA).enabled) SystemFlags::OutputDebug(SystemFlags::debugLUA, "In [%s::%s Line: %d] name [%s], errorCode = %d\n", __FILE__, __FUNCTION__, __LINE__, name.c_str(), errorCode);
		}

		void LuaScript::setSandboxWrapperFunctionName(string name) {
			sandboxWrapperFunctionName = name;
		}

		void LuaScript::setSandboxCode(string code) {
			sandboxCode = code;
		}

		int LuaScript::runCode(string code) {
			Lua_STREFLOP_Wrapper streflopWrapper;

			int errorCode = luaL_dostring(luaState, code.c_str());
			return errorCode;
		}

		void LuaScript::beginCall(string functionName) {
			Lua_STREFLOP_Wrapper streflopWrapper;

			currentLuaFunction = functionName;

			if (SystemFlags::getSystemSettingType(SystemFlags::debugLUA).enabled) SystemFlags::OutputDebug(SystemFlags::debugLUA, "In [%s::%s Line: %d] functionName [%s]\n", __FILE__, __FUNCTION__, __LINE__, functionName.c_str());
			if (SystemFlags::getSystemSettingType(SystemFlags::debugPerformance).enabled) SystemFlags::OutputDebug(SystemFlags::debugPerformance, "In [%s::%s Line: %d] functionName [%s]\n", __FILE__, __FUNCTION__, __LINE__, functionName.c_str());

			//string funcLuaFunction = functionName;
		//	if(sandboxWrapperFunctionName != "" && sandboxCode != "") {
		//		int errorCode= runCode(sandboxCode);
		//		if(errorCode !=0 ) {
		//			throw game_runtime_error("Error calling lua function [" + currentLuaFunction + "] error: " + errorToString(errorCode));
		//		}
		//		//functionName = sandboxWrapperFunctionName;
		//	}
			lua_getglobal(luaState, functionName.c_str());

			currentLuaFunctionIsValid = lua_isfunction(luaState, lua_gettop(luaState));

			//printf("currentLuaFunctionIsValid = %d functionName [%s]\n",currentLuaFunctionIsValid,functionName.c_str());
			argumentCount = 0;
		}

		void LuaScript::endCall() {
			Lua_STREFLOP_Wrapper streflopWrapper;

			if (SystemFlags::getSystemSettingType(SystemFlags::debugLUA).enabled) SystemFlags::OutputDebug(SystemFlags::debugLUA, "In [%s::%s Line: %d] currentLuaFunction [%s], currentLuaFunctionIsValid = %d\n", __FILE__, __FUNCTION__, __LINE__, currentLuaFunction.c_str(), currentLuaFunctionIsValid);

			if (currentLuaFunctionIsValid == true) {
				if (sandboxWrapperFunctionName != "" && sandboxCode != "") {
					//lua_pushstring(luaState, currentLuaFunction.c_str());   // push 1st argument, the real lua function
					//argumentCount = 1;

					string safeWrapper = sandboxWrapperFunctionName + " [[" + currentLuaFunction + "()]]";
					printf("Trying to execute [%s]\n", safeWrapper.c_str());
					int errorCode = runCode(safeWrapper);
					if (errorCode != 0) {
						throw game_runtime_error("Error calling lua function [" + currentLuaFunction + "] error: " + errorToString(errorCode), true);
					}

					//printf("Trying to execute [%s]\n",currentLuaFunction.c_str());
					//lua_getglobal(luaState, sandboxWrapperFunctionName.c_str());
					//argumentCount = 1;
					//lua_pushstring( luaState, currentLuaFunction.c_str() );

		//			int errorCode= lua_pcall(luaState, argumentCount, 0, 0);
		//			if(errorCode !=0 ) {
		//				throw game_runtime_error("Error calling lua function [" + currentLuaFunction + "] error: " + errorToString(errorCode));
		//			}
				} else {
					int errorCode = lua_pcall(luaState, argumentCount, 0, 0);
					if (errorCode != 0) {
						throw game_runtime_error("Error calling lua function [" + currentLuaFunction + "] error: " + errorToString(errorCode), true);
					}
				}
			} else {
				lua_pcall(luaState, argumentCount, 0, 0);
			}
		}

		void LuaScript::registerFunction(LuaFunction luaFunction, string functionName) {
			Lua_STREFLOP_Wrapper streflopWrapper;

			if (SystemFlags::getSystemSettingType(SystemFlags::debugLUA).enabled) SystemFlags::OutputDebug(SystemFlags::debugLUA, "In [%s::%s Line: %d] functionName [%s]\n", __FILE__, __FUNCTION__, __LINE__, functionName.c_str());

			lua_pushcfunction(luaState, luaFunction);
			lua_setglobal(luaState, functionName.c_str());
		}

		string LuaScript::errorToString(int errorCode) {
			Lua_STREFLOP_Wrapper streflopWrapper;

			string error;
			switch (errorCode) {
				case LUA_ERRSYNTAX:
					error += "Syntax error";
					break;
				case LUA_ERRRUN:
					error += "Runtime error";
					break;
				case LUA_ERRMEM:
					error += "Memory allocation error";
					break;
				case LUA_ERRERR:
					error += "Error while running the error handler";
					break;
				default:
					error += "Unknown LUA error" + intToStr(errorCode);
					break;
			}

			error += string(": ") + luaL_checkstring(luaState, -1);

			return error;
		}

		// =====================================================
		//	class LuaArguments
		// =====================================================

		LuaArguments::LuaArguments(lua_State *luaState) {
			Lua_STREFLOP_Wrapper streflopWrapper;

			this->luaState = luaState;
			returnCount = 0;
		}

		int LuaArguments::getInt(int argumentIndex) const {
			Lua_STREFLOP_Wrapper streflopWrapper;

			if (!lua_isnumber(luaState, argumentIndex)) {
				throwLuaError("Can not get int from Lua state");
			}

			int result = (int) luaL_checkinteger(luaState, argumentIndex);
			return result;
		}

		float LuaArguments::getFloat(int argumentIndex) const {
			Lua_STREFLOP_Wrapper streflopWrapper;

			if (!lua_isnumber(luaState, argumentIndex)) {
				throwLuaError("Can not get int from Lua state");
			}
			float result = static_cast<float>(luaL_checknumber(luaState, argumentIndex));
			return result;
		}
		Vec2f LuaArguments::getVec2f(int argumentIndex) const {
			Lua_STREFLOP_Wrapper streflopWrapper;

			Vec2f v;

			if (!lua_istable(luaState, argumentIndex)) {
				throwLuaError("Can not get vec2f from Lua state, value on the stack is not a table");
			}

#if LUA_VERSION_NUM > 501
			if (lua_rawlen(luaState, argumentIndex) != 2) {
#else
			if (luaL_getn(luaState, argumentIndex) != 2) {
#endif
				throwLuaError("Can not get vec2f from Lua state, array size not 2");
			}

			//string stackString = getStackText();
			//printf("Lua Stack:\n%s\n",stackString.c_str());

			lua_rawgeti(luaState, argumentIndex, 1);
			//printf("xa = %s argumentIndex = %d\n",lua_tostring(luaState, argumentIndex),argumentIndex);

			//v.x= (int)luaL_checkinteger(luaState, argumentIndex);
			v.x = static_cast<float>(lua_tonumber(luaState, argumentIndex));
			lua_pop(luaState, 1);

			//printf("X = %d\n",v.x);

			lua_rawgeti(luaState, argumentIndex, 2);
			//printf("ya = %s\n",lua_tostring(luaState, argumentIndex));

			//v.y= (int)luaL_checkinteger(luaState, argumentIndex);
			v.y = static_cast<float>(lua_tonumber(luaState, argumentIndex));
			lua_pop(luaState, 1);

			//printf("Y = %d\n",v.y);

			return v;
			}

		Vec3f LuaArguments::getVec3f(int argumentIndex) const {
			Lua_STREFLOP_Wrapper streflopWrapper;

			Vec3f v;

			if (!lua_istable(luaState, argumentIndex)) {
				throwLuaError("Can not get vec3f from Lua state, value on the stack is not a table");
			}

#if LUA_VERSION_NUM > 501
			if (lua_rawlen(luaState, argumentIndex) != 3) {
#else
			if (luaL_getn(luaState, argumentIndex) != 3) {
#endif
				throwLuaError("Can not get vec3f from Lua state, array size not 3");
			}

			//string stackString = getStackText();
			//printf("Lua Stack:\n%s\n",stackString.c_str());

			lua_rawgeti(luaState, argumentIndex, 1);
			//printf("xa = %s argumentIndex = %d\n",lua_tostring(luaState, argumentIndex),argumentIndex);

			//v.x= (int)luaL_checkinteger(luaState, argumentIndex);
			v.x = static_cast<float>(lua_tonumber(luaState, argumentIndex));
			lua_pop(luaState, 1);

			//printf("X = %d\n",v.x);

			lua_rawgeti(luaState, argumentIndex, 2);
			//printf("ya = %s\n",lua_tostring(luaState, argumentIndex));

			//v.y= (int)luaL_checkinteger(luaState, argumentIndex);
			v.y = static_cast<float>(lua_tonumber(luaState, argumentIndex));
			lua_pop(luaState, 1);

			//printf("Y = %d\n",v.y);

			lua_rawgeti(luaState, argumentIndex, 3);
			//printf("ya = %s\n",lua_tostring(luaState, argumentIndex));

			//v.y= (int)luaL_checkinteger(luaState, argumentIndex);
			v.z = static_cast<float>(lua_tonumber(luaState, argumentIndex));
			lua_pop(luaState, 1);

			return v;
			}

		Vec4f LuaArguments::getVec4f(int argumentIndex) const {
			Lua_STREFLOP_Wrapper streflopWrapper;

			Vec4f v;

			if (!lua_istable(luaState, argumentIndex)) {
				throwLuaError("Can not get vec4f from Lua state, value on the stack is not a table");
			}

#if LUA_VERSION_NUM > 501
			if (lua_rawlen(luaState, argumentIndex) != 4) {
#else
			if (luaL_getn(luaState, argumentIndex) != 4) {
#endif
				throwLuaError("Can not get vec4f from Lua state, array size not 4");
			}

			//string stackString = getStackText();
			//printf("Lua Stack:\n%s\n",stackString.c_str());

			lua_rawgeti(luaState, argumentIndex, 1);
			//printf("xa = %s argumentIndex = %d\n",lua_tostring(luaState, argumentIndex),argumentIndex);

			//v.x= (int)luaL_checkinteger(luaState, argumentIndex);
			v.x = static_cast<float>(lua_tonumber(luaState, argumentIndex));
			lua_pop(luaState, 1);

			//printf("X = %d\n",v.x);

			lua_rawgeti(luaState, argumentIndex, 2);
			//printf("ya = %s\n",lua_tostring(luaState, argumentIndex));

			//v.y= (int)luaL_checkinteger(luaState, argumentIndex);
			v.y = static_cast<float>(lua_tonumber(luaState, argumentIndex));
			lua_pop(luaState, 1);

			//printf("Y = %d\n",v.y);

			lua_rawgeti(luaState, argumentIndex, 3);
			//printf("ya = %s\n",lua_tostring(luaState, argumentIndex));

			//v.y= (int)luaL_checkinteger(luaState, argumentIndex);
			v.z = static_cast<float>(lua_tonumber(luaState, argumentIndex));
			lua_pop(luaState, 1);

			lua_rawgeti(luaState, argumentIndex, 4);
			//printf("ya = %s\n",lua_tostring(luaState, argumentIndex));

			//v.y= (int)luaL_checkinteger(luaState, argumentIndex);
			v.w = static_cast<float>(lua_tonumber(luaState, argumentIndex));
			lua_pop(luaState, 1);

			return v;
			}

		string LuaArguments::getString(int argumentIndex) const {
			Lua_STREFLOP_Wrapper streflopWrapper;

			if (!lua_isstring(luaState, argumentIndex)) {
				throwLuaError("Can not get string from Lua state");
			}
			return luaL_checkstring(luaState, argumentIndex);
		}

		void * LuaArguments::getGenericData(int argumentIndex) const {
			Lua_STREFLOP_Wrapper streflopWrapper;

			if (lua_isstring(luaState, argumentIndex)) {
				const char *result = luaL_checkstring(luaState, argumentIndex);
				//printf("\nGENERIC param %d is a string, %s!\n",argumentIndex,result);
				return (void *) result;
			}
			//else if(lua_isnumber(luaState, argumentIndex)) {
			//	double result = luaL_checknumber(luaState, argumentIndex);
			//	printf("\nGENERIC param %d is a double, %f!\n",argumentIndex,result);
			//	return (void *)result;
			//}
			else if (lua_isnumber(luaState, argumentIndex)) {
				lua_Integer result = luaL_checkinteger(luaState, argumentIndex);
				//printf("\nGENERIC param %d is an int, %d!\n",argumentIndex,(int)result);
				return (void *) result;
			} else {
				//printf("\nGENERIC param %d is a NULL!\n",argumentIndex);
				return NULL;
			}
		}

		Vec2i LuaArguments::getVec2i(int argumentIndex) const {
			Lua_STREFLOP_Wrapper streflopWrapper;

			Vec2i v;

			if (!lua_istable(luaState, argumentIndex)) {
				throwLuaError("Can not get vec2i from Lua state, value on the stack is not a table");
			}

#if LUA_VERSION_NUM > 501
			if (lua_rawlen(luaState, argumentIndex) != 2) {
#else
			if (luaL_getn(luaState, argumentIndex) != 2) {
#endif
				throwLuaError("Can not get vec2i from Lua state, array size not 2");
			}

			//string stackString = getStackText();
			//printf("Lua Stack:\n%s\n",stackString.c_str());

			lua_rawgeti(luaState, argumentIndex, 1);
			//printf("xa = %s argumentIndex = %d\n",lua_tostring(luaState, argumentIndex),argumentIndex);

			//v.x= (int)luaL_checkinteger(luaState, argumentIndex);
			v.x = (int) lua_tointeger(luaState, argumentIndex);
			lua_pop(luaState, 1);

			//printf("X = %d\n",v.x);

			lua_rawgeti(luaState, argumentIndex, 2);
			//printf("ya = %s\n",lua_tostring(luaState, argumentIndex));

			//v.y= (int)luaL_checkinteger(luaState, argumentIndex);
			v.y = (int) lua_tointeger(luaState, argumentIndex);
			lua_pop(luaState, 1);

			//printf("Y = %d\n",v.y);

			return v;
			}

		Vec4i LuaArguments::getVec4i(int argumentIndex) const {
			Lua_STREFLOP_Wrapper streflopWrapper;

			Vec4i v;

			if (!lua_istable(luaState, argumentIndex)) {
				throwLuaError("Can not get vec4i from Lua state, value on the stack is not a table");
			}

#if LUA_VERSION_NUM > 501
			if (lua_rawlen(luaState, argumentIndex) != 4) {
#else
			if (luaL_getn(luaState, argumentIndex) != 4) {
#endif
				throwLuaError("Can not get vec4i from Lua state, array size not 4");
			}

			lua_rawgeti(luaState, argumentIndex, 1);
			v.x = (int) luaL_checkinteger(luaState, argumentIndex);
			lua_pop(luaState, 1);

			lua_rawgeti(luaState, argumentIndex, 2);
			v.y = (int) luaL_checkinteger(luaState, argumentIndex);
			lua_pop(luaState, 1);

			lua_rawgeti(luaState, argumentIndex, 3);
			v.z = (int) luaL_checkinteger(luaState, argumentIndex);
			lua_pop(luaState, 1);

			lua_rawgeti(luaState, argumentIndex, 4);
			v.w = (int) luaL_checkinteger(luaState, argumentIndex);
			lua_pop(luaState, 1);

			return v;
			}

		void LuaArguments::returnInt(int value) {
			Lua_STREFLOP_Wrapper streflopWrapper;

			++returnCount;
			lua_pushinteger(luaState, value);
		}

		void LuaArguments::returnFloat(float value) {
			Lua_STREFLOP_Wrapper streflopWrapper;

			++returnCount;
			lua_pushnumber(luaState, value);
		}

		void LuaArguments::returnString(const string &value) {
			Lua_STREFLOP_Wrapper streflopWrapper;

			++returnCount;
			lua_pushstring(luaState, value.c_str());
		}

		void LuaArguments::returnVec2i(const Vec2i &value) {
			//Lua_STREFLOP_Wrapper streflopWrapper;

			++returnCount;

			lua_newtable(luaState);

			lua_pushnumber(luaState, value.x);
			lua_rawseti(luaState, -2, 1);

			lua_pushnumber(luaState, value.y);
			lua_rawseti(luaState, -2, 2);
		}

		void LuaArguments::returnVec4i(const Vec4i &value) {
			//Lua_STREFLOP_Wrapper streflopWrapper;

			++returnCount;

			lua_newtable(luaState);

			lua_pushnumber(luaState, value.x);
			lua_rawseti(luaState, -2, 1);

			lua_pushnumber(luaState, value.y);
			lua_rawseti(luaState, -2, 2);

			lua_pushnumber(luaState, value.z);
			lua_rawseti(luaState, -2, 3);

			lua_pushnumber(luaState, value.w);
			lua_rawseti(luaState, -2, 4);

		}

		void LuaArguments::returnVectorInt(const vector<int> &value) {
			//Lua_STREFLOP_Wrapper streflopWrapper;

			++returnCount;

			lua_newtable(luaState);

			for (unsigned int i = 0; i < value.size(); ++i) {
				lua_pushnumber(luaState, value[i]);
				lua_rawseti(luaState, -2, i + 1);
			}
		}

		string LuaArguments::getStackText() const {
			Lua_STREFLOP_Wrapper streflopWrapper;

			string stackString;
			int stackSize = lua_gettop(luaState);

			//build stack string
			for (int i = 1; i <= stackSize; ++i) {
				stackString += "-" + intToStr(i) + ": ";
				if (lua_isnumber(luaState, -i)) {
					stackString += "Number: " + doubleToStr(luaL_checknumber(luaState, -i));
				} else if (lua_isstring(luaState, -i)) {
					stackString += "String: " + string(luaL_checkstring(luaState, -i));
				} else if (lua_istable(luaState, -i)) {

					//int tableLen = 0;
#if LUA_VERSION_NUM > 501
					int tableLen = lua_rawlen(luaState, -i);
#else
					int tableLen = luaL_getn(luaState, -i);
#endif
					stackString += "Table (" + intToStr(tableLen) + ")\n";
					//			for(unsigned int j = 1; j < tableLen; ++j) {
					//				stackString+= "entry# " + intToStr(j) + ", value = " + lua_tostring(luaState, -i) + "\n";
					//			}


					//			int j = 1;
					//			lua_pushnil(luaState);  // The initial key for the traversal.
					//
					//			printf("start loop\n");
					//
					//			while (lua_next(luaState, -2)!=0) {
					//				printf("in loop j = %d\n",j);
					//
					//				const char* Param=lua_tostring(luaState, -1);
					//
					//				printf("passed in loop j = %d Param [%s]\n",j,(Param != NULL ? Param : "<nil>"));
					//
					//				if (Param!=NULL) {
					//					stackString+= "entry# " + intToStr(j) + ", value = " + Param + "\n";
					//				}
					//
					//				// Remove the value, keep the key for the next iteration.
					//				lua_pop(luaState, 1);
					//				j++;
					//			}

					//			const int len = lua_objlen( luaState, -i );
					//			printf("Table Len = %d\n",len);
					//
					//			for ( int j = 1; j <= len; ++j ) {
					//				printf("A Table\n");
					//
					//				lua_pushinteger( luaState, j );
					//
					//				printf("B Table\n");
					//
					//				lua_gettable( luaState, -2 );
					//
					//				printf("C Table\n");
					//
					//				//v.push_back( lua_tointeger( L, -1 ) );
					//				const char *value = lua_tostring( luaState, -1 );
					//				printf("D Table value = %s\n",(value != NULL ? value : "<nil>"));
					//
					//				//v.push_back( lua_tointeger( L, -1 ) );
					//				const char *value2 = lua_tostring( luaState, -2 );
					//				printf("E Table value2 = %s\n",(value2 != NULL ? value2 : "<nil>"));
					//
					//				value2 = lua_tostring( luaState, -3 );
					//				printf("F Table value2 = %s\n",(value2 != NULL ? value2 : "<nil>"));
					//
					//				value2 = lua_tostring( luaState, 0 );
					//				printf("G Table value2 = %s\n",(value2 != NULL ? value2 : "<nil>"));
					//
					//				value2 = lua_tostring( luaState, 1 );
					//				printf("H Table value2 = %s\n",(value2 != NULL ? value2 : "<nil>"));
					//
					//				stackString+= "entry# " + intToStr(j) + ", value = " + (value != NULL ? value : "<nil>") + "\n";
					//
					//				printf("E Table\n");
					//
					//				lua_pop( luaState, 1 );
					//			}
				} else {
					stackString += "Unknown";
				}
				stackString += "\n";
			}

			return stackString;
		}
		void LuaArguments::throwLuaError(const string &message) const {
			Lua_STREFLOP_Wrapper streflopWrapper;

			string stackString = getStackText();
			throw game_runtime_error("Lua error: " + message + "\n\nLua Stack:\n" + stackString, true);
		}
	}
} //end namespace
