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

#define DROID_MASS 0.2f
#define DROID_RADIUS 1.0f
#define DROID_COLLIDER_RADIUS 0.5f
#define DROID_SCORE 200

#define HOVER_PERIOD_LENGTH 300

#define TOOL_RENDER_SIZE 1.0f

#define FLUX_FRAME_OFFSET_TO_SHIP 0.8f

typedef enum {SLEEP, MOVE, HOVER, FINISH} MovementState;

struct ZfDroid
{
    unsigned int ref_count;
    CLmatrix frame;/* relative to ship flux_frame + FLUX_FRAME_OFFSET_TO_SHIP*/
    
    int health;

    MovementState movement_state;

    int type; /*which tier it aims for, 0/1/2*/

    /*float hover_angle;*/

    float hover_time;

    float trig_active_t;
    float trig_die_t;

};

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

static CLcontext* context;
static CLmodel* model;

static CLvertex type_0_destination;
static CLvertex type_1_destination;
static CLvertex type_2_destination;

CLtexture* tool_texture;

static bool
is_valid(const ZfDroid* droid)
{
    return droid->health > 0;
}

static void
referencing(ZfDroid* droid)
{
   /* printf("referencing droid %d with %d ref_count\n", droid->type, droid->ref_count);*/
    droid->ref_count++;
}

