
#include "ag.h"
#include "anaglym.h"
#include "Engine.h"
#include "Video.h"
#include <SDL.h>
#include "wfobj/WFObj.h"
#include <unistd.h>                 /* usleep() */
#include <lua.hpp>
#include <iostream>
#include <string>
#include <GL/gl.h>
#include <GL/glu.h>
#ifdef _WIN32
#include <windows.h>                /* Sleep() */
#endif
using namespace std;

namespace ag
{
    void register_functions(lua_State * L)
    {
        static const luaL_Reg functions[] = {
            { "clearEventHandler", clearEventHandler },
            { "doPhysics", doPhysics },
            { "drawArc", drawArc },
            { "drawCircle", drawCircle },
            { "drawImage", drawImage },
            { "drawLine", drawLine },
            { "drawObjects", drawObjects },
            { "drawRect", drawRect },
            { "drawText", drawText },
            { "elapsedTime", elapsedTime },
            { "endFrame", endFrame },
            { "exit", exit },
            { "fillRect", fillRect },
            { "getCamera", getCamera },
            { "getScreenSize", getScreenSize },
            { "getTextSize", getTextSize },
            { "import", import },
            { "isKeyDown", isKeyDown },
            { "loadModel", loadModel },
            { "loadModelStatic", loadModelStatic },
            { "loadTexture", loadTexture },
            { "print", print },
            { "println", println },
            { "registerEventHandler", registerEventHandler },
            { "setAutoDrawObjects", setAutoDrawObjects },
            { "setAutoEndFrame", setAutoEndFrame },
            { "setAutoPhysics", setAutoPhysics },
            { "setAutoStartFrame", setAutoStartFrame },
            { "setCamera", setCamera },
            { "setGravity", setGravity },
//            { "sleep", sleep },
            { "startFrame", startFrame },

            /* managed object functions */
            { "createBox", createBox },
            { "createBoxStatic", createBoxStatic },
            { "createCapsule", createCapsule },
            { "createCapsuleStatic", createCapsuleStatic },
//            { "createCylinder", createCylinder },
//            { "createCylinderStatic", createCylinderStatic },
            { "createPlane", createPlane },
            { "createSphere", createSphere },
            { "createSphereStatic", createSphereStatic },

            { NULL, NULL }
        };
        luaL_register(L, "ag", functions);
    }

    static void print_val(lua_State * L, int index)
    {
        int type = lua_type(L, index);
        switch (type)
        {
            case LUA_TNUMBER:
                cout << lua_tonumber(L, index);
                break;
            case LUA_TSTRING:
                cout << lua_tostring(L, index);
                break;
            case LUA_TTABLE:
                cout << "{ ";

                /* traverse the table and print the keys/values */
                lua_checkstack(L, 3);
                lua_pushnil(L);
                for (bool first = true; lua_next(L, index) != 0; first = false)
                {
                    if (!first)
                        cout << ", ";
                    cout << '[';
                    print_val(L, lua_gettop(L) - 1);
                    cout << ']';
                    cout << " => ";
                    print_val(L, lua_gettop(L));
                    lua_pop(L, 1);
                }

                cout << " }";
                break;
            case LUA_TFUNCTION:
                cout << "<function>";
                break;
            case LUA_TUSERDATA:
                cout << "<userdata>";
                break;
            case LUA_TTHREAD:
                cout << "<thread>";
                break;
            case LUA_TLIGHTUSERDATA:
                cout << "<lightuserdata>";
                break;
        }
    }

    int print(lua_State * L)
    {
        int argc = lua_gettop(L);

        for ( int n = 1; n <= argc; n++ )
        {
            print_val(L, n);
        }
        return 0;
    }

    int println(lua_State * L)
    {
        int ret = print(L);
        cout << endl;
        return ret;
    }

