/******************************************************************************
	Project: 	MicroMacro
	Author: 	SolarStrike Software
	URL:		www.solarstrike.net
	License:	Modified BSD (see license.txt)
******************************************************************************/

#include "class_lua.h"
#include "error.h"
#include "strl.h"
#include "vector3d_lua.h"
#include "types.h"

extern "C"
{
	#include <lua.h>
	#include <lauxlib.h>
	#include <lualib.h>
}


int Class_lua::regmod(lua_State *L)
{
	static const luaL_Reg _funcs[] = {
		{"new", Class_lua::_new},
		{"vector3d", Class_lua::vector3d},
		{NULL, NULL}
	};

	luaL_newlib(L, _funcs);
	lua_setglobal(L, CLASS_MODULE_NAME);

	return MicroMacro::ERR_OK;
}


/*	class.new([table baseclass])
	Returns:	table (new class)

	Create a new class. If baseclass is given, creates a child of it,
	and set the 'parent' variable to its base.

	This will *not* call the constructor. That is what the __call
	operator on the returned class is for: to create an instance of the class.
*/
int Class_lua::_new(lua_State *L)
{
	static const luaL_Reg class_meta[] = {
		{"__call", __call},
		{"__tostring", __tostring},
		{NULL, NULL}
	};

	int top = lua_gettop(L);
	if( top != 0 && top != 1 )
		wrongArgs(L);

	// Basic class setup
	lua_newtable(L);
	int newtab_index = lua_gettop(L);

	if( top == 0 ) // Create a base class
	{
		lua_newtable(L);
		luaL_setfuncs(L, class_meta, 0);

		lua_pushvalue(L, newtab_index);
		lua_setfield(L, newtab_index, "__index");

		lua_setmetatable(L, newtab_index);

		lua_pushstring(L, "__call");
		lua_pushcfunction(L, Class_lua::__call);
		lua_settable(L, -3);

		lua_pushstring(L, "is_a");
		lua_pushcfunction(L, Class_lua::is_a);
		lua_settable(L, -3);
	}
	else if( top == 1 ) // Create a subclass
	{
		// Make sure we've got a table, or bad things will happen.
		checkType(L, LT_TABLE, 1);

		// Copy methods & variables
		lua_pushnil(L);
		while( lua_next(L, 1) )
		{
			lua_pushvalue(L, -2); // Make a copy of key for next iteration
			lua_insert(L, -2); // Move our copy down the stack so it remains after the following
			lua_settable(L, newtab_index);
		}

		lua_newtable(L);
		luaL_setfuncs(L, class_meta, 0);

		lua_pushvalue(L, newtab_index);
		lua_setfield(L, newtab_index, "__index");

		lua_setmetatable(L, newtab_index);

		lua_pushstring(L, "is_a");
		lua_pushcfunction(L, Class_lua::is_a);
		lua_settable(L, -3);

		// Set parent
		lua_pushvalue(L, 1); // Copy table 1
		lua_setfield(L, newtab_index, "parent");
	}

	return 1;
}

/*	class operator (...)
	Returns:	table [class]

	This creates an instance of the given class, sets parent,
	and calls its constructor function with the given parameters
*/
int Class_lua::__call(lua_State *L)
{
	int top = lua_gettop(L);
	int ctor_args = top - 1;
	checkType(L, LT_TABLE, 1);

	// Create a new table
	lua_newtable(L);
	int newtab_index = lua_gettop(L);

	// Copy methods & variables
	lua_pushnil(L);
	while( lua_next(L, 1) )
	{
		lua_pushvalue(L, -2); // Make a copy of key for next iteration
		lua_insert(L, -2); // Move our copy down the stack so it remains after the following
		lua_settable(L, newtab_index);
	}

	// Copy metatable
	lua_getmetatable(L, 1);
	lua_setmetatable(L, -2);

	// Set parent
	lua_pushvalue(L, 1); // Copy table 1
	lua_setfield(L, newtab_index, "parent");

	// Move newtab to index 2; NOTE: newtab_index is now invalid!
	lua_insert(L, 2);


	// If it has a constructor... run it.
	lua_getfield(L, 2, "constructor");
	if( lua_isfunction(L, -1) )
	{
		lua_insert(L, 3); // Move to index 3

		// Create a copy of our index (self), move it to 4
		lua_pushvalue(L, 2);
		lua_insert(L, 4);

		// And run it!
		if( lua_pcall(L, ctor_args+1, 0, 0) != LUA_OK )
		{ // Uh oh... something bad happened. Report it.
			luaL_error(L, lua_tostring(L, -1));
		}
	}
	else
		lua_pop(L, 1); // Pop it if we can't run it

	//lua_pop(L, lua_gettop(L)-2); // For debugging; remove all but the bottom 2

	return 1;
}


