/* tolua: functions to map features
** Support code for Lua bindings.
** Written by Waldemar Celes
** TeCGraf/PUC-Rio
** Apr 2003
** $Id: $
*/

/* This code is free software; you can redistribute it and/or modify it.
** The software provided hereunder is on an "as is" basis, and
** the author has no obligation to provide maintenance, support, updates,
** enhancements, or modifications.
*/

#include "tolua++.h"
#include "tolua_event.h"
#include "lauxlib.h"

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

/* Create metatable
	* Create and register new metatable
*/
static int tolua_newmetatable(lua_State *L, const char *name)
{
	int r = luaL_newmetatable(L, name);

#ifdef LUA_VERSION_NUM /* only lua 5.1 */
	if (r)
	{
		lua_pushvalue(L, -1);
		lua_pushstring(L, name);
		lua_settable(L, LUA_REGISTRYINDEX); /* reg[mt] = type_name */
	}
#endif

	if (r)
		tolua_classevents(L); /* set meta events */
	lua_pop(L, 1);
	return r;
}

/* Map super classes
	* It sets 'name' as being also a 'base', mapping all super classes of 'base' in 'name'
*/
static void mapsuper(lua_State *L, const char *name, const char *base)
{
	/* push registry.super */
	lua_pushstring(L, "tolua_super");
	lua_rawget(L, LUA_REGISTRYINDEX); /* stack: super */
	luaL_getmetatable(L, name);				/* stack: super mt */
	lua_rawget(L, -2);								/* stack: super table */
	if (lua_isnil(L, -1))
	{
		/* create table */
		lua_pop(L, 1);
		lua_newtable(L);						/* stack: super table */
		luaL_getmetatable(L, name); /* stack: super table mt */
		lua_pushvalue(L, -2);				/* stack: super table mt table */
		lua_rawset(L, -4);					/* stack: super table */
	}

	/* set base as super class */
	lua_pushstring(L, base);
	lua_pushboolean(L, 1);
	lua_rawset(L, -3); /* stack: super table */

	/* set all super class of base as super class of name */
	luaL_getmetatable(L, base); /* stack: super table base_mt */
	lua_rawget(L, -3);					/* stack: super table base_table */
	if (lua_istable(L, -1))
	{
		/* traverse base table */
		lua_pushnil(L); /* first key */
		while (lua_next(L, -2) != 0)
		{
			/* stack: ... base_table key value */
			lua_pushvalue(L, -2); /* stack: ... base_table key value key */
			lua_insert(L, -2);		/* stack: ... base_table key key value */
			lua_rawset(L, -5);		/* stack: ... base_table key */
		}
	}
	lua_pop(L, 3); /* stack: <empty> */
}

/* creates a 'tolua_ubox' table for base clases, and
// expects the metatable and base metatable on the stack */
static void set_ubox(lua_State *L)
{

	/* mt basemt */
	if (!lua_isnil(L, -1))
	{
		lua_pushstring(L, "tolua_ubox");
		lua_rawget(L, -2);
	}
	else
	{
		lua_pushnil(L);
	}
	/* mt basemt base_ubox */
	if (!lua_isnil(L, -1))
	{
		lua_pushstring(L, "tolua_ubox");
		lua_insert(L, -2);
		/* mt basemt key ubox */
		lua_rawset(L, -4);
		/* (mt with ubox) basemt */
	}
	else
	{
		/* mt basemt nil */
		lua_pop(L, 1);
		lua_pushstring(L, "tolua_ubox");
		lua_newtable(L);
		/* make weak value metatable for ubox table to allow userdata to be
		garbage-collected */
		lua_newtable(L);
		lua_pushliteral(L, "__mode");
		lua_pushliteral(L, "v");
		lua_rawset(L, -3);			 /* stack: string ubox mt */
		lua_setmetatable(L, -2); /* stack:mt basemt string ubox */
		lua_rawset(L, -4);
	}
}

/* Map inheritance
	* It sets 'name' as derived from 'base' by setting 'base' as metatable of 'name'
*/
static void mapinheritance(lua_State *L, const char *name, const char *base)
{
	/* set metatable inheritance */
	luaL_getmetatable(L, name);

	if (base && *base)
		luaL_getmetatable(L, base);
	else
	{

		if (lua_getmetatable(L, -1))
		{ /* already has a mt, we don't overwrite it */
			lua_pop(L, 2);
			return;
		}
		luaL_getmetatable(L, "tolua_commonclass");
	}

	set_ubox(L);

	lua_setmetatable(L, -2);
	lua_pop(L, 1);
}

