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

#define RING_RADIUS 3.5f
#define FLUX_RING_SCORE 250

//typedef struct ZfFluxRing FluxRing;

struct ZfFluxRing
{
    unsigned int ref_count;
    CLmatrix frame;
    CLUplane plane;

    /* if true is half ring, else is 3/4 ring */
    bool half_ring;
    bool valid;

    bool ship_score; /* only used to update score once ship passes */

    float flux_position;
    float roll_angle;  /* the angle to rotate the ring -> to the right  - about z-axis*/
    float ang0, ang1; /* the angles used to calculate it overlap or not - will hit between ang0->ang1*/
};

static ZfSmartPointer smart_pointer; 


static CLcontext* context_1;
static CLmodel* model_1; /* half ring */
static CLcontext* context_2;
static CLmodel* model_2;  /* 3/4 ring */

static bool
is_valid(const ZfFluxRing* fluxring)
{
   return fluxring->valid;
}

static void
reference(ZfFluxRing* fluxring)
{
    fluxring->ref_count++;
}

static void
release(ZfFluxRing* fluxring)
{
    fluxring->ref_count--;

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

/*
  static void
  animate(FluxRing* fluxring)
  {
  static const CLnormal axis = {0.0f, 0.0f, 1.0f};
    
  CLUquaternion quaternion;
  CLmatrix matrix;
  CLvertex position;
  CLnormal zAxis;
    
  zf_flux_update_frame(&fluxring->frame, 
  fluxring->flux_position);
    
  // Need to update the plane
    
  cluSetVertexMatrixOrigin(&position, &fluxring->frame); 
  cluSetNormalMatrixAxisZ(&zAxis, &fluxring->frame);
  cluNormalScale(&zAxis, -1.0f);
  cluSetPlaneVertexNormal(&fluxring->plane,
  &position,
  &zAxis);
  }
*/
static void
query_position(const ZfFluxRing* fluxring, 
	       CLvertex* position)
{
    cluSetVertexMatrixOrigin(position, &fluxring->frame);
}



static void
fluxring_read(FILE* stream)
{
    float flux_t, roll;
    int half_ring;
    
    fscanf(stream, "ZfFluxRing\n{\n");
    fscanf(stream, "half_ring = %d\n", &half_ring);
    fscanf(stream, "flux_t = %f\n",&flux_t);
    fscanf(stream, "roll = %f\n",&roll);
    fscanf(stream, "}\n");
    
    zf_flux_ring_new(half_ring,(double)flux_t, roll);
}

static void
render_opaque(ZfFluxRing* fluxring)
{
    /* 
       CLvertex position;
       cluSetVertexMatrixOrigin(&position, &fluxring->frame);
    */
    glPushAttrib(GL_ALL_ATTRIB_BITS);
    /*
      glDisable(GL_LIGHTING);
      glColor3f(0.4f, 0.3f, 0.7f);
      glPushMatrix();
      glTranslatef(position.x, position.y, position.z);
      glutWireSphere(RING_RADIUS, 8, 8);
      glPopMatrix();
    */
    
    
    glPushMatrix();
    glMultMatrixf((GLfloat*)&fluxring->frame);

    glColor3f(1.0f, 1.0f,1.0f);
    glEnable(GL_LIGHTING);
    glEnable(GL_TEXTURE_2D);
    glEnable(GL_COLOR_MATERIAL);

 
    glRotatef(fluxring->roll_angle, 0.0f, 0.0f, 1.0f);

    if(fluxring->half_ring)
	cluRenderModel(model_1);
    else
	cluRenderModel(model_2);
    /*
      glDisable(GL_TEXTURE_2D);
      glDisable(GL_LIGHTING);
      glBegin(GL_LINES);
      glColor3f(1.0f, 0.0f, 0.0f);
      glVertex3f(0.0f, 0.0f, 0.0f);
      glVertex3f(10.0f, 0.0f, 0.0f);
      glColor3f(0.0f, 0.0f, 1.0f);
      glVertex3f(0.0f, 0.0f, 0.0f);
      glVertex3f(0.0f, 0.0f, 10.0f);
      glColor3f(0.0f, 1.0f, 0.0f);
      glVertex3f(0.0f, 0.0f, 0.0f);
      glVertex3f(0.0f, 10.0f, 0.0f);
      glEnd();
    */

    glPopMatrix();

    glPopAttrib();
}

#if 0
static void
render_translucent(ZfFluxRing* fluxring)
{

    CLvertex position;

    cluSetVertexMatrixOrigin(&position, &fluxring->frame);

    glPushAttrib(GL_ALL_ATTRIB_BITS);
    glDisable(GL_LIGHTING);

    glColor4f(0.5f, 0.4f, 0.8f, 0.6f);

    /* Draw the centre with 'lightining' */
    glPopAttrib();

}
#endif

static inline int
overlap_angle_range(float a0, float a1, float b0, float b1)
{
/*    printf("before in overlap %f %f %f %f\n", a0, a1, b0 ,b1);*/
    if(a1 < a0)
	a1 += 360.0f;

    if(b1 < b0)
    {
	if(a1 < b1)
	    a1 += 360.0f;

	if(a0 < b1)
	    a0 += 360.0f;

	if(a1 < a0)
	    a1 += 360.0f;
		
	b1 +=360.0f;
    }

 /*   printf("after in overlap %f %f %f %f\n", a0, a1, b0 ,b1);*/
    
    if(a0 > b0 && a1 < b1)
	return 0;

    return 1;
}


/* Need to fix! Still code for rotating flux ring. No longer rotates */
static bool
query_collision(ZfFluxRing* fluxring,
		const CLUsphere* sphere,
		const CLnormal* force,
		ZfType collider_type,
		CLvertex* collision_position,
		CLnormal* collision_force_perp,
		CLnormal* collision_force_tan)
{
    CLvertex position;
    
    /* NODE_RADIUS *2 to make it easier to hit */

    cluSetVertexMatrixOrigin(&position, &fluxring->frame);
    if((collider_type & ZF_SHIP)) 
    {
	float dist;
	/* First check if in sphere */
	dist = cluVertexDistance(&sphere->origin, &position);
	if(dist < sphere->radius + RING_RADIUS)
	{
	    if(fabs(cluPlaneDistance(&fluxring->plane, &sphere->origin)) < sphere->radius)
	    {
		float roll, angle_range, ang0, ang1;
		CLnormal normal, n1, n0;
		CLmatrix frame;
		float cos_angle;
		float sin_angle;
		
		cluSetNormalMatrixAxisY(&n0, &fluxring->frame);
		cluNormalNormalise(&n0);

		zf_ship_query_frame(&frame);
 
		cluSetNormalMatrixAxisY(&n1, &frame);
		n1.k = 0.0f; /* project the ship's z axis to 0.0f to get a more accurate 'up' vector */
		cluNormalNormalise(&n1);
		
		cos_angle = cluNormalDotProduct(&n0, &n1);
		roll = CL_RAD2DEG(acos(cos_angle));

		/* since cos doesn't know which quatrant it's in....*/
		if(n1.i > 0.0f)
		    roll = 360.0f - roll;

		angle_range = 20.0f;  /* hack size that seems to work well with ship size */
		
		ang0 = roll - angle_range;
		ang1 = roll + angle_range;

		if(ang0 < 0.0f)
		    ang0 += 360.0f;
		else if(ang0 > 360.0f)
		    ang0 -= 360.0f;
		
		if(ang1 < 0.0f)
		    ang1 += 360.0f;
		else if(ang1 > 360.0f)
		    ang1 -= 360.0f;
		

		if(overlap_angle_range(ang0, ang1, fluxring->ang0, fluxring->ang1))
		{
		    CLnormal axis, velocity;

		    cluSetNormal(&axis, 0.0f, 1.0f, 0.0f);
		    cluSetNormal(&velocity, 0.0f, 0.0f, 0.0f);
			
		    if(fluxring->half_ring)
			zf_debris_new(&fluxring->frame,
				      &velocity,
				      &axis,
				      0.0f,
				      10.0f,
				      RING_RADIUS,
				      500,
				      model_1);
		    else
			zf_debris_new(&fluxring->frame,
				      &velocity,
				      &axis,
				      0.0f,
				      10.0f,
				      RING_RADIUS,
				      500,
				      model_2);
		    
		    zf_explosion_new(&position, 4.0f);

		    fluxring->valid = false;
		    return true;
		}
		else if(!fluxring->ship_score)
		{
		    zf_hud_increase_score(FLUX_RING_SCORE);
		    fluxring->ship_score = true;
		}
	    }
	}
    }
    return false;
}

/*!
  \todo Move read code to dedicated read() function.
*/
void
zf_flux_ring_init(char* filename)
{
    smart_pointer.is_valid = (ZfIsValid*) is_valid;
    smart_pointer.reference = (ZfReference*) reference;
    smart_pointer.release = (ZfRelease*) release;

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

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

	/* read */
	fscanf(stream, "ZfFluxRings\n{\n");
	fscanf(stream, "num_fluxrings = %d\n", &num_fluxrings);
	
	fscanf(stream, "fluxrings =\n[\n");
	
	for (i = 0; i < num_fluxrings; i++)
	{
	    fluxring_read(stream);   
	}

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

    context_1 = clDefaultContext(clNewContext());
    model_1 = clioLoadModel(context_1, "../data/models/12clearing_ring/12clearing_ring.3DS");

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

/*    cluModelCentre(model_1); */
    cluModelScaleUnitCube(model_1);
    cluModelScale(model_1, 6.0f);
    clUpdateContext(model_1->context);

    context_2 = clDefaultContext(clNewContext());
    model_2 = clioLoadModel(context_2, "../data/models/34clearing_ring/34clearing_ring.3DS");

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

  /*  cluModelCentre(model_2); */
    cluModelScaleUnitCube(model_2); 
    cluModelScale(model_2, 7.0f);
    clUpdateContext(model_2->context);


}

void
zf_flux_ring_write(char* filename, GList* flux_ring_list)
{
    FILE* stream; 
    GList*  li;
    ZfFluxRing* fluxring;
    
    printf("%s() : save flux ring file %s\n", __FUNCTION__, filename);
    
    stream = fopen(filename, "w");
    
    fprintf(stream, "ZfFluxRings\n{\n");
    fprintf(stream, "num_fluxrings = %d\n",  g_list_length(flux_ring_list));
    fprintf(stream, "fluxrings =\n[\n");

    for (li = flux_ring_list; li; li = li->next)
    {
	fluxring = (ZfFluxRing*)li->data;

	fprintf(stream, "ZfFluxRing\n{\n");
	fprintf(stream, "half_ring = %d\n", fluxring->half_ring);
	fprintf(stream, "flux_t = %f\n",fluxring->flux_position);
	fprintf(stream, "roll = %f\n",fluxring->roll_angle);
	fprintf(stream, "}\n");
    }

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

    fclose(stream);
}

void
zf_flux_ring_close(void)
{
    clDeleteContext(context_1);
    clDeleteContext(context_2);
    /* model later maybe ?*/
}

ZfFluxRing*
zf_flux_ring_new(bool half_ring, const double flux_t, const float roll)
{
    CLvertex position;
    CLnormal zAxis, yAxis;
    ZfFluxRing* fluxring;

    cluSetNormal(&yAxis, 0.0f, 1.0f, 0.0f);
    
    fluxring = g_new(ZfFluxRing, 1);
    
    fluxring->ref_count = 0;
    fluxring->ship_score = false;

    clDefaultMatrix(&fluxring->frame);

    /* These two lines must be done to get a close frame. Otherwise,
       difference too large to get accurate alignment with flux */
/*    zf_flux_query_position(&position, flux_t - 0.1);*/
/*    cluSetMatrixPosition(&fluxring->frame, &position);*/

    zf_flux_update_frame(&fluxring->frame,flux_t);
    cluSetVertexMatrixOrigin(&position, &fluxring->frame); 
    cluSetNormalMatrixAxisZ(&zAxis, &fluxring->frame);


    zf_flux_query_position(&position, flux_t - 0.1);
    cluNormalScale(&zAxis, -1.0f);
    zf_align_frame_y_vertex_normal(&fluxring->frame, 
				   &position,
				   &yAxis);
    /* need this to make sure they face the right way up, and the front to the ship...*/
    zf_align_frame_z_vertex_normal(&fluxring->frame, 
				   &position,
				   &zAxis);
    zf_align_frame_y_vertex_normal(&fluxring->frame, 
				   &position,
				   &yAxis);

    cluSetPlaneVertexNormal(&fluxring->plane,
			    &position,
			    &zAxis);


    fluxring->half_ring = half_ring;
    fluxring->valid = true;
    fluxring->flux_position = flux_t;
    fluxring->roll_angle = roll;

    if(fluxring->half_ring)
    {
	fluxring->ang0 = 180.0f + fluxring->roll_angle;
	fluxring->ang1 = 0.0f + fluxring->roll_angle;
    }
    else
    {
	fluxring->ang0 = 270.0f + fluxring->roll_angle;
	fluxring->ang1 = 0.0f + fluxring->roll_angle;
    }
    /* clamp between 0 - 360 */
    if(fluxring->ang0 > 360.0f)
	fluxring->ang0 -= 360.0f;

    if(fluxring->ang1 > 360.0f)
	fluxring->ang1 -= 360.0f;
        
    zf_render_system_add_opaque(fluxring,
				&smart_pointer,
				(ZfRender*) render_opaque);
  
    zf_collision_system_add_static(fluxring,
				   &smart_pointer,
				   (ZfQueryCollision*)query_collision,
				   ZF_FLUX_RING);
    return fluxring;
}

