
#include "ag.h"
#include "anaglym.h"
#include "Engine.h"
#include "Video.h"
#include "sdl_keymap.h"
#include <lua.hpp>
#include <stdlib.h>                 /* exit() */
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <iostream>
#include <sstream>
#include <string>
#include <math.h>                   /* fabs() */
#include <GL/gl.h>
#include <GL/glu.h>
#include "SDL.h"
using namespace std;

#define AG_EVENT_PREFIX "__ag_event_"
#define EVENT_PRESENT_FLAG(event) m_event_ ## event ## _present
#define EVENT_HANDLER_AG_NAME(event) AG_EVENT_PREFIX #event
#define checkForFunction(lua_name, event) \
    checkForFunctionFull(lua_name, #event, EVENT_PRESENT_FLAG(event))
#define doRegisterHandler(index, event) \
    doRegisterHandlerFull(index, EVENT_HANDLER_AG_NAME(event), \
            EVENT_PRESENT_FLAG(event))
#define doClearHandler(event) \
    do { EVENT_PRESENT_FLAG(event) = false; } while(0)

#define WHITESPACE " \t\r\n\f"
#define HOOK_TIMEOUT    10000u
#define HOOK_STEPS      250000000
#define FONT_NAME       "FreeSans.ttf"

#define FP_EQ(a,b) (fabsf(a - b) < 0.0001)

/* Global data */
Engine * g_engine;

/* static helper functions */
static string trim(const string & orig)
{
    string result = orig;
    size_t pos = result.find_first_not_of(WHITESPACE);
    if (pos == string::npos)
    {
        result = "";
    }
    else
    {
        if (pos > 0)
            result = result.substr(pos, result.length() - pos);
        pos = result.find_last_not_of(WHITESPACE);
        if (pos < result.length() - 1)
            result = result.substr(0, pos + 1);
    }
    return result;
}

static void cross_product(dVector3 result, dVector3 first, dVector3 second)
{
    result[0] = first[1] * second[2] - first[2] * second[1];
    result[1] = first[2] * second[0] - first[0] * second[2];
    result[2] = first[0] * second[1] - first[1] * second[0];
}

static void debug_hook(lua_State * L, lua_Debug * debug)
{
    g_engine->debug_hook(debug);
}

#define DEBUG_GL_ERROR
#ifdef DEBUG_GL_ERROR
#define checkGLError() checkGLErrorLine(__FUNCTION__, __LINE__)
static void checkGLErrorLine(const char * function, int line)
{
    GLenum err = glGetError();
    if (err != 0)
    {
        cerr << "gl error in " << function
            << ": " << err << " (0x" << hex << err << ") at line "
            << dec << line << endl;
    }
}
#else
#define checkGLError()
#endif


/******** Engine functions ********/

Engine::Engine(const string & path, Video & video)
    : m_video(video)
{
    m_next_object_index = 1;
    m_next_text_index = 1;
    m_eye[0] = 0;
    m_eye[1] = -1;
    m_eye[2] = 0;
    m_center[0] = 0;
    m_center[1] = 0;
    m_center[2] = 0;
    m_up[0] = 0;
    m_up[1] = 0;
    m_up[2] = 1;
    m_drawing = false;
    m_autoPhysics = true;
    m_autoStartFrame = true;
    m_autoEndFrame = true;
    m_autoDrawObjects = true;
    m_fileLoader = new EngineFileLoader(this);
    m_event_time = 0;
    m_font = NULL;

    size_t pos = path.find_last_of("\\/");
    m_engine_path = (pos != string::npos) ? string(path, 0, pos) : ".";

    /* setup SDL user event structures */
    m_updateEvent.type = SDL_USEREVENT;
    m_updateEvent.user.code = 0;
    m_exitEvent.type = SDL_USEREVENT;
    m_exitEvent.user.code = 1;

    m_event_init_present = false;
    m_event_update_present = false;
    m_event_update_overlay_present = false;
    m_event_key_down_present = false;
    m_event_key_up_present = false;
    m_event_mousebutton_down_present = false;
    m_event_mousebutton_up_present = false;
    m_event_mouse_motion_present = false;

    m_luaState = lua_open();

    registerLibraries();
}

Engine::~Engine()
{
    lua_close(m_luaState);
    for (std::map<int, Object *>::iterator it = m_objects.begin();
         it != m_objects.end();
         it++)
    {
        delete it->second;
    }
    delete m_fileLoader;
    if (m_font != NULL)
        delete m_font;
}

bool Engine::load(const char * program)
{
    m_program_path = program;
    m_program_directory = program;
    size_t pos = m_program_directory.find_last_of("/\\");
    m_program_directory = (pos != string::npos)
                        ? m_program_directory.substr(0, pos)
                        : ".";

    string path = locateResource(FONT_NAME);
    if (path == "")
    {
        cerr << "Couldn't locate " FONT_NAME << endl;
        return false;
    }
    else
    {
        m_font = new FTBufferFont(path.c_str());
        //m_font = new FTTextureFont(path.c_str());
        if (m_font->Error() != 0)
        {
            cerr << "Error loading font '" << path << "'" << endl;
        }
    }

    int s = luaL_loadfile(m_luaState, program);

    if (s == 0)
    {
        // execute Lua program
        lua_sethook(m_luaState, ::debug_hook, LUA_MASKCOUNT, HOOK_STEPS);
        s = lua_pcall(m_luaState, 0, 0, 0);
    }
    else
    {
        reportErrors(s);
        cerr << "Error loading '" << program << "'" << endl;
        cerr << "Exiting." << endl;
        return false;
    }

    if (s != 0)
    {
        reportErrors(s);
        return false;
    }

    checkForAllHandlerFunctions();

    if (m_event_init_present)
    {
        lua_getfield(m_luaState, LUA_GLOBALSINDEX,
                EVENT_HANDLER_AG_NAME(init));
        /* call the init function - pops the function ref from the stack */
        int s = lua_pcall(m_luaState, 0, 0, 0);
        reportErrors(s);
    }

    return true;
}

void Engine::checkForAllHandlerFunctions()
{
    checkForFunction("init_event", init);
    checkForFunction("update_event", update);
    checkForFunction("update_overlay_event", update_overlay);
    checkForFunction("key_down_event", key_down);
    checkForFunction("key_up_event", key_up);
    checkForFunction("mousebutton_down_event", mousebutton_down);
    checkForFunction("mousebutton_up_event", mousebutton_up);
    checkForFunction("mouse_motion_event", mouse_motion);
}

void Engine::reportErrors(int status)
{
    if (status != 0)
    {
        cerr << "Engine: Error: " << lua_tostring(m_luaState, -1) << endl;
        lua_pop(m_luaState, 1); // remove error message
    }
}

void Engine::registerLibraries()
{
    /* Load the Lua string library */
    lua_pushcfunction(m_luaState, luaopen_string);
    lua_pcall(m_luaState, 0, 0, 0);

    /* Load the Lua math library */
    lua_pushcfunction(m_luaState, luaopen_math);
    lua_pcall(m_luaState, 0, 0, 0);

    /* Load the Lua table library */
    lua_pushcfunction(m_luaState, luaopen_table);
    lua_pcall(m_luaState, 0, 0, 0);

    ag::register_functions(m_luaState);
}

string Engine::locateResource(const string & shortname)
{
    string try_path;

    /* look for the resource relative to the loaded script's directory */
    try_path = m_program_directory + "/" + shortname;
    if (fileExists(try_path))
        return try_path;

    /* next look for the resource in the engine's library directory */
    try_path = m_engine_path + "/lib/" + shortname;
    if (fileExists(try_path))
        return try_path;

    return "";
}

bool Engine::fileExists(const string & path)
{
    struct stat st;
    if (stat(path.c_str(), &st) == 0)
    {
        return S_ISREG(st.st_mode) && (st.st_size > 0);
    }
    return false;
}

int Engine::addObject(WFObj * obj, bool is_static, float scale)
{
    int id = m_next_object_index++;
    Object * o = new Object(is_static, m_world, obj, scale);
    m_objects[id] = o;
    return id;
}

int Engine::addObject(bool is_static, OdeWorld::GeomType geom_type,
        refptr< vector<float> > args)
{
    int id = m_next_object_index++;
    Object * o = new Object(is_static, m_world, geom_type, args);
    m_objects[id] = o;
    return id;
}

void Engine::removeObject(int id)
{
    Object * obj = getObject(id);
    if (obj != NULL)
    {
        m_objects.erase(id);
        delete obj;
    }
}

int Engine::cloneObject(const Engine::Object * obj)
{
    int id = m_next_object_index++;
    Object * o = new Object(*obj);
    m_objects[id] = o;
    return id;
}

Engine::Object * Engine::getObject(int id)
{
    return m_objects.find(id) != m_objects.end() ? m_objects[id] : NULL;
}

void Engine::startFrame()
{
    checkGLError();
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt(m_eye[0], m_eye[1], m_eye[2],
              m_center[0], m_center[1], m_center[2],
              m_up[0], m_up[1], m_up[2]);
    checkGLError();
}

void Engine::endFrame()
{
    SDL_GL_SwapBuffers();
}

int Engine::setCamera(lua_State * L)
{
    int argc = lua_gettop(L);

    vector<double> args;
    for (int i = 1; i <= argc; i++)
    {
        int type = lua_type(L, i);
        if (type == LUA_TNUMBER || type == LUA_TSTRING)
            args.push_back(lua_tonumber(L, i));
        else
            args.push_back(0);
    }

    if (argc >= 3)
    {
        m_eye[0] = args[0];
        m_eye[1] = args[1];
        m_eye[2] = args[2];
    }
    if (argc >= 6)
    {
        m_center[0] = args[3];
        m_center[1] = args[4];
        m_center[2] = args[5];
    }
    if (argc >= 9)
    {
        m_up[0] = args[6];
        m_up[1] = args[7];
        m_up[2] = args[8];
    }

    return 0;
}

int Engine::getCamera(lua_State * L)
{
    lua_pushnumber(L, m_eye[0]);
    lua_pushnumber(L, m_eye[1]);
    lua_pushnumber(L, m_eye[2]);
    lua_pushnumber(L, m_center[0]);
    lua_pushnumber(L, m_center[1]);
    lua_pushnumber(L, m_center[2]);
    lua_pushnumber(L, m_up[0]);
    lua_pushnumber(L, m_up[1]);
    lua_pushnumber(L, m_up[2]);
    return 9;
}

int Engine::registerEventHandler(lua_State * L)
{
    int argc = lua_gettop(L);
    if (argc == 2 && lua_isstring(L, 1) && lua_isfunction(L, 2))
    {
        string event = lua_tostring(L, 1);
        if      (event == "update")
            doRegisterHandler(2, update);
        else if (event == "update_overlay")
            doRegisterHandler(2, update_overlay);
        else if (event == "key_down")
            doRegisterHandler(2, key_down);
        else if (event == "key_up")
            doRegisterHandler(2, key_up);
        else if (event == "mousebutton_down")
            doRegisterHandler(2, mousebutton_down);
        else if (event == "mousebutton_up")
            doRegisterHandler(2, mousebutton_up);
        else if (event == "mouse_motion")
            doRegisterHandler(2, mouse_motion);
    }
    return 0;
}

int Engine::clearEventHandler(lua_State * L)
{
    int argc = lua_gettop(L);
    if (argc == 1 && lua_isstring(L, 1))
    {
        string event = lua_tostring(L, 1);
        if      (event == "update")
            doClearHandler(update);
        else if (event == "update_overlay")
            doClearHandler(update_overlay);
        else if (event == "key_down")
            doClearHandler(key_down);
        else if (event == "key_up")
            doClearHandler(key_up);
        else if (event == "mousebutton_down")
            doClearHandler(mousebutton_down);
        else if (event == "mousebutton_up")
            doClearHandler(mousebutton_up);
        else if (event == "mouse_motion")
            doClearHandler(mouse_motion);
    }
    return 0;
}

int Engine::loadModel(const string & name, bool static_data, float scale)
{
    size_t pos = name.find_first_not_of(FILENAME_SAFE_CHARS);
    if (pos == string::npos)
    {
        FileLoader::Path model_path("", name + ".obj");
        FileLoader::Path phys_path("", name + ".phy");

        WFObj * obj = new WFObj(*m_fileLoader, m_textureCache);

        if (obj->load(model_path))
        {
            int id = addObject(obj, static_data, scale);
            Engine::Object * engine_obj = getObject(id);
            if (engine_obj != NULL)
            {
                engine_obj->loadPhy(phys_path);
            }
            return id;
        }
        else
        {
            cerr << "error loading object '" << name << "'" << endl;
        }
        delete obj;
    }
    return 0;
}

bool Engine::isKeyDown(const std::string & key)
{
    return m_keysDown.find(key) != m_keysDown.end();
}

void Engine::exit()
{
    SDL_PushEvent(&m_exitEvent);
}

bool Engine::import(const char * name)
{
    string nm(name);
    size_t pos = nm.find_first_not_of(FILENAME_SAFE_CHARS);
    if (pos == string::npos)
    {
        string file_path = locateResource(nm + ".lua");
        if (file_path != "")
        {
            return importFullPath(file_path.c_str());
        }
    }
    return false;
}

bool Engine::importFullPath(const char * path)
{
    int s = luaL_loadfile(m_luaState, path);
    if (s == 0)
    {
        s = lua_pcall(m_luaState, 0, 0, 0);
        if (s == 0)
            return true;
        reportErrors(s);
    }
    else
    {
        cerr << "Error " << s << " loading '" << path << "'" << endl;
    }
    return false;
}

GLuint Engine::loadTexture(const char * name)
{
    FileLoader::Path path("", name);
    return m_textureCache.load(path, *m_fileLoader);
}

void Engine::debug_hook(lua_Debug * debug)
{
    Uint32 ticks = SDL_GetTicks();
    if (ticks - m_event_time > HOOK_TIMEOUT)
    {
        ::exit(3);
    }
}

void Engine::getScreenSize(int * width, int * height)
{
    *width = m_video.getWidth();
    *height = m_video.getHeight();
}

void Engine::drawText(const char * text, GLfloat r, GLfloat g, GLfloat b,
        int ptsize, float x, float y)
{
    checkGLError();
    m_font->FaceSize(ptsize);
    glPushAttrib(GL_ENABLE_BIT | GL_TEXTURE_BIT);
        glEnable(GL_TEXTURE_2D);
        glDisable(GL_LIGHTING);
        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_BLEND);
        glPushMatrix();
            glTranslatef(x, y, 0);
            glColor3f(r, g, b);
            m_font->Render(text);
        glPopMatrix();
    glPopAttrib();
    checkGLError();
}

