#ifndef SIMULATOR_H_
#define SIMULATOR_H_

#include "config.h"
#include <ode/ode.h>
#include "drawstuff/drawstuff.h"
#include "typedefs.h"
#include <string>

class Simulator {
public:
	/** Types of viewpoint
	 * VP_STATIC - camera is static, user-manipulated
	 * VP_CAR_FOLLOW - camera follows user-chosen car
	 */
	enum ViewpointType {VP_STATIC, VP_CAR_FOLLOW};
	
	/** Singleton function returning the instance */
	static Simulator* getInstance();
	virtual ~Simulator();
	
	/** Initializes the simulator to start state (parses conf files, creates cars, simulation object, etc.) */
	void init(const std::string simfile, const std::string carsfile, const std::string worldfile, bool visualize = true);
	/** Runs the simulation */
	void run();
	/** Callback function for starting the simulation (used by drawstuff) */
	void start();
	/** Callback function which performs one step of the simulation */
	void loop(int pause);
	/** Callback function for handling user commands (keystrokes coming from the main window) */
	void command(int cmd);
	/** Callback function for managing collisions */
	void nearCallback(void *data, dGeomID o1, dGeomID o2);
	
	/** Registers new simulation object to the simulation
	 * @param object the new object
	 */
	void addSimulationObject(SimulationObject* object);
	/** Deregisters new simulation object from the simulation
	 * @param object the object to remove
	 */
	void removeSimulationObject(SimulationObject* object);
	
	/** Allows objects in simulation to create other and register them to the simulation (see CarSource) */
	void scheduleForAddition(SimulationObject* o);
	
	/** Allows objects in simulation to register other for removal from simulation (see CarSink) */
	void scheduleForRemoval(SimulationObject* o);
	
	/** Registers new sector to the simulation */
	void addSector(Sector* sector);

	/** Registers new viewpoint to the simulation */
	void addViewPoint(float* xyz, float* hpr);
	
	/** Prints info about current state of cars */
	std::string getInfo();
	
	/** Prints help instructions */
	void printHelp();
	
private:
	//since Simulator is a singleton, we need to keep these private
	Simulator();
	Simulator(const Simulator&);
	Simulator& operator=(const Simulator&);
	
	/** Adds all objects scheduled for registration to simulation in one batch */
	void addScheduledObjects();
	/** Removes all objects scheduled for removal from simulation in one batch */
	void removeScheduledObjects();
	
	/** Loops the simulation in headless mode (no visualization) */
	void loopHeadLess();
	
	/** Cycles the viewpoint between simulation objects */
	void cycleViewpointObject(bool forward = true);
	
	/** Sets the viewpoint to static position */
	void cycleStaticViewPoints(bool forward = true);
	
	/** The only instance of Simulator */
	static Simulator* _instance;
	
	/** Main container for SimulationObjects */
	SOContainer _sobjects;
	/** Auxiliary container for all animated objects */
	AOContainer _aobjects;
	/** Auxiliary container for all visualized objects */
	VOContainer _vobjects;
	/** Container for SectorVector */
	SectorMap _sectors;
	/** Vector of simulation objects scheduled for addition to simulation */
	SOVector _scheduledForAddition;
	/** Vector of simulation objects scheduled for removal from simulation */
	SOVector _scheduledForRemoval;
	
	/** ODE world reference */
	dWorldID _world;
	/** ODE collision space */
	dSpaceID _space;
	/** ODE main plane (terrain) */
	dGeomID _plane;
	/** ODE group for collision contacts */
	dJointGroupID _contactgroup;
	/** Drawstuff functions structure */
	dsFunctions _fn;
	/** World's gravity */
	dReal _gravity;
	/** World's setting for the ERP ODE parameter */
	dReal _ERP;
	/** World's setting for the CFM (Constraint Mixing Force) ODE parameter */
	dReal _CFM;
	/** length of simulation step in ms */
	double _simstep;
	/** number of cars in simulation */
	int _numOfCars;
	
	/** Type of viewpoint currently used */
	ViewpointType _viewpointType;
	/** Object to follow with viewpoint if VP_CAR_FOLLOW */
	int _lastCarViewpoint;
	/** Viewpoint to use if VP_STATIC */
	int _lastStaticViewpoint;
	/** Container for static viewpoints as defined in world file */
	ViewPointVector _viewpoints;
	/** Flag for visualization */
	bool _visualize;
	
	friend class ConfigurationParser;
	friend class Sector;
};

/** Proxy function for Simulator._fn start function */
static void gStart() { Simulator::getInstance()->start(); }
/** Proxy function for Simulator._fn step function */
static void gLoop(int pause) { Simulator::getInstance()->loop(pause); }
/** Proxy function for Simulator._fn command function */
static void gCommand(int cmd) { Simulator::getInstance()->command(cmd); }
/** Proxy function for ODE's dSpaceCollide function */
static void gNearCallback(void *data, dGeomID o1, dGeomID o2) { Simulator::getInstance()->nearCallback(data, o1, o2); }

#endif /*SIMULATOR_H_*/
