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

#define MISSILE_MASS 0.5f
#define MISSILE_RADIUS 2.0f
#define MISSILE_SPEED_FAST 0.8f
#define MISSILE_SPEED_SLOW 0.2f
/* this should go hand in hand with the speed of the slow missile*/
#define MISSILE_SLOW_FLUX_TRAIL 0.1f  
#define MISSILE_SHAKE 2.0f
#define MISSILE_SCORE 50
#define MISSILE_HOVER_TIME 300
#define ROTATE_SPEED 3.0f


typedef enum {LAUNCH, WAIT, ATTACK_SHIP} MovementState;

typedef struct EnemyMissile EnemyMissile;

struct EnemyMissile
{
    unsigned int ref_count;

    CLmatrix frame;
    int wait_time;
    int type;  /* 0 - straight to ship
		  1 - hover first, the straight to ship 
	          2 - hover first, the random path to ship*/


    MovementState movement_state;

    /* local coords to boss, otherwise will stay there when boss rotates */
    CLvertex local_hover;
    ZfBoss* boss;  /* pointer to the boss*/
    ZfSmartPointer* boss_smarter_pointer;

    /* these are global */
    CLvertex hover_pos;
    CLvertex previous_pos; /* for the purpose of estimating next position, to let ship missle lead the thing */
    float rotate;


    bool valid;
};

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

static CLcontext* context_1;
static CLmodel* missile_1_model;
static CLcontext* context_2;
static CLmodel* missile_2_model;
static CLcontext* context_3;
static CLmodel* missile_3_model;

static bool
is_valid(const EnemyMissile* enemy_missile)
{
    return enemy_missile->valid;
}

static void
reference(EnemyMissile* enemy_missile)
{
    enemy_missile->ref_count++;
}

static void
release(EnemyMissile* enemy_missile)
{
    enemy_missile->ref_count--;
    if (enemy_missile->ref_count == 0)
	g_free(enemy_missile);
}

