/*****************************************************************************
 * $LastChangedDate: 2011-08-20 12:07:38 -0400 (Sat, 20 Aug 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Test of Lua interface.
 *//*
 * LEGAL:   COPYRIGHT (C) 2008 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL 2).
 *****************************************************************************/

#define TESTING 1
#include "test.hh"
#include "base/module.hh"
#include "base/file.hh"
using namespace base;
#include "lua_bind/module.hh"
#include "lua_bind/interpreter.hh"
using namespace lua::interpreter;

namespace lua {
namespace bind {

// Interface for this test.
extern bool gLuaTestObjectWhileHoldingRefCount;

int GetTestClassInstanceCount( void );
int GetTestClassInstanceCountNeverDec( void );

//------------------------------------------------------------------------------
// Expand macros as Register*() empty function definitions in order to link.
// Needed for linker since liblua_bind_test.a omits simulator libs.
#define COMPILE_LUA_BIND_TEST 1
#undef  REG_FUNC_BEGIN
#undef  REG_FUNC_ARGS
#undef  REG_FUNC_END
#undef  REG_FUNC_CMDLINE
#define REG_FUNC_BEGIN  void
#define REG_FUNC_ARGS   lua_State*
#define REG_FUNC_END    { }
#define REG_FUNC_CMDLINE void RegisterCmdLine( lua_State* L, int argc, char** argv ) { }
#include "lua_bind/register.hh"
//------------------------------------------------------------------------------

} // namespace bind
} // namespace lua

using namespace lua::bind;

/*****************************************************************************
 *
 *****************************************************************************/
bool
TestLua( void )
{
    CDEBUG << "============================================\n";
    CDEBUG << "Testing Lua singleton. \n";
    CDEBUG << "============================================\n";

    // Pathname passed to RunScript() is implicitly catenated
    // with the directory containing Lua scripts.

    //..........................................................................
    // Test running simple script.
    try
    {
        GET_LUA().RunScript( "simple.lua" );  // palomino/scripts/tests
        GET_LUA().RunScript( "simple.lua" );  // again to test caching bytecode
        GET_LUA().RunScript( "simple.lua" );  // again to test caching bytecode
    }
    catch ( const std::exception& ex )
    {
        CDEBUG << ex.what() << std::endl;
        return Fail();
    }

    //..........................................................................
    // Test trying to run a missing script.
    try
    {
        GET_LUA().RunScript( "MISSING_SCRIPT.lua" );
        return Fail();  // should've thrown
    }
    catch ( const Exception& ex )
    {
        CDEBUG << "Passed trying to run missing Lua script" << std::endl
               << ex.what() << std::endl;
    }

    //..........................................................................
    // Run the scripts containing the test cases.
    try
    {
        GET_LUA().RunScript( "bools.lua" );
        GET_LUA().RunScript( "numbers.lua" );
        GET_LUA().RunScript( "strings.lua" );
        GET_LUA().RunScript( "tables.lua" );
        GET_LUA().RunScript( "vec.lua" );
    }
    catch ( const std::exception& ex )
    {
        CDEBUG << ex.what() << std::endl;
        return Fail();
    }

    //..........................................................................
    // Test Lua::ReadVarAsBool().
    try
    {
        if ( not GET_LUA().ReadVarAsBool( "bool_true" )
          or     GET_LUA().ReadVarAsBool( "bool_false" )
          or not GET_LUA().ReadVarAsBool( "bool_1" )
          or     GET_LUA().ReadVarAsBool( "bool_0" ) )
        {
            return Fail();
        }
    }
    catch ( const std::exception& ex )
    {
        CDEBUG << ex.what() << std::endl;
        return Fail();
    }

    //..........................................................................
    // Test Lua::ReadVarAsNumber().
    try
    {
        // Test the Lua developer's assertion that float round-off doesn't occur.
        if ( int(GET_LUA().ReadVarAsNumber( "var123" ) != 123 )
          or int(GET_LUA().ReadVarAsNumber( "varNeg456" ) != -456 ) )
        {
            return Fail();
        }

        // Test trying to read a missing variable.
        try
        {
            GET_LUA().ReadVarAsNumber( "MISSING" );
            return Fail();
        }
        catch(...)
        {
            // ok
        }
    }
    catch ( const std::exception& ex )
    {
        CDEBUG << ex.what() << std::endl;
        return Fail();
    }

    //..........................................................................
    // Test Lua::ReadVarAsString().
    try
    {
        // Test the Lua developer's assertion that float round-off doesn't occur.
        if ( GET_LUA().ReadVarAsString( "string_ABC" ) != "ABC"
          or GET_LUA().ReadVarAsString( "string_789" ) != "789"
          or GET_LUA().ReadVarAsString( "string_0123XYZ" ) != "0123XYZ"
          or GET_LUA().ReadVarAsString( "string_empty" ) != "" )
        {
            return Fail();
        }

        // Test trying to read a missing variable.
        try
        {
            GET_LUA().ReadVarAsString( "MISSING" );
            return Fail();
        }
        catch(...)
        {
            // ok
        }
    }
    catch ( const std::exception& ex )
    {
        CDEBUG << ex.what() << std::endl;
        return Fail();
    }

    //..........................................................................
    // Test Lua::ReadTableAsNumber() [array variant].
    try
    {
        // "table_numbers" using integer indexs.
        if ( GET_LUA().ReadTableAsNumber( "table_numbers", 1 ) != 111 )
        {
            return Fail();
        }

        if ( GET_LUA().ReadTableAsNumber( "table_numbers", 2 ) != 222 )
        {
            return Fail();
        }

        // This element is missing so an exception should be thrown.
        try
        {
            GET_LUA().ReadTableAsString( "table_numbers", 9999 );
            return Fail();
        }
        catch(...)
        {
            // ok
        }
    }
    catch ( const std::exception& ex )
    {
        CDEBUG << ex.what() << std::endl;
        return Fail();
    }

    //..........................................................................
    // Test Lua::ReadTableAsNumber() [string key variant].
    try
    {
        // "table_numbers_key" using integer indexs.
        if ( GET_LUA().ReadTableAsNumber( "table_numbers_key", "k1" ) != 111 )
        {
            return Fail();
        }

        if ( GET_LUA().ReadTableAsNumber( "table_numbers_key", "k2" ) != 222 )
        {
            return Fail();
        }

        // This element is missing so an exception should be thrown.
        try
        {
            GET_LUA().ReadTableAsString( "table_numbers_key", "9999" );
            return Fail();
        }
        catch(...)
        {
            // ok
        }
    }
    catch ( const std::exception& ex )
    {
        CDEBUG << ex.what() << std::endl;
        return Fail();
    }

    //..........................................................................
    // Test Lua::ReadTableAsString() [array variant].
    try
    {
        // "table_array" using integer indexs.
        if ( GET_LUA().ReadTableAsString( "table_array", 1 ) != "elem1" )
        {
            return Fail();
        }

        if ( GET_LUA().ReadTableAsString( "table_array", 2 ) != "elem2" )
        {
            return Fail();
        }

        // This element is missing so an exception should be thrown.
        try
        {
            GET_LUA().ReadTableAsString( "table_array", 3 );
            return Fail();
        }
        catch(...)
        {
            // ok
        }
    }
    catch ( const std::exception& ex )
    {
        CDEBUG << ex.what() << std::endl;
        return Fail();
    }

    //..........................................................................
    // Test Lua::ReadTableAsString() [key string variant].
    try
    {
        if ( GET_LUA().ReadTableAsString( "table_string", "a" ) != "A" )
        {
            return Fail();
        }

        if ( GET_LUA().ReadTableAsString( "table_string", "b" ) != "B" )
        {
            return Fail();
        }

        // This element is missing so an exception should be thrown.
        try
        {
            GET_LUA().ReadTableAsString( "table_string", "" );
            return Fail();
        }
        catch(...)
        {
            // ok
        }
    }
    catch ( const std::exception& ex )
    {
        CDEBUG << ex.what() << std::endl;
        return Fail();
    }

    return true;
}