    static void createLuaObject(lua_State * L, int id)
    {
        lua_newtable(L);
        lua_pushinteger(L, id);
        lua_setfield(L, -2, "id");
        lua_pushcfunction(L, object::draw);
        lua_setfield(L, -2, "draw");
        lua_pushcfunction(L, object::setPosition);
        lua_setfield(L, -2, "setPosition");
        lua_pushcfunction(L, object::getPosition);
        lua_setfield(L, -2, "getPosition");
        lua_pushcfunction(L, object::setRotation);
        lua_setfield(L, -2, "setRotation");
        lua_pushcfunction(L, object::clone);
        lua_setfield(L, -2, "clone");
        lua_pushcfunction(L, object::destroy);
        lua_setfield(L, -2, "destroy");
        lua_pushcfunction(L, object::setVisible);
        lua_setfield(L, -2, "setVisible");
        lua_pushcfunction(L, object::addForce);
        lua_setfield(L, -2, "addForce");
        lua_pushcfunction(L, object::addForceRel);
        lua_setfield(L, -2, "addForceRel");
        lua_pushcfunction(L, object::addTorque);
        lua_setfield(L, -2, "addTorque");
        lua_pushcfunction(L, object::addTorqueRel);
        lua_setfield(L, -2, "addTorqueRel");
        lua_pushcfunction(L, object::setColor);
        lua_setfield(L, -2, "setColor");
        lua_pushcfunction(L, object::setTexture);
        lua_setfield(L, -2, "setTexture");
        lua_pushcfunction(L, object::setMass);
        lua_setfield(L, -2, "setMass");
        lua_pushcfunction(L, object::getMass);
        lua_setfield(L, -2, "getMass");
        lua_pushcfunction(L, object::getAABB);
        lua_setfield(L, -2, "getAABB");
        lua_pushcfunction(L, object::getSize);
        lua_setfield(L, -2, "getSize");
    }

    static int loadModelSpecify(lua_State * L, bool static_data)
    {
        bool added = false;
        float scale = 1.0f;
        int argc = lua_gettop(L);

        if (argc >= 2 && lua_isnumber(L, 2))
            scale = lua_tonumber(L, 2);

        if (argc >= 1 && lua_isstring(L, 1))
        {
            string modelname = lua_tostring(L, 1);
            int id = g_engine->loadModel(modelname, static_data, scale);
            if (id > 0)
            {
                createLuaObject(L, id);
                added = true;
            }
        }
        if (!added)
            lua_pushnil(L);
        return 1;
    }

    int loadModel(lua_State * L)
    {
        return loadModelSpecify(L, false);
    }

    int loadModelStatic(lua_State * L)
    {
        return loadModelSpecify(L, true);
    }

    int sleep(lua_State * L)
    {
        int argc = lua_gettop(L);

        if (argc == 1)
        {
            if (lua_isnumber(L, -1))
            {
                double seconds = lua_tonumber(L, -1);
#ifdef _WIN32
                Sleep((DWORD)(seconds * 1000));
#else
                usleep((useconds_t) (seconds * 1000000));
#endif
            }
        }

        return 0;
    }

    int startFrame(lua_State * L)
    {
        g_engine->startFrame();
        return 0;
    }

    int endFrame(lua_State * L)
    {
        g_engine->endFrame();
        return 0;
    }

    int setCamera(lua_State * L)
    {
        return g_engine->setCamera(L);
    }

    int setGravity(lua_State * L)
    {
        int argc = lua_gettop(L);
        if (argc == 3
                && lua_isnumber(L, 1)
                && lua_isnumber(L, 2)
                && lua_isnumber(L, 3))
        {
            g_engine->setGravity(lua_tonumber(L, 1),
                    lua_tonumber(L, 2),
                    lua_tonumber(L, 3));
        }
        return 0;
    }

    int getCamera(lua_State * L)
    {
        return g_engine->getCamera(L);
    }

    int elapsedTime(lua_State * L)
    {
        lua_pushinteger(L, SDL_GetTicks());
        return 1;
    }

    int doPhysics(lua_State * L)
    {
        g_engine->doPhysics();
        return 0;
    }

