#include "main.h"
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include "font.h"
#include "gfx.h"
#ifndef DEDICATED
#include "lib_pbm.h"
#include "lib_surface.h"
#include "lib_screen.h"
#include "lib_pixelmap.h"
#include "lib_snd.h"
#endif
#include "lib_io.h"
#include "lib_file.h"
#include "input.h"
#include "lib_net.h"

#ifndef DEDICATED
static lua_State *cL = NULL;
#endif
static lua_State *sL = NULL;

static int luaf_include(lua_State *L)
{
	const char *name = luaL_checkstring(L,1);
	lua_pop(L,-1);
	char metaname[strlen("include:")+strlen(name)+1];
	sprintf(metaname,"include:%s",name);
	lua_getfield(L,LUA_REGISTRYINDEX,metaname);
	if (!lua_isnil(L,-1))
		return 1; // TODO: make include: a table and allow multiple return values here.
	lua_pop(L,1);
	if (luaL_dofile(L,find_file(name)))
		return fatal_error("%s: %s",(L == cL ? "Client" : "Server"),lua_tostring(L,-1)); //lua_error(L);
	lua_pushvalue(L,1);
	lua_setfield(L,LUA_REGISTRYINDEX,metaname);
	return lua_gettop(L); // returns all return values
}

static lua_State *NewState(bool client)
{
	lua_State *L = luaL_newstate();
	luaopen_base(L);
	luaopen_table(L);
	luaopen_string(L);
	luaopen_math(L);
	lua_pop(L,-1);
	agllib_io(L,client);
	agllib_file(L);
	agllib_net(L,client);

	lua_pushboolean(L,client);
	lua_setglobal(L,"CLIENT");
	lua_pushboolean(L,!client);
	lua_setglobal(L,"SERVER");

	lua_pushcclosure(L,luaf_include,0);
	lua_setglobal(L,"include");

#ifndef DEDICATED
	if (client) {
		agllib_pbm(L);
		agllib_surface(L);
		agllib_screen(L);
		agllib_pixelmap(L);
		agllib_snd(L);
	}
#endif

	lua_newtable(L);
	lua_setfield(L, LUA_REGISTRYINDEX, "NetRecv");
	return L;
}

void Lua_Init(void)
{
#ifndef DEDICATED
	cL = NewState(true);

	lua_pushstring(cL,"init.lua");
	luaf_include(cL);
	lua_pop(cL,-1);

	lua_getglobal(cL,"GAME");
	if (lua_istable(cL,-1)) {
		// Window title
		lua_getfield(cL,-1,"Title");
		if (lua_isstring(cL,-1))
			SDL_WM_SetCaption(lua_tostring(cL,-1),NULL);
		lua_pop(cL,1);

		// TextBox font
		/*lua_getfield(cL,-1,"Font");
		if (lua_isstring(cL,-1)) {
			const char *name = lua_tostring(cL,-1);
			char *path = get_font(name);
			// TODO: free previous loaded fonts?
			smallfont = TTF_OpenFont(path,12);
			largefont = TTF_OpenFont(path,16);
			if (path != name)
				free(path);
		}
		lua_pop(cL,1);*/

		// Set the metatable for later use
		/*lua_newtable(cL);
		lua_pushvalue(cL,-1);
		lua_setfield(cL,-2,"__index");
		luaL_register(cL,NULL,lua_modlib);
		lua_setmetatable(cL, -2);*/
	}
	lua_setfield(cL, LUA_REGISTRYINDEX, "GAME");

	lua_getfield(cL, LUA_REGISTRYINDEX, "GAME");
	if (lua_istable(cL,-1)) {
		// Run the Init function.
		lua_getfield(cL,-1,"Init");
		lua_pushvalue(cL,-2);
		if (lua_pcall(cL,1,0,0))
			fatal_error("Client: %s",lua_tostring(cL,-1));
	}
	lua_pop(cL,1);
#endif

	sL = NewState(false);

	lua_pushstring(sL,"init.lua");
	luaf_include(sL);
	lua_pop(sL,-1);

	lua_getglobal(sL,"GAME");
	lua_setfield(sL, LUA_REGISTRYINDEX, "GAME");

	lua_getfield(sL, LUA_REGISTRYINDEX, "GAME");
	if (lua_istable(sL,-1)) {
		// Run the Init function.
		lua_getfield(sL,-1,"Init");
		lua_pushvalue(sL,-2);
		if (lua_pcall(sL,1,0,0))
			fatal_error("Server: %s",lua_tostring(sL,-1));
	}
	lua_pop(sL,1);
}