static void
release(ZfDroid* droid)
{
    droid->ref_count--;
    /*printf("release droid %d with %d refCount\n", droid->type, droid->ref_count);*/

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


static void
droid_read(FILE* stream)
{
    CLvertex position;
    int type;
    float start_t, end_t;

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

    fscanf(stream, "position = %f %f %f\n",
	   &position.x, &position.y, &position.z);

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

    fscanf(stream, "start_t end_t = %f %f\n", &start_t, &end_t);

    fscanf(stream, "}\n");

    zf_droid_new(&position, type, start_t, end_t);
}


static void
animate(ZfDroid* droid)
{
    if(droid->movement_state != SLEEP)
    {
	CLmatrix destination_frame;
	float ship_flux_t = zf_ship_query_flux_t();
	CLvertex global_destination_position, droid_position;
	CLnormal direction, up;
	
	clDefaultMatrix(&destination_frame);
	ship_flux_t += FLUX_FRAME_OFFSET_TO_SHIP;
	zf_flux_update_frame(&destination_frame, ship_flux_t);

	cluSetVertexMatrixOrigin(&droid_position, &droid->frame);
	
	/* Not sure if this is working properly yet */
	cluSetNormal(&up, 0.0f, 1.0f, 0.0f);
	cluNormalTransform(&up, &destination_frame);
	zf_align_frame_y_vertex_normal(&droid->frame, &droid_position, &up);                                                                                  

	switch(droid->type)
	{
	case 0:
	    clCopyVertex(&global_destination_position, &type_0_destination);
	    cluVertexTransform(&global_destination_position, &destination_frame);
	    break;
	case 1:
	    clCopyVertex(&global_destination_position, &type_1_destination);
	    cluVertexTransform(&global_destination_position, &destination_frame);
	    break;
	case 2:
	    clCopyVertex(&global_destination_position, &type_2_destination);
	    cluVertexTransform(&global_destination_position, &destination_frame);
	    break;
	default:
	    break;
	}
			     

	switch(droid->movement_state)
	{
        case MOVE:
	    {
		cluNormalDifference(&direction, &global_destination_position, &droid_position);
	
		if(cluNormalMagnitude(&direction) < 0.1f)
		{
		    droid->movement_state = HOVER;
		}

		cluNormalNormalise(&direction);

		/* WARNING: magic number */
		cluNormalScale(&direction, 0.5f);

		cluMatrixTranslate(&droid->frame, &direction);
		break;
	    }
	case HOVER:
	    {
		cluNormalDifference(&direction, &global_destination_position, &droid_position);
		/*cluNormalNormalise(&direction);*/
		/* WARNING: magic number */
		cluNormalScale(&direction, 1.0f);
		cluMatrixTranslate(&droid->frame, &direction);

		/*HACK
		if(droid->hover_time == 300)
		{
		    zf_droid_missile_new(&droid_position, droid->type);
		    droid->hover_time--;
		}
		*/

		/* time to shoot a torpedo */
		if(droid->hover_time == 120)
		    zf_droid_missile_new(&droid_position, droid->type);
	       
		
		droid->hover_time--;

            
		if(droid->hover_time <= 0.0f)
		{
		    droid->movement_state = FINISH;
		}
		break;
	    }
	case FINISH:
	    cluSetVertex(&global_destination_position, -3.751400f, -33.580006f, -441.249146f);

	    cluNormalDifference(&direction, &global_destination_position, &droid_position);
	    
	    cluNormalNormalise(&direction);
	    
	    /* WARNING: magic number */
	    cluNormalScale(&direction, 0.3f);  /* faster than others.*/
	    
	    cluMatrixTranslate(&droid->frame, &direction);
	    break;
	}
    }
}

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

static void
collision_response(ZfDroid* droid,
		   const void* collider_data,
		   ZfType collider_type,
		   const CLvertex* collision_position,
		   const CLnormal* collision_force_perp,
		   const CLnormal* collision_force_tan)
{
    if(droid->movement_state != SLEEP)
    {
	CLvertex position;

	cluSetVertexMatrixOrigin(&position, &droid->frame);
	switch(collider_type)
	{
	case ZF_LASER:
	    droid->health -= 100;
	    break;
	case ZF_BEAM:
	    droid->health -= 100;
	    break;
	case ZF_MISSILE:
	    droid->health -= 100;
	    break;
	case ZF_SUPER_MISSILE:
	    droid->health -= 100;
	    break;
	case ZF_BOMB:
	    droid->health -= 100;
	    zf_explosion_new(&position, 2.0f);
	    break;
	case ZF_DECOY:
	    {
		CLnormal dir;
		cluNormalDifference(&dir, &position, collision_position);
		cluNormalScale(&dir, 0.1f);
		cluVertexAdd(&position, &dir);
		cluSetMatrixPosition(&droid->frame, &position);
	    }
	    break;
	    /*
	      case ZF_SHIP:
	      cluSetMatrixPosition(&droid->frame, collision_position);
	      break;
	    */
	default:
	    cluSetMatrixPosition(&droid->frame, collision_position);
	    break;
	}



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


	    cluNormalNormalise(&perp_dir);
	    cluNormalNormalise(&tan_dir);

	    if(cluNormalDotProduct(&perp_dir, &tan_dir) < CL_EPSILON)
	    {
		cluSetNormal(&axis, 0.0f, 1.0f, 0.0f); /* Non-zero*/
		angular_velocity = 0.0f;
	    }
	    else
	    {
		cluNormalCrossProduct(&axis, 
				      collision_force_perp, 
				      collision_force_tan);
		cluNormalNormalise(&axis);
		angular_velocity = 
		    cluNormalMagnitude(collision_force_tan) * DROID_RADIUS;
	    }
	    /*	clPrintNormal(collision_force_tan);
	      printf("angular_velocity = %f\n", angular_velocity);*/
	    zf_explosion_new(&position, 2.0f);

	
	    zf_debris_new(&droid->frame,
			  &velocity,
			  &axis,
			  angular_velocity,
			  DROID_MASS,
			  2.0f * DROID_COLLIDER_RADIUS,
			  500,
			  model);

	    zf_score_indicator_new(&position, DROID_SCORE);
	    zf_hud_increase_score(DROID_SCORE);

	}
    }

}