    int drawObjects(lua_State * L)
    {
        g_engine->drawObjects();
        return 0;
    }

    int setAutoPhysics(lua_State * L)
    {
        int argc = lua_gettop(L);
        if (argc == 1 && lua_isboolean(L, 1))
            g_engine->setAutoPhysics(lua_toboolean(L, 1));
        return 0;
    }

    int setAutoStartFrame(lua_State * L)
    {
        int argc = lua_gettop(L);
        if (argc == 1 && lua_isboolean(L, 1))
            g_engine->setAutoStartFrame(lua_toboolean(L, 1));
        return 0;
    }

    int setAutoEndFrame(lua_State * L)
    {
        int argc = lua_gettop(L);
        if (argc == 1 && lua_isboolean(L, 1))
            g_engine->setAutoEndFrame(lua_toboolean(L, 1));
        return 0;
    }

    int setAutoDrawObjects(lua_State * L)
    {
        int argc = lua_gettop(L);
        if (argc == 1 && lua_isboolean(L, 1))
            g_engine->setAutoDrawObjects(lua_toboolean(L, 1));
        return 0;
    }

    int isKeyDown(lua_State * L)
    {
        int argc = lua_gettop(L);
        if (argc == 1 && lua_isstring(L, 1))
            lua_pushboolean(L, g_engine->isKeyDown(lua_tostring(L, 1)));
        else
            lua_pushboolean(L, false);
        return 1;
    }

    int registerEventHandler(lua_State * L)
    {
        return g_engine->registerEventHandler(L);
    }

    int clearEventHandler(lua_State * L)
    {
        return g_engine->clearEventHandler(L);
    }

    int exit(lua_State * L)
    {
        g_engine->exit();
        return 0;
    }

    int import(lua_State * L)
    {
        int argc = lua_gettop(L);
        if (argc == 1 && lua_isstring(L, 1))
            lua_pushboolean(L, g_engine->import(lua_tostring(L, 1)));
        else
            lua_pushnil(L);
        return 1;
    }

    int loadTexture(lua_State * L)
    {
        int argc = lua_gettop(L);
        if (argc == 1 && lua_isstring(L, 1))
        {
            GLuint texture;
            if ((texture = g_engine->loadTexture(lua_tostring(L, 1))) != 0)
            {
                lua_newtable(L);
                lua_pushinteger(L, texture);
                lua_setfield(L, -2, "id");
                return 1;
            }
        }
        lua_pushnil(L);
        return 1;
    }

    int getScreenSize(lua_State * L)
    {
        int width, height;
        g_engine->getScreenSize(&width, &height);
        lua_pushinteger(L, width);
        lua_pushinteger(L, height);
        return 2;
    }

    int drawText(lua_State * L)
    {
        int argc = lua_gettop(L);
        if (argc == 7
                && lua_isstring(L, 1)
                && lua_isnumber(L, 2)
                && lua_isnumber(L, 3)
                && lua_isnumber(L, 4)
                && lua_isnumber(L, 5)
                && lua_isnumber(L, 6)
                && lua_isnumber(L, 7))
        {
            g_engine->drawText(lua_tostring(L, 1),
                    lua_tonumber(L, 2),
                    lua_tonumber(L, 3),
                    lua_tonumber(L, 4),
                    lua_tointeger(L, 5),
                    lua_tointeger(L, 6),
                    lua_tointeger(L, 7));
        }
        return 0;
    }

    int getTextSize(lua_State * L)
    {
        float width = 0.0f;
        float height = 0.0f;
        int argc = lua_gettop(L);
        if (argc == 2 && lua_isstring(L, 1) && lua_isnumber(L, 2))
        {
            g_engine->getTextSize(lua_tostring(L, 1), lua_tointeger(L, 2),
                    &width, &height);
        }
        lua_pushnumber(L, width);
        lua_pushnumber(L, height);
        return 2;
    }

