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

#define LEECH_MASS 1.0f
#define LEECH_RADIUS 0.5f
#define LEECH_SCORE 130

#define MAX_ENERGY 200
#define ENERGY_INCREMENT 1
#define CLOSENESS_THRESHOLD 0.2f

typedef enum {GROUND, UP_REPEATER, DOWN_REPEATER, ENERGISING, DEENERGISING} MovementState;

struct ZfLeech
{
    unsigned int ref_count;

    CLmatrix frame;
    CLnormal velocity;
    
    int health;
    int energy;
    /*
      void* hive_data;
      ZfSmartPointer* hive_smart_pointer;
    */

    CLvertex repeater_position;

    MovementState movement_state;
};

static CLcontext* context;
static CLmodel* model;

static ZfSmartPointer smart_pointer; /* interface for leeches */
static ZfDynamicCollider dynamic_collider; /* interface for leeches */

static bool
is_valid(const ZfLeech* leech)
{
    return leech->health > 0;
}

static void
reference(ZfLeech* leech)
{
    leech->ref_count++;
}

static void
release(ZfLeech* leech)
{
    leech->ref_count--;

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

static void
animate(ZfLeech* leech)
{
    CLnormal direction;    
    CLvertex target_position;
    CLvertex leech_position;
    bool need_to_move;
    
    cluSetVertexMatrixOrigin(&leech_position, &leech->frame);
    need_to_move = true;

    switch(leech->movement_state)
    {
    case GROUND:    
	clCopyVertex(&target_position, &leech->repeater_position);
	
	target_position.y = zf_heightmap_query_height(target_position.x, target_position.z);
        	
	cluNormalDifference(&direction, &target_position, &leech_position);
        	
	if(cluNormalMagnitude(&direction) < 6.0f) /* this value still buggy */
	    leech->movement_state = UP_REPEATER;            
	
	cluNormalNormalise(&direction);
	break;
    case DOWN_REPEATER:
	clCopyVertex(&target_position, &leech->repeater_position);

	target_position.y = zf_heightmap_query_height(target_position.x, target_position.z);
	    
	cluNormalDifference(&direction, &target_position, &leech_position);

	cluNormalNormalise(&direction);
	break;
    case UP_REPEATER:
	clCopyVertex(&target_position, &leech->repeater_position);

	cluNormalDifference(&direction, &target_position, &leech_position);

	/*
	  if(cluNormalMagnitude(&direction) < CLOSENESS_THRESHOLD)
	  leech->movement_state = ENERGISING;
	*/
	
	cluNormalNormalise(&direction);
	break;
    case ENERGISING:
	leech->energy += ENERGY_INCREMENT;
	if(leech->energy >= MAX_ENERGY)
	    leech->movement_state = DOWN_REPEATER;
	
	need_to_move = false;  /* just change this if want leechs to go back to hive */
	
	break;
    case DEENERGISING:
	leech->energy -= ENERGY_INCREMENT;

	need_to_move = false;  /* just change this if want leechs to go back to repeater */
	break;
    }

    /* WARNING - magic numbers */
    if(need_to_move)
    {
	CLnormal up;
	cluSetNormal(&up, 0.0f, 1.0f, 0.0);
	    
	cluNormalScale(&direction, 0.05f);
	cluVertexAdd(&leech_position, &direction);
	zf_align_frame_y_vertex_normal(&leech->frame, &leech_position, &up);
	zf_align_frame_z_vertex_normal(&leech->frame, &leech_position, &direction);
    }

    /* cluNormalScale(&leech->velocity, 0.975f); *//* friction hack */
   /*  cluVertexAdd(&leech_position, &leech->velocity);*/
    /*
      {
      CLvertex position;
      CLnormal velocity = {0.0f, 0.0f, 0.0f};
      CLcolour colour = {1.0f, 0.8f, 0.5f, 1.0f};
      
      cluSetVertex(&position,
      leech_position.x + ((float)rand() / RAND_MAX - 0.5f),
      leech_position.y + ((float)rand() / RAND_MAX - 0.5f),
      leech_position.z + ((float)rand() / RAND_MAX - 0.5f));
      
      
      zf_particle_system_add_particle(&position,
      &direction,
      0.2f,
      &colour,
      200);

      }
    */
}

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

static void
collision_response(ZfLeech* leech,
		   const void* collider_data,
		   ZfType collider_type,
		   const CLvertex* collision_position,
		   const CLnormal* collision_force_perp,
		   const CLnormal* collision_force_tan)
{
    CLvertex leech_position;

    cluSetVertexMatrixOrigin(&leech_position, &leech->frame);
    
#if 1
    if(collider_type & ZF_DECOY)
    {
	CLnormal dir;

	cluNormalDifference(&dir, &leech_position, collision_position);

	cluNormalScale(&dir, 0.1f);

	cluVertexAdd(&leech_position, &dir);
	
    }
#elif 0
    {
	CLnormal offset;

 	cluNormalDifference(&offset, collision_position, &leech_position);
	cluNormalAdd(&leech->velocity, &offset);
 	clCopyVertex(&leech_position, collision_position);
    }
#elif 0
    /* hack - slimy balls */
    {
 	cluNormalDifference(&leech->velocity, collision_position, &leech_position);
 	clCopyVertex(&leech_position, collision_position);
    }
#elif 0
    /* velocity hack #2 */
    {
	if (!(collider_type & ZF_GROUND))
	    cluNormalAdd(&leech->velocity, &collision_force_perp);

	clCopyVertex(&leech_position, collision_position);
    }

#endif

    else if (collider_type & ZF_GROUND)
    {
	clCopyNormal(&leech->velocity, collision_force_perp);
	cluNormalAdd(&leech->velocity, collision_force_tan);
	leech->movement_state = GROUND;
    }

    else if(collider_type & ZF_WEAPON)
	leech->health -= 100;
    else if(collider_type & ZF_EXPLOSIVE)
	leech->health -= 100;
    else if(collider_type & ZF_DEBRIS)
	leech->health -= 50;
    /* Leeches can get run over? */
    else if(collider_type & ZF_SHIP)
	leech->health -= 100;
    else if(collider_type & ZF_LEECH)
    {
 	cluNormalDifference(&leech->velocity, collision_position, &leech_position);
 	clCopyVertex(&leech_position, collision_position);
    }
    


    cluSetMatrixPosition(&leech->frame, &leech_position);

    /* create debris if leech died due to collision */
    if (!is_valid(leech))
    {
        CLnormal velocity;
        CLnormal axis;
        float angular_velocity;
        
        cluSetNormal(&velocity, 0.0f, 0.0f, 0.0f);
        cluNormalCrossProduct(&axis, 
                    collision_force_tan, 
                    collision_force_perp);

        cluNormalNormalise(&axis);
        
        angular_velocity = 
            cluNormalMagnitude(collision_force_tan);
        
        zf_explosion_new(&leech_position, 2.0f);
        
	zf_debris_new(&leech->frame,
		      &velocity,
		      &axis,
		      angular_velocity,
		      LEECH_MASS,
		      2.0f * LEECH_RADIUS,
		      500,
		      model);

	zf_score_indicator_new(&leech_position, LEECH_SCORE);
	zf_hud_increase_score(LEECH_SCORE);
	
    }

}

static void
render(ZfLeech* leech)
{
    glPushAttrib(GL_ALL_ATTRIB_BITS); /* TESTING!!! super conservative */

    glPushMatrix();
    glMultMatrixf((GLfloat*)&leech->frame);
    //    glEnable(GL_LIGHTING);
    glEnable(GL_COLOR_MATERIAL);
    
    glColor3f(0.0f, 1.0f, 0.0f);
    cluRenderModel(model);
    glPopMatrix();
    
    glPopAttrib();
}

void
zf_leech_close(void)
{
    clDeleteContext(context);
}

void
zf_leech_init(char* filename)
{
    context = clDefaultContext(clNewContext());
    model = clioLoadModel(context, "../data/models/leech/leech.obj");

    /* not clean to just exit, but use this until we have a good error
       system */
    if (!model)
    {
	printf("could not load leech model\n");
	exit(1);
    }

    cluModelCentre(model); 
    cluModelScaleUnitCube(model);
/*    cluModelScale(model, LEECH_RADIUS);*/

    clUpdateContext(context);

    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;

    /* load leeches from filename */
    {
	unsigned int i;
	int num_leeches;
	CLvertex position;    

	FILE* stream;
	
	stream = fopen(filename, "r");

	/* check for error loading file */
	if(!stream)
	{
	    printf("%s() : ERROR : can't load leech file %s\n", __FUNCTION__, filename);
	    exit(1);
	}
	

	fscanf(stream, "ZfLeeches\n{\n");

	fscanf(stream, "num_leeches = %d\n", &num_leeches);

	fscanf(stream, "leeches =\n[\n");

	for (i = 0; i < num_leeches; i++)
	{
	    fscanf(stream, "ZfLeech\n{\n");
	    fscanf(stream, "position = %f %f %f\n",
		   &position.x, &position.y, &position.z);
	    
	    fscanf(stream, "}\n");

	    /* create a new leech at the position read from file */
	    zf_leech_new(&position);
	}

	fscanf(stream, "]\n");
	fscanf(stream, "}\n");

	fclose(stream);
    }
   

}

ZfLeech*
zf_leech_new(const CLvertex* position) /*, const CLvertex* target_near_repeater);*/
{
    ZfLeech* leech;

    leech = g_new(ZfLeech, 1);

    leech->ref_count = 0;

    clDefaultMatrix(&leech->frame);
    cluSetMatrixPosition(&leech->frame, position);
 
    cluSetNormal(&leech->velocity, 0.0f, 0.0f, 0.0f);

    leech->health = 100;
    leech->energy = 0;

    leech->movement_state = GROUND;

    zf_flux_nearest_repeater(position, &leech->repeater_position);


    /* maybe wrap-up all functions below in zf_add_object ? */
    zf_animation_system_add(leech,
			    &smart_pointer,
			    (ZfAnimate*) animate);

    zf_collision_system_add_dynamic(leech,
				    &smart_pointer,
				    &dynamic_collider,
				    ZF_LEECH,
				    LEECH_MASS, /* mass */
				    LEECH_RADIUS); /* radius */

    zf_render_system_add_opaque(leech,
				&smart_pointer,
				(ZfRender*)render);

    return leech;
}

void
zf_leech_write(char* filename, GList* leech_list)
{
    FILE* stream; 
    GList*  li;
    ZfLeech* leech;
    CLvertex position;
    
    printf("%s() : save leech file %s\n", __FUNCTION__, filename);
    
    stream = fopen(filename, "w");
    
    fprintf(stream, "ZfLeeches\n{\n");
    fprintf(stream, "num_leeches = %d\n",  g_list_length(leech_list));
    fprintf(stream, "leeches =\n[\n");

    for (li = leech_list; li; li = li->next)
    {
	leech = (ZfLeech*)li->data;
	
	cluSetVertexMatrixOrigin(&position, &leech->frame);

	fprintf(stream, "ZfLeech\n{\n");
	fprintf(stream, "position = %f %f %f\n",
	       position.x, position.y, position.z);

	fprintf(stream, "}\n");
    }

    fprintf(stream, "]\n");
    fprintf(stream, "}\n");

    fclose(stream);
}

