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

#define EEL_SEGMENT_MASS 0.5
#define EEL_SEGMENT_RADIUS 0.5f
#define EEL_SEGMENT_ROTATION_RATE 2.0f
#define EEL_SCORE 50

struct ZfEelSegment
{
    unsigned int ref_count;

    CLvertex position;
    bool started;

    CLmatrix flux_frame;

    /*need this for trigger system */
    float start_flux_t;
    float end_flux_t;
    int segment_num; /*decreases from total num_segments */

    int type;   /* 0 - head
		   1 - body
		   2 - tail */

    float flux_t;
    float roll;

   /* bool valid;*/
    
    int health;
    bool decrement_health;
    
    ZfEelSegment* previous;
    ZfEelSegment* next;
};

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

static CLcontext* context_1;
static CLcontext* context_2;
static CLcontext* context_3;
static CLmodel* model_head;
static CLmodel* model_body;
static CLmodel* model_tail;


static bool
is_valid(const ZfEelSegment* eel_segment)
{
    return eel_segment->health > 0;
}

static void
reference(ZfEelSegment* eel_segment)
{
    eel_segment->ref_count++;
}

static void
release(ZfEelSegment* eel_segment)
{
    eel_segment->ref_count--;

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

static void
animate(ZfEelSegment* eel_segment)
{
    if(eel_segment->started)
    {
	zf_flux_update_frame(&eel_segment->flux_frame, 
			     eel_segment->flux_t);

	cluSetVertexMatrixOrigin(&eel_segment->position, &eel_segment->flux_frame);

	eel_segment->flux_t = zf_flux_query_previous_t_given_distance(eel_segment->flux_t,
								      0.05f); /*HACK - MAGIC number - slighting faster than ship*/
    
	if(eel_segment->decrement_health)
	{
	    eel_segment->health -= 8; 

	    if(!is_valid(eel_segment))
	    {
		zf_explosion_new(&eel_segment->position, 2.0f);
		zf_score_indicator_new(&eel_segment->position, EEL_SCORE);
		zf_hud_increase_score(EEL_SCORE);

		/* set a new debris */
		{        
		    CLnormal velocity;
		    CLnormal axis;
		    float angular_velocity = 0;
		    CLmodel* model;

		    cluSetNormal(&velocity, 0.0f, 0.0f, 0.0f);
		    cluSetNormal(&axis, 0.0f, -1.0f, 0.0f);

		    switch(eel_segment->type)
		    {
		    case 0:
			model = model_head;
			break;
		    case 1:
			model = model_body;
			break;
		    case 2:
			model = model_tail;
			break;
		    }

		    
		    zf_debris_new(&eel_segment->flux_frame,
				  &velocity,
				  &axis,
				  angular_velocity,
				  EEL_SEGMENT_MASS,
				  2.0f * EEL_SEGMENT_RADIUS,
				  500,
				  model);
		   
		    release(eel_segment);
		}
		
		if(eel_segment->next)
		    if(is_valid(eel_segment->next))
			eel_segment->next->decrement_health = true;
                
	    }
	}

	eel_segment->roll -= EEL_SEGMENT_ROTATION_RATE;
    }
}

static void
query_frame(ZfEelSegment* eel_segment, 
	    CLmatrix* frame)
{
    CLnormal offset;
    CLvertex origin;
    
    static const CLnormal axis = {0.0f, 0.0f, 1.0f};
    CLUquaternion quaternion;
    CLmatrix matrix;

    /* for the rotation */
    cluSetQuaternionAxisAngle(&quaternion, &axis, eel_segment->roll);
    cluQuaternionNormalise(&quaternion);
    clDefaultMatrix(&matrix);
    cluSetMatrixOrientation(&matrix, &quaternion);

    cluMatrixTransform(&matrix, &eel_segment->flux_frame);
    clCopyMatrix(frame, &matrix);

    /* for the offset */
    cluSetNormalMatrixAxisY(&offset, frame);
    cluNormalScale(&offset, 0.8f);

    cluSetVertexMatrixOrigin(&origin, frame);
    cluVertexAdd(&origin, &offset);

    cluSetMatrixPosition(frame, &origin);
}

static void
query_position(ZfEelSegment* eel_segment,
	       CLvertex* position)
{
    CLmatrix frame;
    query_frame(eel_segment, &frame); /* Need this to get the eel frame */

    cluSetVertexMatrixOrigin(position, &frame);
}

static void
render(ZfEelSegment* eel_segment)
{
    if(eel_segment->started)
    {
	CLmatrix frame;
	glPushAttrib(GL_ALL_ATTRIB_BITS);

	query_frame(eel_segment, &frame); /* Need this to get the eel frame */

	glEnable(GL_LIGHTING);
	glEnable(GL_TEXTURE_2D);
	glEnable(GL_COLOR_MATERIAL);

	glPushMatrix();
	glMultMatrixf((GLfloat*)&frame);

	glColor3f(1.0f, 1.0f, 1.0f);

	/* different colour for head */
	if(eel_segment->type == 0 && !eel_segment->decrement_health)
	{
	    glColor3f(0.0f, 1.0f, 0.0f);
	    clRenderModel(model_head); 
	    /*	    glutSolidSphere(EEL_SEGMENT_RADIUS, 8, 8);*/
	}
	else if(eel_segment->type == 2) /* last segment */
	    clRenderModel(model_tail);
	else
	    clRenderModel(model_body);

	if (zf_render_system_get_tool_mode())
	{
	    glPointSize(8.0f);
	    glBegin(GL_POINTS);
	    glVertex3f(0.0f, 0.0f, 0.0f);
	    glEnd();
	}
	
	glPopMatrix();

	glPopAttrib();
    }
}

static void
collision_response(ZfEelSegment* eel_segment,
		   const void* collider_data,
		   ZfType collider_type,
		   const CLvertex* collision_position,
		   const CLnormal* collision_force_perp,
		   const CLnormal* collision_force_tan)
{
    if(eel_segment->health > 0)
    {
	switch(collider_type)
	{
	case ZF_LASER:
	    eel_segment->health -= 100;
	    break;
	case ZF_MISSILE:
	    eel_segment->health -= 100;
	    break;
	case ZF_TRITOR:
	    eel_segment->health -= 100;
	    break;
	case ZF_DEBRIS:
	    eel_segment->health -= 10;
	    break;
	case ZF_BOMB:
	    eel_segment->health -= 100;
	    break;
	case ZF_SHIP:
	    eel_segment->health = 100;
	    break;
	}
   
    
	if (eel_segment->health <= 0)
	{
	    zf_explosion_new(&eel_segment->position, 2.0f);
	    zf_score_indicator_new(&eel_segment->position, EEL_SCORE);
	    zf_hud_increase_score(EEL_SCORE);

	    
	    if(eel_segment->next)
		if(is_valid(eel_segment->next))
		    eel_segment->next->decrement_health = true;

	    /* set a new debris */
	    {        
		CLmodel* model;
		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);
		cluSetNormal(&velocity, 0.0f, 0.0f, 0.0f);
		cluSetNormal(&axis, 0.0f, -1.0f, 0.0f);
		
		switch(eel_segment->type)
		{
		case 0:
		    model = model_head;
		    break;
		case 1:
		    model = model_body;
		    break;
		case 2:
		    model = model_tail;
		    break;
		}

		zf_flux_update_frame(&eel_segment->flux_frame, 
				     eel_segment->flux_t);
		
		zf_debris_new(&eel_segment->flux_frame,
			      &velocity,
			      &axis,
			      angular_velocity,
			      EEL_SEGMENT_MASS,
			      2.0f * EEL_SEGMENT_RADIUS,
			      500,
			      model);
		
	    }

	    if(eel_segment->type != 0)
		release(eel_segment);
	}
    }
}

