/* tolua: functions to check types.
** 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 "lauxlib.h"

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

/* a fast check if a is b, without parameter validation
 i.e. if b is equal to a or a superclass of a. */
TOLUA_API int tolua_fast_isa(lua_State *L, int mt_indexa, int mt_indexb, int super_index)
{
 int result;
	if (lua_rawequal(L,mt_indexa,mt_indexb))
		result = 1;
	else
	{
		if (super_index) {
			lua_pushvalue(L, super_index);
		} else {
			lua_pushliteral(L,"tolua_super");
			lua_rawget(L,LUA_REGISTRYINDEX);  /* stack: super */
		};
		lua_pushvalue(L,mt_indexa);       /* stack: super mta */
		lua_rawget(L,-2);                 /* stack: super super[mta] */
		lua_pushvalue(L,mt_indexb);       /* stack: super super[mta] mtb */
		lua_rawget(L,LUA_REGISTRYINDEX);  /* stack: super super[mta] typenameB */
		lua_rawget(L,-2);                 /* stack: super super[mta] bool */
		result = lua_toboolean(L,-1);
		lua_pop(L,3);
	}
	return result;
}

/* Push and returns the corresponding object typename */
TOLUA_API const char* tolua_typename (lua_State* L, int lo)
{
	int tag = lua_type(L,lo);
 if (tag == LUA_TNONE)
  lua_pushstring(L,"[no object]");
 else if (tag != LUA_TUSERDATA && tag != LUA_TTABLE)
  lua_pushstring(L,lua_typename(L,tag));
 else if (tag == LUA_TUSERDATA)
 {
  if (!lua_getmetatable(L,lo))
   lua_pushstring(L,lua_typename(L,tag));
		else
		{
		 lua_rawget(L,LUA_REGISTRYINDEX);
		 if (!lua_isstring(L,-1))
			{
		  lua_pop(L,1);
				lua_pushstring(L,"[undefined]");
			}
		}
	}
	else  /* is table */
	{
		lua_pushvalue(L,lo);
		lua_rawget(L,LUA_REGISTRYINDEX);
		if (!lua_isstring(L,-1))
		{
			lua_pop(L,1);
			lua_pushstring(L,"table");
		}
		else
		{
   lua_pushstring(L,"class ");
			lua_insert(L,-2);
			lua_concat(L,2);
		}
	}
	return lua_tostring(L,-1);
}

TOLUA_API void tolua_error (lua_State* L, char* msg, tolua_Error* err)
{
	if (msg[0] == '#')
	{
  const char* expected = err->type;
		const char* provided = tolua_typename(L,err->index);
  if (msg[1]=='f')
  {
   int narg = err->index;
			if (err->array)
    luaL_error(L,"%s\n     argument #%d is array of '%s'; array of '%s' expected.\n",
               msg+2,narg,provided,expected);
			else
    luaL_error(L,"%s\n     argument #%d is '%s'; '%s' expected.\n",
               msg+2,narg,provided,expected);
  }
  else if (msg[1]=='v')
		{
			if (err->array)
    luaL_error(L,"%s\n     value is array of '%s'; array of '%s' expected.\n",
               msg+2,provided,expected);
			else
    luaL_error(L,"%s\n     value is '%s'; '%s' expected.\n",
               msg+2,provided,expected);
		}
 }
 else
  luaL_error(L,msg);
}

/* the equivalent of lua_is* for usertable */
static  int lua_isusertable (lua_State* L, int lo, const char* type)
{
	int r = 0;
	if (lo < 0) lo = lua_gettop(L)+lo+1;
	lua_pushvalue(L,lo);
	lua_rawget(L,LUA_REGISTRYINDEX);  /* get registry[t] */
	if (lua_isstring(L,-1))
	{
		r = strcmp(lua_tostring(L,-1),type)==0;
		if (!r)
		{
			/* try const */
			lua_pushstring(L,"const ");
			lua_insert(L,-2);
			lua_concat(L,2);
			r = lua_isstring(L,-1) && strcmp(lua_tostring(L,-1),type)==0;
		}
	}
	lua_pop(L, 1);
	return r;
}

