#ifndef _COLL2D_H
#define _COLL2D_H

/** \brief Coll2d - A 2d collision detection library
 * \author Martin Felis <martin@silef.de>
 *
 * This library provides functions to detect collisions between polygons and
 * spheres.
 *
 * Notes:
 * - vertices of polygons are described in local coordinates
 * - return values of check_collision are in global coordinates
 * - all Shapes get copied to a temporary instance which will then get
 *   transferred into global coordinates
 * - Polygons are assumed to be convex and the vertices are stored
 *   counter clockwise.
 * - The transformation of the global position and velocities towards
 *   relative position and velocities happens in the
 *   int check_collision_<type>_<type> functions!
 */

#include <mathlib.h>
#include <iostream>
#include <cstring>

#include <coll2d_errors.h>

namespace coll2d {

/** \brief Contains the information of a collision
 *
 * \param normal  The normal of the reference plane
 * \param point   The actual point where the collision happens in global
 *                cooldinates
 * \param time    If both objects move for this amount of time the contact
 *                will occur.
 * \param reference_shape
 *                If 0, the first shape passed to he check_collision function
 *                is the reference shape, otherwise the second.
*/
struct CollisionInfo {
	vector3d normal;
	vector3d point;
	float time;
	int reference_shape;

	CollisionInfo () : normal (0., 0., 0.), point (0., 0., 0.), time (-1.), reference_shape (-1) {
	}

	CollisionInfo& operator= (const CollisionInfo& info) {
		if (this != &info) {
			normal = info.normal;
			point = info.point;
			time = info.time;
			reference_shape = info.reference_shape;
		}

		return *this;
	}

	void doPrint (const char* msg) {
		std::cout << msg;
		std::cout <<  "Time   = " << time << std::endl;
		normal.print ("Normal = ");
		point.print ( "Point  = ");
		std::cout <<  "Reference = " << reference_shape << std::endl;
	}
};

/** \brief Base class for all shapes
 *
 */
class Shape {
protected:
	vector3d mPosition;
	vector3d mVelocity;
	float mAngle;
	float mAngleVelocity;

	virtual void dummy() {
	}
public:
	Shape():
		mPosition (0., 0., 0.),
		mVelocity (0., 0., 0.),
		mAngle (0.),
		mAngleVelocity (0.) {
	}
	Shape (const Shape &shape):
		mPosition (shape.mPosition),
		mVelocity (shape.mVelocity),
		mAngle (shape.mAngle),
		mAngleVelocity (shape.mAngleVelocity)
	{ }

	virtual ~Shape () {};

	/** \brief Creates and returns a copy of itself */
	virtual Shape* getCopy () = 0;

	void setPosition(vector3d position) {
		mPosition = position;
	}
	vector3d getPosition() {
		return mPosition;
	}
	void setVelocity(vector3d velocity) {
		mVelocity = velocity;
	}
	vector3d getVelocity() {
		return mVelocity;
	}

	void setAngle (const float &angle) {
		mAngle = angle;
	}
	float getAngle () {
		return mAngle;
	}
	void setAngleVelocity (float angle_velocity) {
		mAngleVelocity = angle_velocity;
	}
	float getAngleVelocity () {
		return mAngleVelocity;
	}

	virtual void doPrintType() {
		std::cout << "Shape" << std::endl;
	}
	virtual void doPrint (const char* name) {
		std::cout << name << "<unknown shape>" << std::endl;
	}
	friend int check_collision_rel(Shape *shape_a, Shape *shape_b,
			vector3d *velocity_b, CollisionInfo* info);
};

class Polygon: public Shape {
private:
	unsigned int mVerticeCount;
	vector3d *mVertices;
	bool mFreeVertices;
public:
	Polygon() {
		mVerticeCount = 0;
		mVertices = NULL;
		mFreeVertices = false;
	}
	Polygon(unsigned int n) {
		mVerticeCount = n;
		mVertices = new vector3d[n];
		mFreeVertices = true;
	}
	Polygon(unsigned int n, vector3d *vertices) {
		mVerticeCount = n;
		mFreeVertices = false;

		if (vertices == NULL && n > 0) {
			mVertices = new vector3d [n];
			mFreeVertices = true;
		}

		mVertices = vertices;
	}
	Polygon (const Polygon &polygon) : Shape (polygon) {
		mVerticeCount = polygon.mVerticeCount;
		mFreeVertices = true;

		mVertices = new vector3d[mVerticeCount];
		memcpy (mVertices, polygon.mVertices, sizeof (vector3d) * mVerticeCount);
	}
	virtual ~Polygon () {
		if (mFreeVertices == true && mVertices)
			delete[] mVertices;
	}

	virtual Polygon* getCopy () {
		Polygon *copy = new Polygon (*this);

		assert (copy);

		return copy;
	}

	virtual void doPrintType() {
		std::cout << "Polygon" << std::endl;
	}

