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

#define WASP_MASS 1.0f
#define WASP_RADIUS 1.0f
#define WASP_COLLIDER_RADIUS 0.5f

#define PRE_ATTACK_PERIOD_LENGTH 150
#define PRE_ATTACK_PERIOD_DECREMENT 0.1f

typedef enum {SLEEP, FLY_TO_SHIP, HOVER_BEFORE_SHIP, ATTACK_SHIP} MovementState;

struct ZfWasp
{
    unsigned int ref_count;
    CLmatrix frame;
    
    float trigger_time;

    int health;

    MovementState movement_state;

    float hover_offset_s;
    float hover_offset_t;

    float time_until_attack;

};

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

static CLcontext* context;
static CLmodel* model;

/* stores wasps so they can be saved, only used with tool */
static GList* wasp_list;

static bool
is_valid(const ZfWasp* wasp)
{
    return wasp->health > 0;
}

static void
reference(ZfWasp* wasp)
{
    wasp->ref_count++;
}

static void
release(ZfWasp* wasp)
{
    wasp->ref_count--;

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

static void
animate(ZfWasp* wasp)
{
    CLnormal direction;
    CLnormal ship_x_axis_offset;
    CLnormal ship_y_axis_offset;
    CLnormal ship_z_axis_offset;
    CLvertex point;
    CLvertex wasp_position;
    CLmatrix ship_frame;
    CLnormal up;

    cluSetNormal(&up, 0.0f, 1.0f, 0.0f);
    cluSetVertexMatrixOrigin(&wasp_position, &wasp->frame);

#if 1
    /* WARNING: can optimise this by only doing this calculation once and
     * storing direction although this will mean that the wasp will always head
     * to the same control point */
    zf_ship_query_position(&point);

    zf_ship_query_frame(&ship_frame);
    
    cluSetNormalMatrixAxisX(&ship_x_axis_offset, &ship_frame);
    cluSetNormalMatrixAxisY(&ship_y_axis_offset, &ship_frame);
    cluSetNormalMatrixAxisZ(&ship_z_axis_offset, &ship_frame);

    cluNormalScale(&ship_z_axis_offset, -10.0f);

    cluVertexAdd(&point, &ship_z_axis_offset);
    
    
    switch(wasp->movement_state)
    {
    case SLEEP:
	/* do nothing */
	break;
	
    case FLY_TO_SHIP:
	cluNormalDifference(&direction, &point, &wasp_position);
	
	if(cluNormalMagnitude(&direction) < 1.0f)
	{
	    wasp->movement_state = HOVER_BEFORE_SHIP;
	    wasp->time_until_attack = PRE_ATTACK_PERIOD_LENGTH;
	}
	
	cluNormalNormalise(&direction);
	
	/*! \todo WARNING: magic number */
	cluNormalScale(&direction, 0.15f);
	
	cluVertexAdd(&wasp_position, &direction);
	zf_align_frame_y_vertex_normal(&wasp->frame, &wasp_position, &up);
	zf_align_frame_z_vertex_normal(&wasp->frame, &wasp_position, &direction);
	break;
	
    case HOVER_BEFORE_SHIP:
	/* get it to move elsewhere on the plane in front of the ship */
	cluNormalScale(&ship_x_axis_offset, wasp->hover_offset_s);
	cluVertexAdd(&point, &ship_x_axis_offset);
	
	cluNormalScale(&ship_y_axis_offset, wasp->hover_offset_t);
	cluVertexAdd(&point, &ship_y_axis_offset);
	
	cluNormalDifference(&direction, &point, &wasp_position);
	
	if(cluNormalMagnitude(&direction) <= 1.0f)
	{
	    wasp->hover_offset_s = (float) (6.0*rand()/(RAND_MAX));
	    wasp->hover_offset_t = (float) (6.0*rand()/(RAND_MAX));
	    
	    wasp->hover_offset_s += -3.0f;
	    wasp->hover_offset_t += -3.0f;
	}
	
	cluNormalNormalise(&direction);
	
	/*! \todo WARNING: magic number */
	cluNormalScale(&direction, 0.05f);
	
	cluVertexAdd(&wasp_position, &direction);
	
	/* Make model face the ship */
	{
	    CLvertex ship_pos;
	    zf_ship_query_position(&ship_pos);
	    cluNormalDifference(&direction, &ship_pos, &wasp_position);
	    zf_align_frame_y_vertex_normal(&wasp->frame, &wasp_position, &up);
	    zf_align_frame_z_vertex_normal(&wasp->frame, &wasp_position, &direction);
	}
	
	wasp->time_until_attack -= PRE_ATTACK_PERIOD_DECREMENT;
	
	if(wasp->time_until_attack <= 0.0f)
	    wasp->movement_state = ATTACK_SHIP;
	
	break;
    
    case ATTACK_SHIP:
	zf_ship_query_position(&point);
	
	cluNormalDifference(&direction, &point, &wasp_position);
	
	if(cluNormalMagnitude(&direction) <= 1.0f)
	{
	    wasp->movement_state = HOVER_BEFORE_SHIP;
	    wasp->time_until_attack = PRE_ATTACK_PERIOD_LENGTH;
	}
	
	/*! \todo WARNING: magic number */
	cluNormalScale(&direction, 0.08f);
	
	cluVertexAdd(&wasp_position, &direction);
	zf_align_frame_y_vertex_normal(&wasp->frame, &wasp_position, &up);
	zf_align_frame_z_vertex_normal(&wasp->frame, &wasp_position, &direction);
    }
#endif
}

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

static void
collision_response(ZfWasp* wasp,
		   const void* collider_data,
		   ZfType collider_type,
		   const CLvertex* collision_position,
		   const CLnormal* collision_force_perp,
		   const CLnormal* collision_force_tan)
{
    CLvertex position;

    cluSetVertexMatrixOrigin(&position, &wasp->frame);
    switch(collider_type)
    {
    case ZF_MISSILE:
	wasp->health -= 50;
	break;
     case ZF_TRITOR:
	wasp->health -= 100;
	break;
    case ZF_DEBRIS:
	wasp->health -= 10;
	break;
    case ZF_BOMB:
	wasp->health -= 100;
	zf_explosion_new(&position, 2.0f);
	break;
    case ZF_DECOY_EXPLODED:
	wasp->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(&wasp->frame, &position);
	}
	break;
    case ZF_SHIP:
	cluSetMatrixPosition(&wasp->frame, collision_position);
	wasp->movement_state = HOVER_BEFORE_SHIP;
	wasp->time_until_attack = PRE_ATTACK_PERIOD_LENGTH;
	break;
    default:
	cluSetMatrixPosition(&wasp->frame, collision_position);
	break;
    }



    /* create debris if wasp died due to collision */
    if (!is_valid(wasp))
    {
	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) * WASP_RADIUS;
	}