int push_table_instance(lua_State* L, int lo) {

	if (lua_istable(L, lo)) {

		lua_pushstring(L, ".c_instance");
		lua_gettable(L, lo);
		if (lua_isuserdata(L, -1)) {

			lua_replace(L, lo);
			return 1;
		} else {

			lua_pop(L, 1);
			return 0;
		};
	} else {
		return 0;
	};

	return 0;
};

/* the equivalent of lua_is* for usertype */
static int lua_isusertype (lua_State* L, int lo, const char* type)
{
	if (!lua_isuserdata(L,lo)) {
		if (!push_table_instance(L, lo)) {
			return 0;
		};
	};
	{
		/* check if it is of the same type */
		int r;
		const char *tn;
		if (lua_getmetatable(L,lo))        /* if metatable? */
		{
		 lua_rawget(L,LUA_REGISTRYINDEX);  /* get registry[mt] */
		 tn = lua_tostring(L,-1);
		 r = tn && (strcmp(tn,type) == 0);
		 lua_pop(L, 1);
			if (r)
			 return 1;
			else
			{
				/* check if it is a specialized class */
				lua_pushstring(L,"tolua_super");
				lua_rawget(L,LUA_REGISTRYINDEX); /* get super */
				lua_getmetatable(L,lo);
				lua_rawget(L,-2);                /* get super[mt] */
				if (lua_istable(L,-1))
				{
					int b;
					lua_pushstring(L,type);
					lua_rawget(L,-2);                /* get super[mt][type] */
					b = lua_toboolean(L,-1);
					lua_pop(L,3);
					if (b)
					 return 1;
				}
			}
		}
 }
	return 0;
}

TOLUA_API int tolua_isnoobj (lua_State* L, int lo, tolua_Error* err)
{
 if (lua_gettop(L)<abs(lo))
		return 1;
	err->index = lo;
	err->array = 0;
	err->type = "[no object]";
 return 0;
}
TOLUA_API int tolua_isvalue (lua_State* L, int lo, int def, tolua_Error* err)
{
	if (def || abs(lo)<=lua_gettop(L))  /* any valid index */
		return 1;
	err->index = lo;
	err->array = 0;
	err->type = "value";
	return 0;
}

TOLUA_API int tolua_isboolean (lua_State* L, int lo, int def, tolua_Error* err)
{
	if (def && lua_gettop(L)<abs(lo))
		return 1;
	if (lua_isnil(L,lo) || lua_isboolean(L,lo))
		return 1;
	err->index = lo;
	err->array = 0;
	err->type = "boolean";
	return 0;
}

TOLUA_API int tolua_isnumber (lua_State* L, int lo, int def, tolua_Error* err)
{
	if (def && lua_gettop(L)<abs(lo))
		return 1;
	if (lua_isnumber(L,lo))
		return 1;
	err->index = lo;
	err->array = 0;
	err->type = "number";
	return 0;
}

TOLUA_API int tolua_isstring (lua_State* L, int lo, int def, tolua_Error* err)
{
	if (def && lua_gettop(L)<abs(lo))
		return 1;
 if (lua_isnil(L,lo) || lua_isstring(L,lo))
		return 1;
	err->index = lo;
	err->array = 0;
	err->type = "string";
	return 0;
}

TOLUA_API int tolua_istable (lua_State* L, int lo, int def, tolua_Error* err)
{
	if (def && lua_gettop(L)<abs(lo))
		return 1;
	if (lua_istable(L,lo))
		return 1;
	err->index = lo;
	err->array = 0;
	err->type = "table";
	return 0;
}

TOLUA_API int tolua_isusertable (lua_State* L, int lo, const char* type, int def, tolua_Error* err)
{
	if (def && lua_gettop(L)<abs(lo))
		return 1;
	if (lua_isusertable(L,lo,type))
		return 1;
	err->index = lo;
	err->array = 0;
	err->type = type;
	return 0;
}


