#ifndef PHYSICS_HPP
#define PHYSICS_HPP

#include "../../../eshared.hpp"
#include "coll_detect.hpp"
#include "collision_body.hpp"
#include "constraint.hpp"

#include <vector>

class Physics_controller;
class Rigid_body;

class Physics
{
public:
  Physics();

  void set_timestep(Scalar timestep) {m_timestep = timestep;}

  void set_allow_smaller_timesteps(bool allow) {m_allow_smaller_timesteps = allow;}

  void set_num_collision_iterations(int num) {m_num_collision_iterations = num;}

  void set_num_contact_iterations(int num) {m_num_contact_iterations = num;}

  void set_num_penetration_iterations(int num) {m_num_penetration_iterations = num;}
  
  void set_penetration_resolve_fraction(Scalar frac) {m_penetration_resolve_fraction = frac;}

  void set_time_scale(Scalar scale) {m_time_scale = scale;}

  /// we get a big speedup by only doing one collision check per update...
  /// at the cost of accuracy
  void set_reuse_collisions(bool reuse) {m_reuse_collisions = reuse;}

  void add_body(Rigid_body * body);
  void remove_body(Rigid_body * body);

  void add_constraint(Constraint * constraint);
  void remove_constraint(Constraint * constraint);

  void add_controller(Physics_controller * controller);
  void remove_controller(Physics_controller * controller);

  void integrate(Scalar total_time);

  Scalar physics_time() const {return m_physics_time;}

  static const Vector3 & get_gravity() {return m_gravity;}
  static void set_gravity(const Vector3 & gravity) {m_gravity = gravity;}

  // activate the object, and also pick up any collisions between it
  // and the rest of the world.  Also activates any adjacent objects
  // that would move if this object moved away Be careful about when
  // you use this function - bear in mind that it adds elements to the
  // internal list of collisions, which can relocate the collision
  // list. I.e. be careful about calling it from within a traversal of
  // the collision list.
  void activate_object(Rigid_body * body);

  /// enable/disable object freezing. if freezing is disabled, all
  /// frozen object will be activated
  void enable_freezing(bool freeze);

  /// indicates if freezing is currently allowed
  bool is_freezing_enabled() const {return m_freezing_enabled;}
private:
  void do_timestep(Scalar dt);

  // functions working on multiple bodies etc
  void handle_all_collisions(Scalar dt);
  void handle_all_constraints(Scalar dt);
  void get_all_external_forces(Scalar dt);
  void update_all_velocities(Scalar dt);
  void update_all_positions(Scalar dt);
  void detect_all_collisions(Scalar dt);
  void clear_all_forces();
  void try_to_freeze_all_objects(Scalar dt);
  void try_to_activate_all_frozen_objects(Scalar step_frac);
  /// try to activate frozen objects that are affected by a touching
  /// active object moving away from them
  void activate_all_frozen_objects_left_hanging();

  // ======== helpers for individual cases =========

  /// Handle an individual collision by classifying it, calculating
  /// impulse, applying impulse and updating the velocities of the
  /// objects. Allows over-riding of the elasticity. Ret val indicates
  /// if an impulse was applied
  bool process_collision(Collision_info & collision, 
                         Scalar dt, 
                         bool override_elasticity,
                         Scalar epsilon = 0.0f);

  /// Sets up any parameters that will not change as the collision
  /// gets processed - e.g. the relative position, elasticity etc.
  void preprocess_collision(Collision_info & collision, Scalar dt);

  /// separate two objects involved in a penetration. This will update
  /// not only the objects, but all other collision structures involved
  /// with the object.  factor indicates how much of the penetration
  /// should be resolved.
  void separate_objects(Collision_info & collision, Scalar factor);

  eArray<Rigid_body *> m_rigid_bodies;
  std::vector<Collision_info> m_collisions;
  eArray<Constraint *> m_constraints;
  eArray<Physics_controller *> m_controllers;

  struct Stored_data
  {
    inline Stored_data(Rigid_body * rb);
    Stored_data() {}
    Position position;
    Orientation orientation;
    Velocity velocity;
    Rotation rotation;
  };
  std::vector<Stored_data> m_stored_data;

  /// list of the collision bodies - keep as a member var so it
  /// doesn't keep getting resized
  std::vector<Collision_body *> m_collision_bodies;

  /// time left over from the last step
  Scalar m_overlap_time;

  /// do a fixed timestep
  Scalar m_timestep;

  /// allow the timestep to vary (so we always do at least one update
  /// per frame)
  bool m_allow_smaller_timesteps;

  /// Our idea of time
  Scalar m_physics_time;

  /// number of collision iterations
  int m_num_collision_iterations;

  /// number of contact iteratrions
  int m_num_contact_iterations;

  /// number of penetration-resolution iterations
  int m_num_penetration_iterations;

  /// reuse the collision list from the first (collision) step when
  /// handling contacts
  bool m_reuse_collisions;

  /// amount to resolve by each penetration resolution iteration
  Scalar m_penetration_resolve_fraction;

  /// traverse the contact list forward or backward (to reduce bias)
  enum Traversal_dir {TRAVERSE_FORWARD, TRAVERSE_BACKWARD} m_traverse_dir;

  /// global gravity acceleration
  static Vector3 m_gravity;

  /// allow objects to freeze
  bool m_freezing_enabled;

  /// allow physics time to run faster/slower
  Scalar m_time_scale;
};

#endif
