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

#define SUPER_BATTERY_TIME_LIMIT 500
#define LANDING_THESHOLD 1.5f
#define LANDING_SPEED 0.04f
#define END_PLATFORM_HEIGHT 0.18f
#define START_PLATFORM_HEIGHT 0.08f
#define HOVER_HEIGHT_TIER_1 0.5f
#define HOVER_HEIGHT_TIER_2 1.0f
#define HOVER_HEIGHT_TIER_3 1.5f
#define MAX_HOVER_HEIGHT_TIER 3

#define SHIP_ROLL_RATE_TIER_1 5.0f
#define SHIP_ROLL_RATE_TIER_2 4.5f
#define SHIP_ROLL_RATE_TIER_3 4.0f

#define SMALL_DAMAGE_CAMERA_SHAKE 0.05f
#define REGULAR_DAMAGE_CAMERA_SHAKE 0.5f
#define MASSIVE_DAMAGE_CAMERA_SHAKE 0.9f

#define SHIP_HIT_TIME 40
#define TIER_HIT_TIME 20

#define SHIP_START_LIVES 3

#define MIN_BLEND_VALUE 0.1f

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

static float ship_hover_height;
static int   ship_hover_height_tier;

static float ship_battery_power;
    
static float ship_thrust;
static float ship_thrust_rate;
static float ship_min_thrust;
static float ship_max_thrust;

static float ship_roll; /* current rate of roll */
static float ship_roll_rate;

static float tier_blend_value;

static double ship_flux_t;
static double checkpoint_flux_t;

static int tier_1_hit_age;  /*for the 'animation' when hit */
static int tier_2_hit_age;  /*for the 'animation' when hit */
static int tier_3_hit_age;  /*for the 'animation' when hit */

static bool ship_super_battery_mode;
static int ship_super_battery_timer;

static bool ship_pre_start;
static bool ship_starting;
static bool ship_started;
static bool ship_dead;
static bool ship_landing;
static bool ship_landed;
static bool ship_defeat_boss;

static int ship_lives;

/* used to store the interval needed to reach a roll of 0.0f when landed */
static float roll_difference;
static float landing_roll;

/*static CLvertex hit_pos;  testing only - where the crosshairs hit*/

static CLmatrix flux_frame;

/* Things needed for the ship to act like debris when dead*/
static CLUquaternion ship_orientation;
static CLnormal ship_velocity;
static CLnormal ship_axis; /* of rotation */
static float ship_angular_speed;


static CLcontext* context;
static CLmodel* model;
/* each ship uses the same model, even if we add more types of ships,
   we should only load one model per type */

/* NEW! */
static CLtexture* env_texture;

static bool print_time_values = false;

static void
init(void)
{
    context = clDefaultContext(clNewContext());
    model = clioLoadModel(context, "../data/models/manta/manta.3DS");

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

    /* NEW!! */
    env_texture = clioLoadTexture("../data/textures/shiny.png");
    clContextAddTexture(context, env_texture);

    /* cluModelCentre(model); */
    cluModelScaleUnitCube(model);
    cluModelScale(model, 0.75f);
    clUpdateContext(model->context);
    
    ship_flux_t = 0.0f;  /* 73.0f for good flux and boss testing */
    checkpoint_flux_t = 0.0f;

    ship_hover_height = START_PLATFORM_HEIGHT;
    ship_hover_height_tier = 1; /* start with tier 1 */

    ship_battery_power = 1.0f;

    ship_min_thrust = 0.08f;/*0.05f; */
    ship_max_thrust = 0.2f;
    ship_thrust = 0.0f; /* for the start instead of //ship_min_thrust;*/
    ship_thrust_rate = 0.005f;

    ship_roll = 0.0f;
    ship_roll_rate = SHIP_ROLL_RATE_TIER_1;
    tier_blend_value = 0.0f;

    tier_1_hit_age = 0;   /* 0 when normal.. ie, non-hit */
    tier_2_hit_age = 0;
    tier_3_hit_age = 0;
    
    ship_lives = SHIP_START_LIVES;
      
    ship_pre_start = true;
    ship_super_battery_mode = false;
    ship_dead = false;
    ship_landing = false;
    ship_landed = false;
    ship_starting = false;
    ship_started = false;
    ship_defeat_boss = false;

    clDefaultMatrix(&flux_frame);
    zf_flux_update_frame(&flux_frame, 
			 ship_flux_t);
    /* To get ship facing right way */
    ship_flux_t+=0.01f;
    zf_flux_update_frame(&flux_frame, 
			 ship_flux_t);
    /* Align ship to the start platform */
    {
	CLmatrix start_frame;
	CLvertex start_position;
	CLnormal start_y;

	zf_flux_query_start_platform_frame(&start_frame);
	cluSetVertexMatrixOrigin(&start_position, &start_frame);
	cluSetNormalMatrixAxisY(&start_y, &start_frame);
	zf_align_frame_y_vertex_normal(&flux_frame, 
				       &start_position,
				       &start_y);
    }
    
}