static void
render(ZfDroid* droid)
{
    if (zf_render_system_get_tool_mode())
    {
	CLvertex start;
	CLvertex end;
	CLvertex droid_position;
	
	glDisable(GL_COLOR_MATERIAL);
	glDisable(GL_TEXTURE_2D);
	glDisable(GL_LIGHTING);

	/* get droid position */
	cluSetVertexMatrixOrigin(&droid_position, &droid->frame);
	
	/* get start and end trigger positions */
	zf_flux_query_position(&start, droid->trig_active_t);
	zf_flux_query_position(&end, droid->trig_die_t);
	
	glLineWidth(2.0f);
	glBegin(GL_LINES);
	/* draw green line from start to droid */
	glColor3f(0.0f, 1.0f, 0.0f);
	glVertex3fv((GLfloat*)&start);
	glVertex3fv((GLfloat*)&droid_position);
	
	/* draw red line from droid to end */
	glColor3f(1.0f, 0.0f, 0.0f);
	glVertex3fv((GLfloat*)&droid_position);
	glVertex3fv((GLfloat*)&end);
	glEnd();
	
	glPointSize(8.0f);
	glBegin(GL_POINTS);
	/* draw green point at start trigger */
	glColor3f(0.0f, 1.0f, 0.0f);
	glVertex3fv((GLfloat*)&start);
	
	/* draw yellow point at position */
	switch(droid->type)
	{
	case 0:
	    glColor3f(1.0f, 0.0f, 0.0f);
	    break;
	case 1:
	    glColor3f(0.0f, 1.0f, 0.0f);
	    break;
	case 2:
	    glColor3f(0.0f, 0.0f, 1.0f);
	    break;
	default:
	    glColor3f(1.0f, 1.0f, 1.0f);
	    break;
	}
	glVertex3fv((GLfloat*)&droid_position);

	/* draw red point at end trigger */
	glColor3f(1.0f, 0.0f, 0.0f);
	glVertex3fv((GLfloat*)&end);
	glEnd();
	
    }
    else /* tool mode OFF */
    {
	if(droid->movement_state!=SLEEP)
	{
	    glPushAttrib(GL_ALL_ATTRIB_BITS); /* TESTING!!! super conservative */
	    
	    glPushMatrix();
	    glMultMatrixf((GLfloat*)&droid->frame);
	    
	    glEnable(GL_COLOR_MATERIAL);
	    glEnable(GL_TEXTURE_2D);
	    glEnable(GL_LIGHTING);
	    
	    /* change color depending on droid type */
	    switch(droid->type)
	    {
	    case 0:
		glColor3f(1.0f, 0.0f, 0.0f);
		break;
	    case 1:
		glColor3f(0.0f, 1.0f, 0.0f);
		break;
	    case 2:
		glColor3f(0.0f, 0.0f, 1.0f);
		break;
	    default:
		glColor3f(1.0f, 1.0f, 1.0f);
		break;
	    }
	 
	    /* render model */
	    cluRenderModel(model);
	    
	    glPopMatrix();
	    
	    glPopAttrib();
	}
    }
}

#if 0
{
    GLdouble projection[16];
    GLdouble modelview[16];
    GLint viewport[4];
    GLdouble winX, winY, winZ;
    double aspect;
    unsigned int i;
    
    glGetDoublev(GL_PROJECTION_MATRIX, projection);
    glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
    glGetIntegerv(GL_VIEWPORT, viewport);
    
    aspect = (double)viewport[2] / (double)viewport[3];
    
    glPushMatrix();
    glLoadIdentity();
    
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    
    
    gluProject((GLdouble)droid_position.x,
	       (GLdouble)droid_position.y,
	       (GLdouble)droid_position.z,
	       modelview,
	       projection,
	       viewport,
	       &winX, 
	       &winY,
	       &winZ);
    
    /* screen coords to identity projection matrix */
    winX = winX / (double)viewport[2];
    winY = winY / (double)viewport[3];
    
    winX -= 0.5f;
    winX *= 2.0f;
    winY -= 0.5f;
    winY *= 2.0f;
    
    glColor3f(1.0f, 1.0f, 0.0f);
    glEnable(GL_TEXTURE_2D);
    clLoadTexture(tool_texture);
    
    glBegin(GL_QUADS);
    glTexCoord2f(0.0f, 0.0f);
    glVertex2f(winX - TOOL_RENDER_SIZE / aspect, winY - TOOL_RENDER_SIZE);
    glTexCoord2f(0.0f, 1.0f);
    glVertex2f(winX - TOOL_RENDER_SIZE / aspect, winY + TOOL_RENDER_SIZE);
    glTexCoord2f(1.0f, 1.0f);
    glVertex2f(winX + TOOL_RENDER_SIZE / aspect, winY + TOOL_RENDER_SIZE);
    glTexCoord2f(1.0f, 0.0f);
    glVertex2f(winX + TOOL_RENDER_SIZE / aspect, winY - TOOL_RENDER_SIZE);
    glEnd();
    
    glPopMatrix();
    
    glMatrixMode(GL_MODELVIEW);		
}
#endif

