/*****************************************************************************
 * $LastChangedDate: 2011-08-20 12:07:38 -0400 (Sat, 20 Aug 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Macros to simplify glue code between C++ and Lua (generic).
 * @remarks See macros.hh
 *//*
 * LEGAL:   COPYRIGHT (C) 2009 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL 2).
 *****************************************************************************/

// Avoid lua_/LUA_ which is reserved by Lua.

#ifndef LUABIND_MACROS_GENERIC_HH
#define LUABIND_MACROS_GENERIC_HH 1

#include "lua_bind/interpreter.hh"
using namespace lua::interpreter;

namespace lua {
namespace bind {

//------------------------------------------------------------------------------
// Basic macros.
//------------------------------------------------------------------------------

const bool OPTIONAL = true;

/// Unlike lua_isnumber/string(), these precisely identify a number or string (no coercion).
INLINE bool IF_NIL( lua_State* L, int idx )    { return lua_type( L, idx ) == LUA_TNIL; }
INLINE bool IF_BOOL( lua_State* L, int idx )   { return lua_type( L, idx ) == LUA_TBOOLEAN; }
INLINE bool IF_NUMBER( lua_State* L, int idx ) { return lua_type( L, idx ) == LUA_TNUMBER; }
INLINE bool IF_STRING( lua_State* L, int idx ) { return lua_type( L, idx ) == LUA_TSTRING; }
INLINE bool IF_TABLE( lua_State* L, int idx )  { return lua_type( L, idx ) == LUA_TTABLE; }

/// Save/restore stack.
/// Note: The Lua class has its own private methods SaveStack()/RestoreStack().
#define SAVE_STACK(L)    const int savedStack = lua_gettop( (L) )
#define RESTORE_STACK(L) lua_settop( (L), savedStack )

//------------------------------------------------------------------------------
// Table registration.
//------------------------------------------------------------------------------

// Macro to expand metatable's name as a C string.
#define LUABIND_METATABLE_STRING(NAME) LUABIND_MODULE_NAME "_" NAME

// Register a binding to emulate a class in Lua.
void
REGISTER_BINDING( lua_State* L,
                  const char* metaTableName,
                  const char* className,
                  const struct luaL_reg methodTable[] );

//------------------------------------------------------------------------------
// Error handling.
//------------------------------------------------------------------------------

/// Block that tries to access optional arg as a Lua table member.
#define BEGIN_OPTIONAL_TABLE_MEMBER \
    { \
        SAVE_STACK( L ); \
        try {
#define END_OPTIONAL_TABLE_MEMBER \
        } \
        catch( const LuaException& ) { RESTORE_STACK( L ); } \
    }

/// @brief Auto-class to temporarily suppress raising an error in Lua.
/// Instead, a C++ exception will be thrown, which allows C++ bindings
/// to use try/catch to test for optional table members.
class AutoSuppressLuaError
{
public:
    AutoSuppressLuaError( bool suppress ) { Lua::msTypeErrorAsException = suppress; }
    ~AutoSuppressLuaError()               { Lua::msTypeErrorAsException = false; }
};

/// Don't confuse this with Lua::ThrowLuaException() which restores stack.
#define THROW_LUA_EXCEPTION( TEXT ) throw LuaException( TEXT )

/// For terseness.
#define CATCH_LUA_EXCEPTION                         \
    catch ( const LuaException& ex )                \
    {                                               \
        luaL_error( L, ex.what() );                 \
    }

/// typeErrorAsException=true: Throw C++ exception.  If caught, C++ binding code can continue executing.
/// typeErrorAsException=false: Stop executing C++ binding.  Do longjump back to Lua.  Raise error in Lua.
#define IF_TYPE_ERROR_AS_EXCEPTION() (Lua::msTypeErrorAsException)

/// If type error, either throw C++ exception, or longjump back to Lua and raise error in Lua.
INLINE void
CHECK_TYPE_ERROR( lua_State* L, const int idx, const int typeId )
{
    if ( UX( lua_type( L, idx ) != typeId ) )
    {
        if ( IF_TYPE_ERROR_AS_EXCEPTION() )
        {
            // This throws a C++ exception.
            THROW_LUA_EXCEPTION( "Lua type error" );
        }
        else
        {
            // This causes a longjump back to Lua interpreter (unwinds C++ call stack).
            // lua_checktype() calls lua_error() which never returns.
            luaL_checktype( L, idx, typeId );
        }
    }
}

//------------------------------------------------------------------------------
// Lua/C++ bindings (basic types).
//------------------------------------------------------------------------------

/// Pop a boolean (or number converted to a boolean) from Lua stack else throws exception.
INLINE bool
POP_BOOL( lua_State* L )
{
    // Using lua_toboolean() alone isn't correct,
    // because the number 0 is true (indeed, true).
    // "lua_toboolean returns 1 for any Lua value different"
    // "from false and nil, otherwise it returns 0."
    bool tf = false;
    const int luaType = lua_type( L, -1 );
    if ( luaType == LUA_TBOOLEAN )
    {
        tf = lua_toboolean( L, -1 );
    }
    else if ( luaType == LUA_TNIL )
    {
        tf = false;
    }
    else if ( luaType == LUA_TNUMBER )
    {
        tf = (lua_tonumber( L, -1 ) != 0.0);
    }
    else
    {
        throw LuaException( "POP_BOOL: type error" );
    }
    lua_pop( L, 1 );
    return tf;
}

/// Pop a number (double) from Lua stack else throws exception.
INLINE LuaNumber
POP_NUMBER( lua_State* L )
{
    CHECK_TYPE_ERROR( L, -1, LUA_TNUMBER );
    const LuaNumber num = lua_tonumber( L, -1 );
    lua_pop( L, 1 );
    return num;
}
#define POP_FP POP_NUMBER

/// Pop a number (int) from Lua stack else throws exception.
INLINE int
POP_INT( lua_State* L )
{
    return int(POP_NUMBER(L));
}

/// Pop a string from Lua stack else throws exception.
/// Returns copy of string (instead of char*) since Lua might garbage-collect it.
INLINE const string
POP_STRING( lua_State* L )
{
    CHECK_TYPE_ERROR( L, -1, LUA_TSTRING );
    const char* str = lua_tostring( L, -1 );
    if ( UX( str == NULL ) )
        THROW_LUA_EXCEPTION( "POP_STRING: type error" );
    lua_pop( L, 1 );
    return str;
}

//------------------------------------------------------------------------------
// Lua/C++ bindings (tables).
//------------------------------------------------------------------------------

/// Get table member by name as bool type else throws exception (stack UNDEFINED).
INLINE bool
GET_MEMBER_AS_BOOL( lua_State* L, const char* memberName, const bool optional = false )
{
    AutoSuppressLuaError autoSuppressLuaError( optional );
    CHECK_TYPE_ERROR( L, -1, LUA_TTABLE );
    lua_getfield( L, -1, memberName );
    return POP_BOOL( L );
}

/// Get table member by index as number type else throws exception (stack UNDEFINED).
INLINE LuaNumber
GET_MEMBER_AS_NUMBER( lua_State* L, const int idx )
{
    CHECK_TYPE_ERROR( L, -1, LUA_TTABLE );
    lua_pushnumber( L, idx );  // push idx as key
    lua_gettable( L, -2 );     // -2:table, -1:key
    return POP_NUMBER( L );    // keep table on stack
}

/// Get table member by name as number type else throws exception (stack UNDEFINED).
INLINE LuaNumber
GET_MEMBER_AS_NUMBER( lua_State* L, const char* memberName, const bool optional = false )
{
    AutoSuppressLuaError autoSuppressLuaError( optional );
    CHECK_TYPE_ERROR( L, -1, LUA_TTABLE );
    lua_getfield( L, -1, memberName );
    return POP_NUMBER( L );
}

/// Get table member by name as string type else throws exception (stack UNDEFINED).
INLINE const string
GET_MEMBER_AS_STRING( lua_State* L, const char* memberName, const bool optional = false )
{
    AutoSuppressLuaError autoSuppressLuaError( optional );
    CHECK_TYPE_ERROR( L, -1, LUA_TTABLE );
    lua_getfield( L, -1, memberName );
    return POP_STRING( L );
}

/// Assign a table member (bool).
/// PRE: Table is on stack top.  POST: Table is still on stack top.
INLINE void
SET_MEMBER_AS_BOOL( lua_State* L, const char* key, const bool val )
{
    CHECK_TYPE_ERROR( L, -1, LUA_TTABLE );
    lua_pushboolean( L, val );
    lua_setfield( L, -2, key );  // and keep table on top
}

/// Write number to table member by a key string.
/// PRE: Table is on stack top.  POST: Table is still on stack top.
INLINE void
SET_MEMBER_AS_NUMBER( lua_State* L, const char* key, const LuaNumber val )
{
    CHECK_TYPE_ERROR( L, -1, LUA_TTABLE );
    lua_pushnumber( L, val );
    lua_setfield( L, -2, key );  // and keep table on top
}

/// Write number to table member by integer index.
/// PRE: Table is on stack top.  POST: Table is still on stack top.
INLINE void
SET_MEMBER_AS_NUMBER( lua_State* L, const int idx, const LuaNumber val )
{
    CHECK_TYPE_ERROR( L, -1, LUA_TTABLE );
    lua_pushnumber( L, idx );    // -2
    lua_pushnumber( L, val );    // -1
    lua_settable( L, -3 );       // -3:table
}

#define SET_MEMBER_AS_INT  SET_MEMBER_AS_NUMBER
#define SET_MEMBER_AS_FP   SET_MEMBER_AS_NUMBER

//------------------------------------------------------------------------------
// Lua/C++ bindings (userdata).
//------------------------------------------------------------------------------

/// Pop a Lua "lightuserdata" object (C pointer) from Lua stack else throws exception.
template<typename T>
T*
POP_LIGHTUSERDATA( lua_State* L )
{
    CHECK_TYPE_ERROR( L, -1, LUA_TLIGHTUSERDATA );
    T* lightuserdata = const_cast<T*>( reinterpret_cast<const T*>( lua_topointer( L, -1 ) ) );
    lua_pop( L, 1 );
    return lightuserdata;
}

/// Directly pop Lua userdata and return its address.
/// This is low-level and doesn't know what is stored in the userdata.
INLINE void*
POP_USERDATA_ADDRESS( lua_State* L )
{
    CHECK_TYPE_ERROR( L, -1, LUA_TUSERDATA );
    void* addr = const_cast<void*>( lua_touserdata( L, -1 ) );
    lua_pop( L, 1 );
    return addr;
}

/// Pop Lua userdata, then deference the pointer it contains.
/// return *((T**)userdata)
template<typename T>
T*
POP_USERDATA_AS_PTR( lua_State* L )
{
    CHECK_TYPE_ERROR( L, -1, LUA_TUSERDATA );
    T** pPtr = reinterpret_cast<T**>( lua_touserdata( L, -1 ) );
    lua_pop( L, 1 );
    return *pPtr;  // dereference pointer-to-pointer
}

/// Pop user data, then return a copy of a refCount variable that it contains.
/// Popping a base class pushed as a derivative should succeed
/// (shptr has the same memory layout regardless of the object's type it points to).
template<typename T>
shptr<T>
POP_USERDATA_AS_SHPTR( lua_State* L )
{
    CHECK_TYPE_ERROR( L, -1, LUA_TUSERDATA );
    shptr<T>* pShptr = reinterpret_cast<shptr<T>*>( lua_touserdata( L, -1 ) );
    CHECK_TYPESIG((*pShptr),TYPESIG_SHARED);
    lua_pop( L, 1 );
    return *pShptr;  // idiom: dereference pointer to shptr
}

/// This creates a method in Lua that produces a raw pointer to C/C++ object.
template<typename T>
int
NEW_USERDATA_PTR( lua_State* L, T* cxxObj, const char* metaTableName )
{
    // Allocate memory for Lua userdata.
    void* userdata = lua_newuserdata( L, sizeof(T*) );

    // Write a pointer over Lua userdata.
    *reinterpret_cast<T**>(userdata) = cxxObj;  

    // Metatable was created by REGISTER_BINDING().
    // The userdata requires a metatable for it to function as a Lua table.
    luaL_getmetatable( L, metaTableName );
    lua_setmetatable( L, -2 );

    // Return amount of stack items back to Lua.
    return 1;
}

/// This creates a method in Lua that produces a reference-counted C++ object.
template<typename T>
int
NEW_USERDATA_SHPTR( lua_State* L, shptr<T> cxxObj, const char* metaTableName )
{
    // Allocate memory for Lua userdata.
    void* userdata = lua_newuserdata( L, sizeof(shptr<T>) );    // stack { userdata }

    // Now use C++ "placement" new to construct a reference-counted pointer variable.
    new (userdata) shptr<T>( cxxObj.PTR() );

    // Metatable was created by REGISTER_BINDING().
    // The userdata requires a metatable for it to function as a Lua table.
    luaL_getmetatable( L, metaTableName );                      // stack { metatable, userdata }
    lua_setmetatable( L, -2 );                                  // stack { userdata }

    // Return amount of stack items back to Lua.
    return 1;
}

/// Destroy a smart pointer (not necessarily the object being pointed to).
/// This implements a Lua "__gc" method.
template<typename T>
int
DESTROY_USERDATA_SHPTR( lua_State* L, void(*CheckTypesig)(shptr<T>) )
{
    // Pop the userdata as a pointer to a reference-counted pointer (shptr).
    shptr<T>* pShptr = reinterpret_cast<shptr<T>*>( POP_USERDATA_ADDRESS( L ) );

    // Check integrity of C++ object that is pointed to.
    CHECK_TYPESIG( (*pShptr), TYPESIG_SHARED );
    CheckTypesig( *pShptr );

    // Explicitly call the shptr destructor in order to unreference the object.
    // This is the opposite of placement calling the constructor.
    // Calling a destructor doesn't free memory (shptr is at memory owned by Lua).
    pShptr->~shptr<T>();  // yuck

    // Return amount of stack items.
    return 0;
}

} // namespace bind
} // namespace lua

#endif // LUABIND_MACROS_GENERIC_HH