void
zf_ship_restart(void)
{
    
    ship_hover_height = START_PLATFORM_HEIGHT;
    ship_hover_height_tier = 1; /* start with tier 1 */

    ship_battery_power = 1.0f;

    ship_flux_t = checkpoint_flux_t;
    
    ship_thrust = 0.0f; /* for the start instead of //ship_min_thrust;*/

    ship_roll = 0.0f;
    ship_roll_rate = SHIP_ROLL_RATE_TIER_1;
    tier_blend_value = 0.0f;

    tier_1_hit_age = 0;   /* 0 when normal.. ie, non-hit */
    tier_2_hit_age = 0;
    tier_3_hit_age = 0;
    
    ship_pre_start = true;
    ship_super_battery_mode = false;
    ship_dead = false;
    ship_landing = false;
    ship_landed = false;
    ship_starting = false;
    ship_started = false;
    ship_defeat_boss = false;

    zf_weapon_control_reset_bomb_tritor_stock();
    
    clDefaultMatrix(&flux_frame);
    if(checkpoint_flux_t < CL_EPSILON)
	checkpoint_flux_t = 0.01f;
    zf_flux_update_frame(&flux_frame, 
			 checkpoint_flux_t-0.01f);
    
    /* To get ship facing right way */
    zf_flux_update_frame(&flux_frame, 
			 checkpoint_flux_t);
    /* Align ship to UP */
    {
	CLmatrix start_frame;
	CLvertex start_position;
	CLnormal up, z_axis;

	zf_flux_query_start_platform_frame(&start_frame);
	cluSetNormalMatrixAxisY(&up, &start_frame);

	cluSetVertexMatrixOrigin(&start_position, &flux_frame);
	cluSetNormalMatrixAxisZ(&z_axis, &flux_frame);

	zf_align_frame_y_vertex_normal(&flux_frame, 
				       &start_position,
				       &up);
	zf_align_frame_z_vertex_normal(&flux_frame, 
				       &start_position,
				       &z_axis);
    }

    if(zf_camera_query_boss_mode())
    {
	zf_camera_deactivate_boss_mode();
	zf_hud_set_boss_mode(0);
	zf_boss_restart();
    }
}

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