    int drawLine(lua_State * L)
    {
        int argc = lua_gettop(L);
        if (argc == 7 || argc == 8)
        {
            bool valid = true;
            for (int i = 1; i <= argc; i++)
                if (!lua_isnumber(L, i))
                    valid = false;
            if (valid)
            {
                float width = 1.0f;
                if (argc == 8)
                    width = lua_tonumber(L, 8);
                g_engine->drawLine(
                        lua_tonumber(L, 1),
                        lua_tonumber(L, 2),
                        lua_tonumber(L, 3),
                        lua_tonumber(L, 4),
                        lua_tonumber(L, 5),
                        lua_tonumber(L, 6),
                        lua_tonumber(L, 7),
                        width);
            }
        }
        return 0;
    }

    int drawRect(lua_State * L)
    {
        int argc = lua_gettop(L);
        if (argc == 7 || argc == 8)
        {
            bool valid = true;
            for (int i = 1; i <= argc; i++)
                if (!lua_isnumber(L, i))
                    valid = false;
            if (valid)
            {
                float rot = 0.0f;
                if (argc == 8)
                    rot = lua_tonumber(L, 8);
                g_engine->drawRect(
                        lua_tonumber(L, 1),
                        lua_tonumber(L, 2),
                        lua_tonumber(L, 3),
                        lua_tonumber(L, 4),
                        lua_tonumber(L, 5),
                        lua_tonumber(L, 6),
                        lua_tonumber(L, 7),
                        rot);
            }
        }
        return 0;
    }

    int fillRect(lua_State * L)
    {
        int argc = lua_gettop(L);
        if (argc == 7 || argc == 8)
        {
            bool valid = true;
            for (int i = 1; i <= argc; i++)
                if (!lua_isnumber(L, i))
                    valid = false;
            if (valid)
            {
                float rot = 0.0f;
                if (argc == 8)
                    rot = lua_tonumber(L, 8);
                g_engine->fillRect(
                        lua_tonumber(L, 1),
                        lua_tonumber(L, 2),
                        lua_tonumber(L, 3),
                        lua_tonumber(L, 4),
                        lua_tonumber(L, 5),
                        lua_tonumber(L, 6),
                        lua_tonumber(L, 7),
                        rot);
            }
        }
        return 0;
    }

    int drawArc(lua_State * L)
    {
        int argc = lua_gettop(L);
        if (argc == 8)
        {
            bool valid = true;
            for (int i = 1; i <= argc; i++)
            {
                if (!lua_isnumber(L, i))
                    valid = false;
            }
            if (valid)
            {
                g_engine->drawArc(
                        lua_tonumber(L, 1),
                        lua_tonumber(L, 2),
                        lua_tonumber(L, 3),
                        lua_tonumber(L, 4),
                        lua_tonumber(L, 5),
                        lua_tonumber(L, 6),
                        lua_tonumber(L, 7),
                        lua_tonumber(L, 8));
            }
        }
        return 0;
    }

    int drawCircle(lua_State * L)
    {
        int argc = lua_gettop(L);
        if (argc == 6)
        {
            bool valid = true;
            for (int i = 1; i <= argc; i++)
            {
                if (!lua_isnumber(L, i))
                    valid = false;
            }
            if (valid)
            {
                g_engine->drawArc(
                        lua_tonumber(L, 1),
                        lua_tonumber(L, 2),
                        lua_tonumber(L, 3),
                        lua_tonumber(L, 4),
                        lua_tonumber(L, 5),
                        lua_tonumber(L, 6),
                        0.0,
                        360.0);
            }
        }
        return 0;
    }

