
#include <SDL.h>
#include <unistd.h>                 /* usleep() */
#include <GL/gl.h>
#include <GL/glu.h>
#ifdef _WIN32
#include <windows.h>                /* Sleep() */
#endif
#include <limits.h>                 /* INT_MAX */

#include <lua.hpp>
#include <iostream>
#include <string>

#include "ag.h"
#include "cfs.h"
#include "anaglym.h"
#include "Engine.h"
#include "AV.h"

using namespace std;

namespace ag
{
    void register_functions(lua_State * L)
    {
        static const luaL_Reg functions[] = {
            { "callList", callList },
            { "clearEventHandler", clearEventHandler },
            { "createQuad", createQuad },
            { "doPhysics", doPhysics },
            { "drawArc", drawArc },
            { "drawCircle", drawCircle },
            { "drawImage", drawImage },
            { "drawLine", drawLine },
            { "drawObjects", drawObjects },
            { "drawPoint", drawPoint },
            { "drawRect", drawRect },
            { "drawText", drawText },
            { "elapsedTime", elapsedTime },
            { "endFrame", endFrame },
            { "endList", endList },
            { "exit", exit },
            { "fillArc", fillArc },
            { "fillCircle", fillCircle },
            { "fillRect", fillRect },
            { "getCamera", getCamera },
            { "getCursorVisible", getCursorVisible },
            { "getScreenSize", getScreenSize },
            { "getTextSize", getTextSize },
            { "import", import },
            { "isKeyDown", isKeyDown },
            { "loadModelSpecify", loadModelSpecify },
            { "loadSound", loadSound },
            { "loadTexture", loadTexture },
            { "next", next },
            { "pickObjects", pickObjects },
            { "pickOne", pickOne },
            { "print", print },
            { "println", println },
            { "registerEventHandler", registerEventHandler },
            { "setAutoDrawObjects", setAutoDrawObjects },
            { "setAutoEndFrame", setAutoEndFrame },
            { "setAutoPhysics", setAutoPhysics },
            { "setAutoStartFrame", setAutoStartFrame },
            { "setCamera", setCamera },
            { "setCursorVisible", setCursorVisible },
            { "setGravity", setGravity },
//            { "sleep", sleep },
            { "startFrame", startFrame },
            { "startList", startList },
            { "type", type },
            { "clearWorld", clearWorld },

            { "_createAMotor", _createAMotor },
            { "_createHinge", _createHinge },

            /* managed object functions */
            { "createBoxSpecify", createBoxSpecify },
            { "createCapsuleSpecify", createCapsuleSpecify },
            { "createPlaneSpecify", createPlaneSpecify },
            { "createSphereSpecify", createSphereSpecify },

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

        luaL_loadstring(L, (const char *) getFile("src/ag.lua", NULL));

        lua_pcall(L, 0, 0, 0);
    }

    int pickObjects(lua_State * L)
    {
        int argc = lua_gettop(L);
        if (!(argc == 2 && lua_isnumber(L, 1) && lua_isnumber(L, 2)))
        {
            lua_pushnil(L);
            return 1;
        }
        lua_newtable(L);
        int next_index = 1;
        refptr< vector<Engine::PickedObjectRef> > objects =
            g_engine->pickObjects(lua_tointeger(L, 1), lua_tointeger(L, 2));
        for (vector<Engine::PickedObjectRef>::const_iterator it
                = objects->begin();
             it != objects->end();
             it++)
        {
            lua_getfield(L, LUA_GLOBALSINDEX, "ag");/* {ag} */
            lua_getfield(L, -1, "_objects");        /* {ag,_objects} */
            lua_pushinteger(L, (*it)->id);          /* {ag,_objects,id} */
            lua_gettable(L, -2);                    /* {ag,_objects,obj} */
            lua_remove(L, -3);                      /* {_objects,obj} */
            lua_remove(L, -2);                      /* {obj} */

            lua_pushinteger(L, next_index++);       /* {obj,index} */
            lua_pushvalue(L, -2);                   /* {obj,index,obj} */
            lua_settable(L, argc + 1);              /* {obj} */

            lua_pushstring(L, "pick_dist");         /* {obj,"pick_dist"} */
            lua_pushnumber(L, (*it)->dist);         /* {obj,"pick_dist",dist} */
            lua_settable(L, -3);                    /* {obj} */
            lua_createtable(L, 3, 0);               /* {obj,arr} */
            for (int i = 0; i < 3; i++)
            {
                lua_pushinteger(L, i + 1);          /* {obj,arr,index} */
                lua_pushnumber(L, (*it)->pos[i]);   /* {obj,arr,index,pos_x} */
                lua_settable(L, -3);                /* {obj,arr} */
            }
            lua_pushstring(L, "pick_pos");          /* {obj,arr,"pick_pos"} */
            lua_pushvalue(L, -2);                 /* {obj,arr,"pick_pos",arr} */
            lua_settable(L, -4);                    /* {obj,arr} */
            lua_pop(L, 1);                          /* {obj} */
            lua_createtable(L, 3, 0);               /* {obj,arr} */
            for (int i = 0; i < 3; i++)
            {
                lua_pushinteger(L, i + 1);          /* {obj,arr,index} */
                lua_pushnumber(L, (*it)->normal[i]);/* {obj,arr,index,n} */
                lua_settable(L, -3);                /* {obj,arr} */
            }
            lua_pushstring(L, "pick_normal");      /* {obj,arr,"pick_normal"} */
            lua_pushvalue(L, -2);              /* {obj,arr,"pick_normal",arr} */
            lua_settable(L, -4);                    /* {obj,arr} */
            lua_pop(L, 1);                          /* {obj} */
            lua_pop(L, 1);                          /* {} */
        }

        return 1;
    }

    int pickOne(lua_State * L)
    {
        int argc = lua_gettop(L);
        Engine::Object *obj;
        Engine::PickedObjectRef por;

        if (!(argc == 3
                && lua_isnumber(L, 1)
                && lua_isnumber(L, 2)
                && lua_istable(L, 3)))
            goto fail;
        obj = object::getObject(L, 3);
        if (obj == NULL)
            goto fail;
        por = g_engine->pickOne(lua_tointeger(L, 1), lua_tointeger(L, 2), obj);
        if (por.isNull())
            goto fail;
        lua_getfield(L, LUA_GLOBALSINDEX, "ag");/* {ag} */
        lua_getfield(L, -1, "_objects");        /* {ag,_objects} */
        lua_pushinteger(L, por->id);            /* {ag,_objects,id} */
        lua_gettable(L, -2);                    /* {ag,_objects,obj} */
        lua_remove(L, -3);                      /* {_objects,obj} */
        lua_remove(L, -2);                      /* {obj} */

        lua_pushstring(L, "pick_dist");         /* {obj,"pick_dist"} */
        lua_pushnumber(L, por->dist);           /* {obj,"pick_dist",dist} */
        lua_settable(L, -3);                    /* {obj} */
        lua_createtable(L, 3, 0);               /* {obj,arr} */
        for (int i = 0; i < 3; i++)
        {
            lua_pushinteger(L, i + 1);          /* {obj,arr,index} */
            lua_pushnumber(L, por->pos[i]);     /* {obj,arr,index,pos_x} */
            lua_settable(L, -3);                /* {obj,arr} */
        }
        lua_pushstring(L, "pick_pos");          /* {obj,arr,"pick_pos"} */
        lua_pushvalue(L, -2);                   /* {obj,arr,"pick_pos",arr} */
        lua_settable(L, -4);                    /* {obj,arr} */
        lua_pop(L, 1);                          /* {obj} */
        lua_createtable(L, 3, 0);               /* {obj,arr} */
        for (int i = 0; i < 3; i++)
        {
            lua_pushinteger(L, i + 1);          /* {obj,arr,index} */
            lua_pushnumber(L, por->normal[i]);  /* {obj,arr,index,n} */
            lua_settable(L, -3);                /* {obj,arr} */
        }
        lua_pushstring(L, "pick_normal");       /* {obj,arr,"pick_normal"} */
        lua_pushvalue(L, -2);                  /* {obj,arr,"pick_normal",arr} */
        lua_settable(L, -4);                    /* {obj,arr} */
        lua_pop(L, 1);                          /* {obj} */
        lua_pop(L, 1);                          /* {} */

        lua_pushboolean(L, true);
        return 1;

fail:
        lua_pushboolean(L, false);
        return 1;
    }

    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;
    }