static void
animate(const CLvertex* cross_hair_position)
{
    if(!ship_dead && !ship_landing && !ship_starting)
    {
	zf_flux_update_frame(&flux_frame, 
			     ship_flux_t);

	if(ship_started)
	{
	    switch(ship_hover_height_tier)
	    {
	    case 1:
		if(ship_hover_height < HOVER_HEIGHT_TIER_1)
		    ship_hover_height += 0.05f;

		if(ship_hover_height > HOVER_HEIGHT_TIER_1)
		    ship_hover_height -= 0.05f;
		break;
	    case 2:
		if(ship_hover_height < HOVER_HEIGHT_TIER_2)
		    ship_hover_height += 0.05f;

		if(ship_hover_height > HOVER_HEIGHT_TIER_2)
		    ship_hover_height -= 0.05f;
		break;
	    case 3:
		if(ship_hover_height < HOVER_HEIGHT_TIER_3)
		    ship_hover_height += 0.05f;

		if(ship_hover_height > HOVER_HEIGHT_TIER_3)
		    ship_hover_height -= 0.05f;
		break;
	    }
	
	    if(fabs(zf_flux_query_max_t() - ship_flux_t) < LANDING_THESHOLD)
	    {
		CLmatrix end_frame, ship_frame;
		CLnormal ship_up, landing_up, normal;
		CLnormal landing_z;
		CLvertex landing_pos;
		float cos_angle;
		float sin_angle;

		/*  printf("going into landing mode\n");*/
		ship_landing = true;
 
		zf_flux_query_end_platform_frame(&end_frame);
		cluSetVertexMatrixOrigin(&landing_pos, &end_frame);
		cluSetNormalMatrixAxisY(&landing_up, &end_frame);
		cluSetNormalMatrixAxisZ(&landing_z, &end_frame);

		/* Need to align z axes of the the two matrics first. */
		clCopyMatrix(&ship_frame, &flux_frame);
		zf_align_frame_y_vertex_normal(&ship_frame, 
					       &landing_pos,
					       &landing_up);
		cluSetNormalMatrixAxisY(&ship_up, &ship_frame);
	    

		cluNormalCrossProduct(&normal, &ship_up, &landing_up);
    
		cos_angle = cluNormalDotProduct(&ship_up, &landing_up);
		sin_angle = cluNormalMagnitude(&normal);
    
		landing_roll = CL_RAD2DEG(atan2(sin_angle, cos_angle));
	    
		
		if (landing_roll < 0.0f)
		    landing_roll = 360.0f + landing_roll;
		else if(landing_roll > 360.0f)
		    landing_roll = landing_roll - 360.0f;
		

		/* set the roll_difference */
		roll_difference = (ship_roll - landing_roll) / (float)(LANDING_THESHOLD / LANDING_SPEED);
	    }
	}

	{
	    float old_flux_t;

	    old_flux_t = ship_flux_t;
	    ship_flux_t = zf_flux_query_next_t_given_distance(ship_flux_t,
							      ship_thrust);
	    
#if 0
	    if (old_flux_t > ship_flux_t)
	    {
		printf("%s() : ERROR : old_flux_t = %f, new ship_flux_t = %f\n",
		       __FUNCTION__,
		       old_flux_t,
		       ship_flux_t);
		/* although error occurs, can still recover from it */
		//print_time_values = true;
	    }

	    if (print_time_values)
		printf("%s() : ship_flux_t = %f\n", __FUNCTION__, ship_flux_t);
#endif
	}
	
    }
    else if(ship_landing)
    {
	float angle_diff;

	if(ship_flux_t >= zf_flux_query_max_t() - 1.0f)
	{
	    if(ship_hover_height > END_PLATFORM_HEIGHT)
		ship_hover_height -= 0.002f;
	    else
		ship_landed = true;
	}
	else
	{
	     zf_flux_update_frame(&flux_frame, 
				 ship_flux_t);
	    
	    ship_flux_t = zf_flux_query_next_t_given_distance(ship_flux_t,
							      LANDING_SPEED);
	    
	    angle_diff = ship_roll - landing_roll;
	    if(fabs(angle_diff) <= fabs(roll_difference))
		ship_roll = landing_roll;
	    else
		ship_roll -= roll_difference;

	}	    
    }
    else if(ship_starting)
    {
	if(ship_hover_height < HOVER_HEIGHT_TIER_1)
	{
	    ship_hover_height += 0.002f;
	    tier_blend_value += 0.002f;
	    if(tier_blend_value > 0.5f)
		tier_blend_value = 0.5f;
	}
	else
	{
	    ship_hover_height = HOVER_HEIGHT_TIER_1;
	    ship_starting = false;
	    ship_started = true;
	    ship_thrust = ship_min_thrust;
	}
    }
    else /* dead */
    {
	CLvertex ship_position;
	CLUquaternion angular_velocity;
	
	/* gravity */
	/*    debris->velocity.j -= 0.0098f;*/
	ship_velocity.j -= 0.00098f;
    
	/* friction (maybe scale due to radius?) */
	/*
	  cluNormalScale(&ship_velocity, 0.995f);
	  ship_angular_speed *= 0.995f;
	*/

	cluSetVertexMatrixOrigin(&ship_position, &flux_frame);

	/* update position and orientation */
	cluVertexAdd(&ship_position, &ship_velocity);
    
	ship_angular_speed = 5.0f; /*hack*/
	cluSetQuaternionAxisAngle(&angular_velocity,
				  &ship_axis, ship_angular_speed);
	cluQuaternionMultiply(&ship_orientation, &angular_velocity);
    
	/* Do I need this? */
	cluQuaternionNormalise(&ship_orientation);

	cluSetMatrixPosition(&flux_frame, &ship_position);
	cluSetMatrixOrientation(&flux_frame, &ship_orientation);

    }

    if(ship_super_battery_mode)
    {
	ship_super_battery_timer--;
	if(ship_super_battery_timer <= 0)
	{
	    ship_super_battery_mode = false;
	}
    }
}