/* Object type
*/
static int tolua_bnd_type(lua_State *L)
{
	tolua_typename(L, lua_gettop(L));
	return 1;
}

/* Take ownership
*/
static int tolua_bnd_takeownership(lua_State *L)
{
	int success = 0;
	if (lua_isuserdata(L, 1))
	{
		if (lua_getmetatable(L, 1)) /* if metatable? */
		{
			lua_pop(L, 1); /* clear metatable off stack */
/* force garbage collection to avoid C to reuse a to-be-collected address */
#ifdef LUA_VERSION_NUM
			lua_gc(L, LUA_GCCOLLECT, 0);
#else
			lua_setgcthreshold(L, 0);
#endif

			success = tolua_register_gc(L, 1);
		}
	}
	lua_pushboolean(L, success != 0);
	return 1;
}

/* Release ownership
*/
static int tolua_bnd_releaseownership(lua_State *L)
{
	int done = 0;
	if (lua_isuserdata(L, 1))
	{
		void *u = *((void **)lua_touserdata(L, 1));
/* force garbage collection to avoid releasing a to-be-collected address */
#ifdef LUA_VERSION_NUM
		lua_gc(L, LUA_GCCOLLECT, 0);
#else
		lua_setgcthreshold(L, 0);
#endif
		lua_pushstring(L, "tolua_gc");
		lua_rawget(L, LUA_REGISTRYINDEX);
		lua_pushlightuserdata(L, u);
		lua_rawget(L, -2);
		lua_getmetatable(L, 1);
		if (lua_rawequal(L, -1, -2)) /* check that we are releasing the correct type */
		{
			lua_pushlightuserdata(L, u);
			lua_pushnil(L);
			lua_rawset(L, -5);
			done = 1;
		}
	}
	lua_pushboolean(L, done != 0);
	return 1;
}

/* Type casting
*/
static int tolua_bnd_cast(lua_State *L)
{

	/* // old code
        void* v = tolua_tousertype(L,1,NULL);
        const char* s = tolua_tostring(L,2,NULL);
        if (v && s)
         tolua_pushusertype(L,v,s);
        else
         lua_pushnil(L);
        return 1;
*/

	void *v;
	const char *s;
	if (lua_islightuserdata(L, 1))
	{
		v = tolua_touserdata(L, 1, NULL);
	}
	else
	{
		v = tolua_tousertype(L, 1, 0);
	}

	s = tolua_tostring(L, 2, NULL);
	if (v && s)
		tolua_pushusertype(L, v, s);
	else
		lua_pushnil(L);
	return 1;
}

/* Inheritance
*/
static int tolua_bnd_inherit(lua_State *L)
{

	/* stack: lua object, c object */
	lua_pushstring(L, ".c_instance");
	lua_pushvalue(L, -2);
	lua_rawset(L, -4);
	/* l_obj[".c_instance"] = c_obj */

	return 0;
}

#ifdef LUA_VERSION_NUM /* lua 5.1 */
static int tolua_bnd_setpeer(lua_State *L)
{

	/* stack: userdata, table */
	if (!lua_isuserdata(L, -2))
	{
		lua_pushstring(L, "Invalid argument #1 to setpeer: userdata expected.");
		lua_error(L);
	}

	if (lua_isnil(L, -1))
	{

		lua_pop(L, 1);
		lua_pushvalue(L, TOLUA_NOPEER);
	}
#if LUA_VERSION_NUM > 501
	lua_setuservalue(L, -2);
#else
	lua_setfenv(L, -2);
#endif

	return 0;
}

static int tolua_bnd_getpeer(lua_State *L)
{

	/* stack: userdata */
#if LUA_VERSION_NUM > 501
	lua_getuservalue(L, -1);
#else
	lua_getfenv(L, -1);
#endif
	if (lua_rawequal(L, -1, TOLUA_NOPEER))
	{
		lua_pop(L, 1);
		lua_pushnil(L);
	}
	return 1;
}
#endif

/* static int class_gc_event (lua_State* L); */

