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

#define LAST_CAMERA_RATIO 0.8f
#define LAST_CAMERA_ONE_MINUS_RATIO 0.2f
#define CAMERA_SHAKE_MAX_MAGNITUDE 0.5f
#define CAMERA_SHAKE_RECOVER_INCREMENT 0.1f

static CLvertex last_eye;
static CLvertex last_centre;
static CLnormal up;

static double look_up;
static double look_left;

static bool boss_mode;
static CLvertex boss_position;

static bool camera_shake;
static float shake_mag;
static float shake_recovery;

void
zf_camera_init(void)
{
    cluSetVertex(&last_eye, 0.0f, 10.0f, -15.0f);
    cluSetVertex(&last_centre, 0.0f, 0.0f, 0.0f);
    cluSetNormal(&up, 0.0f, 1.0f, 0.0f);

    boss_mode = false;
    clDefaultVertex(&boss_position);

    camera_shake = false;
    shake_mag = 0.0f;
    shake_recovery = CAMERA_SHAKE_RECOVER_INCREMENT;
}

void
zf_camera_close(void)
{
}

/* should only be called by boss when activated, to ensure there is a pos */
void
zf_camera_activate_boss_mode(const CLvertex* pos)
{
    boss_mode = true;
    clCopyVertex(&boss_position, pos);
}

void
zf_camera_deactivate_boss_mode(void)
{
    boss_mode = false;
}

bool
zf_camera_query_boss_mode(void)
{
    return boss_mode;
}


void
zf_camera_step(void)
{

    
}

/* generate matrix, invert it, load into gl push and pop matrix done
   outside of this method */