static void
render_translucent(void* no_ship_here)
{
    if(!ship_dead)
    {
	float blend_a = tier_blend_value;
	float blend_b = tier_blend_value;
	float blend_c = tier_blend_value;

	glPushAttrib(GL_ALL_ATTRIB_BITS);
	glPushMatrix();
	glMultMatrixf((GLfloat*)&flux_frame);

	glDisable(GL_LIGHTING);
	glDisable(GL_COLOR_MATERIAL);

	/*need this to get the rings to appear on top of the flux */
	glEnable(GL_DEPTH_TEST);
	glDepthMask(GL_TRUE);


	if(ship_started)
	{
	    switch(ship_hover_height_tier)
	    {
	    case 1:
		blend_b = MIN_BLEND_VALUE;
		blend_c = MIN_BLEND_VALUE;
		break;
	    case 2:
		blend_a = MIN_BLEND_VALUE;
		blend_c = MIN_BLEND_VALUE;
		break;
	    case 3:
		blend_a = MIN_BLEND_VALUE;
		blend_b = MIN_BLEND_VALUE;
		break;
	    }
	}
	if(tier_1_hit_age > 0)
	{
	    glDisable(GL_DEPTH_TEST);
	    glColor4f(1.0f,0.3f,0.3f, 0.9f); 
	    glutSolidTorus(0.01f, 0.45f + 0.2f*sin((float)tier_1_hit_age/ (float) TIER_HIT_TIME * M_PI * 2.0f), 16, 32); 
/*	    printf("%d,%f\n", tier_1_hit_age,sin((float)tier_1_hit_age/ (float) TIER_HIT_TIME * M_PI * 2.0f));*/
/*	    glutSolidTorus(0.01f, 0.45f, 16, 32); */

	    tier_1_hit_age--;
	    glEnable(GL_DEPTH_TEST);
	}
	else
	{
	    glColor4f(1.0f,0.0f,0.0f, blend_a); 
	    glutSolidTorus(0.01f, 0.45f, 16, 32);
	}

       	if(tier_2_hit_age > 0)
	{
	    glColor4f(0.3f,1.0f,0.3f,0.9f); 
	    glutSolidTorus(0.01f, 0.9f + 0.2f*sin((float)tier_2_hit_age/ (float) TIER_HIT_TIME * M_PI * 2.0f), 16, 32); 
/*	    glutSolidTorus(0.01f, 0.9f, 16, 32);  /*0.9f is HOVER_HEIGHT_TIER_2 - 0.1f */
	    tier_2_hit_age--;
	}
	else
	{
	    glColor4f(0.0f,1.0f,0.0f, blend_b);
	    glutSolidTorus(0.01f, 0.9f, 16, 32);  /*0.9f is HOVER_HEIGHT_TIER_2 - 0.1f */
	}
	
	if(tier_3_hit_age > 0)
	{
	    glColor4f(0.3f,0.3f,1.0f, 0.9f); 
	    glutSolidTorus(0.01f, 1.45f + 0.2f*sin((float)tier_3_hit_age/ (float) TIER_HIT_TIME * M_PI * 2.0f), 16, 32); 
/*	    glutSolidTorus(0.01f, 1.45f, 16, 32); */
	    tier_3_hit_age--;
	}
	else
	{
	    glColor4f(0.0f,0.0f,1.0f, blend_c);
	    glutSolidTorus(0.01f, 1.45f, 16, 32); 
	}

	/*	glEnable(GL_LIGHTING);*/
	/*    glDisable(GL_DEPTH_TEST);*/
	/*    glDepthMask(GL_FALSE);*/
	glPopAttrib();
	glPopMatrix();

    }
}