TOLUA_API void tolua_open(lua_State *L)
{
	int top = lua_gettop(L);
	lua_pushstring(L, "tolua_opened");
	lua_rawget(L, LUA_REGISTRYINDEX);
	if (!lua_isboolean(L, -1))
	{
		lua_pushstring(L, "tolua_opened");
		lua_pushboolean(L, 1);
		lua_rawset(L, LUA_REGISTRYINDEX);

#ifndef LUA_VERSION_NUM /* only prior to lua 5.1 */
		/* create peer object table */
		lua_pushstring(L, "tolua_peers");
		lua_newtable(L);
		/* make weak key metatable for peers indexed by userdata object */
		lua_newtable(L);
		lua_pushliteral(L, "__mode");
		lua_pushliteral(L, "k");
		lua_rawset(L, -3);			 /* stack: string peers mt */
		lua_setmetatable(L, -2); /* stack: string peers */
		lua_rawset(L, LUA_REGISTRYINDEX);
#endif

		/* create object ptr -> udata mapping table */
		lua_pushstring(L, "tolua_ubox");
		lua_newtable(L);
		/* make weak value metatable for ubox table to allow userdata to be
     garbage-collected */
		lua_newtable(L);
		lua_pushliteral(L, "__mode");
		lua_pushliteral(L, "v");
		lua_rawset(L, -3);			 /* stack: string ubox mt */
		lua_setmetatable(L, -2); /* stack: string ubox */
		lua_rawset(L, LUA_REGISTRYINDEX);

		lua_pushstring(L, "tolua_super");
		lua_newtable(L);
		lua_rawset(L, LUA_REGISTRYINDEX);
		lua_pushstring(L, "tolua_gc");
		lua_newtable(L);
		lua_rawset(L, LUA_REGISTRYINDEX);

		/* create gc_event closure */
		lua_pushstring(L, "tolua_gc_event");
		lua_pushstring(L, "tolua_gc");
		lua_rawget(L, LUA_REGISTRYINDEX);
		lua_pushstring(L, "tolua_super");
		lua_rawget(L, LUA_REGISTRYINDEX);
		lua_pushcclosure(L, class_gc_event, 2);
		lua_rawset(L, LUA_REGISTRYINDEX);

		tolua_newmetatable(L, "tolua_commonclass");

		tolua_module(L, NULL, 0);
		tolua_beginmodule(L, NULL);
		tolua_module(L, "tolua", 0);
		tolua_beginmodule(L, "tolua");
		tolua_function(L, "type", tolua_bnd_type);
		tolua_function(L, "takeownership", tolua_bnd_takeownership);
		tolua_function(L, "releaseownership", tolua_bnd_releaseownership);
		tolua_function(L, "cast", tolua_bnd_cast);
		tolua_function(L, "inherit", tolua_bnd_inherit);
#ifdef LUA_VERSION_NUM /* lua 5.1 */
		tolua_function(L, "setpeer", tolua_bnd_setpeer);
		tolua_function(L, "getpeer", tolua_bnd_getpeer);
#endif

		tolua_endmodule(L);
		tolua_endmodule(L);
	}
	lua_settop(L, top);
}

/* Copy a C object
*/
TOLUA_API void *tolua_copy(lua_State *L, void *value, unsigned int size)
{
	void *clone = (void *)malloc(size);
	if (clone)
		memcpy(clone, value, size);
	else
		tolua_error(L, "insuficient memory", NULL);
	return clone;
}

/* Default collect function
*/
TOLUA_API int tolua_default_collect(lua_State *tolua_S)
{
	void *self = tolua_tousertype(tolua_S, 1, 0);
	free(self);
	return 0;
}

/* Do clone
*/
TOLUA_API int tolua_register_gc(lua_State *L, int lo)
{
	int success = 1;
	void *value = *(void **)lua_touserdata(L, lo);
	lua_pushstring(L, "tolua_gc");
	lua_rawget(L, LUA_REGISTRYINDEX);
	lua_pushlightuserdata(L, value);
	lua_rawget(L, -2);
	if (!lua_isnil(L, -1)) /* make sure that object is not already owned */
		success = 0;
	else
	{
		lua_pushlightuserdata(L, value);
		lua_getmetatable(L, lo);
		lua_rawset(L, -4);
	}
	lua_pop(L, 2);
	return success;
}

/* Register a usertype
	* It creates the correspoding metatable in the registry, for both 'type' and 'const type'.
	* It maps 'const type' as being also a 'type'
*/
TOLUA_API void tolua_usertype(lua_State *L, const char *type)
{
	char ctype[128] = "const ";
	strncat(ctype, type, 120);

	/* create both metatables */
	if (tolua_newmetatable(L, ctype) && tolua_newmetatable(L, type))
		mapsuper(L, type, ctype); /* 'type' is also a 'const type' */
}