/*	clPrintNormal(collision_force_tan);
	printf("angular_velocity = %f\n", angular_velocity);*/
	/*zf_explosion_new(&position, 2.0f);*/
	zf_debris_new(&wasp->frame,
		      &velocity,
		      &axis,
		      angular_velocity,
		      WASP_MASS,
		      2.0f * WASP_COLLIDER_RADIUS,
		      2000,
		      model);
    }

}

static void
render(ZfWasp* wasp)
{
    glPushAttrib(GL_ALL_ATTRIB_BITS); /* TESTING!!! super conservative */

    glPushMatrix();
    glMultMatrixf((GLfloat*)&wasp->frame);

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

    glColor3f(0.0f, 0.0f, 1.0f);
    /* HACK!!! */
    glRotatef(-90.0f, 1.0f, 0.0f, 0.0f);

    cluRenderModel(model);

    glPopMatrix();

    glPopAttrib();
}

static void
read(FILE* stream)
{
    CLvertex position;
    float trigger_time;
    
    int i, num_wasps;
    
    fscanf(stream, "ZfWasps\n{\n");
    
    fscanf(stream, "num_wasps = %d\n", &num_wasps);
    
    fscanf(stream, "wasps =\n[\n");
    
    for (i = 0; i < num_wasps; i++)
    {
        fscanf(stream, "ZfWasp\n{\n");
	fscanf(stream, "position = %f %f %f\n",
	       &position.x,
	       &position.y,
	       &position.z);
	fscanf(stream, "trigger_time = %f\n",
	       &trigger_time);
        fscanf(stream, "}\n");
    }

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

void
zf_wasp_close()
{
    clDeleteContext(context);
}

void
zf_wasp_init(char* filename)
{
    FILE* stream;

    printf("%s() : load wasps file %s\n", __FUNCTION__, filename);
    if(!(stream = fopen(filename, "r")))
    {
        fprintf(stderr, "error loading %s\n", filename);
        return;
    }

    read(stream);
    
    fclose(stream);

    context = clDefaultContext(clNewContext());
    model = clioLoadModel(context, "../data/models/wasp/wasp.3DS");

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

    cluModelCentre(model); 
    cluModelScaleUnitCube(model);
    clUpdateContext(model->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;

    /* read(false); */
 }

void
zf_wasp_tool_init(void)
{
    context = clDefaultContext(clNewContext());
    model = clioLoadModel(context, "../data/models/wasp/wasp.3DS");

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

    cluModelCentre(model); 
    cluModelScaleUnitCube(model);
    clUpdateContext(model->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;

    /* read(true); */
 }


void
zf_wasp_new(const CLvertex* position)
{
    ZfWasp* wasp;

    wasp = g_new(ZfWasp, 1);

    wasp->ref_count = 0;

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

    wasp->health = 100;
 
    wasp->movement_state = FLY_TO_SHIP;
 
    wasp->hover_offset_s = (float) (6.0*rand()/(RAND_MAX));
    wasp->hover_offset_t = (float) (6.0*rand()/(RAND_MAX));

    wasp->hover_offset_s += -3.0f;
    wasp->hover_offset_t += -3.0f;

    /*! \todo Maybe wrap-up all functions below in zf_add_object ? */

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

    zf_collision_system_add_dynamic(wasp,
				    &smart_pointer,
				    &dynamic_collider,
				    ZF_WASP,
				    WASP_MASS, /* mass */
				    WASP_COLLIDER_RADIUS); /* radius */

    zf_render_system_add_opaque(wasp,
				&smart_pointer,
				(ZfRender*) render);
}

/* save the wasp to a file */
void
zf_wasp_save(void)
{
	FILE* stream;
	
    GList* li;

	stream = fopen("../data/level/wasps.zf", "w");

	fprintf(stream, "ZfWasps\n{\n");
    
	fprintf(stream, "num_wasps = %d\n", g_list_length(wasp_list));

	fprintf(stream, "wasps =\n[\n");

    for (li = wasp_list; li; li = li->next)
    {
        fprintf(stream, "ZfWasp\n{\n");
	    fprintf(stream, "position = %f %f %f\n",
	       ((ZfWasp*)li->data)->frame.m30,
           ((ZfWasp*)li->data)->frame.m31,
           ((ZfWasp*)li->data)->frame.m32);
	    fprintf(stream, "trigger_time = %f\n",
            ((ZfWasp*)li->data)->trigger_time);
        fprintf(stream, "}\n");
    }

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

	fclose(stream);
}