static void
render_opaque(void* no_ship_here)
{
    CLmatrix frame;
    //    CLvertex flux_origin;

    glPushAttrib(GL_ALL_ATTRIB_BITS); /* GL_LIGHTING_BIT | GL_CURRENT_BIT); */

    /* environment map */
    glEnable(GL_TEXTURE_2D);
    glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
    glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
    glEnable(GL_TEXTURE_GEN_S);
    glEnable(GL_TEXTURE_GEN_T);
    clLoadTexture(env_texture);
    

    zf_ship_query_frame(&frame); /* Need this to get the ship's frame */

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

    /* for the particle trail behind the ship */
    if(ship_started && !ship_landed && !ship_dead)
    {
    	unsigned int i;
	unsigned int num_particles = 2;
	CLnormal temp_velocity;
	CLvertex temp_pos;
	CLcolour temp_colour;
	
	for (i = 0 ; i < num_particles ; i++)
	{
	    switch(ship_hover_height_tier)
	    {
	    case 1:
		cluSetColour(&temp_colour, 
			     0.6f * rand() / (float)RAND_MAX + 0.3f,
			     0.4f * rand() / (float)RAND_MAX + 0.1f,
			     0.4f * rand() / (float)RAND_MAX + 0.1f,
			     0.3f);
		break;
	    case 2:
		cluSetColour(&temp_colour, 
			     0.4f * rand() / (float)RAND_MAX + 0.1f,
			     0.6f * rand() / (float)RAND_MAX + 0.3f,
			     0.4f * rand() / (float)RAND_MAX + 0.1f,
			     0.3f);
		break;
	    case 3:
		cluSetColour(&temp_colour, 
			     0.4f * rand() / (float)RAND_MAX + 0.1f,
			     0.4f * rand() / (float)RAND_MAX + 0.1f,
			     0.6f * rand() / (float)RAND_MAX + 0.3f,
			     0.3f);
		break;
	    }

	    

	    /* the right engine */
	    cluSetNormal(&temp_velocity, (float)rand() / (RAND_MAX * 100.0f) - 0.005f,
			 -0.01f,
			 (float)rand() / (RAND_MAX * 100.0f) - 0.005f);
	    
	    cluSetVertex(&temp_pos, 0.11f, 0.0f, 0.13f);
	    
	    cluVertexTransform(&temp_pos, &frame);
	    cluNormalTransform(&temp_velocity, &frame);

	    zf_particle_system_add_particle(&temp_pos,
					    &temp_velocity, 
					    0.0f,
					    &temp_colour,
					    50,
					    2.0f);

	    /* the left engine */
	    cluSetNormal(&temp_velocity, (float)rand() / (RAND_MAX * 100.0f) - 0.005f,
			 -0.01f,
			 (float)rand() / (RAND_MAX * 100.0f) - 0.005f);

	    cluSetVertex(&temp_pos, -0.11f, 0.0f, 0.13f);

	    cluVertexTransform(&temp_pos, &frame);
	    cluNormalTransform(&temp_velocity, &frame);

	    zf_particle_system_add_particle(&temp_pos,
					    &temp_velocity, 
					    0.0f,
					    &temp_colour,
					    50,
					    2.0f);
	}
    }

    clRenderModel(model);

    glPopMatrix();

    glPopAttrib();

   /* printf("ship flux at %f\n", ship_flux_t);*/
}

void
zf_ship_query_frame(CLmatrix* frame)
{
    CLnormal offset;
    CLvertex origin;

    static const CLnormal axis = {0.0f, 0.0f, 1.0f};
    CLUquaternion quaternion;
    CLmatrix matrix;

    //  if(!ship_dead)
    {
	/* for the rotation */
	cluSetQuaternionAxisAngle(&quaternion, &axis, ship_roll);
	cluQuaternionNormalise(&quaternion);
	clDefaultMatrix(&matrix);
	cluSetMatrixOrientation(&matrix, &quaternion);
	
	cluMatrixTransform(&matrix, &flux_frame);
	clCopyMatrix(frame, &matrix);
	
	/*    clCopyMatrix(frame, &ship_frame);*/
	
	/* for the offset */
	cluSetNormalMatrixAxisY(&offset, frame);
	cluNormalScale(&offset, ship_hover_height);
	
	cluSetVertexMatrixOrigin(&origin, frame);
	cluVertexAdd(&origin, &offset);
	
	cluSetMatrixPosition(frame, &origin);
    }
    //  else
    //	clCopyMatrix(frame, &flux_frame);  /* tyring to remove to see if dying looks cooler*/
  
}