    int drawImage(lua_State * L)
    {
        int argc = lua_gettop(L);
        if (argc == 5 || argc == 6)
        {
            bool valid = true;
            for (int i = 1; i <= argc; i++)
                if (i != 5 && !lua_isnumber(L, i))
                    valid = false;
            if (!lua_istable(L, 5))
                valid = false;
            if (valid)
            {
                lua_getfield(L, 5, "id");
                if (lua_isnumber(L, -1))
                {
                    float rot = 0.0f;
                    if (argc == 6)
                        rot = lua_tonumber(L, 6);
                    GLuint texture_id = (GLuint) lua_tointeger(L, -1);
                    g_engine->drawImage(
                            lua_tonumber(L, 1),
                            lua_tonumber(L, 2),
                            lua_tonumber(L, 3),
                            lua_tonumber(L, 4),
                            texture_id,
                            rot);
                }
                lua_pop(L, 1);
            }
        }
        return 0;
    }

    static void addManagedObject(lua_State * L, bool is_static,
            OdeWorld::GeomType geom_type, refptr< vector<float> > args)
    {
        int id = g_engine->addObject(is_static, geom_type, args);
        createLuaObject(L, id);
    }

    static int createBoxSpecify(lua_State * L, bool is_static)
    {
        int argc = lua_gettop(L);
        if (argc == 3 &&
                lua_isnumber(L, 1) &&
                lua_isnumber(L, 2) &&
                lua_isnumber(L, 3))
        {
            refptr< vector<float> > args = new vector<float>();
            for (int i = 1; i <= 3; i++)
                args->push_back(lua_tonumber(L, i));
            addManagedObject(L, is_static, OdeWorld::BOX, args);
        }
        else
            lua_pushnil(L);
        return 1;
    }

    int createBox(lua_State * L)
    {
        return createBoxSpecify(L, false);
    }

    int createBoxStatic(lua_State * L)
    {
        return createBoxSpecify(L, true);
    }

    static int createSphereSpecify(lua_State * L, bool is_static)
    {
        int argc = lua_gettop(L);
        if (argc == 1 && lua_isnumber(L, 1))
        {
            refptr< vector<float> > args = new vector<float>();
            args->push_back(lua_tonumber(L, 1));
            addManagedObject(L, is_static, OdeWorld::SPHERE, args);
        }
        else
            lua_pushnil(L);
        return 1;
    }

    int createSphere(lua_State * L)
    {
        return createSphereSpecify(L, false);
    }

    int createSphereStatic(lua_State * L)
    {
        return createSphereSpecify(L, true);
    }

    int createPlane(lua_State * L)
    {
        int argc = lua_gettop(L);
        if (argc == 4 || argc == 6)
        {
            bool valid = true;
            for (int i = 1; i <= argc; i++)
            {
                if (!lua_isnumber(L, i))
                {
                    valid = false;
                    break;
                }
            }
            if (valid)
            {
                refptr< vector<float> > args = new vector<float>();
                for (int i = 1; i <= argc; i++)
                    args->push_back(lua_tonumber(L, i));
                addManagedObject(L, true, OdeWorld::PLANE, args);
                return 1;
            }
        }
        lua_pushnil(L);
        return 1;
    }

    static int createCylinderSpecify(lua_State * L, bool is_static)
    {
        int argc = lua_gettop(L);
        if (argc == 2 && lua_isnumber(L, 1) && lua_isnumber(L, 2))
        {
            refptr< vector<float> > args = new vector<float>();
            args->push_back(lua_tonumber(L, 1));
            args->push_back(lua_tonumber(L, 2));
            addManagedObject(L, is_static, OdeWorld::CYLINDER, args);
        }
        else
            lua_pushnil(L);
        return 1;
    }

    int createCylinder(lua_State * L)
    {
        return createCylinderSpecify(L, false);
    }

    int createCylinderStatic(lua_State * L)
    {
        return createCylinderSpecify(L, true);
    }

    static int createCapsuleSpecify(lua_State * L, bool is_static)
    {
        int argc = lua_gettop(L);
        if (argc == 2 && lua_isnumber(L, 1) && lua_isnumber(L, 2))
        {
            refptr< vector<float> > args = new vector<float>();
            args->push_back(lua_tonumber(L, 1));
            args->push_back(lua_tonumber(L, 2));
            addManagedObject(L, is_static, OdeWorld::CAPSULE, args);
        }
        else
            lua_pushnil(L);
        return 1;
    }