TOLUA_API int tolua_isuserdata (lua_State* L, int lo, int def, tolua_Error* err)
{
	if (def && lua_gettop(L)<abs(lo))
		return 1;
	if (lua_isnil(L,lo) || lua_isuserdata(L,lo))
		return 1;
	err->index = lo;
	err->array = 0;
	err->type = "userdata";
	return 0;
}

TOLUA_API int tolua_isusertype (lua_State* L, int lo, const char* type, int def, tolua_Error* err)
{
	if (def && lua_gettop(L)<abs(lo))
		return 1;
	if (lua_isnil(L,lo) || lua_isusertype(L,lo,type))
		return 1;
	err->index = lo;
	err->array = 0;
	err->type = type;
	return 0;
}

TOLUA_API int tolua_isvaluearray
 (lua_State* L, int lo, int dim, int def, tolua_Error* err)
{
	if (!tolua_istable(L,lo,def,err))
		return 0;
	else
		return 1;
}

TOLUA_API int tolua_isbooleanarray
 (lua_State* L, int lo, int dim, int def, tolua_Error* err)
{
	if (!tolua_istable(L,lo,def,err))
		return 0;
	else
	{
		int i;
		for (i=1; i<=dim; ++i)
		{
			lua_pushnumber(L,i);
			lua_gettable(L,lo);
	  if (!(lua_isnil(L,-1) || lua_isboolean(L,-1)) &&
					  !(def && lua_isnil(L,-1))
						)
			{
				err->index = lo;
				err->array = 1;
				err->type = "boolean";
				return 0;
			}
			lua_pop(L,1);
		}
 }
 return 1;
}

TOLUA_API int tolua_isnumberarray
 (lua_State* L, int lo, int dim, int def, tolua_Error* err)
{
	if (!tolua_istable(L,lo,def,err))
		return 0;
	else
	{
		int i;
		for (i=1; i<=dim; ++i)
		{
			lua_pushnumber(L,i);
			lua_gettable(L,lo);
			if (!lua_isnumber(L,-1) &&
					  !(def && lua_isnil(L,-1))
						)
			{
				err->index = lo;
				err->array = 1;
				err->type = "number";
				return 0;
			}
			lua_pop(L,1);
		}
 }
 return 1;
}

TOLUA_API int tolua_isstringarray
 (lua_State* L, int lo, int dim, int def, tolua_Error* err)
{
	if (!tolua_istable(L,lo,def,err))
		return 0;
	else
	{
		int i;
		for (i=1; i<=dim; ++i)
		{
			lua_pushnumber(L,i);
			lua_gettable(L,lo);
   if (!(lua_isnil(L,-1) || lua_isstring(L,-1)) &&
			    !(def && lua_isnil(L,-1))
						)
			{
				err->index = lo;
				err->array = 1;
				err->type = "string";
				return 0;
			}
			lua_pop(L,1);
		}
 }
 return 1;
}

TOLUA_API int tolua_istablearray
 (lua_State* L, int lo, int dim, int def, tolua_Error* err)
{
	if (!tolua_istable(L,lo,def,err))
		return 0;
	else
	{
		int i;
		for (i=1; i<=dim; ++i)
		{
			lua_pushnumber(L,i);
			lua_gettable(L,lo);
	  if (! lua_istable(L,-1) &&
			    !(def && lua_isnil(L,-1))
						)
			{
				err->index = lo;
				err->array = 1;
				err->type = "table";
				return 0;
			}
			lua_pop(L,1);
		}
 }
 return 1;
}

TOLUA_API int tolua_isuserdataarray
 (lua_State* L, int lo, int dim, int def, tolua_Error* err)
{
	if (!tolua_istable(L,lo,def,err))
		return 0;
	else
	{
		int i;
		for (i=1; i<=dim; ++i)
		{
			lua_pushnumber(L,i);
			lua_gettable(L,lo);
	  if (!(lua_isnil(L,-1) || lua_isuserdata(L,-1)) &&
			    !(def && lua_isnil(L,-1))
						)
			{
				err->index = lo;
				err->array = 1;
				err->type = "userdata";
				return 0;
			}
			lua_pop(L,1);
		}
 }
 return 1;
}