double
zf_ship_query_power(void)
{
    return ship_battery_power;
}

void
zf_ship_query_position(CLvertex* position)
{
    CLmatrix ship_frame;

    zf_ship_query_frame(&ship_frame);

    cluSetVertexMatrixOrigin(position, &ship_frame);
}

void
zf_ship_query_flux_frame(CLmatrix* matrix)
{
    clCopyMatrix(matrix, &flux_frame);
}

float
zf_ship_query_flux_t(void)
{
    return ship_flux_t;
}

float
zf_ship_query_hover_height(void)
{
    return ship_hover_height;
}

int
zf_ship_query_tier_level(void)
{
    return ship_hover_height_tier;
}

float
zf_ship_query_thrust(void)
{
    return ship_thrust;
}

float
zf_ship_query_current_roll(void)
{
    return ship_roll;
}

bool
zf_ship_query_super_mode(void)
{
    return ship_super_battery_mode;
}

bool
zf_ship_query_landed(void)
{
    return ship_landed;
}

bool
zf_ship_query_defeat_boss(void)
{
    return ship_defeat_boss;
}

/* Hack - only boss should call this */
void
zf_ship_set_defeat_boss(bool defeat)
{
    ship_defeat_boss = defeat;
}

bool
zf_ship_query_dead(void)
{
    return ship_dead;
}

int
zf_ship_query_lives(void)
{
    return ship_lives;
}

bool
zf_ship_query_end_state(void)
{
    if(ship_landing || ship_starting || ship_pre_start || ship_dead)
	return true;
    return false;
}

void
zf_ship_super_battery_mode(void)
{
    ship_battery_power = 1.0f;
    ship_super_battery_timer = SUPER_BATTERY_TIME_LIMIT;
    ship_super_battery_mode = true;
}

void
zf_ship_increment_life(void)
{
    ship_lives++;
}

void
zf_ship_increase_thrust(void)
{
    if (ship_thrust >= ship_max_thrust)
	return;

    ship_thrust += ship_thrust_rate;
    ship_thrust = CL_MIN(ship_thrust, ship_max_thrust);
}

void
zf_ship_decrease_thrust(void)
{
    if (ship_thrust <= ship_min_thrust)
	return;
    
    ship_thrust -= 5.0f * ship_thrust_rate;
    ship_thrust = CL_MAX(ship_thrust, ship_min_thrust);
}


void
zf_ship_move_up_tier(void)
{
    if(ship_started)
    {
	ship_hover_height_tier++;
	if(ship_hover_height_tier > MAX_HOVER_HEIGHT_TIER)
	    ship_hover_height_tier = MAX_HOVER_HEIGHT_TIER;
    }

    switch(ship_hover_height_tier)
    {
    case 1:
	ship_roll_rate = SHIP_ROLL_RATE_TIER_1;
	break;
    case 2:
	ship_roll_rate = SHIP_ROLL_RATE_TIER_2;
	break;
    case 3:
	ship_roll_rate = SHIP_ROLL_RATE_TIER_3;
	break;
    }
}

void
zf_ship_move_down_tier(void)
{
    if(ship_started)
    {
	ship_hover_height_tier--;
	if(ship_hover_height_tier < 1)  /* tiers start from 1*/
	    ship_hover_height_tier = 1;
    }

    
    switch(ship_hover_height_tier)
    {
    case 1:
	ship_roll_rate = SHIP_ROLL_RATE_TIER_1;
	break;
    case 2:
	ship_roll_rate = SHIP_ROLL_RATE_TIER_2;
	break;
    case 3:
	ship_roll_rate = SHIP_ROLL_RATE_TIER_3;
	break;
    }
}


void
zf_ship_increase_roll(void)
{
    ship_roll += ship_roll_rate;

    while (ship_roll > 360.0f)
	ship_roll -= 360.0f;
}

void
zf_ship_decrease_roll(void)
{
    ship_roll -= ship_roll_rate;

    while (ship_roll < 0.0f)
	ship_roll += 360.0f;
}