    int next(lua_State * L)
    {
        int argc = lua_gettop(L);
        if (argc == 1 || argc == 2)
        {
            if (argc == 1)
            {
                lua_pushnil(L);
            }
            else
            {
                lua_pushvalue(L, 2);
            }
            if (lua_next(L, 1) == 0)
            {
                lua_pushnil(L);
                return 1;
            }
            return 2;
        }
        else
        {
            lua_pushnil(L);
            return 1;
        }
    }

    int type(lua_State * L)
    {
        int argc = lua_gettop(L);
        if (argc == 1)
        {
            int type = lua_type(L, 1);
            switch (type)
            {
                case LUA_TNUMBER:
                    lua_pushstring(L, "number");
                    break;
                case LUA_TBOOLEAN:
                    lua_pushstring(L, "boolean");
                    break;
                case LUA_TSTRING:
                    lua_pushstring(L, "string");
                    break;
                case LUA_TTABLE:
                    lua_pushstring(L, "table");
                    break;
                case LUA_TFUNCTION:
                    lua_pushstring(L, "function");
                    break;
                case LUA_TTHREAD:
                    lua_pushstring(L, "thread");
                    break;
                case LUA_TUSERDATA:
                case LUA_TLIGHTUSERDATA:
                    lua_pushstring(L, "userdata");
                    break;
                case LUA_TNIL:
                default:
                    lua_pushstring(L, "nil");
                    break;
            }
        }
        else
        {
            lua_pushstring(L, "nil");
        }
        return 1;
    }

    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::setTransparency);
        lua_setfield(L, -2, "setTransparency");
        lua_pushcfunction(L, object::setGravityMode);
        lua_setfield(L, -2, "setGravityMode");
        lua_pushcfunction(L, object::setTexture);
        lua_setfield(L, -2, "setTexture");
        lua_pushcfunction(L, object::setTextureScale);
        lua_setfield(L, -2, "setTextureScale");
        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");

        /* ag._objects[id] = obj */
        lua_getfield(L, LUA_GLOBALSINDEX, "ag");        /* pushes 1 */
        lua_getfield(L, -1, "_objects");                /* pushes 1 */
        lua_pushinteger(L, id);                         /* pushes 1 */
        lua_pushvalue(L, -4);                           /* pushes 1 */
        lua_settable(L, -3);                            /* pops 2 */
        lua_pop(L, 2);                                  /* pops 2 */
    }

    static void createLuaSound(lua_State * L, int id)
    {
        lua_newtable(L);
        lua_pushinteger(L, id);
        lua_setfield(L, -2, "id");
        lua_pushcfunction(L, sound::play);
        lua_setfield(L, -2, "play");
        lua_pushcfunction(L, sound::resume);
        lua_setfield(L, -2, "resume");
        lua_pushcfunction(L, sound::stop);
        lua_setfield(L, -2, "stop");
        lua_pushcfunction(L, sound::loop);
        lua_setfield(L, -2, "loop");
        lua_pushcfunction(L, sound::loopForever);
        lua_setfield(L, -2, "loopForever");
        lua_pushcfunction(L, sound::getVolume);
        lua_setfield(L, -2, "getVolume");
        lua_pushcfunction(L, sound::setVolume);
        lua_setfield(L, -2, "setVolume");
    }

    static void createLuaAMotor(lua_State * L, int id)
    {
        lua_newtable(L);
        lua_pushinteger(L, id);
        lua_setfield(L, -2, "id");
        lua_pushcfunction(L, joint::setAMotorLoStop);
        lua_setfield(L, -2, "setLoStop");
        lua_pushcfunction(L, joint::setAMotorHiStop);
        lua_setfield(L, -2, "setHiStop");
        lua_pushcfunction(L, joint::setAMotorVel);
        lua_setfield(L, -2, "setVel");
        lua_pushcfunction(L, joint::setAMotorFMax);
        lua_setfield(L, -2, "setFMax");
        lua_pushcfunction(L, joint::setAMotorBounce);
        lua_setfield(L, -2, "setBounce");
        lua_pushcfunction(L, joint::setAMotorAxis);
        lua_setfield(L, -2, "setAxis");
        lua_pushcfunction(L, joint::setAMotorNumAxes);
        lua_setfield(L, -2, "setNumAxes");
        lua_pushcfunction(L, joint::setAMotorAngle);
        lua_setfield(L, -2, "setAngle");
    }

    static void createLuaHinge(lua_State * L, int id)
    {
        lua_newtable(L);
        lua_pushinteger(L, id);
        lua_setfield(L, -2, "id");
    }

    int _createAMotor(lua_State * L)
    {
        int argc = lua_gettop(L);
        if (argc == 2)
        {
            Engine::Object * o1 = object::getObject(L, 1);
            Engine::Object * o2 = object::getObject(L, 2);
            /* it is ok if o1 or o2 are NULL, this means that the joint
             * is connected to the global environment */
            if (o1 != NULL || o2 != NULL)
            {
                int jid = g_engine->addAMotor(o1, o2);
                if (jid != 0)
                {
                    createLuaAMotor(L, jid);
                    return 1;
                }
            }
        }
        lua_pushnil(L);
        return 1;
    }

    int _createHinge(lua_State * L)
    {
        int argc = lua_gettop(L);
        if (argc == 8
                && lua_isnumber(L, 3)
                && lua_isnumber(L, 4)
                && lua_isnumber(L, 5)
                && lua_isnumber(L, 6)
                && lua_isnumber(L, 7)
                && lua_isnumber(L, 8))
        {
            Engine::Object * o1 = object::getObject(L, 1);
            Engine::Object * o2 = object::getObject(L, 2);
            /* it is ok if o1 or o2 are NULL, this means that the joint
             * is connected to the global environment */
            if (o1 != NULL || o2 != NULL)
            {
                int jid = g_engine->addHinge(o1, o2,
                        lua_tonumber(L, 3),
                        lua_tonumber(L, 4),
                        lua_tonumber(L, 5),
                        lua_tonumber(L, 6),
                        lua_tonumber(L, 7),
                        lua_tonumber(L, 8));
                if (jid != 0)
                {
                    createLuaHinge(L, jid);
                    return 1;
                }
            }
        }
        lua_pushnil(L);
        return 1;
    }

    int loadModelSpecify(lua_State * L)
    {
        bool added = false;
        int argc = lua_gettop(L);

        if (argc == 5
                && lua_isstring(L, 1)
                && lua_isnumber(L, 2)
                && lua_isboolean(L, 3)
                && lua_isboolean(L, 4)
                && lua_isboolean(L, 5))
        {
            int id = g_engine->loadModel(
                    lua_tostring(L, 1),     /* name */
                    lua_toboolean(L, 3),    /* static */
                    lua_toboolean(L, 4),    /* reference */
                    lua_toboolean(L, 5),    /* enable blending */
                    lua_tonumber(L, 2));    /* scale */
            if (id > 0)
            {
                createLuaObject(L, id);
                added = true;
            }
        }
        if (!added)
            lua_pushnil(L);
        return 1;
    }

    int loadSound(lua_State * L)
    {
        bool created = false;
        int argc = lua_gettop(L);
        if (argc == 1 && lua_isstring(L, 1))
        {
            int id = g_engine->loadSound(lua_tostring(L, 1));
            if (id > 0)
            {
                created = true;
                createLuaSound(L, id);
            }
        }
        if (!created)
        {
            lua_pushnil(L);
        }
        return 1;
    }

    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 setCursorVisible(lua_State * L)
    {
        int argc = lua_gettop(L);
        if (argc == 1 && lua_isboolean(L, 1))
        {
            g_engine->setScriptCursorVisible(lua_toboolean(L, 1));
        }
        return 0;
    }

    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 getCursorVisible(lua_State * L)
    {
        lua_pushboolean(L, g_engine->getScriptCursorVisible());
        return 1;
    }

    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 createQuad(lua_State * L)
    {
        int argc = lua_gettop(L);
        bool valid = true;
        if (argc == 9)
        {
            for (int i = 1; i <= argc; i++)
            {
                if (!lua_isnumber(L, i))
                {
                    valid = false;
                    break;
                }
            }
        }
        else
        {
            valid = false;
        }
        if (valid)
        {
            int qid = g_engine->addQuad(
                    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),
                    lua_tonumber(L, 9));
            lua_newtable(L);
            lua_pushinteger(L, qid);
            lua_setfield(L, -2, "id");
            lua_pushcfunction(L, quad::setBlending);
            lua_setfield(L, -2, "setBlending");
            lua_pushcfunction(L, quad::setColor);
            lua_setfield(L, -2, "setColor");
            lua_pushcfunction(L, quad::setOffset);
            lua_setfield(L, -2, "setOffset");
            lua_pushcfunction(L, quad::setPosition);
            lua_setfield(L, -2, "setPosition");
            lua_pushcfunction(L, quad::setTexture);
            lua_setfield(L, -2, "setTexture");
            lua_pushcfunction(L, quad::setVisible);
            lua_setfield(L, -2, "setVisible");
        }
        else
        {
            lua_pushnil(L);
        }
        return 1;
    }

    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 drawPoint(lua_State * L)
    {
        int argc = lua_gettop(L);
        if (argc == 6 || argc == 7)
        {
            bool valid = true;
            for (int i = 1; i <= argc; i++)
                if (!lua_isnumber(L, i))
                    valid = false;
            if (valid)
            {
                float z = 0.0f;
                if (argc == 7)
                    z = lua_tonumber(L, 7);
                g_engine->drawPoint(
                        lua_tonumber(L, 1),
                        lua_tonumber(L, 2),
                        lua_tonumber(L, 3),
                        lua_tonumber(L, 4),
                        lua_tonumber(L, 5),
                        lua_tonumber(L, 6),
                        z);
            }
        }
        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 fillArc(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->fillArc(
                        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 fillCircle(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->fillArc(
                        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 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,
            bool is_reference, bool enable_blending,
            OdeWorld::GeomType geom_type, refptr< vector<float> > args)
    {
        int id = g_engine->addObject(is_static, is_reference, enable_blending,
                geom_type, args);
        createLuaObject(L, id);
    }

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

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

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

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

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

    int startList(lua_State * L)
    {
        GLuint list = g_engine->startList();
        lua_pushnumber(L, list);
        return 1;
    }

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

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

    int callList(lua_State * L)
    {
        int argc = lua_gettop(L);
        if (argc == 1 && lua_isnumber(L, 1))
        {
            g_engine->callList(lua_tointeger(L, 1));
        }
        return 0;
    }

    namespace object
    {
        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);

                    /* ag._objects[id] = nil */
                    lua_getfield(L, LUA_GLOBALSINDEX, "ag");    /* pushes 1 */
                    lua_getfield(L, -1, "_objects");            /* pushes 1 */
                    lua_pushinteger(L, id);                     /* pushes 1 */
                    lua_pushnil(L);                             /* pushes 1 */
                    lua_settable(L, -3);                        /* pops 2 */
                    lua_pop(L, 2);                              /* pops 2 */
                }
                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 setTransparency(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 2 && lua_isnumber(L, 2))
            {
                Engine::Object * obj = getObject(L, 1);
                if (obj != NULL)
                {
                    obj->setTransparency(lua_tonumber(L, 2));
                }
            }
            return 0;
        }

        int setGravityMode(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 2 && lua_isboolean(L, 2))
            {
                Engine::Object * obj = getObject(L, 1);
                if (obj != NULL)
                {
                    obj->setGravityMode(lua_toboolean(L, 2));
                }
            }
            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 setTextureScale(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->setTextureScale(lua_tonumber(L, 2));
                }
            }
            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;
        }
    }

    namespace quad
    {
        Engine::Quad * getQuad(lua_State * L, int index)
        {
            Engine::Quad * ret = NULL;

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

            return ret;
        }

        int setBlending(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 2 && lua_istable(L, 1) && lua_isboolean(L, 2))
            {
                Engine::Quad * q = getQuad(L, 1);
                if (q != NULL)
                {
                    q->setBlending(lua_toboolean(L, 2));
                }
            }
            return 0;
        }

        int setColor(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 5 && lua_istable(L, 1)
                    && lua_isnumber(L, 2)
                    && lua_isnumber(L, 3)
                    && lua_isnumber(L, 4)
                    && lua_isnumber(L, 5))
            {
                Engine::Quad * q = getQuad(L, 1);
                if (q != NULL)
                {
                    q->setColor(lua_tonumber(L, 2),
                            lua_tonumber(L, 3),
                            lua_tonumber(L, 4),
                            lua_tonumber(L, 5));
                }
            }
            return 0;
        }

        int setOffset(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 2 && lua_istable(L, 1) && lua_isnumber(L, 2))
            {
                Engine::Quad * q = getQuad(L, 1);
                if (q != NULL)
                {
                    q->setOffset(lua_tonumber(L, 2));
                }
            }
            return 0;
        }

        int setPosition(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 4 && lua_istable(L, 1)
                    && lua_isnumber(L, 2)
                    && lua_isnumber(L, 3)
                    && lua_isnumber(L, 4))
            {
                Engine::Quad * q = getQuad(L, 1);
                if (q != NULL)
                {
                    q->setPosition(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::Quad * q = getQuad(L, 1);
                if (q != NULL)
                {
                    lua_getfield(L, 2, "id");
                    if (lua_isnumber(L, -1))
                    {
                        GLuint id = (GLuint) lua_tointeger(L, -1);
                        q->setTexture(id);
                    }
                    lua_pop(L, 1);
                }
            }
            return 0;
        }

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

    namespace joint
    {
        int setAMotorAxis(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 6
                    && lua_istable(L, 1)
                    && lua_isnumber(L, 2)
                    && lua_isnumber(L, 3)
                    && lua_isnumber(L, 4)
                    && lua_isnumber(L, 5)
                    && lua_isnumber(L, 6))
            {
                lua_getfield(L, 1, "id");
                if (lua_isnumber(L, -1))
                {
                    int jid = lua_tointeger(L, -1);
                    g_engine->setAMotorAxis(jid,
                            lua_tointeger(L, 2),
                            lua_tointeger(L, 3),
                            lua_tonumber(L, 4),
                            lua_tonumber(L, 5),
                            lua_tonumber(L, 6));
                }
                lua_pop(L, 1);
            }
            return 0;
        }

        int setAMotorNumAxes(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 2 && lua_istable(L, 1) && lua_isnumber(L, 2))
            {
                lua_getfield(L, 1, "id");
                if (lua_isnumber(L, -1))
                {
                    int jid = lua_tointeger(L, -1);
                    g_engine->setAMotorNumAxes(jid, lua_tointeger(L, 2));
                }
                lua_pop(L, 1);
            }
            return 0;
        }

        int setAMotorAngle(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 3
                    && lua_istable(L, 1)
                    && lua_isnumber(L, 2)
                    && lua_isnumber(L, 3))
            {
                lua_getfield(L, 1, "id");
                if (lua_isnumber(L, -1))
                {
                    int jid = lua_tointeger(L, -1);
                    g_engine->setAMotorAngle(jid,
                            lua_tointeger(L, 2),
                            lua_tonumber(L, 3));
                }
                lua_pop(L, 1);
            }
            return 0;
        }

        int setAMotorLoStop(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 2 && lua_istable(L, 1) && lua_isnumber(L, 2))
            {
                lua_getfield(L, 1, "id");
                if (lua_isnumber(L, -1))
                {
                    int jid = lua_tointeger(L, -1);
                    g_engine->setAMotorLoStop(jid, lua_tonumber(L, 2));
                }
                lua_pop(L, 1);
            }
            return 0;
        }

        int setAMotorHiStop(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 2 && lua_istable(L, 1) && lua_isnumber(L, 2))
            {
                lua_getfield(L, 1, "id");
                if (lua_isnumber(L, -1))
                {
                    int jid = lua_tointeger(L, -1);
                    g_engine->setAMotorHiStop(jid, lua_tonumber(L, 2));
                }
                lua_pop(L, 1);
            }
            return 0;
        }

        int setAMotorVel(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 2 && lua_istable(L, 1) && lua_isnumber(L, 2))
            {
                lua_getfield(L, 1, "id");
                if (lua_isnumber(L, -1))
                {
                    int jid = lua_tointeger(L, -1);
                    g_engine->setAMotorVel(jid, lua_tonumber(L, 2));
                }
                lua_pop(L, 1);
            }
            return 0;
        }

        int setAMotorFMax(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 2 && lua_istable(L, 1) && lua_isnumber(L, 2))
            {
                lua_getfield(L, 1, "id");
                if (lua_isnumber(L, -1))
                {
                    int jid = lua_tointeger(L, -1);
                    g_engine->setAMotorFMax(jid, lua_tonumber(L, 2));
                }
                lua_pop(L, 1);
            }
            return 0;
        }

        int setAMotorBounce(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 2 && lua_istable(L, 1) && lua_isnumber(L, 2))
            {
                lua_getfield(L, 1, "id");
                if (lua_isnumber(L, -1))
                {
                    int jid = lua_tointeger(L, -1);
                    g_engine->setAMotorBounce(jid, lua_tonumber(L, 2));
                }
                lua_pop(L, 1);
            }
            return 0;
        }
    }

    namespace sound
    {
        refptr<AV::Sound> getSound(lua_State * L, int index)
        {
            refptr<AV::Sound> ret;

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

            return ret;
        }

        int play(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 1 && lua_istable(L, 1))
            {
                refptr<AV::Sound> snd = getSound(L, 1);
                if (!snd.isNull())
                {
                    snd->play();
                }
            }
            return 0;
        }

        int resume(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 1 && lua_istable(L, 1))
            {
                refptr<AV::Sound> snd = getSound(L, 1);
                if (!snd.isNull())
                {
                    snd->resume();
                }
            }
            return 0;
        }

        int stop(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 1 && lua_istable(L, 1))
            {
                refptr<AV::Sound> snd = getSound(L, 1);
                if (!snd.isNull())
                {
                    snd->stop();
                }
            }
            return 0;
        }

        int loop(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 2 && lua_istable(L, 1) && lua_isnumber(L, 2))
            {
                refptr<AV::Sound> snd = getSound(L, 1);
                if (!snd.isNull())
                {
                    int count = lua_tointeger(L, 2);
                    snd->loop(count);
                }
            }
            return 0;
        }

        int loopForever(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 1 && lua_istable(L, 1))
            {
                refptr<AV::Sound> snd = getSound(L, 1);
                if (!snd.isNull())
                {
                    snd->loop(INT_MAX);
                }
            }
            return 0;
        }

        int getVolume(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 1 && lua_istable(L, 1))
            {
                refptr<AV::Sound> snd = getSound(L, 1);
                if (!snd.isNull())
                {
                    float v = snd->getVolume();
                    lua_pushnumber(L, v);
                    return 1;
                }
            }
            return 0;
        }

        int setVolume(lua_State * L)
        {
            int argc = lua_gettop(L);
            if (argc == 2 && lua_istable(L, 1) && lua_isnumber(L, 2))
            {
                refptr<AV::Sound> snd = getSound(L, 1);
                if (!snd.isNull())
                {
                    float volume = lua_tonumber(L, 2);
                    snd->setVolume(volume);
                }
            }
            return 0;
        }
    }
}