static void
animate(EnemyMissile* enemy_missile)
{
    if(enemy_missile->valid) 
    {
	CLvertex missile_position;

	/* rotate, get the missile position, and set previous position (used for ship missile to guess a lead to missile */
	enemy_missile->rotate+=ROTATE_SPEED;
	cluSetVertexMatrixOrigin(&missile_position, &enemy_missile->frame);
	clCopyVertex(&enemy_missile->previous_pos, &missile_position);

	if(enemy_missile->boss_smarter_pointer->is_valid(enemy_missile->boss) && !zf_boss_query_dying(enemy_missile->boss))
	{
	    CLvertex ship_pos;
	    CLnormal velocity;
	    CLmatrix destination_frame;
	    CLmatrix* boss_frame;

	    /* get the local hover position into global coords */
	    boss_frame = zf_boss_query_frame(enemy_missile->boss);
	    clCopyVertex(&enemy_missile->hover_pos, &enemy_missile->local_hover);
	    cluVertexTransform(&enemy_missile->hover_pos, boss_frame);

	    /* get the ship postion. */
	    zf_ship_query_frame(&destination_frame);
	    cluSetVertexMatrixOrigin(&ship_pos, &destination_frame);

	    if(enemy_missile->movement_state == ATTACK_SHIP)
	    {
		float dist;

		cluNormalDifference(&velocity, &ship_pos, &missile_position);
		dist = cluNormalMagnitude(&velocity);
		cluNormalNormalise(&velocity);
	
		switch(enemy_missile->type)
		{
		case 2: 
		    cluNormalScale(&velocity, MISSILE_SPEED_SLOW);
		    
		    cluVertexAdd(&missile_position, &velocity);
		    zf_align_frame_y_vertex_normal(&enemy_missile->frame, &missile_position, &velocity);

		    break;
		case 1:
		    cluNormalScale(&velocity, MISSILE_SPEED_FAST);
	
		    cluVertexAdd(&missile_position, &velocity);
		    zf_align_frame_y_vertex_normal(&enemy_missile->frame, &missile_position, &velocity);
		    break;
		case 0:
		    {
			float shake_mag, tot_dist;
			CLnormal total;

			cluNormalScale(&velocity, MISSILE_SPEED_FAST);

			cluNormalDifference(&total, &ship_pos, &enemy_missile->hover_pos);
			tot_dist = cluNormalMagnitude(&total);
		
			shake_mag= MISSILE_SHAKE * dist/tot_dist;
			cluSetNormal(&velocity, 
				     velocity.i + (float) (shake_mag * rand()/RAND_MAX - shake_mag/2.0f),
				     velocity.j + (float) (shake_mag * rand()/RAND_MAX - shake_mag/2.0f),
				     velocity.k + (float) (shake_mag * rand()/RAND_MAX - shake_mag/2.0f));
		    }
		    cluVertexAdd(&missile_position, &velocity);
		    zf_align_frame_y_vertex_normal(&enemy_missile->frame, &missile_position, &velocity);
		    break;
		}

	    }
	    else if(enemy_missile->movement_state == LAUNCH || enemy_missile->movement_state == WAIT)
	    {
		float dist;
		    
		switch(enemy_missile->type)
		{
		case 0:
		    {
			CLvertex trail_ship;
			CLmatrix ship_frame;
			float ship_flux, lead_flux;

			/* given these things so slow, have to aim to lead the ship */			
			ship_flux = zf_ship_query_flux_t();
			ship_flux = ship_flux - MISSILE_SLOW_FLUX_TRAIL; 

			zf_ship_query_frame(&ship_frame);
			zf_flux_update_frame(&ship_frame, ship_flux);
			cluSetVertexMatrixOrigin(&trail_ship, &ship_frame);

			cluNormalDifference(&velocity, &trail_ship, &missile_position);
			dist = cluNormalMagnitude(&velocity);

			if(dist > MISSILE_SPEED_SLOW)
			{
			    cluNormalNormalise(&velocity);
			    cluNormalScale(&velocity, MISSILE_SPEED_SLOW);
			}

			cluVertexAdd(&missile_position, &velocity);
			zf_align_frame_y_vertex_normal(&enemy_missile->frame, &missile_position, &velocity);

			if(dist < 0.1f && enemy_missile->movement_state != WAIT)
			{
			    enemy_missile->wait_time = MISSILE_HOVER_TIME;
			    enemy_missile->movement_state = WAIT;
			}

		    }
		    break;
		case 1:
		case 2:
		    {   
			cluNormalDifference(&velocity, &enemy_missile->hover_pos, &missile_position);
			dist = cluNormalMagnitude(&velocity);

			if(dist > MISSILE_SPEED_FAST)
			{
			    cluNormalNormalise(&velocity);
			    cluNormalScale(&velocity, MISSILE_SPEED_FAST);
			}

			cluVertexAdd(&missile_position, &velocity);

			/* setting the missile to look at ship */
			cluNormalDifference(&velocity, &ship_pos, &missile_position);
			cluNormalNormalise(&velocity);
			zf_align_frame_z_vertex_normal(&enemy_missile->frame, &missile_position, &velocity);

			/* thresholding, because the hover point is always moving - since boss is moving */
			if((dist < 0.2f) && (enemy_missile->movement_state != WAIT))
			{   
			    enemy_missile->wait_time = MISSILE_HOVER_TIME;
			    enemy_missile->movement_state = WAIT;
			}
			break;
		    }
		}

		if(enemy_missile->movement_state == WAIT)
		{
		    enemy_missile->wait_time--;
		    if(enemy_missile->wait_time <= 0)
			enemy_missile->movement_state = ATTACK_SHIP;
		}
	    }
#if 0
	    else if(enemy_missile->movement_state == WAIT)
	    {
		switch(enemy_missile->type)
		{
		case 0:
		    float dist;

		    cluNormalDifference(&velocity, &enemy_missile->hover_pos, &missile_position);
		    dist = cluNormalMagnitude(&velocity);
		    /* no need to check for slow, since type 0 - slow ones, do not wait */
		    if(dist > MISSILE_SPEED_SLOW)
		    {
			cluNormalNormalise(&velocity);
			cluNormalScale(&velocity, MISSILE_SPEED_SLOW);
		    }
		    /* even though waiting, have to keep missiles moving to the hover point*/
		    cluVertexAdd(&missile_position, &velocity);
		    cluSetMatrixPosition(&enemy_missile->frame, &missile_position);

		    /* keep the missile looking at ship */
		    cluNormalDifference(&velocity, &ship_pos, &missile_position);
		    cluNormalNormalise(&velocity);
		    zf_align_frame_z_vertex_normal(&enemy_missile->frame, &missile_position, &velocity);

		    enemy_missile->wait_time--;
		    if(enemy_missile->wait_time <= 0)
			enemy_missile->movement_state = ATTACK_SHIP;
		    break;
		case 1:
		case 2:
		    {
			float dist;

			cluNormalDifference(&velocity, &enemy_missile->hover_pos, &missile_position);
			dist = cluNormalMagnitude(&velocity);
			/* no need to check for slow, since type 0 - slow ones, do not wait */
			if(dist > MISSILE_SPEED_FAST)
			{
			    cluNormalNormalise(&velocity);
			    cluNormalScale(&velocity, MISSILE_SPEED_FAST);
			}
			/* even though waiting, have to keep missiles moving to the hover point*/
			cluVertexAdd(&missile_position, &velocity);
			cluSetMatrixPosition(&enemy_missile->frame, &missile_position);

			/* keep the missile looking at ship */
			cluNormalDifference(&velocity, &ship_pos, &missile_position);
			cluNormalNormalise(&velocity);
			zf_align_frame_z_vertex_normal(&enemy_missile->frame, &missile_position, &velocity);

			enemy_missile->wait_time--;
			if(enemy_missile->wait_time <= 0)
			    enemy_missile->movement_state = ATTACK_SHIP;
			break;
		    }
		}
#endif
	}
	else /* boss destroyed. explode missile */
	{
	    enemy_missile->valid = false;
	    zf_explosion_new(&missile_position, 0.5f);
	    // printf("missile %p died, releasing boss\n", enemy_missile);
	    enemy_missile->boss_smarter_pointer->release(enemy_missile->boss);
	}
    }
}


/* hacked to let ship missile lead the target. Not accurate when checking with other dynamic colliders, but ok, since only check against weapon and ship. Seems to work ok. TODO: Do this a better way*/
static void
query_position(EnemyMissile* enemy_missile,
	       CLvertex* position)
{
    CLnormal velocity;
    /* calculate a position that actually leads the target - for ship missile collision*/
    cluSetVertexMatrixOrigin(position, &enemy_missile->frame);
    cluNormalDifference(&velocity, position, &enemy_missile->previous_pos);
    cluVertexAdd(position, &velocity);
    
}

static void
collision_response(EnemyMissile* enemy_missile,
		   const void* collider_data,
		   ZfType collider_type,
		   const CLvertex* collision_position,
		   const CLnormal* collision_force_perp,
		   const CLnormal* collision_force_tan)
{
    CLvertex missile_position;

    cluSetVertexMatrixOrigin(&missile_position, &enemy_missile->frame);

    if (collider_type & ZF_SHIP || collider_type & ZF_WEAPON || collider_type & ZF_EXPLOSIVE)
    {
        enemy_missile->valid = false;
        zf_explosion_new(&missile_position, 0.5f);
	//	printf("missile %p died, releasing boss\n", enemy_missile);
	enemy_missile->boss_smarter_pointer->release(enemy_missile->boss);
	zf_hud_increase_score(MISSILE_SCORE);

    }
}

static void
render(EnemyMissile* enemy_missile)
{
    if(enemy_missile->valid)
    {
	glPushAttrib(GL_ALL_ATTRIB_BITS);

	glDisable(GL_LIGHTING);
	glEnable(GL_TEXTURE_2D);

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

	switch(enemy_missile->type)
	{
	case 0:
	    glColor3f(0.0f, 0.0f, 0.0f);
	    glutSolidSphere(MISSILE_RADIUS, 8,8);
	    break;
	case 1:
	    glColor3f(1.0f, 1.0f, 0.0f);
	    /* cluSetNormalMatrixAxisZ(&axis,&enemy_missile->frame);*/
	    glRotatef(enemy_missile->rotate, 
		      0.0f, 0.0f, 1.0f);
	    clRenderModel(missile_2_model);
	    break;
	case 2:
	    glColor3f(1.0f, 0.0f, 1.0f);
	    /*cluSetNormalMatrixAxisY(&axis,&enemy_missile->frame);*/
	    glRotatef(enemy_missile->rotate, 
		      0.0f, 0.0f, 1.0f);
	    clRenderModel(missile_1_model);
	
	    break;
	default:
	    glColor3f(1.0f, 1.0f, 1.0f);
	    glutSolidSphere(MISSILE_RADIUS, 8,8);	
	    break;
	}


	glPopMatrix();

	glPopAttrib();
    }
}

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

void
zf_enemy_missile_init(void)
{
    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;

    context_1 = clDefaultContext(clNewContext());
    missile_1_model = clioLoadModel(context_1, "../data/models/missile1/missile1.3DS");

    context_2 = clDefaultContext(clNewContext());
    missile_2_model = clioLoadModel(context_2, "../data/models/missile2/missile2.3DS");

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

    cluModelScaleUnitCube(missile_1_model);
    cluModelScale(missile_1_model, MISSILE_RADIUS - 0.1f); /*hack */
    clUpdateContext(missile_1_model->context);

    cluModelScaleUnitCube(missile_2_model);
    cluModelScale(missile_2_model, MISSILE_RADIUS + 0.1f); /*hack */
    clUpdateContext(missile_2_model->context);
}

/* launch position is the global coords on the missile
   hover_position is the local coords on where to go relative to the boss */
void	
zf_enemy_missile_new(const CLvertex* launch_position, 
		     const int type, 
		     const CLvertex* hover_position,
		     ZfBoss* boss,
		     ZfSmartPointer* boss_smarter_pointer)
{
    EnemyMissile* enemy_missile;

    assert(type >= 0 && type <= 2);

    enemy_missile = g_new(EnemyMissile, 1);
    enemy_missile->ref_count = 0;
    enemy_missile->valid = true;
    enemy_missile->wait_time = 0;
    enemy_missile->rotate = 0.0f;
    enemy_missile->boss = boss;
    enemy_missile->boss_smarter_pointer = boss_smarter_pointer;

    clDefaultMatrix(&enemy_missile->frame);
    cluSetMatrixPosition(&enemy_missile->frame, launch_position);

    enemy_missile->movement_state = LAUNCH;

    enemy_missile->type = type;
    switch(type)
    {
    case 0:
	cluSetVertex(&enemy_missile->local_hover, 0.0f, 0.0f, 0.0f);
	break;
    case 1:
    case 2:
	clCopyVertex(&enemy_missile->local_hover, hover_position);
	break;
    }


    cluSetVertex(&enemy_missile->previous_pos, 0.0f, 0.0f, 0.0f);
    cluSetVertex(&enemy_missile->hover_pos, 0.0f, 0.0f, 0.0f);

    // printf("missile %p created, referencing boss\n", enemy_missile);
    enemy_missile->boss_smarter_pointer->reference(enemy_missile->boss);
   
    zf_animation_system_add(enemy_missile,
			    &smart_pointer,
			    (ZfAnimate*) animate);

    zf_collision_system_add_dynamic(enemy_missile,
				    &smart_pointer,
				    &dynamic_collider,
				    ZF_ENEMY_MISSILE,
				    MISSILE_MASS, /* mass */
				    MISSILE_RADIUS); /* radius */

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

