
#ifndef ENGINE_H
#define ENGINE_H

#include <string>
#include <lua.hpp>
#include <map>
#include <vector>
#include <SDL.h>
#include "OdeWorld/OdeWorld.h"
#include "TextureCache/TextureCache.h"
#include "wfobj/WFObj.h"
#include "AV.h"
#include "refptr/refptr.h"
#include "ftgl.h"
#include "PhyObj/PhyObj.h"
#include "IDSet.h"

class Engine
{
    public:
        class Object
        {
            public:
                Object(bool is_static, bool is_reference, bool enable_blending,
                        OdeWorld & world, Engine & e, WFObj * wfobj,
                        float scale = 1.0f);
                Object(bool is_static, bool is_reference, bool enable_blending,
                        OdeWorld & world, Engine & e,
                        OdeWorld::GeomType geom_type,
                        refptr< std::vector<float> > args);
                Object(const Object & orig);
                ~Object();

                void setPosition(double x, double y, double z);
                void getPosition(double * x, double * y, double * z);
                void loadPhy(const unsigned char *data, unsigned int size);
                void instantiatePhy();
                void setVisible(bool visible) { m_is_visible = visible; }
                bool getVisible() { return m_is_visible; }
                void addForce(dReal fx, dReal fy, dReal fz)
                {
                    if (m_ode_object != NULL)
                        m_ode_object->addForce(fx, fy, fz);
                }
                void addForceRel(dReal fx, dReal fy, dReal fz)
                {
                    if (m_ode_object != NULL)
                        m_ode_object->addForceRel(fx, fy, fz);
                }
                void addTorque(dReal fx, dReal fy, dReal fz)
                {
                    if (m_ode_object != NULL)
                        m_ode_object->addTorque(fx, fy, fz);
                }
                void addTorqueRel(dReal fx, dReal fy, dReal fz)
                {
                    if (m_ode_object != NULL)
                        m_ode_object->addTorqueRel(fx, fy, fz);
                }
                void setRotation(dReal x, dReal y, dReal z)
                {
                    if (m_ode_object != NULL)
                        m_ode_object->setRotation(x, y, z);
                }
                void setColor(float r, float g, float b)
                {
                    m_color[0] = r;
                    m_color[1] = g;
                    m_color[2] = b;
                    render();
                }
                void setTransparency(float t)
                {
                    m_color[3] = 1.0f - t;
                    render();
                }
                void setTexture(GLuint tex);
                void setTextureScale(float scale)
                {
                    m_texture_scale = scale;
                    render();
                }
                void draw();
                dReal getMass() const;
                void setMass(dReal mass);
                const float * getAABB() { return m_aabb; }
                dBodyID getBody()
                {
                    return (m_ode_object != NULL) ? m_ode_object->getBody() : 0;
                }
                void setGravityMode(bool enabled)
                {
                    m_gravity_mode = enabled;
                    if (m_ode_object != NULL)
                        m_ode_object->setGravityMode(enabled);
                }
                void setID(int id) { m_id = id; }
                int getID() { return m_id; }
                OdeWorld::Object *getOdeWorldObject() { return m_ode_object; }

            protected:
                void createManagedObject();
                void render();

                Engine & m_engine;
                OdeWorld::Object * m_ode_object;
                GLuint m_display_list;
                bool m_is_visible;
                float m_scale;
                bool m_is_scaled;
                bool m_is_managed;
                bool m_is_reference;
                bool m_is_static;
                float m_aabb[6];
                OdeWorld & m_world;
                refptr<PhyObj> m_phy;
                dReal m_mass;
                bool m_mass_is_set;
                bool m_gravity_mode;
                int m_id;
                bool m_enable_blending;

                /* for "pre-loaded" objects */
                WFObj * m_obj;
                int * m_obj_refcnt;

                /* for "managed" objects */
                OdeWorld::GeomType  m_geom_type;
                refptr< std::vector<float> > m_args;
                float m_color[4];
                GLuint m_texture;
                float m_texture_scale;
        };

        class PickedObject
        {
            public:
                int id;
                float dist;
                float pos[3];
                float normal[3];

                PickedObject(OdeWorld::PickedObjectRef por);
        };
        typedef refptr<PickedObject> PickedObjectRef;