void
zf_ship_increase_power(float amount)
{    
    ship_battery_power += amount;
    if(ship_battery_power > 1.0f)
    {
	ship_battery_power = 1.0f;
    }
}


bool
zf_ship_decrease_power(float amount)
{
    if(!ship_super_battery_mode)
    {
	if(amount < 0.25f)
	    zf_camera_shake_start(SMALL_DAMAGE_CAMERA_SHAKE);
	else if(amount < 0.55f)
	    zf_camera_shake_start(REGULAR_DAMAGE_CAMERA_SHAKE);
	else
	    zf_camera_shake_start(MASSIVE_DAMAGE_CAMERA_SHAKE);

	if(ship_battery_power > amount)
	{
	    ship_battery_power -= amount;
	    return true;
	}    
	else if(!ship_dead)
	{
	    ship_battery_power = 0.0f;
	    ship_lives--;
	    ship_dead = true;
	}
	return false;
    }
    return true;
}

/*
  STUFF FOR NEW ZF (THE ONE TRUE PATH!!!!... for code)
*/

/* this doesn't really do anything because there is only ever one instance! */

/* these will be the same for all singletons - maybe provide standard
   "empty" functions - MAYBE EVEN A STANDARD zf_static_smart_pointer!
   (OMFG!) */
static bool
is_valid(const void* nothing_to_see_here)
{
    return true;
}

static void
reference(void* nothing_to_see_here)
{
}

static void
release(void* nothing_to_see_here)
{
}
/* end of stuff we don't really need */

static void
query_position(void* nothing_here,
	       CLvertex* position)
{
    zf_ship_query_position(position);
}


static void
collision_response(void* data,
		   const void* collider_data,
		   ZfType collider_type,
		   const CLvertex* collision_position,
		   const CLnormal* collision_force_perp,
		   const CLnormal* collision_force_tan)
{    
    if(!ship_dead)
    {
	if(collider_type & ZF_BOSS_BOMB)
	{
	    if(zf_shield_activate(SHIP_HIT_TIME))
	    {
		zf_ship_decrease_power(1.0f);
	    }
	}
	else if(collider_type & ZF_BATTERY_RING)
	{
	    ship_battery_power = 1.0f;
	    checkpoint_flux_t = ship_flux_t;
	}
	else if(collider_type & ZF_FLUX_RING)
	{
	    if(zf_shield_activate(SHIP_HIT_TIME))
	    {
		zf_ship_decrease_power(0.4f);
		
	    }
	}
	else if((collider_type & ZF_HEXFIELD) | (collider_type & ZF_TIER_RING))
	{
	    
	    if(zf_shield_activate(SHIP_HIT_TIME))
	    {
		zf_ship_decrease_power(0.35f);
	    }
	}
	else if (collider_type & ZF_LEECH)
	{

	    if(zf_shield_activate(SHIP_HIT_TIME))
	    {
		zf_ship_decrease_power(0.05f);
	    }
	}
	else if (collider_type & ZF_EEL_SEGMENT)
	{

	    if(zf_shield_activate(SHIP_HIT_TIME))
	    {
		zf_ship_decrease_power(0.2f);
	    }
	}
	else if (collider_type & ZF_ENEMY_MISSILE)
	{
	    if(zf_shield_activate(SHIP_HIT_TIME))
	    {
		zf_ship_decrease_power(0.1f);

	    }
	}

	/* sets the spin on the dead ship */
	if(ship_battery_power < CL_EPSILON)
	{
	    CLnormal perp_dir, tan_dir;

	    zf_shield_deactivate();
	
	    ship_battery_power = 0.0f; /* just in case it isn't*/
	    if(!ship_dead)
	    {
		ship_lives--;
		ship_dead = true;
	    }

	    /* Things needed for the ship to act like debris when dead*/
	    cluSetQuaternionMatrix(&ship_orientation, &flux_frame);
	    cluSetNormal(&ship_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(&ship_axis, 0.0f, 1.0f, 0.0f); /* Non-zero*/
		ship_angular_speed = 90.0f;
	    }
	    else
	    {
		cluNormalCrossProduct(&ship_axis, 
				      collision_force_perp, 
				      collision_force_tan);
		cluNormalNormalise(&ship_axis);
		ship_angular_speed = 
		    cluNormalMagnitude(collision_force_tan); /* times 1.0f the ship's radius*/
	    }
	}

    }
    else if(collider_type & ZF_GROUND)  /* Ship is dead  - only react to ground*/
    {
	cluSetMatrixPosition(&flux_frame, collision_position);
	
	/* bounce off ground */
	/*   if (collider_type & ZF_GROUND)*/
	{
	    clCopyNormal(&ship_velocity, collision_force_perp);
	    cluNormalAdd(&ship_velocity, collision_force_tan);
	    {
		CLUquaternion angular_velocity;
		CLUquaternion collision_torque;
		CLnormal collision_axis;
		float collision_angular_speed;
	    
		cluNormalCrossProduct(&collision_axis, 
				      collision_force_perp,
				      collision_force_tan);

		cluNormalNormalise(&collision_axis);
	    
		collision_angular_speed = 
		    cluNormalMagnitude(collision_force_tan); /* times the ship_radius = 1.0f */
	    
		/* ... ur should we be using mass as well...? */
		cluSetQuaternionAxisAngle(&collision_torque,
					  &collision_axis, collision_angular_speed);

		/* current angular velocity */
		cluSetQuaternionAxisAngle(&angular_velocity,
					  &ship_axis, ship_angular_speed);

		/* add torque..or acceleration... or whatever it is... */
		cluQuaternionMultiply(&angular_velocity, &collision_torque);

		/* extract new axis and angular speed */
		cluSetNormalQuaternionAxis(&ship_axis, &angular_velocity);
		ship_angular_speed = cluQuaternionAngle(&angular_velocity);

		/* HACK - reduce speed and angular velocity!!! */
		cluNormalScale(&ship_velocity, 0.995f);
		ship_angular_speed *= 0.99f;
	    }
	}
    }
}