    int createCapsule(lua_State * L)
    {
        return createCapsuleSpecify(L, false);
    }

    int createCapsuleStatic(lua_State * L)
    {
        return createCapsuleSpecify(L, true);
    }

    namespace object
    {
        static Engine::Object * getObject(lua_State * L, int index)
        {
            Engine::Object * ret = NULL;

            lua_getfield(L, index, "id");
            if (lua_isnumber(L, -1))
            {
                int id = lua_tointeger(L, -1);
                ret = g_engine->getObject(id);
            }
            lua_pop(L, 1);

            return ret;
        }

        int draw(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 1 && lua_istable(L, -1))
            {
                Engine::Object * obj = getObject(L, -1);
                if (obj != NULL)
                    obj->draw();
            }
            return 0;
        }

        int setPosition(lua_State * L)
        {
            int argc = lua_gettop(L);

            if (argc == 4)
            {
                Engine::Object * obj = getObject(L, 1);
                if (obj != NULL)
                {
                    double position[3];
                    for (int i = 0; i < 3; i++)
                    {
                        position[i] = (lua_isnumber(L, i + 2))
                            ? lua_tonumber(L, i + 2)
                            : 0.0;
                    }
                    obj->setPosition(position[0], position[1], position[2]);
                }
            }

            return 0;
        }