TOLUA_API int tolua_isusertypearray
 (lua_State* L, int lo, const char* type, int dim, int def, tolua_Error* err)
{
	if (!tolua_istable(L,lo,def,err))
		return 0;
	else
	{
		int i;
		for (i=1; i<=dim; ++i)
		{
			lua_pushnumber(L,i);
			lua_gettable(L,lo);
	  if (!(lua_isnil(L,-1) || lua_isuserdata(L,-1)) &&
			    !(def && lua_isnil(L,-1))
						)
			{
				err->index = lo;
				err->type = type;
				err->array = 1;
				return 0;
			}
			lua_pop(L,1);
		}
 }
 return 1;
}

#if 0
int tolua_isbooleanfield
 (lua_State* L, int lo, int i, int def, tolua_Error* err)
{
	lua_pushnumber(L,i);
	lua_gettable(L,lo);
	if (!(lua_isnil(L,-1) || lua_isboolean(L,-1)) &&
			  !(def && lua_isnil(L,-1))
				)
	{
		err->index = lo;
		err->array = 1;
		err->type = "boolean";
		return 0;
	}
	lua_pop(L,1);
 return 1;
}

int tolua_isnumberfield
 (lua_State* L, int lo, int i, int def, tolua_Error* err)
{
	lua_pushnumber(L,i);
	lua_gettable(L,lo);
	if (!lua_isnumber(L,-1) &&
			  !(def && lua_isnil(L,-1))
				)
	{
		err->index = lo;
		err->array = 1;
		err->type = "number";
		return 0;
	}
	lua_pop(L,1);
 return 1;
}

int tolua_isstringfield
 (lua_State* L, int lo, int i, int def, tolua_Error* err)
{
	lua_pushnumber(L,i);
	lua_gettable(L,lo);
 if (!(lua_isnil(L,-1) || lua_isstring(L,-1)) &&
	    !(def && lua_isnil(L,-1))
				)
	{
		err->index = lo;
		err->array = 1;
		err->type = "string";
		return 0;
	}
	lua_pop(L,1);
 return 1;
}

int tolua_istablefield
 (lua_State* L, int lo, int i, int def, tolua_Error* err)
{
	lua_pushnumber(L,i+1);
	lua_gettable(L,lo);
	if (! lua_istable(L,-1) &&
	    !(def && lua_isnil(L,-1))
				)
	{
		err->index = lo;
		err->array = 1;
		err->type = "table";
		return 0;
	}
	lua_pop(L,1);
}

int tolua_isusertablefield
 (lua_State* L, int lo, const char* type, int i, int def, tolua_Error* err)
{
	lua_pushnumber(L,i);
	lua_gettable(L,lo);
	if (! lua_isusertable(L,-1,type) &&
	    !(def && lua_isnil(L,-1))
				)
	{
		err->index = lo;
		err->array = 1;
		err->type = type;
		return 0;
	}
	lua_pop(L,1);
 return 1;
}

int tolua_isuserdatafield
 (lua_State* L, int lo, int i, int def, tolua_Error* err)
{
	lua_pushnumber(L,i);
	lua_gettable(L,lo);
	if (!(lua_isnil(L,-1) || lua_isuserdata(L,-1)) &&
	    !(def && lua_isnil(L,-1))
				)
	{
		err->index = lo;
		err->array = 1;
		err->type = "userdata";
		return 0;
	}
	lua_pop(L,1);
 return 1;
}

int tolua_isusertypefield
 (lua_State* L, int lo, const char* type, int i, int def, tolua_Error* err)
{
	lua_pushnumber(L,i);
	lua_gettable(L,lo);
	if (!(lua_isnil(L,-1) || lua_isusertype(L,-1,type)) &&
	    !(def && lua_isnil(L,-1))
				)
	{
		err->index = lo;
		err->type = type;
		err->array = 1;
		return 0;
	}
	lua_pop(L,1);
 return 1;
}

#endif