static void
start(ZfDroid* droid)
{
    droid->movement_state = MOVE;
}


static void
kill(ZfDroid* droid)
{
    if(droid)
	droid->health = 0;
}

void
zf_droid_close()
{
    clDeleteContext(context);
}


void
zf_droid_init(char* filename)
{
    context = clDefaultContext(clNewContext());
    model = clioLoadModel(context, "../data/models/attack_droid/attack_droid.3DS");

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

    cluModelCentre(model); 
    cluModelScaleUnitCube(model);
    clUpdateContext(model->context);

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

    dynamic_collider.query_position = (ZfQueryPosition*) query_position;
    dynamic_collider.collision_response = (ZfCollisionResponse*) collision_response;


    cluSetVertex(&type_0_destination, -2.0f, 0.0f, 0.0f);
    cluSetVertex(&type_1_destination, 0.0f, 2.0f, 0.0f);
    cluSetVertex(&type_2_destination, 2.0f, 0.0f, 0.0f);

    /* load the texture */
    tool_texture = clioLoadTexture("../data/textures/tool/tool_droid.png");

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

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

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

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

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

	for (i = 0; i < num_droids; i++)
	{
	    droid_read(stream);   
	}

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

	fclose(stream);
    }
   
}

void
zf_droid_write(char* filename, GList* droid_list)
{
    FILE* stream; 
    GList*  li;
    ZfDroid* droid;
    CLvertex position;
    
    printf("%s() : save droid file %s\n", __FUNCTION__, filename);
    
    stream = fopen(filename, "w");
    
    fprintf(stream, "ZfDroids\n{\n");
    fprintf(stream, "num_droids = %d\n",  g_list_length(droid_list));
    fprintf(stream, "droids =\n[\n");

    for (li = droid_list; li; li = li->next)
    {
	droid = (ZfDroid*)li->data;
	
	cluSetVertexMatrixOrigin(&position, &droid->frame);

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

	fprintf(stream, "type = %d\n", droid->type);

	fprintf(stream, "start_t end_t = %f %f\n", droid->trig_active_t, droid->trig_die_t);

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

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

    fclose(stream);
}
  
ZfDroid*
zf_droid_new(const CLvertex* position, 
	     const int droid_type,
	     const float start_t,
	     const float end_t)
{
    ZfDroid* droid;

    droid = g_new(ZfDroid, 1);

    droid->ref_count = 0;
    droid->health = 100;

    clDefaultMatrix(&droid->frame);
    cluSetMatrixPosition(&droid->frame, position);

    droid->movement_state = SLEEP;
    droid->hover_time = HOVER_PERIOD_LENGTH;
    
    droid->trig_active_t = start_t;
    droid->trig_die_t = end_t;
    
    assert(droid_type >=0 && droid_type <=2);
    droid->type = droid_type;
    
    zf_trigger_system_add_trigger(droid->trig_active_t,
				  droid,
				  &smart_pointer,
				  (ZfSpawn*)start); 
    

				  
    zf_trigger_system_add_trigger(droid->trig_die_t,
				  droid,
				  &smart_pointer,
				  (ZfSpawn*)kill);  
    
	
    /* maybe wrap-up all functions below in zf_add_object ? */
#if 1
    zf_animation_system_add(droid,
			    &smart_pointer,
			    (ZfAnimate*) animate);
  
    zf_collision_system_add_dynamic(droid,
				    &smart_pointer,
				    &dynamic_collider,
				    ZF_DROID,
				    DROID_MASS, /*mass */
				    DROID_COLLIDER_RADIUS); /* radius */

    zf_render_system_add_opaque(droid,
				&smart_pointer,
				(ZfRender*) render);
#endif


    return droid;
}