void Engine::getTextSize(const char * text, int ptsize,
        float * width, float * height)
{
    m_font->FaceSize(ptsize);
    FTBBox box = m_font->BBox(text);
    *width = box.Upper().Xf() - box.Lower().Xf();
    *height = box.Upper().Yf() - box.Lower().Yf();
}

void Engine::drawLine(float r, float g, float b,
        float x1, float y1, float x2, float y2, float width)
{
    checkGLError();
    glPushAttrib(GL_LINE_BIT | GL_ENABLE_BIT);
        glEnable(GL_LINE_SMOOTH);
        glLineWidth(width);
        glBegin(GL_LINES);
            glColor3f(r, g, b);
            glVertex2f(x1, y1);
            glVertex2f(x2, y2);
        glEnd();
    glPopAttrib();
    checkGLError();
}

void Engine::drawRect(float r, float g, float b,
        float width, float height, float x, float y, float rot)
{
    checkGLError();
    glPushAttrib(GL_ENABLE_BIT);
        glPushMatrix();
            glTranslatef(x, y, 0);
            glRotatef(rot, 0, 0, 1);
            glEnable(GL_LINE_SMOOTH);
            glBegin(GL_LINE_LOOP);
                glColor3f(r, g, b);
                glVertex2f(-width / 2, -height / 2);
                glVertex2f(width / 2, -height / 2);
                glVertex2f(width / 2, height / 2);
                glVertex2f(-width / 2, height / 2);
            glEnd();
        glPopMatrix();
    glPopAttrib();
    checkGLError();
}