/* Begin module
	* It pushes the module (or class) table on the stack
*/
TOLUA_API void tolua_beginmodule(lua_State *L, const char *name)
{
	if (name)
	{
		lua_pushstring(L, name);
		lua_rawget(L, -2);
	}
	else
#if LUA_VERSION_NUM > 501
		lua_pushglobaltable(L);
#else
		lua_pushvalue(L, LUA_GLOBALSINDEX);
#endif
}

/* End module
	* It pops the module (or class) from the stack
*/
TOLUA_API void tolua_endmodule(lua_State *L)
{
	lua_pop(L, 1);
}

/* Map module
	* It creates a new module
*/
#if 1
TOLUA_API void tolua_module(lua_State *L, const char *name, int hasvar)
{
	if (name)
	{
		/* tolua module */
		lua_pushstring(L, name);
		lua_rawget(L, -2);
		if (!lua_istable(L, -1)) /* check if module already exists */
		{
			lua_pop(L, 1);
			lua_newtable(L);
			lua_pushstring(L, name);
			lua_pushvalue(L, -2);
			lua_rawset(L, -4); /* assing module into module */
		}
	}
	else
	{
		/* global table */
#if LUA_VERSION_NUM > 501
		lua_pushglobaltable(L);
#else
		lua_pushvalue(L, LUA_GLOBALSINDEX);
#endif
	}
	if (hasvar)
	{
		if (!tolua_ismodulemetatable(L)) /* check if it already has a module metatable */
		{
			/* create metatable to get/set C/C++ variable */
			lua_newtable(L);
			tolua_moduleevents(L);
			if (lua_getmetatable(L, -2))
				lua_setmetatable(L, -2); /* set old metatable as metatable of metatable */
			lua_setmetatable(L, -2);
		}
	}
	lua_pop(L, 1); /* pop module */
}
#else
TOLUA_API void tolua_module(lua_State *L, const char *name, int hasvar)
{
	if (name)
	{
		/* tolua module */
		lua_pushstring(L, name);
		lua_newtable(L);
	}
	else
	{
		/* global table */
#if LUA_VERSION_NUM > 501
		lua_pushglobaltable(L);
#else
		lua_pushvalue(L, LUA_GLOBALSINDEX);
#endif
	}
	if (hasvar)
	{
		/* create metatable to get/set C/C++ variable */
		lua_newtable(L);
		tolua_moduleevents(L);
		if (lua_getmetatable(L, -2))
			lua_setmetatable(L, -2); /* set old metatable as metatable of metatable */
		lua_setmetatable(L, -2);
	}
	if (name)
		lua_rawset(L, -3); /* assing module into module */
	else
		lua_pop(L, 1); /* pop global table */
}
#endif

static void push_collector(lua_State *L, const char *type, lua_CFunction col)
{

	/* push collector function, but only if it's not NULL, or if there's no
	   collector already */
	if (!col)
		return;
	luaL_getmetatable(L, type);
	lua_pushstring(L, ".collector");
	/*
	if (!col) {
		lua_pushvalue(L, -1);
		lua_rawget(L, -3);
		if (!lua_isnil(L, -1)) {
			lua_pop(L, 3);
			return;
		}
		lua_pop(L, 1);
	}
	//	*/
	lua_pushcfunction(L, col);

	lua_rawset(L, -3);
	lua_pop(L, 1);
}

/* Map C class
	* It maps a C class, setting the appropriate inheritance and super classes.
*/
TOLUA_API void tolua_cclass(lua_State *L, const char *lname, const char *name, const char *base, lua_CFunction col)
{
	char cname[128] = "const ";
	char cbase[128] = "const ";
	strncat(cname, name, 120);
	strncat(cbase, base, 120);

	mapinheritance(L, name, base);
	mapinheritance(L, cname, name);

	mapsuper(L, cname, cbase);
	mapsuper(L, name, base);

	lua_pushstring(L, lname);

	push_collector(L, name, col);
	/*
	luaL_getmetatable(L,name);
	lua_pushstring(L,".collector");
	lua_pushcfunction(L,col);

	lua_rawset(L,-3);
	*/

	luaL_getmetatable(L, name);
	lua_rawset(L, -3); /* assign class metatable to module */

	/* now we also need to store the collector table for the const
	   instances of the class */
	push_collector(L, cname, col);
	/*
	luaL_getmetatable(L,cname);
	lua_pushstring(L,".collector");
	lua_pushcfunction(L,col);
	lua_rawset(L,-3);
	lua_pop(L,1);
	*/
}

