/* 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, const 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 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_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_isvaluenil( lua_State* L, int lo, tolua_Error* err ) {
    if ( lua_gettop( L ) < abs( lo ) ) {
        return 0;    /* somebody else should chack this */
    }

    if ( !lua_isnil( L, lo ) ) {
        return 0;
    }

    err->index = lo;
    err->array = 0;
    err->type = "value";
    return 1;
};

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_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
