/**
 * \file   lua_auxlib.c
 * \brief  Modifications and extensions to Lua's library functions.
 * \author Copyright (c) 2014-2017 Jason Perkins and the Premake project
 */

#include "premake.h"


static int chunk_wrapper(lua_State* L);



/* Pull in Lua's aux lib implementation, but rename luaL_loadfilex() so I
 * can replace it with my own implementation. */

#define luaL_loadfilex  original_luaL_loadfilex
#include "lauxlib.c"
#undef luaL_loadfilex



/**
 * Extend the default implementation of luaL_loadfile() to call my chunk
 * wrapper, above, before executing any scripts loaded from a file.
 */
LUALIB_API int luaL_loadfilex (lua_State* L, const char* filename, const char* mode)
{
	const char* script_dir;
	const char* test_name;

	int bottom = lua_gettop(L);
	int z = !OKAY;

	/* If filename starts with "$/" then we want to load the version that
	 * was embedded into the executable and skip the local file system */
	if (filename[0] == '$') {
		z = premake_load_embedded_script(L, filename + 2); /* Skip over leading "$/" */
		if (z != OKAY) {
			return z;
		}
	}

	/* If the currently running script was embedded, try to load this file
	 * as it if were embedded too. */
	if (z != OKAY) {
		lua_getglobal(L, "_SCRIPT_DIR");
		script_dir = lua_tostring(L, -1);

		if (script_dir && script_dir[0] == '$') {
			/* Call `path.getabsolute(filename, _SCRIPT_DIR)` to resolve any
			 * "../" sequences in the filename */
			lua_pushcfunction(L, path_getabsolute);
			lua_pushstring(L, filename);
			lua_pushvalue(L, -3);
			lua_call(L, 2, 1);
			test_name = lua_tostring(L, -1);

			/* if successful, filename and chunk will be on top of stack */
			z = premake_load_embedded_script(L, test_name + 2); /* Skip over leading "$/" */

			/* remove test_name */
			lua_remove(L, bottom + 1);
		}

		/* remove _SCRIPT_DIR */
		lua_remove(L, bottom);
	}

	/* Try to locate the script on the filesystem */
	if (z != OKAY) {
		lua_pushcfunction(L, os_locate);
		lua_pushstring(L, filename);
		lua_call(L, 1, 1);

		test_name = lua_tostring(L, -1);

		if (test_name) {
			z = original_luaL_loadfilex(L, test_name, mode);
		}

		/* If the file exists but errors, pass that through */
		if (test_name && z != OKAY && z != LUA_ERRFILE) {
			return z;
		}

		/* If the file didn't exist, remove the result and the test
		 * name from the stack before checking embedded scripts */
		if (z != OKAY) {
			lua_pop(L, 1);
		}
	}

	/* Try to load from embedded scripts */
	if (z != OKAY) {
		z = premake_load_embedded_script(L, filename);
	}

	/* Either way I should have ended up with the file name followed by the
	 * script chunk on the stack. Turn these into a closure that will call my
	 * wrapper below when the loaded script needs to be executed. */
	if (z == OKAY) {
		lua_pushcclosure(L, chunk_wrapper, 2);
	}
	else if (z == LUA_ERRFILE) {
		lua_pushfstring(L, "cannot open %s: No such file or directory", filename);
	}

	return z;
}



/**
 * Execute a chunk of code previously loaded by my customized version of
 * luaL_loadfile(), below. Sets the _SCRIPT global variable to the absolute
 * path of the loaded chunk, and makes its enclosing directory current so
 * that relative path references to other files or scripts can be used.
 */

static int chunk_wrapper(lua_State* L)
{
	char cwd[PATH_MAX];
	const char* filename;
	char* ptr;
	int i, args;

	args = lua_gettop(L);

	/* Remember the current _SCRIPT and working directory so I can
	 * restore them after this new chunk has been run. */

	do_getcwd(cwd, PATH_MAX);
	lua_getglobal(L, "_SCRIPT");
	lua_getglobal(L, "_SCRIPT_DIR");

	/* Set the new _SCRIPT variable */

	lua_pushvalue(L, lua_upvalueindex(1));
	lua_setglobal(L, "_SCRIPT");

	/* And the new _SCRIPT_DIR variable (const cheating) */

	filename = lua_tostring(L, lua_upvalueindex(1));
	ptr = strrchr(filename, '/');
	if (ptr) *ptr = '\0';
	lua_pushlstring(L, filename, strlen(filename));
	lua_setglobal(L, "_SCRIPT_DIR");

	/* And make that the CWD (and fix the const cheat) */

	if (filename[0] != '$') {
		do_chdir(L, filename);
	}
	if (ptr) *ptr = '/';

	/* Move the function's arguments to the top of the stack and
	 * execute the function created by luaL_loadfile() */

	lua_pushvalue(L, lua_upvalueindex(2));
	for (i = 1; i <= args; ++i) {
		lua_pushvalue(L, i);
	}

	lua_call(L, args, LUA_MULTRET);

	/* Finally, restore the previous _SCRIPT variable and working directory
	 * before returning control to the previously executing script. */

	do_chdir(L, cwd);
	lua_pushvalue(L, args + 1);
	lua_setglobal(L, "_SCRIPT");
	lua_pushvalue(L, args + 2);
	lua_setglobal(L, "_SCRIPT_DIR");

	return lua_gettop(L) - args - 2;
}