/* Add base
	* It adds additional base classes to a class (for multiple inheritance)
	* (not for now)
TOLUA_API void tolua_addbase(lua_State* L, char* name, char* base) {

	char cname[128] = "const ";
	char cbase[128] = "const ";
	strncat(cname,name,120);
	strncat(cbase,base,120);

	mapsuper(L,cname,cbase);
	mapsuper(L,name,base);
}
*/

/* Map function
	* It assigns a function into the current module (or class)
*/
TOLUA_API void tolua_function(lua_State *L, const char *name, lua_CFunction func)
{
	lua_pushstring(L, name);
	lua_pushcfunction(L, func);
	lua_rawset(L, -3);
}

/* sets the __call event for the class (expects the class' main table on top) */
/*	never really worked :(
TOLUA_API void tolua_set_call_event(lua_State* L, lua_CFunction func, char* type) {

	lua_getmetatable(L, -1);
	//luaL_getmetatable(L, type);
	lua_pushstring(L,"__call");
	lua_pushcfunction(L,func);
	lua_rawset(L,-3);
	lua_pop(L, 1);
}
*/

/* Map constant number
	* It assigns a constant number into the current module (or class)
*/
TOLUA_API void tolua_constant(lua_State *L, const char *name, lua_Number value)
{
	lua_pushstring(L, name);
	tolua_pushnumber(L, value);
	lua_rawset(L, -3);
}

/* Map variable
	* It assigns a variable into the current module (or class)
*/
TOLUA_API void tolua_variable(lua_State *L, const char *name, lua_CFunction get, lua_CFunction set)
{
	/* get func */
	lua_pushstring(L, ".get");
	lua_rawget(L, -2);
	if (!lua_istable(L, -1))
	{
		/* create .get table, leaving it at the top */
		lua_pop(L, 1);
		lua_newtable(L);
		lua_pushstring(L, ".get");
		lua_pushvalue(L, -2);
		lua_rawset(L, -4);
	}
	lua_pushstring(L, name);
	lua_pushcfunction(L, get);
	lua_rawset(L, -3); /* store variable */
	lua_pop(L, 1);		 /* pop .get table */

	/* set func */
	if (set)
	{
		lua_pushstring(L, ".set");
		lua_rawget(L, -2);
		if (!lua_istable(L, -1))
		{
			/* create .set table, leaving it at the top */
			lua_pop(L, 1);
			lua_newtable(L);
			lua_pushstring(L, ".set");
			lua_pushvalue(L, -2);
			lua_rawset(L, -4);
		}
		lua_pushstring(L, name);
		lua_pushcfunction(L, set);
		lua_rawset(L, -3); /* store variable */
		lua_pop(L, 1);		 /* pop .set table */
	}
}

/* Access const array
	* It reports an error when trying to write into a const array
*/
static int const_array(lua_State *L)
{
	luaL_error(L, "value of const array cannot be changed");
	return 0;
}

/* Map an array
	* It assigns an array into the current module (or class)
*/
TOLUA_API void tolua_array(lua_State *L, const char *name, lua_CFunction get, lua_CFunction set)
{
	lua_pushstring(L, ".get");
	lua_rawget(L, -2);
	if (!lua_istable(L, -1))
	{
		/* create .get table, leaving it at the top */
		lua_pop(L, 1);
		lua_newtable(L);
		lua_pushstring(L, ".get");
		lua_pushvalue(L, -2);
		lua_rawset(L, -4);
	}
	lua_pushstring(L, name);

	lua_newtable(L); /* create array metatable */
	lua_pushvalue(L, -1);
	lua_setmetatable(L, -2); /* set the own table as metatable (for modules) */
	lua_pushstring(L, "__index");
	lua_pushcfunction(L, get);
	lua_rawset(L, -3);
	lua_pushstring(L, "__newindex");
	lua_pushcfunction(L, set ? set : const_array);
	lua_rawset(L, -3);

	lua_rawset(L, -3); /* store variable */
	lua_pop(L, 1);		 /* pop .get table */
}

TOLUA_API void tolua_dobuffer(lua_State *L, char *B, unsigned int size, const char *name)
{

#ifdef LUA_VERSION_NUM /* lua 5.1 */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-value"
	luaL_loadbuffer(L, B, size, name) || lua_pcall(L, 0, 0, 0);
#pragma GCC diagnostic pop
#else
	lua_dobuffer(L, B, size, name);
#endif
}