static void
eel_read(FILE* stream)
{
    float start_flux_t, end_flux_t;
    float roll;
    int num_segments;

    fscanf(stream, "ZfEelSegment\n{\n");
    fscanf(stream, "start_end_flux_t  = %f %f\n",&start_flux_t, &end_flux_t);
    fscanf(stream, "roll = %f\n", &roll);
    fscanf(stream, "num_segments = %d\n", &num_segments);
    fscanf(stream, "}\n");

    zf_eel_segment_new(start_flux_t, end_flux_t, roll, num_segments, NULL);  /* null for parent*/
}

static void
start(ZfEelSegment* eel_segment)
{
    eel_segment->started = true;
    while(eel_segment->next)
    {
	eel_segment = eel_segment->next;
	eel_segment->started = true;
    }
}

/*
  static void
  kill(ZfEelSegment* eel_segment)
  {
  if(eel_segment)
    {
    eel_segment->health = 0;
    while(eel_segment->next)
    {
    eel_segment = eel_segment->next;
    eel_segment->health = 0;
    }
    }
    }
*/

void
zf_eel_segment_init(char* filename)
{
    //printf("%s()\n", __FUNCTION__);

    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;

    {
	unsigned int i;
	int num_eels;
    
	FILE* stream;
	
	stream = fopen(filename, "r");

	/* check fopen */
	if(!stream)
	{
	    printf("%s() : ERROR : cant load eel file %s\n", __FUNCTION__, filename);
	    exit(1);
	}

	fscanf(stream, "ZfEels\n{\n");
	fscanf(stream, "num_eels = %d\n", &num_eels);
	fscanf(stream, "eels =\n[\n");

	for (i = 0; i < num_eels; i++)
	{
	    eel_read(stream);   
	}

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

	fclose(stream);
    }

    context_1 = clDefaultContext(clNewContext());
    model_head = clioLoadModel(context_1, "../data/models/eel/eel_head.3ds");
    context_2 = clDefaultContext(clNewContext());
    model_body = clioLoadModel(context_2, "../data/models/eel/eel_body.3ds");
    context_3 = clDefaultContext(clNewContext());
    model_tail = clioLoadModel(context_3, "../data/models/eel/eel_tail.3ds");

     
    if (!model_head)
    {
	printf("could not load eel head model\n");
	exit(1);
    }
    
    if (!model_body)
    {
	printf("could not load eel body model\n");
	exit(1);
    }
    
    if (!model_tail)
    {
	printf("could not load eel tail model\n");
	exit(1);
    }

    cluModelCentre(model_head); 
    cluModelScaleUnitCube(model_head);
    cluModelScale(model_head, 1.6f);
    clUpdateContext(model_head->context);

    cluModelCentre(model_body); 
    cluModelScaleUnitCube(model_body);
    cluModelScale(model_body, 1.3f);
    clUpdateContext(model_body->context);

    cluModelCentre(model_tail); 
    cluModelScaleUnitCube(model_tail);
    cluModelScale(model_tail, 1.4f);
    clUpdateContext(model_tail->context);
    
}