void Engine::fillRect(float r, float g, float b,
        float width, float height, float x, float y, float rot)
{
    checkGLError();
    glPushAttrib(GL_ENABLE_BIT);
        glPushMatrix();
            glTranslatef(x, y, 0);
            glRotatef(rot, 0, 0, 1);
            glEnable(GL_POLYGON_SMOOTH);
            glBegin(GL_QUADS);
                glColor3f(r, g, b);
                glVertex2f(-width / 2, -height / 2);
                glVertex2f(width / 2, -height / 2);
                glVertex2f(width / 2, height / 2);
                glVertex2f(-width / 2, height / 2);
            glEnd();
        glPopMatrix();
    glPopAttrib();
    checkGLError();
}

void Engine::drawImage(float width, float height, float x, float y,
        int tex, float rot)
{
    checkGLError();
    glPushAttrib(GL_ENABLE_BIT          /* for enablings */
            | GL_TEXTURE_BIT            /* texture bindings and env. mode */
            | GL_COLOR_BUFFER_BIT);     /* blend src. and dst. equations */
        glPushMatrix();
            glTranslatef(x, y, 0);
            glRotatef(rot, 0, 0, 1);
            glEnable(GL_POLYGON_SMOOTH);
            glEnable(GL_TEXTURE_2D);
            glEnable(GL_BLEND);
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
            glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
            glBindTexture(GL_TEXTURE_2D, tex);
            glBegin(GL_QUADS);
                glTexCoord2f(0, 0);
                glVertex2f(-width / 2, -height / 2);
                glTexCoord2f(1, 0);
                glVertex2f(width / 2, -height / 2);
                glTexCoord2f(1, 1);
                glVertex2f(width / 2, height / 2);
                glTexCoord2f(0, 1);
                glVertex2f(-width / 2, height / 2);
            glEnd();
        glPopMatrix();
    glPopAttrib();
    checkGLError();
}