        int getPosition(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 1)
            {
                Engine::Object * obj = getObject(L, 1);
                if (obj != NULL)
                {
                    double x, y, z;
                    obj->getPosition(&x, &y, &z);
                    lua_pushnumber(L, x);
                    lua_pushnumber(L, y);
                    lua_pushnumber(L, z);
                    return 3;
                }
            }
            return 0;
        }

        int setRotation(lua_State * L)
        {
            int argc = lua_gettop(L);

            if (argc == 4)
            {
                Engine::Object * obj = getObject(L, 1);
                if (obj != NULL)
                {
                    double rot[3];
                    for (int i = 0; i < 3; i++)
                    {
                        rot[i] = (lua_isnumber(L, i + 2))
                            ? lua_tonumber(L, i + 2)
                            : 0.0;
                    }
                    obj->setRotation(rot[0], rot[1], rot[2]);
                }
            }

            return 0;
        }

        int clone(lua_State * L)
        {
            int argc = lua_gettop(L);
            bool found = false;
            if (argc == 1)
            {
                Engine::Object * obj = getObject(L, 1);
                if (obj != NULL)
                {
                    found = true;
                    int id = g_engine->cloneObject(obj);
                    createLuaObject(L, id);
                }
            }
            if (!found)
                lua_pushnil(L);
            return 1;
        }

        static void clearLuaTable(lua_State * L, int index)
        {
            lua_pushnil(L);
            /*
             * lua_next pops the key from the stack
             * it pushes the key, value pair if there is one
             */
            while (lua_next(L, index) != 0)
            {
                lua_pop(L, 1);          /* pop old value */
                lua_pushvalue(L, -1);   /* duplicate the key */
                lua_pushnil(L);
                lua_settable(L, index); /* set table[key] = nil */
            }
        }

        int destroy(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 1)
            {
                lua_getfield(L, 1, "id");
                if (lua_isnumber(L, -1))
                {
                    int id = lua_tointeger(L, -1);
                    g_engine->removeObject(id);
                }
                lua_pop(L, 1);
                clearLuaTable(L, 1);
            }
            return 0;
        }

        int setVisible(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 2)
            {
                Engine::Object * obj = getObject(L, 1);
                if (obj != NULL && lua_isboolean(L, 2))
                {
                    obj->setVisible(lua_toboolean(L, 2));
                }
            }
            return 0;
        }

        int addForce(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 4)
            {
                Engine::Object * obj = getObject(L, 1);
                if (obj != NULL &&
                    lua_isnumber(L, 2) &&
                    lua_isnumber(L, 3) &&
                    lua_isnumber(L, 4))
                {
                    obj->addForce(lua_tonumber(L, 2),
                            lua_tonumber(L, 3),
                            lua_tonumber(L, 4));
                }
            }
            return 0;
        }

        int addForceRel(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 4)
            {
                Engine::Object * obj = getObject(L, 1);
                if (obj != NULL &&
                    lua_isnumber(L, 2) &&
                    lua_isnumber(L, 3) &&
                    lua_isnumber(L, 4))
                {
                    obj->addForceRel(lua_tonumber(L, 2),
                            lua_tonumber(L, 3),
                            lua_tonumber(L, 4));
                }
            }
            return 0;
        }

        int addTorque(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 4)
            {
                Engine::Object * obj = getObject(L, 1);
                if (obj != NULL &&
                    lua_isnumber(L, 2) &&
                    lua_isnumber(L, 3) &&
                    lua_isnumber(L, 4))
                {
                    obj->addTorque(lua_tonumber(L, 2),
                            lua_tonumber(L, 3),
                            lua_tonumber(L, 4));
                }
            }
            return 0;
        }

        int addTorqueRel(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 4)
            {
                Engine::Object * obj = getObject(L, 1);
                if (obj != NULL &&
                    lua_isnumber(L, 2) &&
                    lua_isnumber(L, 3) &&
                    lua_isnumber(L, 4))
                {
                    obj->addTorqueRel(lua_tonumber(L, 2),
                            lua_tonumber(L, 3),
                            lua_tonumber(L, 4));
                }
            }
            return 0;
        }

        int setColor(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 4 &&
                lua_isnumber(L, 2) &&
                lua_isnumber(L, 3) &&
                lua_isnumber(L, 4))
            {
                Engine::Object * obj = getObject(L, 1);
                if (obj != NULL)
                {
                    obj->setColor(lua_tonumber(L, 2),
                            lua_tonumber(L, 3),
                            lua_tonumber(L, 4));
                }
            }
            return 0;
        }

        int setTexture(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 2 && lua_istable(L, 1) && lua_istable(L, 2))
            {
                Engine::Object * obj = getObject(L, 1);
                if (obj != NULL)
                {
                    lua_getfield(L, 2, "id");
                    if (lua_isnumber(L, -1))
                    {
                        GLuint id = (GLuint) lua_tointeger(L, -1);
                        obj->setTexture(id);
                    }
                    lua_pop(L, 1);
                }
            }
            return 0;
        }

        int getMass(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 1 && lua_istable(L, 1))
            {
                Engine::Object * obj = getObject(L, 1);
                if (obj != NULL)
                {
                    lua_pushnumber(L, obj->getMass());
                    return 1;
                }
            }
            lua_pushnumber(L, 0);
            return 1;
        }

        int setMass(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 2 && lua_istable(L, 1) && lua_isnumber(L, 2))
            {
                Engine::Object * obj = getObject(L, 1);
                if (obj != NULL)
                {
                    obj->setMass(lua_tonumber(L, 2));
                }
            }
            return 0;
        }

        int getAABB(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 1 && lua_istable(L, 1))
            {
                Engine::Object * obj = getObject(L, 1);
                if (obj != NULL)
                {
                    const float * aabb = obj->getAABB();
                    for (int i = 0; i < 6; i++)
                    {
                        lua_pushnumber(L, aabb[i]);
                    }
                    return 6;
                }
            }
            lua_pushnil(L);
            return 1;
        }

        int getSize(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 1 && lua_istable(L, 1))
            {
                Engine::Object * obj = getObject(L, 1);
                if (obj != NULL)
                {
                    const float * aabb = obj->getAABB();
                    for (int i = 0; i < 3; i++)
                    {
                        lua_pushnumber(L, aabb[i+3] - aabb[i]);
                    }
                    return 3;
                }
            }
            lua_pushnil(L);
            return 1;
        }
    }
}
