#include "../include/zf.h"

typedef struct ZfDebris ZfDebris; /* should be Debris? */

struct ZfDebris
{
    unsigned int ref_count;

    /* Why can't I use the matrix to get orientation??*/
    CLUquaternion orientation;
    
    CLmatrix frame;
    
    CLnormal velocity;
    CLnormal axis; /* of rotation */
    float angular_speed;

    float radius;

    int age;
    int lifespan;
    
    float mass;

    CLmodel* model; /* not *owned* by this debris */
};

static CLmaterial* material; /* grey "dead" material */

static ZfSmartPointer smart_pointer;
static ZfDynamicCollider dynamic_collider;

static bool
is_valid(const ZfDebris* debris)
{
    return debris->age < debris->lifespan;
}

static void
reference(ZfDebris* debris)
{
    debris->ref_count++;
}

static void
release(ZfDebris* debris)
{
    debris->ref_count--;

    if (debris->ref_count == 0)
 	g_free(debris);
}

static void
animate(ZfDebris* debris)
{
    CLvertex debris_position;
    CLUquaternion angular_velocity;

    /* gravity */
/*    debris->velocity.j -= 0.0098f;*/
    debris->velocity.j -= 0.00098f;
    
    /* friction (maybe scale due to radius?) */
    /*
    cluNormalScale(&debris->velocity, 0.995f);
    debris->angular_speed *= 0.995f;
    */

    cluSetVertexMatrixOrigin(&debris_position, &debris->frame);

    /* update position and orientation */
    cluVertexAdd(&debris_position, &debris->velocity);
    
    cluSetQuaternionAxisAngle(&angular_velocity,
			      &debris->axis, debris->angular_speed);
    cluQuaternionMultiply(&debris->orientation, &angular_velocity);
    
    /* Do I need this? */
    cluQuaternionNormalise(&debris->orientation);

    cluSetMatrixPosition(&debris->frame, &debris_position);

    cluSetMatrixOrientation(&debris->frame, &debris->orientation);



    /* increase age */
    debris->age++;
}

static void
query_position(ZfDebris* debris,
	       CLvertex* position)
{
    cluSetVertexMatrixOrigin(position, &debris->frame);
}

static void
collision_response(ZfDebris* debris,
		   const void* collider_data,
		   ZfType collider_type,
		   const CLvertex* collision_position,
		   const CLnormal* collision_force_perp,
		   const CLnormal* collision_force_tan)
{
    if (collider_type & ZF_WEAPON)
    {
	debris->age = debris->lifespan;
    }
    else
    {
	cluSetMatrixPosition(&debris->frame, collision_position);
	
	/* bounce off ground */
	/*   if (collider_type & ZF_GROUND)*/
	{
	    clCopyNormal(&debris->velocity, collision_force_perp);
	    cluNormalAdd(&debris->velocity, collision_force_tan);
	    {
		CLUquaternion angular_velocity;
		CLUquaternion collision_torque;
		CLnormal collision_axis;
		float collision_angular_speed;
	    
		cluNormalCrossProduct(&collision_axis, 
				      collision_force_perp,
				      collision_force_tan);

		cluNormalNormalise(&collision_axis);
	    
		collision_angular_speed = 
		    cluNormalMagnitude(collision_force_tan) * debris->radius;
	    
		/* ... ur should we be using mass as well...? */
		cluSetQuaternionAxisAngle(&collision_torque,
					  &collision_axis, collision_angular_speed);

		/* current angular velocity */
		cluSetQuaternionAxisAngle(&angular_velocity,
					  &debris->axis, debris->angular_speed);

		/* add torque..or acceleration... or whatever it is... */
		cluQuaternionMultiply(&angular_velocity, &collision_torque);

		/* extract new axis and angular speed */
		cluSetNormalQuaternionAxis(&debris->axis, &angular_velocity);
		debris->angular_speed = cluQuaternionAngle(&angular_velocity);

		/* HACK - reduce speed and angular velocity!!! */
		cluNormalScale(&debris->velocity, 0.995f);
		debris->angular_speed *= 0.99f;
	    }
	}
    }
}

static void
render(ZfDebris* debris)
{
    glPushAttrib(GL_ALL_ATTRIB_BITS); /* TESTING!!! super conservative */

    glPushMatrix();
    glMultMatrixf((GLfloat*)&debris->frame);
   
    /*clLoadMaterial(material);*/
 /*   glEnable(GL_COLOR_MATERIAL);
    glColor4f(1.0f, 1.0f, 1.0f, 1.0f - (float)debris->age/(float)debris->lifespan);*/

    if (debris->model)
    {
	clDisable(CL_MATERIAL);
	cluRenderModel(debris->model);
	/*glutWireCube(debris->radius);*/
	clEnable(CL_MATERIAL);
    }

    glPopMatrix();

    glPopAttrib();
}

void
zf_debris_close(void)
{
    clDeleteMaterial(material);
}

void
zf_debris_init(void)
{
    material = clDefaultMaterial(clNewMaterial());

    smart_pointer.is_valid = (ZfIsValid*) is_valid;
    smart_pointer.reference = (ZfReference*) reference;
    smart_pointer.release = (ZfRelease*) release;

    dynamic_collider.query_position =
	(ZfQueryPosition*) query_position;

    dynamic_collider.collision_response =
	(ZfCollisionResponse*) collision_response;
}

void
zf_debris_new(const CLmatrix* frame,
	      const CLnormal* velocity,
	      const CLnormal* axis,
	      float angular_speed,
	      float mass,
	      float radius,
	      int lifespan,
	      const CLmodel* model)
{
    ZfDebris* debris;

    debris = g_new(ZfDebris, 1);

    debris->ref_count = 0;

    clCopyMatrix(&debris->frame, frame);
    /*
      clDefaultMatrix(&debris->frame);
      cluSetVertexMatrixOrigin(&temp, frame);
      cluSetMatrixPosition(&debris->frame, &temp);
      cluDefaultQuaternion(&debris->orientation);
    */
    cluSetQuaternionMatrix(&debris->orientation, &debris->frame);

    clCopyNormal(&debris->velocity, velocity);
    clCopyNormal(&debris->axis, axis);

    debris->angular_speed = angular_speed;
    debris->mass = mass;
    debris->radius = radius;

    debris->age = 0;
    debris->lifespan = lifespan;

    debris->model = (CLmodel*)model;

    zf_animation_system_add(debris,
			    &smart_pointer,
			    (ZfAnimate*) animate);

    zf_collision_system_add_dynamic(debris,
				    &smart_pointer,
				    &dynamic_collider,
				    ZF_DEBRIS,
				    mass,
				    radius);
/*
  zf_render_system_add_translucent(debris,
  &smart_pointer,
  (ZfRender*)render);
*/
    zf_render_system_add_opaque(debris,
				&smart_pointer,
				(ZfRender*)render);
}