void Engine::drawArc(float r, float g, float b, float x, float y,
        float radius, float a1, float a2)
{
    a1 *= M_PI / 180.0f;
    a2 *= M_PI / 180.0f;
    int segments = 1 + (int) (fabsf(a2 - a1) / (M_2_PI / 16.0f));
    float step = (a2 - a1) / segments;
    glPushAttrib(GL_ENABLE_BIT);
        glPushMatrix();
            glTranslatef(x, y, 0);
            glColor3f(r, g, b);
            glBegin(GL_LINE_STRIP);
            float angle = a1;
            for (int i = 0; i <= segments; i++)
            {
                glVertex2f(radius * cos(angle), radius * sin(angle));
                angle += step;
            }
            glEnd();
        glPopMatrix();
    glPopAttrib();
}

/* called by SDL when the update timer expires */
Uint32 Engine::updateCallback(Uint32 interval, void * param)
{
    Engine * engine = (Engine *) param;
    return engine->updateCallback(interval);
}

/* member update function to be called by our registered
 * SDL callback non-member function */
Uint32 Engine::updateCallback(Uint32 interval)
{
    if (!m_drawing)
    {
        SDL_PushEvent(&m_updateEvent);
    }
    return interval;
}

void Engine::run()
{
    /* register a screen redrawing SDL event */
    SDL_AddTimer(25, &updateCallback, this);

    SDL_Event event;
    while (SDL_WaitEvent(&event))
    {
        m_event_time = SDL_GetTicks();
        switch (event.type)
        {
            case SDL_KEYDOWN:
                switch (event.key.keysym.sym)
                {
                    case SDLK_ESCAPE:
                        goto RET;
                    case SDLK_F1:
                        m_video.toggleFullScreen();
                        break;
                    case SDLK_F2:
                        SDL_ShowCursor(
                                SDL_ShowCursor(SDL_QUERY) == SDL_ENABLE
                                ? SDL_DISABLE
                                : SDL_ENABLE);
                        SDL_WM_GrabInput(
                                SDL_WM_GrabInput(SDL_GRAB_QUERY) == SDL_GRAB_ON
                                ? SDL_GRAB_OFF
                                : SDL_GRAB_ON);
                        break;
                    case SDLK_F5:
                        reloadProgram();
                        break;
                    default:
                        key_down_event(event.key.keysym.sym);
                }
                break;
            case SDL_KEYUP:
                key_up_event(event.key.keysym.sym);
                break;
            case SDL_MOUSEBUTTONDOWN:
                mousebutton_down_event(event.button.button,
                        event.button.x, event.button.y);
                break;
            case SDL_MOUSEBUTTONUP:
                mousebutton_up_event(event.button.button,
                        event.button.x, event.button.y);
                break;
            case SDL_MOUSEMOTION:
                mouse_motion_event(event.motion.x, event.motion.y,
                        event.motion.xrel, event.motion.yrel);
                break;
            case SDL_QUIT:
                goto RET;
                break;
            case SDL_USEREVENT:
                switch (event.user.code)
                {
                    case 0:
                        update_event();
                        break;
                    case 1:
                        goto RET;
                        break;
                }
                break;
        }
    }
RET:
    ;
}

void Engine::update_event()
{
    m_drawing = true;
    if (m_autoPhysics)
        doPhysics();
    if (m_autoStartFrame)
        startFrame();
    if (m_event_update_present)
    {
        lua_getfield(m_luaState, LUA_GLOBALSINDEX,
                EVENT_HANDLER_AG_NAME(update));
        /* call the update function - pops the function ref from the stack */
        int s = lua_pcall(m_luaState, 0, 0, 0);
        reportErrors(s);
    }
    if (m_autoDrawObjects)
        drawObjects();
    update_overlay_event();
    if (m_autoEndFrame)
        endFrame();
    m_drawing = false;
}