        class Quad
        {
            public:
                Quad(float cx, float cy, float cz,
                        float v1x, float v1y, float v1z,
                        float v2x, float v2y, float v2z);
                ~Quad();
                int render();
                void setVisible(bool v) { m_visible = v; }
                void draw()
                {
                    if (m_visible)
                        glCallList(m_dl);
                }
                void setOffset(float f)
                {
                    m_offset = f;
                    render();
                }
                void setPosition(float x, float y, float z)
                {
                    m_position[0] = x;
                    m_position[1] = y;
                    m_position[2] = z;
                    render();
                }
                void setTexture(int t)
                {
                    m_texture = t;
                    render();
                }
                void setBlending(bool b)
                {
                    m_enable_blending = b;
                    render();
                }
                void setColor(float r, float g, float b, float a)
                {
                    m_color[0] = r;
                    m_color[1] = g;
                    m_color[2] = b;
                    m_color[3] = a;
                    render();
                }
            protected:
                dVector3 m_center;
                dVector3 m_v1, m_v2, m_normal;
                int m_dl;
                bool m_visible;
                float m_offset;
                int m_texture;
                bool m_enable_blending;
                float m_color[4];
                float m_position[3];
        };

        Engine(const std::string & path, AV & av);
        ~Engine();

        std::string locateResource(const std::string & shortname);
        bool load(const char * program);
        void run();
        void reportErrors(int status);
        OdeWorld & getWorld() { return m_world; }
        int addObject(WFObj * obj, bool is_static, bool is_reference,
                bool enable_blending, float scale = 1.0f);
        int addObject(bool is_static, bool is_reference, bool enable_blending,
                OdeWorld::GeomType geom_type,
                refptr< std::vector<float> > args);
        int addQuad(float cx, float cy, float cz,
                float v1x, float v1y, float v1z,
                float v2x, float v2y, float v2z);
        int addSound(refptr<AV::Sound> avs);
        int addAMotor(Object * o1, Object * o2);
        int addHinge(Object * o1, Object * o2,
                dReal anchor_x, dReal anchor_y, dReal anchor_z,
                dReal axis_x, dReal axis_y, dReal axis_z);
        void setAMotorAxis(int jid, int anum, int rel,
                dReal x, dReal y, dReal z);
        void setAMotorNumAxes(int jid, int num_axes);
        void setAMotorAngle(int jid, int anum, dReal val);
        void setAMotorLoStop(int jid, dReal val);
        void setAMotorHiStop(int jid, dReal val);
        void setAMotorVel(int jid, dReal val);
        void setAMotorFMax(int jid, dReal val);
        void setAMotorBounce(int jid, dReal val);
        void removeObject(int id);
        int cloneObject(const Object * obj);
        Object * getObject(int id);
        Quad * getQuad(int id);
        refptr<AV::Sound> getSound(int id);
        void doPhysics();
        void drawObjects();
        void setAutoPhysics(bool val) { m_autoPhysics = val; }
        bool getAutoPhysics() { return m_autoPhysics; }
        void setAutoStartFrame(bool val) { m_autoStartFrame = val; }
        bool getAutoStartFrame() { return m_autoStartFrame; }
        void setAutoEndFrame(bool val) { m_autoEndFrame = val; }
        bool getAutoEndFrame() { return m_autoEndFrame; }
        void setAutoDrawObjects(bool val) { m_autoDrawObjects = val; }
        bool getAutoDrawObjects() { return m_autoDrawObjects; }
        void startFrame();
        void endFrame();
        int loadModel(const std::string & name, bool is_static,
                bool is_reference, bool enable_blending, float scale = 1.0f);
        int loadSound(const std::string & name);
        bool isKeyDown(const std::string & key);
        void exit();
        bool import(const char * name);
        bool importFullPath(const char * path);
        const unsigned char *loadFile(const char *fname, unsigned int *len);
        bool loadWFObjFile(const char *fname, WFObj::Buffer & buff);
        GLuint loadTexture(const char * name);
        void debug_hook(lua_Debug * debug);
        void setGravity(float gx, float gy, float gz)
        {
            m_world.setGravity(gx, gy, gz);
        }
        GLuint startList();
        void endList();
        void drawList(GLuint list);
        void callList(GLuint list);
        void clearWorld();
        void setScriptCursorVisible(bool visible)
        {
            m_script_cursor_visible = visible;
            updateCursorVisibility();
        }
        bool getScriptCursorVisible() { return m_script_cursor_visible; }
        refptr< std::vector<PickedObjectRef> > pickObjects(int x, int y);
        PickedObjectRef pickOne(int x, int y, Object *obj);

        void getScreenSize(int * width, int * height);
        void drawText(const char * text, GLfloat r, GLfloat g, GLfloat b,
                int ptsize, float x, float y);
        void getTextSize(const char * text, int ptsize,
                float * width, float * height);
        void drawLine(float r, float g, float b,
                float x1, float y1, float x2, float y2, float width = 1.0f);
        void drawRect(float r, float g, float b,
                float width, float height, float x, float y, float rot = 0.0f);
        void fillRect(float r, float g, float b,
                float width, float height, float x, float y, float rot = 0.0f);
        void drawImage(float width, float height, float x, float y,
                int tex, float rot = 0.0f);
        void fillArc(float r, float g, float b, float x, float y,
                float radius, float a1, float a2);
        void drawArc(float r, float g, float b, float x, float y,
                float radius, float a1, float a2);
        void drawPoint(float size, float r, float g, float b,
                float x, float y, float z);