void Lua_Close(void)
{
#ifdef DEDICATED
	if (sL)
		lua_close(sL);
	sL = NULL;
#else
	if (cL)
		lua_close(cL);
	if (sL)
		lua_close(sL);
	cL = sL = NULL;
#endif
}

void Lua_NetRecv(Sint16 uid, const char *msg, size_t len)
{
	lua_State *L;
	if (uid == -1)
		L = cL;
	else
		L = sL;

	lua_getfield(L,LUA_REGISTRYINDEX,"NetRecv");
	lua_pushinteger(L,lua_objlen(L,-1)+1);
	if (uid > -1) {
		lua_newtable(L);
		lua_pushinteger(L,uid);
		lua_setfield(L,-2,"uid");
		lua_pushlstring(L,msg,len);
		lua_setfield(L,-2,"msg");
	} else
		lua_pushlstring(L,msg,len);
	lua_settable(L,-3);
	lua_pop(L,1);
}

static void think(lua_State *L)
{
	lua_getfield(L, LUA_REGISTRYINDEX, "GAME");
	if (lua_istable(L,-1)) {
		lua_getfield(L,LUA_REGISTRYINDEX,"NetRecv"); // REGISTRY.NetRecv
		unsigned int i,n = lua_objlen(L,-1);
		if (n) {
			lua_getfield(L,-2,"NetRecv"); // MOD.NetRecv
			for (i = 1; i <= n; i++) {
				lua_pushvalue(L,-1); // MOD.NetRecv
				lua_pushvalue(L,-4); // MOD
				if (L == sL) {
					lua_pushinteger(L,i);
					lua_gettable(L,-5); // REGISTRY.NetRecv[i]
					lua_getfield(L,-1,"uid");
					lua_getfield(L,-2,"msg");
					lua_remove(L,-3);
					if (lua_pcall(L,3,0,0)) // MOD.NetRecv(MOD,REGISTRY.NetRecv[i].uid,REGISTRY.NetRecv[i].msg)
						fatal_error("Server: %s",lua_tostring(L,-1));
				} else {
					lua_pushinteger(L,i);
					lua_gettable(L,-5); // REGISTRY.NetRecv[i]
					if (lua_pcall(L,2,0,0)) // MOD.NetRecv(MOD,REGISTRY.NetRecv[i])
						fatal_error("Client: %s",lua_tostring(L,-1));
				}
			}
			lua_pop(L,1);
		}
		lua_pop(L,1);
		lua_newtable(L);
		lua_setfield(L,LUA_REGISTRYINDEX,"NetRecv");

		// Run the Think function.
		lua_getfield(L,-1,"Think");
		lua_pushvalue(L,-2);
		if (lua_pcall(L,1,0,0))
			fatal_error("%s: %s",(L == cL ? "Client" : "Server"),lua_tostring(L,-1));
	}
	lua_pop(L,1);
	lua_gc(L,LUA_GCCOLLECT,0);
}

void Lua_FPS(float fps)
{
	lua_pushnumber(cL,fps);
	lua_setglobal(cL,"FPS");
}

void Lua_Think(void)
{
#ifndef DEDICATED
	lua_getglobal(cL,"input");
	if (lua_istable(cL,-1)) {
		lua_pushinteger(cL,buttons);
		lua_setfield(cL,-2,"buttons");
		lua_pushinteger(cL,btn_start);
		lua_setfield(cL,-2,"btn_start");
		lua_pushinteger(cL,btn_end);
		lua_setfield(cL,-2,"btn_end");
		lua_newtable(cL);
		int i;
		for (i = 0; i < BTN_MAX; i++) {
			lua_pushinteger(cL,i);
			lua_pushinteger(cL,button_time[i]);
			lua_settable(cL,-3);
		}
		lua_setfield(cL,-2,"btn_time");
	}
	lua_pop(cL,1);

	think(cL);
#endif

	if (sL)
		think(sL);
}

#ifndef DEDICATED
void Lua_Draw(void)
{
	lua_getfield(cL, LUA_REGISTRYINDEX, "GAME");
	if (lua_istable(cL,-1)) {
		// Run the Draw function.
		lua_getfield(cL,-1,"Draw");
		lua_pushvalue(cL,-2);
		luaL_getmetatable(cL,"screen");
		if (lua_pcall(cL,2,0,0))
			fatal_error("Client: %s",lua_tostring(cL,-1));
	}
	lua_pop(cL,1);
}
#endif