void Engine::update_overlay_event()
{
    if (m_event_update_overlay_present)
    {
        checkGLError();
        glPushAttrib(GL_ENABLE_BIT);
            glDisable(GL_DEPTH_TEST);
            glDisable(GL_LIGHTING);
            int width = m_video.getWidth();
            int height = m_video.getHeight();
            glMatrixMode(GL_PROJECTION);
            glPushMatrix();
            glLoadIdentity();
            glOrtho(0, width, 0, height, -1.01, 1.01);
            glMatrixMode(GL_MODELVIEW);
            glLoadIdentity();

            lua_getfield(m_luaState, LUA_GLOBALSINDEX,
                    EVENT_HANDLER_AG_NAME(update_overlay));
            lua_pushnumber(m_luaState, width);
            lua_pushnumber(m_luaState, height);
            /* call the update_overlay function
             * - pops the function ref from the stack, then the arguments */
            int s = lua_pcall(m_luaState, 2, 0, 0);
            reportErrors(s);
            glMatrixMode(GL_PROJECTION);
            glPopMatrix();
            glMatrixMode(GL_MODELVIEW);
        glPopAttrib();
        checkGLError();
    }
}

void Engine::key_down_event(int keysym)
{
    m_keysDown[sdl_keymap[keysym]] = true;
    if (m_event_key_down_present)
    {
        lua_getfield(m_luaState, LUA_GLOBALSINDEX,
                EVENT_HANDLER_AG_NAME(key_down));
        lua_pushstring(m_luaState, sdl_keymap[keysym]);
        /* call the key down event function
         * This pops the function ref and arguments from the stack */
        int s = lua_pcall(m_luaState, 1, 0, 0);
        reportErrors(s);
    }
}

void Engine::key_up_event(int keysym)
{
    m_keysDown.erase(sdl_keymap[keysym]);
    if (m_event_key_up_present)
    {
        lua_getfield(m_luaState, LUA_GLOBALSINDEX,
                EVENT_HANDLER_AG_NAME(key_up));
        lua_pushstring(m_luaState, sdl_keymap[keysym]);
        /* call the key up event function
         * This pops the function ref and arguments from the stack */
        int s = lua_pcall(m_luaState, 1, 0, 0);
        reportErrors(s);
    }
}

void Engine::mousebutton_down_event(int button, int x, int y)
{
    if (m_event_mousebutton_down_present)
    {
        lua_getfield(m_luaState, LUA_GLOBALSINDEX,
                EVENT_HANDLER_AG_NAME(mousebutton_down));
        lua_pushinteger(m_luaState, button);
        lua_pushnumber(m_luaState, x);
        lua_pushnumber(m_luaState, y);
        /* call the mouse button down event function
         * This pops the function ref and arguments from the stack */
        int s = lua_pcall(m_luaState, 3, 0, 0);
        reportErrors(s);
    }
}

void Engine::mousebutton_up_event(int button, int x, int y)
{
    if (m_event_mousebutton_up_present)
    {
        lua_getfield(m_luaState, LUA_GLOBALSINDEX,
                EVENT_HANDLER_AG_NAME(mousebutton_up));
        lua_pushinteger(m_luaState, button);
        lua_pushnumber(m_luaState, x);
        lua_pushnumber(m_luaState, y);
        /* call the mouse button up event function
         * This pops the function ref and arguments from the stack */
        int s = lua_pcall(m_luaState, 3, 0, 0);
        reportErrors(s);
    }
}

void Engine::mouse_motion_event(int x, int y, int xrel, int yrel)
{
    if (m_event_mouse_motion_present)
    {
        lua_getfield(m_luaState, LUA_GLOBALSINDEX,
                EVENT_HANDLER_AG_NAME(mouse_motion));
        lua_pushnumber(m_luaState, x);
        lua_pushnumber(m_luaState, y);
        lua_pushnumber(m_luaState, xrel);
        lua_pushnumber(m_luaState, yrel);
        /* call the mouse motion event function
         * This pops the function ref and arguments from the stack */
        int s = lua_pcall(m_luaState, 4, 0, 0);
        reportErrors(s);
    }
}

void Engine::checkForFunctionFull(const std::string & lua_fn_name,
        const std::string & event_name, bool & presentFlag)
{
    if (!presentFlag)   /* only look for events we do not have a handler for */
    {
        lua_getfield(m_luaState, LUA_GLOBALSINDEX, lua_fn_name.c_str());
        if (lua_isfunction(m_luaState, -1))
        {
            lua_setfield(m_luaState, LUA_GLOBALSINDEX,
                    (AG_EVENT_PREFIX + event_name).c_str());
            presentFlag = true;
        }
        else
        {
            lua_pop(m_luaState, 1);
            presentFlag = false;
        }
    }
}

void Engine::doRegisterHandlerFull(int index,
        const std::string & event_name, bool & presentFlag)
{
    lua_pushvalue(m_luaState, index);
    lua_setfield(m_luaState, LUA_GLOBALSINDEX, event_name.c_str());
    presentFlag = true;
}

void Engine::reloadProgram()
{
    importFullPath(m_program_path.c_str());
    checkForAllHandlerFunctions();
}