void
zf_eel_segment_write(char* filename, GList* eel_segment_list)
{
    FILE* stream; 
    GList*  li;
    ZfEelSegment* eel_segment;
    
    printf("%s() : save eel file %s\n", __FUNCTION__, filename);
    
    stream = fopen(filename, "w");
    
    fprintf(stream, "ZfEels\n{\n");
    fprintf(stream, "num_eels = %d\n",  g_list_length(eel_segment_list));
    fprintf(stream, "eels =\n[\n");

    for (li = eel_segment_list; li; li = li->next)
    {
	eel_segment = (ZfEelSegment*)li->data;

	fprintf(stream, "ZfEelSegment\n{\n");
	fprintf(stream, "start_end_flux_t  = %f %f\n",eel_segment->start_flux_t, eel_segment->end_flux_t);
	fprintf(stream, "roll = %f\n", eel_segment->roll);
	fprintf(stream, "num_segments = %d\n", eel_segment->segment_num);

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

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

    fclose(stream);
}

void
zf_eel_segment_close(void)
{
    clDeleteContext(context_1);
    clDeleteContext(context_2);
    clDeleteContext(context_3);
}

ZfEelSegment*
zf_eel_segment_new(float start_flux_t,
		   float end_flux_t,
		   float roll, 
                   int num_segments,
                   ZfEelSegment* parent)
     
{
    /* return from function if there are no more segments to be created */
    if(num_segments == 0)
        return NULL;

    ZfEelSegment* eel_segment;

    eel_segment = g_new(ZfEelSegment, 1);
    
    eel_segment->ref_count = 0;
    zf_ship_query_flux_frame(&eel_segment->flux_frame);
    zf_flux_update_frame(&eel_segment->flux_frame, end_flux_t);

    eel_segment->flux_t = end_flux_t;  /* start the eel from the end trigger flux t */
    eel_segment->start_flux_t = start_flux_t;
    eel_segment->end_flux_t = end_flux_t;
    eel_segment->segment_num = num_segments;

    eel_segment->roll = roll;
    eel_segment->started = false;

    eel_segment->health = 100;
    eel_segment->decrement_health = false;

    eel_segment->previous = parent;

    /* TODO - make the next segments based on distance and not 0.1f flux - can vary a lot with repeater distances */
    eel_segment->next = zf_eel_segment_new(0.0f, end_flux_t + 0.1f, roll + 60, num_segments - 1, eel_segment);

    if(eel_segment->next)
	reference(eel_segment->next);


    /* which means it is the head */
    if(!parent)
    {
	eel_segment->type = 0; /* head */

	zf_trigger_system_add_trigger(eel_segment->start_flux_t,
				      eel_segment,
				      &smart_pointer,
				      (ZfSpawn*)start); 
	
	/*
	  shouldn't need this - eel will die in one way or another when ship passes it.
	  zf_trigger_system_add_trigger(eel_segment->end_flux_t,
	  eel_segment,
	  &smart_pointer,
	  (ZfSpawn*)kill);*/
    }
    else if(num_segments == 1)
	eel_segment->type = 2;  /* tail */
    else
	eel_segment->type = 1;  /* body */

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

    zf_collision_system_add_dynamic(eel_segment,
				    &smart_pointer,
				    &dynamic_collider,
				    ZF_EEL_SEGMENT,
				    EEL_SEGMENT_MASS, /* mass */
				    EEL_SEGMENT_RADIUS); /* radius */

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


    return eel_segment;
}