	virtual void doPrint (const char* name) {
		std::cout << name << " (Polygon)" << std::endl;

		std::cout << "mVerticeCount = " << mVerticeCount << std::endl;
		unsigned int i;
		for (i = 0; i < mVerticeCount; i ++) {
			std::cout << i << " = " << mVertices[i][0] << ", " <<
				mVertices[i][1] << ", " <<
				mVertices[i][2] << std::endl;
		}
		std::cout << "mPosition = " << mPosition[0] <<", " <<
			mPosition[1] << ", " <<
			mPosition[2] << std::endl;

		std::cout << "mVelocity = " << mVelocity[0] <<", " <<
			mVelocity[1] << ", " <<
			mVelocity[2] << std::endl;

		std::cout << "mAngle = " << mAngle << std::endl;
		std::cout << "mAngleVelocity = " << mAngleVelocity << std::endl;
	}

	unsigned int getVerticeCount () {
		return mVerticeCount;
	}

	vector3d& getVertice (unsigned int i) {
		assert (i >= 0 && i < mVerticeCount);
		return mVertices [i];
	}

	void setVertice (unsigned int i, const vector3d &vertice) {
		assert (i >= 0 && i < mVerticeCount);
		mVertices[i] = vertice;
	}

	friend int check_collision_polygon_sphere(Shape *polygon_a,
			Shape *sphere_b, CollisionInfo* info);
};

class Sphere: public Shape {
private:
	float mRadius;
public:
	Sphere (float radius) {
		mRadius = radius;
		Shape::setPosition (vector3d (0., 0., 0.));
	}

	Sphere(float radius, const vector3d &position) {
		mRadius = radius;
		Shape::setPosition(position);
	}

	Sphere (const Sphere &sphere):
		Shape (sphere),
		mRadius (sphere.mRadius) { }

	virtual Sphere* getCopy() {
		Sphere* copy = new Sphere (*this);

		return copy;
	}

	virtual void doPrintType() {
		std::cout << "Sphere" << std::endl;
	}

	virtual void doPrint (const char* name) {
		std::cout << name << " (Sphere)" << std::endl;

		std::cout << "mRadius = " << mRadius << std::endl;
		std::cout << "mPosition = " << mPosition[0] <<", " <<
			mPosition[1] << ", " <<
			mPosition[2] << std::endl;

		std::cout << "mVelocity = " << mVelocity[0] <<", " <<
			mVelocity[1] << ", " <<
			mVelocity[2] << std::endl;

		std::cout << "mAngle = " << mAngle << std::endl;
		std::cout << "mAngleVelocity = " << mAngleVelocity << std::endl;
	}


	void setRadius (float radius) {
		mRadius = radius;
	}

	float getRadius () {
		return mRadius;
	}

	friend int check_collision_polygon_sphere(Shape *polygon_a,
			Shape *sphere_b, CollisionInfo* info);
};

/** \brief The higher level function to call for collision detection
 *
 * \param stepsize   the timestep within we want to check for the collision 
 * \param shape_a    first shape
 * \param shape_b    second shape
 * \param info       information about the collision will be to info
 *
 * \returns 0 - no collision, negative on error, positive on collision
 */
int check_collision(float timestep, Shape *shape_a, Shape *shape_b, CollisionInfo* info);

/** \brief The higher level function to call for collision detection
 *
 * \param shape_a    first shape
 * \param shape_b    second shape
 * \param velocity_b relative velocity of b to a
 * \param info       information about the collision will be to info
 *
 * \returns 0 - no collision, negative on error, positive on collision
 */
int check_collision_rel(float timestep, Shape *shape_a, Shape *shape_b, vector3d *velocity_b,
		CollisionInfo* info);

/** \brief Callback signature for the check functions */
typedef int (*check_cb)(float timestep, Shape *shape_a, Shape *shape_b,	CollisionInfo* info);

/** \brief Returns the check functions which performs the checks depending
 * on their types. */
check_cb get_check(Shape *shape_a, Shape *shape_b);

/** \brief Performs a check between a polygon and a sphere
 */
int check_collision_polygon_sphere(float timestep, Shape *shape_a, Shape *shape_b,
		CollisionInfo* info);

/** \brief Performs a check between a sphere and a sphere
 */
int check_collision_sphere_sphere(float timestep, Shape *shape_a, Shape *shape_b,
		CollisionInfo* info);


/** \brief Calculates the time it takes for a point to touch a plane
 *
 * \param normal   normal vector of the plane
 * \param plane_point point on the vector
 * \param point    point that is moving
 * \param velocity velocity of the point
 *
 * \returns -1 if point is moving away from the plane (or is below and
 *                moves even further below
 *          >= 0 if point is moving along the plane or towards the plane
 *
 * This function depends on the scale of velocity. It is assumed
 * that velocity represents the displacement in one frame. In this
 * case a return value > 1 means there will not be a contact
 * within this frame.
 */
float calculate_contact_plane_point(vector3d &normal, vector3d &plane_point, vector3d &point, vector3d &velocity);

}

#endif /* _COLL2D_H */