/*****************************************************************************
 * Test the exported C++ class Test (lua_bind/bind_test.hh).
 *****************************************************************************/
bool
TestCxxClass( void )
{
    CDEBUG << "============================================\n";
    CDEBUG << "Testing the exported C++ class \"Test\". \n";
    CDEBUG << "============================================\n";

    //..........................................................................
    // Run Lua script to test the exported C++ class.
    try
    {
        CDEBUG << "Testing that garbage-collector destroys C++ object...\n";

        GET_LUA().RunScript( "class_Test.lua" );

        if ( GET_LUA().ReadVarAsString( "test_GetABC" ) != string("ABC") )
        {
            return Fail();
        }

        // Test that the C++ objects was created exactly once.
        if ( GetTestClassInstanceCountNeverDec() != 1 )
        { return Fail(); }

        // Test that Lua's garbage-collector indirectly
        // caused the C++ object to be destroyed.
        if ( GetTestClassInstanceCount() != 0 )
        { return Fail(); }
    }
    catch ( const std::exception& ex )
    {
        CDEBUG << ex.what() << std::endl;
        return Fail();
    }

    //..........................................................................
    // Now test when a reference is being held.
    // See bind_test.cc.
    try
    {
        CDEBUG << "Testing garbage-collector lets C++ object survive when C++ holds a reference\n";

        // -- See lua_bind/bind_test.cc --
        gLuaTestObjectWhileHoldingRefCount = true;

        GET_LUA().RunScript( "class_Test.lua" );

        if ( GET_LUA().ReadVarAsString( "test_GetABC" ) != string("ABC") )
        {
            return Fail();
        }

        // Ok, this C++ object was instantiated twice,
        // and the second instance should still exist,
        // even though the Lua script called collectgarbage().

        if ( GetTestClassInstanceCountNeverDec() != 2 )
        { return Fail(); }

        // Test that Lua's garbage-collector indirectly
        // caused the C++ object to be destroyed.
        if ( GetTestClassInstanceCount() != 1 )
        { return Fail(); }
    }
    catch ( const std::exception& ex )
    {
        CDEBUG << ex.what() << std::endl;
        return Fail();
    }

    return true;
}

/*****************************************************************************
 * Run test.
 *****************************************************************************/
bool
RunTest( void )
{
    // Find the dir containing test scripts.
    string scriptDir;
    if ( IfDirExists( "scripts" ) )
    {
        GET_LUA().SetScriptDir( "scripts" );
    }
    else if ( IfDirExists( "../scripts" ) )
    {
        GET_LUA().SetScriptDir( "../scripts" );
    }
    else if ( IfDirExists( "../../scripts" ) )
    {
        GET_LUA().SetScriptDir( "../../scripts" );
    }
    else
    {
        CDEBUG << "ERROR: cannot find Lua scripts directory" << std::endl;
        return false;
    }

    return TestLua()
       and TestCxxClass();
}

/*****************************************************************************
 * main.
 *****************************************************************************/
int main( int argc, char** argv )
{
    RUN_TEST( "lua bind", TEST_ERROR_MODE_ABORT,
              RunTest() )
}