void Engine::doPhysics()
{
    static Uint32 last_updated = 0;
    Uint32 current_ticks = SDL_GetTicks();
    if (last_updated > 0)
    {
        Uint32 msec_steps = current_ticks - last_updated;
        for (Uint32 i = 0; i < msec_steps; i++)
            m_world.step();
    }
    last_updated = current_ticks;
}

void Engine::drawObjects()
{
    std::map<int, Object *>::iterator it;
    for (it = m_objects.begin(); it != m_objects.end(); it++)
    {
        it->second->draw();
    }
}

/******** Engine::Object functions ********/

Engine::Object::Object(bool is_static, OdeWorld & world, WFObj * obj,
        float scale)
{
    m_ode_object = world.createObject(is_static, scale);
    m_display_list = obj->render();
    const float * obj_aabb = obj->getAABB();
    for (int i = 0; i < 6; i++)
        m_aabb[i] = obj_aabb[i];
    m_display_list_refcnt = new int;
    *m_display_list_refcnt = 1;
    m_is_visible = true;
    m_scale = scale;
    m_is_scaled = ! (fabs(scale - 1.0) < 0.0001);
    m_is_managed = false;
}

Engine::Object::Object(bool is_static, OdeWorld & world,
                        OdeWorld::GeomType geom_type,
                        refptr< std::vector<float> > args)
{
    m_is_visible = true;
    m_scale = 1.0f;
    m_is_scaled = false;
    m_display_list = 0;
    m_display_list_refcnt = NULL;
    m_ode_object = world.createObject(
            geom_type == OdeWorld::PLANE ? false : is_static,
            m_scale);
    m_is_managed = true;
    m_geom_type = geom_type;
    m_args = args;
    for (int i = 0; i < 4; i++)
        m_color[i] = 1.0f;
    m_texture = 0;
    createManagedObject();
}

Engine::Object::Object(const Engine::Object & orig)
{
    m_is_visible = orig.m_is_visible;
    m_ode_object = new OdeWorld::Object(*orig.m_ode_object);
    m_is_managed = orig.m_is_managed;
    if (m_is_managed)
        m_display_list = 0;
    else
        m_display_list = orig.m_display_list;
    m_display_list_refcnt = orig.m_display_list_refcnt;
    if (m_display_list_refcnt != NULL)
        (*m_display_list_refcnt)++;
    m_scale = orig.m_scale;
    m_is_scaled = orig.m_is_scaled;
    m_geom_type = orig.m_geom_type;
    m_args = orig.m_args;
    for (int i = 0; i < 4; i++)
        m_color[i] = orig.m_color[i];
    m_texture = orig.m_texture;
    if (m_is_managed)
        render();
}

Engine::Object::~Object()
{
    delete m_ode_object;
    if (m_display_list_refcnt != NULL)
    {
        (*m_display_list_refcnt)--;
        if (*m_display_list_refcnt < 1)
        {
            /* we hold the last reference to the OpenGL display list */
            delete m_display_list_refcnt;
            glDeleteLists(m_display_list, 1);
        }
    }
    else
    {
        glDeleteLists(m_display_list, 1);
    }
}

void Engine::Object::createManagedObject()
{
    switch (m_geom_type)
    {
        case OdeWorld::BOX: {
            while (m_args->size() < 9)
                m_args->push_back(0);
            m_ode_object->addBox(m_args);
            float aabb[6] = {-(*m_args)[0], -(*m_args)[1], -(*m_args)[2],
                             (*m_args)[0], (*m_args)[1], (*m_args)[2]};
            memcpy(m_aabb, aabb, sizeof(m_aabb));
            break;
        }
        case OdeWorld::SPHERE: {
            while (m_args->size() < 4)
                m_args->push_back(0);
            m_ode_object->addSphere(m_args);
            float aabb[6] = {-(*m_args)[0], -(*m_args)[0], -(*m_args)[0],
                             (*m_args)[0], (*m_args)[0], (*m_args)[0]};
            memcpy(m_aabb, aabb, sizeof(m_aabb));
            break;
        }
        case OdeWorld::PLANE: {
            while (m_args->size() < 4 || m_args->size() == 5)
                m_args->push_back(0);
            m_ode_object->addPlane(m_args);
            for (int i = 0; i < 6; i++)
                m_aabb[i] = 0.0f;
            break;
        }
        case OdeWorld::CYLINDER: {
            while (m_args->size() < 8)
                m_args->push_back(0);
            m_ode_object->addCylinder(m_args);
            float aabb[6] = {-(*m_args)[0], -(*m_args)[0], -(*m_args)[1],
                             (*m_args)[0], (*m_args)[0], (*m_args)[1]};
            memcpy(m_aabb, aabb, sizeof(m_aabb));
            break;
        }
        case OdeWorld::CAPSULE: {
            while (m_args->size() < 8)
                m_args->push_back(0);
            m_ode_object->addCapsule(m_args);
            float aabb[6] = {-(*m_args)[0], -(*m_args)[0], -(*m_args)[1],
                             (*m_args)[0], (*m_args)[0], (*m_args)[1]};
            memcpy(m_aabb, aabb, sizeof(m_aabb));
            break;
        }
    }
    m_ode_object->finalize();
    render();
}