        /* lua services */
        int setCamera(lua_State * L);
        int getCamera(lua_State * L);
        int registerEventHandler(lua_State * L);
        int clearEventHandler(lua_State * L);

    protected:
        static Uint32 updateCallback(Uint32 interval, void * param);

        Uint32 updateCallback(Uint32 interval);
        void registerLibraries();
        bool fileExists(const std::string & path);
        void update_event();
        void update_overlay_event();
        void key_down_event(int keysym);
        void key_up_event(int keysym);
        void mousebutton_down_event(int button, int x, int y);
        void mousebutton_up_event(int button, int x, int y);
        void mouse_motion_event(int x, int y, int xrel, int yrel);
        void checkForFunctionFull(const std::string & lua_fn_name,
                const std::string & event_name, bool & presentFlag);
        void doRegisterHandlerFull(int index,
                const std::string & event_name, bool & presentFlag);
        void reloadProgram();
        void checkForAllHandlerFunctions();
        void updateCursorVisibility()
        {
            m_av.setCursorVisible(
                    m_engine_cursor_visible || m_script_cursor_visible);
        }
        bool validatePath(const std::string & path);
        int getFileSize(const char *fname);

        AV &            m_av;
        bool            m_engine_cursor_visible;
        bool            m_script_cursor_visible;
        bool            m_input_grabbed;
        TextureCache    m_textureCache;
        lua_State *     m_luaState;
        std::string     m_program_path;
        std::string     m_program_directory;
        std::string     m_engine_path;
        OdeWorld        m_world;
        IDSet<Object *> m_objects;
        IDSet<Quad *>   m_quads;
        IDSet< refptr<AV::Sound> > m_sounds;
        IDSet<dJointID> m_joints;
        GLdouble        m_eye[3];
        GLdouble        m_center[3];
        GLdouble        m_up[3];
        bool            m_drawing;
        bool            m_autoPhysics;
        bool            m_autoStartFrame;
        bool            m_autoEndFrame;
        bool            m_autoDrawObjects;
        std::map<std::string, bool> m_keysDown;
        SDL_Event       m_updateEvent;
        SDL_Event       m_exitEvent;
        Uint32          m_event_time;
        FTFont *        m_font;
        double          m_screen_dist;

        bool            m_event_init_present;
        bool            m_event_reinit_present;
        bool            m_event_update_present;
        bool            m_event_update_overlay_present;
        bool            m_event_key_down_present;
        bool            m_event_key_up_present;
        bool            m_event_mousebutton_down_present;
        bool            m_event_mousebutton_up_present;
        bool            m_event_mouse_motion_present;

        enum { PROG_OBJ, PROG_OBJ_TEX, PROG_COUNT };
        GLuint          m_programs[PROG_COUNT];
        enum {
            UNIFORM_OBJ_PROJECTION,
            UNIFORM_OBJ_MODELVIEW,
            UNIFORM_OBJ_AMBIENT,
            UNIFORM_OBJ_DIFFUSE,
            UNIFORM_OBJ_SPECULAR,
            UNIFORM_OBJ_SHININESS,
            UNIFORM_OBJ_COUNT
        };
        enum {
            ATTRIBUTE_OBJ_POS,
            ATTRIBUTE_OBJ_NORMAL,
            ATTRIBUTE_OBJ_COUNT
        };
        GLint           m_uniforms_obj[UNIFORM_OBJ_COUNT];
        enum {
            UNIFORM_OBJ_TEX_PROJECTION,
            UNIFORM_OBJ_TEX_MODELVIEW,
            UNIFORM_OBJ_TEX_AMBIENT,
            UNIFORM_OBJ_TEX_SPECULAR,
            UNIFORM_OBJ_TEX_SHININESS,
            UNIFORM_OBJ_TEX_COUNT
        };
        enum {
            ATTRIBUTE_OBJ_TEX_POS,
            ATTRIBUTE_OBJ_TEX_NORMAL,
            ATTRIBUTE_OBJ_TEX_TEX_COORD,
            ATTRIBUTE_OBJ_TEX_COUNT
        };
        GLint           m_uniforms_obj_tex[UNIFORM_OBJ_TEX_COUNT];
};

extern Engine * g_engine;

#endif
