#include "script-lua.h"

#include "data.h"

using namespace Retro;
using namespace std;

shared_ptr<ScriptContext> ScriptLua::create() {
	return make_shared<ScriptLua>();
}

ScriptLua::~ScriptLua() {
	if (m_L) {
		lua_close(m_L);
	}
}

static int _getData(lua_State* L) {
	const GameData* data = static_cast<const GameData*>(lua_touserdata(L, 1));
	int64_t datum;
	if (lua_isnumber(L, 2)) {
		int64_t address = lua_tonumber(L, 2);
		const AddressSpace& as = data->addressSpace();
		if (address < 0 || !as.hasBlock(address)) {
			lua_pushstring(L, "Out of bounds access");
			lua_error(L);
		}
		datum = as[address];
	} else {
		const char* name = lua_tostring(L, 2);
		datum = data->lookupValue(name);
	}

	lua_pushnumber(L, datum);
	return 1;
}

static int _noop(lua_State*) {
	return 0;
}

void ScriptLua::setData(const GameData* data) {
	ScriptContext::setData(data);

	// Make "table"
	lua_pushlightuserdata(m_L, const_cast<void*>(static_cast<const void*>(data)));

	// Make metatable
	lua_createtable(m_L, 0, 2);
	lua_pushcfunction(m_L, _getData);
	lua_setfield(m_L, -2, "__index");

	lua_pushcfunction(m_L, _noop);
	lua_setfield(m_L, -2, "__newindex");

	lua_setmetatable(m_L, -2);
	lua_setglobal(m_L, "data");
};

bool ScriptLua::init() {
	m_L = luaL_newstate();
	if (!m_L) {
		return false;
	}
	luaopen_base(m_L);
	luaopen_table(m_L);
	luaopen_string(m_L);
	luaopen_math(m_L);

	vector<string> functions = listFunctions();
	m_blacklist = { functions.begin(), functions.end() };
	return true;
}

bool ScriptLua::load(const string& filename) {
	return luaL_dofile(m_L, filename.c_str()) == 0;
}

Variant ScriptLua::callFunction(const string& funcName) {
	lua_getglobal(m_L, funcName.c_str());
	int status = lua_pcall(m_L, 0, 1, 0);
	if (status != 0) {
		string error = string("Lua call failed: ") + lua_tostring(m_L, -1);
		lua_pop(m_L, 1);
		throw runtime_error(error);
	}

	Variant v;
	switch (lua_type(m_L, -1)) {
	case LUA_TNUMBER:
		v = lua_tonumber(m_L, -1);
		break;
	case LUA_TBOOLEAN:
		v = static_cast<bool>(lua_toboolean(m_L, -1));
		break;
	default:
		break;
	}
	lua_pop(m_L, 1);
	return v;
}

vector<string> ScriptLua::listFunctions() {
	vector<string> funcs;
	lua_pushvalue(m_L, LUA_GLOBALSINDEX);
	lua_pushnil(m_L);
	while (lua_next(m_L, -2) != 0) {
		if (lua_isfunction(m_L, -1)) {
			lua_pushvalue(m_L, -2);
			const char* str = lua_tostring(m_L, -1);
			if (!m_blacklist.count(str)) {
				funcs.emplace_back(str);
			}
			lua_pop(m_L, 1);
		}
		lua_pop(m_L, 1);
	}
	lua_pop(m_L, 1);
	return funcs;
}