/* render a managed object */
/* prerequisite: m_args->size() is big enough */
void Engine::Object::render()
{
    if (!m_is_managed)
        return;
    GLUquadric * quad = gluNewQuadric();
    if (m_display_list <= 0)
    {
        m_display_list = glGenLists(1);
    }
    checkGLError();
    glNewList(m_display_list, GL_COMPILE);
    glPushAttrib(GL_ENABLE_BIT);
    if (m_texture != 0)
    {
        gluQuadricTexture(quad, 1);
        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D, m_texture);
    }
    else
    {
        glDisable(GL_TEXTURE_2D);
        glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, m_color);
    }
    switch (m_geom_type)
    {
        case OdeWorld::BOX: {
                const static struct {
                    float norm[3];
                    float verts[4][3];
                } sides[] = {
                    { {1, 0, 0},        /* right */
                        {{1, -1, -1}, {1, 1, -1}, {1, 1, 1}, {1, -1, 1}} },
                    { {-1, 0, 0},       /* left */
                        {{-1, 1, -1}, {-1, -1, -1}, {-1, -1, 1}, {-1, 1, 1}} },
                    { {0, 1, 0},        /* back */
                        {{1, 1, -1}, {-1, 1, -1}, {-1, 1, 1}, {1, 1, 1}} },
                    { {0, -1, 0},       /* front */
                        {{-1, -1, -1}, {1, -1, -1}, {1, -1, 1}, {-1, -1, 1}} },
                    { {0, 0, 1},        /* top */
                        {{-1, -1, 1}, {1, -1, 1}, {1, 1, 1}, {-1, 1, 1}} },
                    { {0, 0, -1},       /* bottom */
                        {{1, -1, -1}, {-1, -1, -1}, {-1, 1, -1}, {1, 1, -1}} }
                };
                const float tex_coords[4][2] = {
                    {0, 0}, {1, 0}, {1, 1}, {0, 1}
                };
                glBegin(GL_QUADS);
                float width = (*m_args)[0];
                float depth = (*m_args)[1];
                float height = (*m_args)[2];
                for (unsigned int s = 0;
                        s < (sizeof(sides)/sizeof(sides[0]));
                        s++)
                {
                    glNormal3fv(sides[s].norm);
                    for (int v = 0; v < 4; v++)
                    {
                        if (m_texture != 0)
                        {
                            glTexCoord2fv(tex_coords[v]);
                        }
                        glVertex3f(sides[s].verts[v][0] * width / 2.0,
                                sides[s].verts[v][1] * depth / 2.0,
                                sides[s].verts[v][2] * height / 2.0);
                    }
                }
                glEnd();
            }
            break;
        case OdeWorld::SPHERE:
            gluSphere(quad, (*m_args)[0], 16, 8);
            break;
        case OdeWorld::PLANE: {
            dVector3 normal;
            dVector3 rotated_x;
            dVector3 rotated_y;
            float x_o, y_o, z_o;
            if (m_args->size() == 6)
            {
                dMatrix3 r;
                dRFromEulerAngles(r, (*m_args)[3], (*m_args)[4], (*m_args)[5]);
                dVector3 default_plane_direction = {0, 0, 1, 0};
                dVector3 default_x = {1, 0, 0, 0};
                dVector3 default_y = {0, 1, 0, 0};
                dMultiply0(normal, default_plane_direction,
                        r, 1, 3, 3);
                dMultiply0(rotated_x, default_x, r, 1, 3, 3);
                dMultiply0(rotated_y, default_y, r, 1, 3, 3);
                x_o = (*m_args)[0];
                y_o = (*m_args)[1];
                z_o = (*m_args)[2];
            }
            else
            {
                normal[0] = (*m_args)[0];
                normal[1] = (*m_args)[1];
                normal[2] = (*m_args)[2];
                float d = (*m_args)[3];
                float len = normal[0] * normal[0]
                            + normal[1] * normal[1]
                            + normal[2] * normal[2];
                if (! FP_EQ(len, 1.0))          /* normalize if necessary */
                {
                    len = sqrtf(len);
                    normal[0] /= len;
                    normal[1] /= len;
                    normal[2] /= len;
                    d /= len;
                }
                int min_component = 0;
                float comp = 10000.0f;
                for (int i = 0; i < 3; i++)
                {
                    if (normal[i] < comp)
                    {
                        min_component = i;
                        comp = normal[i];
                    }
                }
                dVector3 cross_vec;
                for (int i = 0; i < 3; i++)
                    cross_vec[i] = (i == min_component) ? 1 : 0;
                cross_product(rotated_x, normal, cross_vec);
                cross_product(rotated_y, normal, rotated_x);
                x_o = normal[0] * d;
                y_o = normal[1] * d;
                z_o = normal[2] * d;
            }

            /* plane equation: ax + by + cz = d, plane normal: (a, b, c) */

            const int num_sections = 10;
            const float section_size = 100.0f;
#ifdef dSINGLE
            glNormal3fv(normal);
#else
            glNormal3dv(normal);
#endif
            for (int x = 0; x < num_sections; x++)
            {
                glBegin(GL_QUAD_STRIP);
                for (int y = 0; y <= num_sections; y++)
                {
                    float half_span = num_sections * section_size / 2.0;
                    float x_c = x * section_size - half_span;
                    float y_c = y * section_size - half_span;
                    glTexCoord2f(0.0, y);
                    glVertex3f(x_o + rotated_x[0] * x_c + rotated_y[0] * y_c,
                            y_o + rotated_x[1] * x_c + rotated_y[1] * y_c,
                            z_o + rotated_x[2] * x_c + rotated_y[2] * y_c);
                    x_c += section_size;
                    glTexCoord2f(1.0, y);
                    glVertex3f(x_o + rotated_x[0] * x_c + rotated_y[0] * y_c,
                            y_o + rotated_x[1] * x_c + rotated_y[1] * y_c,
                            z_o + rotated_x[2] * x_c + rotated_y[2] * y_c);
                }
                glEnd();
            }
            }
            break;
        case OdeWorld::CYLINDER: {
            glPushMatrix();
            float half_span = (*m_args)[1] / 2.0;
            glTranslatef(0, 0, -half_span);
            gluCylinder(quad, (*m_args)[0], (*m_args)[0], (*m_args)[1], 12, 1);
            gluDisk(quad, 0.0, (*m_args)[0], 12, 4);
            glTranslatef(0, 0, 2 * half_span);
            gluDisk(quad, 0.0, (*m_args)[0], 12, 4);
            glPopMatrix();
            }
            break;
        case OdeWorld::CAPSULE: {
            glPushAttrib(GL_TRANSFORM_BIT);      /* save current clip planes */
            double halfheight = (*m_args)[1] / 2.0;
            glPushMatrix();
            glTranslatef(0, 0, -halfheight);
            gluCylinder(quad, (*m_args)[0], (*m_args)[0], (*m_args)[1], 12, 1);

            double clip_plane[] = {0, 0, -1, halfheight};
            glClipPlane(GL_CLIP_PLANE0, clip_plane);
            glEnable(GL_CLIP_PLANE0);
            gluSphere(quad, (*m_args)[0], 12, 8);
            glTranslatef(0, 0, 2*halfheight);
            clip_plane[2] = 1.0;
            glClipPlane(GL_CLIP_PLANE0, clip_plane);
            gluSphere(quad, (*m_args)[0], 12, 8);
            glPopMatrix();
            glPopAttrib();
            }
            break;
    }
    gluDeleteQuadric(quad);
    glPopAttrib();
    glEndList();
    checkGLError();
}