/*	class __tostring() metamethod
	Returns:	string (Class: 0x%p)

	This is the default "tostring" metamethod for classes
	that do not overload it in their metatables.
	Nothing fancy here.
*/
int Class_lua::__tostring(lua_State *L)
{
	checkType(L, LT_TABLE, 1);
	char buffer[32];

	slprintf(buffer, sizeof(buffer)-1, "Class: 0x%p", lua_topointer(L, 1));
	lua_pushstring(L, buffer);
	return 1;
}


/*	class.vector3d([number x, number y, number z])
	Returns:	table (class)

	Create a new table (class) of vector3d.
	If x, y and z are given, the new vector3d retains
	the given values.

	The vector3d class contains metamethods for
	operations such as vector scaling and dot product.
*/
int Class_lua::vector3d(lua_State *L)
{
	int top = lua_gettop(L);
	if( top != 0 && top !=2 && top != 3 )
		wrongArgs(L);

	MicroMacro::Vector3d vec(0.0, 0.0, 0.0);
	if( top == 3 )
	{
		checkType(L, LT_NUMBER, 1);
		checkType(L, LT_NUMBER, 2);
		checkType(L, LT_NUMBER, 3);
		vec.x = lua_tonumber(L, 1);
		vec.y = lua_tonumber(L, 2);
		vec.z = lua_tonumber(L, 3);
	}
	else if( top == 2 )
	{
		checkType(L, LT_NUMBER, 1);
		checkType(L, LT_NUMBER, 2);
		vec.x = lua_tonumber(L, 1);
		vec.y = lua_tonumber(L, 2);
		vec.z = 0;
	}

	lua_pushvector3d(L, vec);
	return 1;
}


/*	class.is_a(table BaseClass)
	Returns:	boolean

	Checks if this class is an instance of, or child of
	the given base class.
*/
int Class_lua::is_a(lua_State *L)
{
	int top = lua_gettop(L);
	if( top != 2 )
		wrongArgs(L);

	const void *comparisonPointer	=	lua_topointer(L, 2);
	lua_pop(L, 1); // Pop the comparisonPointer off now so that our main class is on top of the stack.

	// Check if they are the same before checking parents
	if( lua_topointer(L, 1) == comparisonPointer )
	{
		lua_pushboolean(L, true);
		return 1;
	}

	lua_getfield(L, 1, "parent");
	lua_pop(L, 1);

	bool foundParent = false;
	int origStackCount = lua_gettop(L);
	while(true)
	{
		// Check if it even has a parent
		lua_getfield(L, -1, "parent");
		if( lua_isnil(L, -1) || !lua_istable(L, -1) )
		{
			break;
		}
		else
		{	// Check if the parent matches our comparison
			if( lua_topointer(L, -1) == comparisonPointer )
			{
				foundParent = true;
				break;
			}
		}
	}

	// Pop any extra junk we pushed onto the stack
	int newStackCount = lua_gettop(L);
	lua_pop(L, newStackCount - origStackCount);
	lua_pushboolean(L, foundParent);
	return 1;
}