void
zf_ship_init(void)
{
    smart_pointer.is_valid = is_valid;
    smart_pointer.reference = reference;
    smart_pointer.release = release;

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

    init(); /* should be in reference! and close() should be in release! */

    zf_animation_system_add(0, /* null */
			    &smart_pointer,
			    (ZfAnimate*) animate);

#if 1
    zf_collision_system_add_dynamic(0, /* null */
				    &smart_pointer,
				    &dynamic_collider,
				    ZF_SHIP,
				    1.0f, /*mass */
				    1.0f); /* radius */
#endif

    /* collision system for the tier rings */
    zf_collision_system_add_dynamic(0, /* null */
				    &smart_pointer,
				    &dynamic_collider,
				    ZF_TIER_1_RING,
				    1.0f, /*mass */
				    1.0f); /* radius */
    zf_collision_system_add_dynamic(0, /* null */
				    &smart_pointer,
				    &dynamic_collider,
				    ZF_TIER_2_RING,
				    1.0f, /*mass */
				    2.0f); /* radius */    
    zf_collision_system_add_dynamic(0, /* null */
				    &smart_pointer,
				    &dynamic_collider,
				    ZF_TIER_3_RING,
				    1.0f, /*mass */
				    3.0f); /* radius */



    zf_render_system_add_opaque(0, /* null */
				&smart_pointer,
				(ZfRender*) render_opaque);

    zf_render_system_add_translucent(0, /* null */
				     &smart_pointer,
				     (ZfRender*) render_translucent);
}


bool
zf_ship_start(void)
{
    if(!ship_started)
    {
	ship_starting = true;
	ship_pre_start = false;
	
	return true;
    }
    return false;
}

void
zf_ship_jump_flux(float value)
{
    ship_flux_t += value;
}

void
zf_ship_hit_tier(const int tier)
{
    if(tier == ship_hover_height_tier)
    {
	if(zf_shield_activate(SHIP_HIT_TIME))
	{
	    zf_ship_decrease_power(0.3f);
	}
    }
    zf_ship_animate_hit_tier(tier);
}

void
zf_ship_animate_hit_tier(const int tier)
{
    switch(tier)
    {
    case 1:
	tier_1_hit_age = TIER_HIT_TIME;
	break;
    case 2:
	tier_2_hit_age = TIER_HIT_TIME;
	break;
    case 3:
	tier_3_hit_age = TIER_HIT_TIME;
	break;
    }
}