void Engine::Object::loadPhy(const FileLoader::Path & path)
{
    FileLoader::Buffer buff = g_engine->m_fileLoader->load(path);
    if (buff.size <= 0)
        return;
    string str(buff.data, buff.size);
    stringstream istr(str, ios_base::in);
    while (!istr.eof())
    {
        string line;
        getline(istr, line);
        line = trim(line);
        if (line == "" || line[0] == '#')
            continue;
        size_t pos = line.find_first_of(WHITESPACE);
        if (pos == string::npos)
            continue;
        string type = line.substr(0, pos);
        pos = line.find("\"", pos);
        if (pos == string::npos)
            continue;
        size_t pos2 = line.find("\"", pos + 1);
        if (pos2 == string::npos)
            continue;
        string name = line.substr(pos + 1, pos2 - pos - 1);
        pos = pos2 + 1;
        refptr< vector<float> > args = new vector<float>();
        for (;;)
        {
            pos = line.find_first_not_of(WHITESPACE, pos);
            if (pos == string::npos)
                break;
            pos2 = line.find_first_of(WHITESPACE, pos);
            string n = line.substr(pos, pos2 - pos);
            float f = atof(n.c_str());
            args->push_back(f);
            if (pos2 == string::npos)
                break;
            pos = pos2 + 1;
        }
        if      (type == "cube")
        {
            m_ode_object->addBox(args);
        }
        else if (type == "sphere")
        {
            m_ode_object->addSphere(args);
        }
        else if (type == "capsule")
        {
            m_ode_object->addCapsule(args);
        }
        else if (type == "plane")
        {
            m_ode_object->addPlane(args);
        }
    }
    m_ode_object->finalize();
}

void Engine::Object::setTexture(GLuint tex)
{
    if (m_is_managed)
    {
        m_texture = tex;
        render();
    }
}

void Engine::Object::draw()
{
    if (m_is_visible)
    {
        checkGLError();
        const dReal * pos = m_ode_object->getPosition();
        const dReal * rot = m_ode_object->getRotation();
        bool transform = (pos != NULL && rot != NULL);

        if (transform)
            OdeWorld::pushTransform(pos, rot);

        if (m_is_scaled)
        {
            glPushAttrib(GL_TRANSFORM_BIT);
            glEnable(GL_NORMALIZE);
            glScalef(m_scale, m_scale, m_scale);
        }

        checkGLError();
        glCallList(m_display_list);
        checkGLError();

        if (m_is_scaled)
            glPopAttrib();

        if (transform)
            glPopMatrix();
        checkGLError();
    }
}


/******** Engine::EngineFileLoader functions ********/

Engine::EngineFileLoader::EngineFileLoader(Engine * engine)
{
    m_engine = engine;
}

int Engine::EngineFileLoader::getSize(const Path & path)
{
    struct stat st;
    string file_path = resolvePath(path);

    if (file_path == "")
        return -1;
    if (stat(file_path.c_str(), &st))
        return -2;
    return st.st_size;
}

FileLoader::Buffer Engine::EngineFileLoader::load(const Path & path)
{
    string file_path = resolvePath(path);
    int size = getSize(path);
    if (size > 0)
    {
        FILE * fp = fopen(file_path.c_str(), "rb");
        if (fp != NULL)
        {
            Buffer buf(size);
            int num_read = fread(buf.data, size, 1, fp);
            fclose(fp);
            if (num_read > 0)
            {
                return buf;
            }
        }
    }
    return Buffer(0);
}

string Engine::EngineFileLoader::resolvePath(const Path & path)
{
    string file_path = m_engine->locateResource(path.shortPath);
    if (file_path == "")
        file_path = path.fullPath;
    return file_path;
}
