#ifndef DOJO_WORLD_H
#define DOJO_WORLD_H

#include "g3dhelper.h"
#include "Entity.h"
#include "NamedArray.h"

namespace dojo {

class Entity;

class World {
    friend class Entity;
    ToneMap                         toneMap;

    class Physics {
    public:
        /** Subset of entities that receive physics */
        Array<EntityRef>            simArray;

        dWorldID                    worldID;
        dSpaceID                    spaceID;
        dJointGroupID               contactGroup;

        Vector3                     gravity;

        /** When true, the physics computation requires only linear space and time
            in the number of object parts.  When false, the physics simulation is
            more accurate but requires cubic memory and quadratic space. */
        bool                        fastMode;

        Physics();
    };

    Physics                         physics;

    /** When true, collisions add contact information to debugContactArray. */
    bool                            debugContacts;

    /** All points currently in contact when debugContacts is true.
        Cleared by doGraphics.*/
    Array<Vector3>                  debugContactArray;

    /** Called from onGraphics for debugging purposes.  Modifies sort keys */
    void renderPhysicsModels(RenderDevice* rd);

    /** Invoked by ODE when objects o1 and o2 are in contact or colliding. */
    static void ODENearCallback(void *data, dGeomID o1, dGeomID o2);

    /** Called at the beginning of simulation to update all ODE data in Entitys
        from the corresponding local G3D data. */
    void beforeSimulation();

    /** Copies velocity and position data from ODE to G3D. 
        Executes at the end of simulation.*/
    void afterSimulation();

    /** Called exclusively by the World::world() function */
    World();

    /** Objects that are the ground.  These are also in entityArray. */
    Array<EntityRef>                groundArray;

private:

    NamedArray<EntityRef>           entityArray;

    /** Entitys to be removed at the end of the timestep.*/
    Array<std::string>              removeList;

    /** Called from afterSimulation to remove all entitys in removeList. */
    void processRemoveList();

protected:
    // Helpers used by Entity as well

    /** Given a geom, returns the Part associated with it or NULL if there isn't one. 
        Used during collision response. */
    static Entity::Part* geomToPart(dGeomID g);

public:

    int debugNumOpaqueRendered;
    int debugNumTransparentRendered;

    /** Returns the global world instance.  Creates
        the global world instance the first time it is 
        called.*/
    static World* world();

    /** Called exclusively by PhysicsData to obtain the ODE handle for the world.*/
    inline dWorldID worldID() const {
        return physics.worldID;
    }

    enum RenderMode {RENDER_NORMAL = 0, RENDER_PHYSICS, RENDER_MAX};
    RenderMode                      renderMode;

    SkyRef                          sky;
    LightingParameters              skyParameters;

    LightingRef                     lighting;

    /** Pointer to the active camera for use during rendering. */
    GCamera*                        activeCamera;

    ~World();

    /** Set the acceleration due to gravity in <i>m/s<sup>2</sup></i> */
    void setGravity(const Vector3& g);

    inline Vector3 gravity() const {
        return physics.gravity;
    }

    /** Insert this new entity into the World
        at the specified location.*/
    void insert(
        EntityRef&              e, 
        const CoordinateFrame&  location);

    /** Schedules the entity for removal.
        It is an error to remove an entity that 
        does not exist in the world, however, it
        is legal to remove the same entity several times during
        a single timestep.*/
    void remove(const std::string& name);

    /** Hit information filled out by rayCast. */
    class RayCastHit {
    public:
        /** NULL if nothing was hit */
        EntityRef       entity;

        /** Name of the part that was hit. */
        std::string     part;
        
        /** Distance from, G3D::inf() if there was no hit. */
        float           distance;

        /** World-space surface normal to the part's physics geometry at the hit location */
        Vector3         normal;
    };

    /** Ray cast to nearest intersection with physics geometry. */
    void rayCast(
        const Ray&          ray, 
        RayCastHit&         hit, 
        const EntityRef&    ignore = NULL) const;

    /** Ray cast to nearest intersection with physics geometry, ignoring
        intersections with the specified Entity. */
    void rayCast(
        const Vector3&      start, 
        const Vector3&      direction,
        RayCastHit&         hit, 
        const EntityRef&    ignore = NULL) const;

    /** Set up the empty world */
    void init();

    /** 
     Returns null if the entity is not found or has the wrong subclass. 

     Example:
     <pre>
     EntityRef e = world->entity("Player 1");
     Q2CharacterRef q2 = world->typedEntity<Q2Character>("Player 1");
     </pre>
    */
    template<class EntitySubclass>
    inline ReferenceCountedPointer<EntitySubclass> typedEntity(const std::string& name) {
        EntityRef e;
        entityArray.get(name, e);
        return e.downcast<EntitySubclass>();
    }

    /** 
     Returns null if the entity is not found.  See also World::typedEntity. 
    */
    inline EntityRef entity(const std::string& name) {
        EntityRef e;
        entityArray.get(name, e);
        return e;
    }

    void onCleanup();

    /**
     @param sdt Simulation time to advance forward.
     */
    void onSimulation(SimTime sdt);

    void onGraphics(RenderDevice* rd);
};


} // namespace

#endif