void
zf_camera_load(void)
{
    if(!boss_mode)
    {
	CLmatrix frame;
	CLnormal axis_y;
	CLnormal axis_z;
	CLvertex origin;

	zf_ship_query_frame(&frame);

#if 0 /* rotate with ship */
	cluSetNormalMatrixAxisY(&axis_y, &frame);
	cluNormalScale(&axis_y, 0.25f);
#else /* don't rotate with ship */
	cluSetNormal(&axis_y, 0.0f, 0.5f, 0.0f);
#endif

	cluSetNormalMatrixAxisZ(&axis_z, &frame);
	cluNormalScale(&axis_z, 1.0f);

	cluSetVertexMatrixOrigin(&origin, &frame);
	//cluVertexAdd(&origin, &axis_y);
	//cluVertexAdd(&origin, &axis_z);

	//cluSetMatrixPosition(&frame, &origin);

	glRotated(look_left, 0.0, 1.0, 0.0);
	glRotated(look_up, 1.0, 0.0, 0.0);

#if 1 /*! \todo THIS SHOULD BE IN STEP, NOT LOAD!!! */
	{
	    CLvertex eye;
	    CLvertex centre;
	    CLnormal offset;
	    float flux_t;
	    
	    cluSetVertexMatrixOrigin(&eye, &frame);
	    //  cluSetVertexMatrixOrigin(&centre, &frame);
	    
	    
	    flux_t = zf_ship_query_flux_t();

	    if((flux_t+2.0f) > zf_flux_query_max_t())
	    {
		cluSetVertexMatrixOrigin(&centre, &frame);
		cluSetNormalMatrixAxisZ(&offset, &frame);
		cluNormalNormalise(&offset);
		cluNormalScale(&offset, -2.0f);
	    }
	    else
	    {
		zf_flux_query_position(&centre, flux_t + 1.0f);
		cluNormalDifference(&offset, &centre, &eye);
		cluNormalNormalise(&offset);
	    }

		
	    cluVertexSubtract(&eye, &offset);
	    cluVertexAdd(&eye, &axis_y);
	    
	    /*
	      cluSetNormalMatrixAxisZ(&offset, &frame);
	      cluVertexSubtract(&centre, &offset);
	    */
	    
	    

	    if(zf_ship_query_dead())
	    {
		/* Also make it spin around the ship */
		last_eye.x = 0.5f * last_eye.x + 0.5f * eye.x;
		last_eye.y = 0.5f * last_eye.y + 0.5f * eye.y;
		last_eye.z = 0.5f * last_eye.z + 0.5f * eye.z;
		
		last_centre.x = 0.5f * last_centre.x + 0.5f * centre.x;
		last_centre.y = 0.5f * last_centre.y + 0.5f * centre.y;
		last_centre.z = 0.5f * last_centre.z + 0.5f * centre.z;
	    }
	    else
	    {
#if 0
		CLnormal diff;
		CLvertex ship_pos, look_pos;
		CLnormal axis_y;

		zf_ship_query_position(&ship_pos);
		cluSetNormal(&axis_y, 0.0f, 0.5f, 0.0f);

		zf_flux_query_position(&look_pos, zf_ship_query_flux_t() + 2.0f);

		cluNormalDifference(&diff, &look_pos, &ship_pos);
		cluNormalNormalise(&diff);
		cluNormalScale(&diff, 2.0f); /* distance 'behind' ship */
		cluVertexSubtract(&ship_pos, &diff);

		gluLookAt(ship_pos.x, ship_pos.y, ship_pos.z,
			  look_pos.x, look_pos.y, look_pos.z,
			  axis_y.i, axis_y.j, axis_y.k);
#else
		last_eye.x = LAST_CAMERA_RATIO * last_eye.x + LAST_CAMERA_ONE_MINUS_RATIO * eye.x;
		last_eye.y = LAST_CAMERA_RATIO * last_eye.y + LAST_CAMERA_ONE_MINUS_RATIO * eye.y;
		last_eye.z = LAST_CAMERA_RATIO * last_eye.z + LAST_CAMERA_ONE_MINUS_RATIO * eye.z;
		
		last_centre.x = LAST_CAMERA_RATIO * last_centre.x + LAST_CAMERA_ONE_MINUS_RATIO * centre.x;
		last_centre.y = LAST_CAMERA_RATIO * last_centre.y + LAST_CAMERA_ONE_MINUS_RATIO * centre.y;
		last_centre.z = LAST_CAMERA_RATIO * last_centre.z + LAST_CAMERA_ONE_MINUS_RATIO * centre.z;
#endif
		  
		/*clCopyVertex(&last_centre, &centre);*/
	    }
	    
	    if(camera_shake)
	    {
		cluSetVertex(&last_centre, 
			     last_centre.x + (float) (shake_mag * rand()/RAND_MAX - shake_mag/2.0f),
			     last_centre.y + (float) (shake_mag * rand()/RAND_MAX - shake_mag/2.0f),
			     last_centre.z + (float) (shake_mag * rand()/RAND_MAX - shake_mag/2.0f));
			     
		shake_mag -= shake_recovery;
		if(shake_mag < CL_EPSILON)
		    camera_shake = false;
	    }

	    gluLookAt(last_eye.x, last_eye.y, last_eye.z,
		      last_centre.x, last_centre.y, last_centre.z,
		      axis_y.i, axis_y.j, axis_y.k);
	    /* up.i, up.j, up.k); */
#else
	    cluMatrixInvert(&frame);
	    glMultMatrixf((GLfloat*)&frame);
#endif
	}
    }
    else
    {
	CLnormal diff;
	CLvertex ship_pos;
	CLnormal axis_y;
	
	zf_ship_query_position(&ship_pos);
	cluSetNormal(&axis_y, 0.0f, 0.5f, 0.0f);

	cluNormalDifference(&diff, &boss_position, &ship_pos);
	cluNormalNormalise(&diff);
	cluNormalScale(&diff, 2.0f); /* distance 'behind' ship */
	cluVertexSubtract(&ship_pos, &diff);
	cluVertexAdd(&ship_pos, &axis_y);  /* 'lift' it up */
	
	if(camera_shake)
	{
	    cluSetVertex(&boss_position, 
			 boss_position.x + (float) (shake_mag * rand()/RAND_MAX - shake_mag/2.0f),
			 boss_position.y + (float) (shake_mag * rand()/RAND_MAX - shake_mag/2.0f),
			 boss_position.z + (float) (shake_mag * rand()/RAND_MAX - shake_mag/2.0f));
	    
	    shake_mag -= shake_recovery;
	    if(shake_mag < CL_EPSILON)
		camera_shake = false;
	}
	
	gluLookAt(ship_pos.x, ship_pos.y, ship_pos.z,
		  boss_position.x, boss_position.y, boss_position.z,
		  axis_y.i, axis_y.j, axis_y.k);
	/* up.i, up.j, up.k); */
    }
}

/* recovery should be BETWEEN 0.0f - 1.0f, indicating the scale of recovery time between max_min*/ 
void
zf_camera_shake_start(float recovery)
{
    assert(recovery > 0.0f && recovery < 1.0f);
    
    camera_shake = true;
    shake_mag = CAMERA_SHAKE_MAX_MAGNITUDE;
    shake_recovery = CAMERA_SHAKE_RECOVER_INCREMENT - recovery * CAMERA_SHAKE_RECOVER_INCREMENT;
}


/* is this even used anymore?*/
void
zf_camera_set_lookat(int x, int y, int dx, int dy)
{
    double viewport[4];
    
    glGetDoublev(GL_VIEWPORT, viewport);

    {
	double new_look_up;
	
	new_look_up = look_up;
	new_look_up += 360.0f * ((double)dy - viewport[1]) / (double)viewport[3];
	new_look_up = CL_CROP(new_look_up, -90.0, 90.0);

	look_up = 0.9f * look_up + 0.1f * new_look_up;
    }

    {
	double new_look_left;
	
	new_look_left = look_left;
	new_look_left += 360.0f * ((double)dx - viewport[0]) / (double)viewport[2];
	new_look_left = CL_CROP(new_look_left, -90.0, 90.0);

	look_left = 0.9f * look_left + 0.1f * new_look_left;
    }
}
