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

#define RADIUS 0.25f
#define BPM 120.0f

static float power; /* of power */

static GList* repeater_list;
static GList* repeater_frame_list;

/* runtime members */
static CLtexture* texture;

/* static CLtexture* texture = 0; */
static CLcontext* context;
static CLmodel* model_repeater;
static CLmodel* model_start_platform;
static CLmodel* model_end_platform;
static float    colour;

static CLmatrix start_platform_frame;
static CLmatrix end_platform_frame;

/* z axis is aligned to normal, normal is normalised in this function */
void
zf_align_frame_z_vertex_normal(CLmatrix* frame,
			     CLvertex* vertex,
			     CLnormal* normal)
{
    float angle;
    CLnormal n;
    CLnormal x, y, z;
    CLnormal axis;
    CLUquaternion quaternion;
    CLmatrix rot_matrix;
	
    clCopyNormal(&n, normal);

    if (!cluNormalNormalise(&n))
    {
	/* orientation not determinable, just set position */
	cluSetMatrixPosition(frame, vertex);
	return;
    }

    cluSetNormalMatrixAxisX(&x, frame);
    cluSetNormalMatrixAxisY(&y, frame);
    cluSetNormalMatrixAxisZ(&z, frame);

    angle = CL_RAD2DEG(acos(cluNormalDotProduct(&n, &z)));
    cluNormalCrossProduct(&axis, &n, &z);
    cluNormalNormalise(&axis);

    cluSetQuaternionAxisAngle(&quaternion, &axis, angle);
    clDefaultMatrix(&rot_matrix);
    cluSetMatrixOrientation(&rot_matrix, &quaternion);

    /*
      glPushMatrix();
      glLoadIdentity();
      glRotatef(angle, axis.i, axis.j, axis.k);
      glGetFloatv(GL_MODELVIEW_MATRIX, (GLfloat*)&rot_matrix);
      glPopMatrix();
    */

    if (angle && !(isnan((double)angle)) && (axis.i || axis.j || axis.k))
    {
	cluNormalTransform(&y, &rot_matrix);
	cluNormalTransform(&x, &rot_matrix);
	
	cluSetMatrixAxesOrigin(frame, &x, &y, &n, vertex);
    }
    else
    {
	cluSetMatrixPosition(frame, vertex);
    }
}

/* y axis is aligned to normal, normal is normalised in this function */
void
zf_align_frame_y_vertex_normal(CLmatrix* frame,
			       CLvertex* vertex,
			       CLnormal* normal)
{
    float angle;
    CLnormal n;
    CLnormal x, y, z;
    CLnormal axis;
    CLUquaternion quaternion;
    CLmatrix rot_matrix;
	
    clCopyNormal(&n, normal);

    if (!cluNormalNormalise(&n))
    {
	/* orientation not determinable, just set position */
	cluSetMatrixPosition(frame, vertex);
	return;
    }

    cluSetNormalMatrixAxisX(&x, frame);
    cluSetNormalMatrixAxisY(&y, frame);
    cluSetNormalMatrixAxisZ(&z, frame);

    angle = CL_RAD2DEG(acos(cluNormalDotProduct(&n, &y)));
    cluNormalCrossProduct(&axis, &n, &y);
    cluNormalNormalise(&axis);

    cluSetQuaternionAxisAngle(&quaternion, &axis, angle);
    clDefaultMatrix(&rot_matrix);
    cluSetMatrixOrientation(&rot_matrix, &quaternion);

    /*
      glPushMatrix();
      glLoadIdentity();
      glRotatef(angle, axis.i, axis.j, axis.k);
      glGetFloatv(GL_MODELVIEW_MATRIX, (GLfloat*)&rot_matrix);
      glPopMatrix();
    */

    if (angle && !(isnan((double)angle)) && (axis.i || axis.j || axis.k))
    {
	cluNormalTransform(&z, &rot_matrix);
	cluNormalTransform(&x, &rot_matrix);
	
	cluSetMatrixAxesOrigin(frame, &x, &n, &z, vertex);
    }
    else
    {
	cluSetMatrixPosition(frame, vertex);
    }
}

/* catmull from from v1 to v2 */
static void
solve_catmull_rom(CLvertex* vf,
		  const CLvertex* v0,
		  const CLvertex* v1,
		  const CLvertex* v2,
		  const CLvertex* v3,
		  float t)
{
    CLnormal m1;
    CLnormal m2;

    float tpow2;
    float tpow3;

    float h0;
    float h1;
    float h2;
    float h3;

    CLnormal p1;
    CLnormal p2;

    cluNormalDifference(&m1, v2, v0);
    cluNormalScale(&m1, 0.5f);
    cluNormalDifference(&m2, v3, v1);
    cluNormalScale(&m2, 0.5f);

    tpow2 = t * t;
    tpow3 = tpow2 * t;

    h0 = 2 * tpow3 - 3 * tpow2 + 1;
    h1 = tpow3 - 2 * tpow2 + t;
    h2 = -2 * tpow3 + 3 * tpow2;
    h3 = tpow3 - tpow2;

    clCopyNormal(&p1, (CLnormal*)v1);
    clCopyNormal(&p2, (CLnormal*)v2);

    cluNormalScale(&p1, h0);
    cluNormalScale(&m1, h1);
    cluNormalScale(&p2, h2);
   cluNormalScale(&m2, h3);

    cluSetVertex(vf, 0.0f, 0.0f, 0.0f);
    cluVertexAdd(vf, &p1);
    cluVertexAdd(vf, &m1);
    cluVertexAdd(vf, &p2);
    cluVertexAdd(vf, &m2);
}

static void
draw_catmull_rom(const CLvertex* v0,
		 const CLvertex* v1,
		 const CLvertex* v2,
		 const CLvertex* v3,
		 float scale)
{
    double t;
    double dt;
    
    CLmatrix modelview;
    CLnormal up;
    CLnormal right;
    CLvertex vf0;
    CLvertex vf1;
    CLvertex centre0;
    CLvertex centre1;
    CLvertex corner0;
    CLvertex corner1;
    CLvertex corner2;
    CLvertex corner3;
    
    glGetFloatv(GL_MODELVIEW_MATRIX, (GLfloat*)&modelview);
  
    /* taken axes from transpose/inverse */
    cluSetNormal(&right, modelview.m00, modelview.m10, modelview.m20);
    cluNormalScale(&right, RADIUS);
    cluSetNormal(&up, modelview.m01, modelview.m11, modelview.m21);
    cluNormalScale(&up, RADIUS);

    glDepthMask(GL_FALSE);
    
    glEnable(GL_BLEND);
    /* glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); */
    glBlendFunc(GL_SRC_ALPHA, GL_ONE);
    
    glEnable(GL_TEXTURE_2D);
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    /* clLoadTexture(texture); */
    
    glBegin(GL_QUADS);

    clCopyVertex(&vf0, v1);

    /* glColor4f(colour, 0.5f, 1.0f - colour, 1.0f); */

    dt = 0.05;

    for (t = dt; t <= 1.0 + CL_EPSILON; t+=dt)
    {
	solve_catmull_rom(&vf1, v0, v1, v2, v3, t);
	
	/* horizontal billboard */
	if (vf1.y <= vf0.y)
	{      
	    clCopyVertex(&centre0, &vf1);
	    clCopyVertex(&centre1, &vf0);
	}
	else
	{
	    clCopyVertex(&centre0, &vf0);
	    clCopyVertex(&centre1, &vf1);
	}

	clCopyVertex(&corner0, &centre0);
	clCopyVertex(&corner1, &centre0);
	clCopyVertex(&corner2, &centre1);
	clCopyVertex(&corner3, &centre1);
    
	/* bottom left */
	cluVertexSubtract(&corner0, &right);

	/* bottom right */
	cluVertexAdd(&corner1, &right);

	/* top right */
	cluVertexAdd(&corner2, &right);

	/* top left */
	cluVertexSubtract(&corner3, &right);

	/*glColor3f(1.0f, 1.0f, 1.0f);*/
    
	glTexCoord2d( 0.0, scale * (t - dt));
	glVertex3fv((GLfloat*)&corner0);
    
	glTexCoord2d( 1.0, scale * (t - dt));
	glVertex3fv((GLfloat*)&corner1);
    
	glTexCoord2d( 1.0, scale * t);
	glVertex3fv((GLfloat*)&corner2);
    
	glTexCoord2d( 0.0, scale * t);
	glVertex3fv((GLfloat*)&corner3);

	/* vertical billboard */
	if (vf1.x <= vf0.x)
	{      
	    clCopyVertex(&centre0, &vf1);
	    clCopyVertex(&centre1, &vf0);
	}
	else
	{
	    clCopyVertex(&centre0, &vf0);
	    clCopyVertex(&centre1, &vf1);
	}

	clCopyVertex(&corner0, &centre0);
	clCopyVertex(&corner1, &centre0);
	clCopyVertex(&corner2, &centre1);
	clCopyVertex(&corner3, &centre1);
    
	/* bottom left */
	cluVertexSubtract(&corner0, &up);

	/* bottom right */
	cluVertexAdd(&corner1, &up);

	/* top right */
	cluVertexAdd(&corner2, &up);

	/* top left */
	cluVertexSubtract(&corner3, &up);

	/*glColor3f(1.0f, 1.0f, 1.0f);*/
    
	glTexCoord2d( 0.0, scale * (t - dt));
	glVertex3fv((GLfloat*)&corner0);
    
	glTexCoord2d( 1.0, scale * (t - dt));
	glVertex3fv((GLfloat*)&corner1);
    
	glTexCoord2d( 1.0, scale * t);
	glVertex3fv((GLfloat*)&corner2);
    
	glTexCoord2d( 0.0, scale * t);
	glVertex3fv((GLfloat*)&corner3);

	clCopyVertex(&vf0, &vf1);
    }
  
    glEnd();

    glDisable(GL_TEXTURE_2D);
    glDisable(GL_BLEND);
    glDepthMask(GL_TRUE);
}


static CLtexture*
new_texture(void)
{
    CLimage* image;
    CLtexture* texture;

    texture = clInitTexture(clNewTexture());

    texture->min_filter = GL_LINEAR_MIPMAP_LINEAR;
    texture->mag_filter = GL_LINEAR;
  
    texture->wrap_s = GL_REPEAT;
    texture->wrap_t = GL_REPEAT;
  
    image = &texture->image;
  
    image->width = 64;
    image->height = 256;
    image->format = GL_RGBA;
    image->type = GL_UNSIGNED_BYTE;
    image->data = (void*)malloc(64 * 256 * 4 * sizeof(GLubyte));

    /* HAXOR! - create flux texture */

    {
	unsigned int i;
	unsigned int j;

	for (i = 0; i < 64; i++)
	{
	    float ds;
	    
	    ds = (float)i / 64.0f - 0.5f;
	    ds = (CL_MIN(sqrt(ds*ds), 0.5f) / 0.5f);

	    for (j = 0; j < 256; j++)
	    {
		float a;

		a = 255 * (1.0f - (CL_MIN(sqrt(ds*ds), 0.5f) / 0.5f));

		((GLubyte*)image->data)[4 * (i + 64 * j)] = 255;
		((GLubyte*)image->data)[4 * (i + 64 * j) + 1] = 255;
		((GLubyte*)image->data)[4 * (i + 64 * j) + 2] = 255;
		((GLubyte*)image->data)[4 * (i + 64 * j) + 3] = a;
	    }
	}
    }
	

    clUpdateTexture(texture);

    return texture;
}

static void
step(void)
{
    static double t = 0.0;
#if 1
    t += 0.001f * ZF_TIME_STEP;

    /* warning: haxor! */
    /*colour =  0.2f + 0.8f * (0.5f * sin((BPM / 60.0f) * 2.0f * CL_PI * t) + 0.5f); */

    colour = 
	0.2f + 0.8f * (0.5f * sin((BPM / 60.0f) * 2.0f * CL_PI * t) + 0.5f);
#endif

    
#if 0
    {
	unsigned int i;
	unsigned int j;
	float spectrum[256];
	CLimage* image;

	memcpy(spectrum, zf_audio_get_spectrum(), 256 * sizeof(float));

	image = &texture->image;

	for (i = 0; i < 64; i++)
	{
	    float ds;
	    
	    ds = (float)i / 64.0f - 0.5f;
	    ds = (CL_MIN(sqrt(ds*ds), 0.5f) / 0.5f);

	    for (j = 0; j < 256; j++)
	    {
		GLubyte a;
		
		/* a = 255 * (1.0f - (CL_MIN(sqrt(ds*ds), 0.5f) / 0.5f)); */
		if (ds < spectrum[j] * 100.0f)
		    a = 255;
		else
		    a = 64 * (1.0f - ds);

		((GLubyte*)image->data)[4 * (i + 64 * j)] = 255;
		((GLubyte*)image->data)[4 * (i + 64 * j) + 1] = 255;
		((GLubyte*)image->data)[4 * (i + 64 * j) + 2] = 255;
		((GLubyte*)image->data)[4 * (i + 64 * j) + 3] = a;
	    }
	}
	
	clUpdateTexture(texture);

	/*
	t += 2.0f * zf_audio_get_spectrum_average();
	*/

	t+= 0.1f * spectrum[0];

	colour = 
	    0.2f + 0.8f * (0.5f * sin(2.0f * CL_PI * t) + 0.5f);
    }
#endif
}

/*!
  \todo This function returns early in cases where the number of repeaters
  is fewer than 4. This check is based on a poor understanding of the way
  catmull-roms work. Someone should check that the rest of the code isn't also
  making checks on the number of repeaters.
*/
static void
setup_repeater_frames(void)
{
    GList* li; GList* rl;
    
    //printf("%s() : START : repeater_list has %u elements\n", __FUNCTION__, g_list_length(repeater_list));

    /* check for minimum necessary points in repeater, since using catmull-rom */
    if (g_list_length(repeater_list) < 4)
	return;

    li = repeater_list;
    rl = repeater_frame_list;

    if (li)
    {
	clDefaultMatrix((CLmatrix*)rl->data);
	if (li->next && li->next->next)
	{
	    /* draw start platform */
	    CLvertex* position;
	    CLnormal tangent;
	    CLmatrix frame;

	    /* move to second "repeater" (actual start) */
	    li = li->next;
	    rl = rl->next;

	    position = (CLvertex*)li->data;
	    cluSetNormal(&tangent, 0.0f, 0.0f, 0.0f);

	    cluNormalDifference(&tangent, 
				(CLvertex*)li->next->data,
				(CLvertex*)li->prev->data);
	
	    clDefaultMatrix(&frame);
	    zf_align_frame_z_vertex_normal(&frame,
					   position,
					   &tangent);
	
	    clCopyMatrix((CLmatrix*)rl->data, &frame);
	    clCopyMatrix(&start_platform_frame, &frame);
	    

	    rl = rl->next;
	    for (li = li->next; li->next->next; li = li->next)
	    {
		position = (CLvertex*)li->data;
		cluSetNormal(&tangent, 0.0f, 0.0f, 0.0f);
		
		cluNormalDifference(&tangent, 
				    (CLvertex*)li->next->data,
				    (CLvertex*)li->prev->data);
		
		clDefaultMatrix(&frame);
		zf_align_frame_z_vertex_normal(&frame,
					  position,
					  &tangent);
		
		clCopyMatrix((CLmatrix*)rl->data, &frame);
		rl = rl->next;
	    }
	    
	    /* draw end platform */
	    rl = rl->next;
	    position = (CLvertex*)li->data;
	    cluSetNormal(&tangent, 0.0f, 0.0f, 0.0f);

	    cluNormalDifference(&tangent, 
				(CLvertex*)li->next->data,
				(CLvertex*)li->prev->data);
	
	    clDefaultMatrix(&frame);
	    zf_align_frame_z_vertex_normal(&frame,
					   position,
					   &tangent);
	    
	    clCopyMatrix((CLmatrix*)rl->data, &frame);
	    clCopyMatrix(&end_platform_frame, &frame);
	}
    }
    
    //printf("%s() : END\n", __FUNCTION__);
}

static void
render_opaque(void)
{
    GList* li;

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

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

    /*Draw start platform*/
    glPushMatrix();
    glMultMatrixf((GLfloat*)&start_platform_frame);
    clRenderModel(model_start_platform);
    glPopMatrix();
	    

    /* Draw end platform */
    glPushMatrix();
    glMultMatrixf((GLfloat*)&end_platform_frame);
    clRenderModel(model_end_platform);
    glPopMatrix();

    /* draw the repeaters, at least four repeaters are needed */
    if (g_list_length(repeater_frame_list) >= 4)
    {
	li = repeater_frame_list;
	if (li)
	{
	    /* skip until the "third" repeater */
	    if(li->next)
	    {
		li = li->next;
		if(li->next)
		{
		    for (li = li->next; li->next->next; li = li->next)
		    {
			CLmatrix* repeater_frame;
			CLvertex position;
			
			/* get the coordinate frame for this repeater */
			repeater_frame = (CLmatrix *)li->data;

			/* get repeater position from frame */
			cluSetVertexMatrixOrigin(&position, repeater_frame);

			glEnable(GL_TEXTURE_2D);

			glPushMatrix();
			/* move to repeater position/orientation */
			glMultMatrixf((GLfloat*)repeater_frame);
				
			/* draw repeater */
			clRenderModel(model_repeater);
	
			glPopMatrix();			

			glDisable(GL_TEXTURE_2D);
			
			/* draw supporting pole (in world coordinates) */
			glPushMatrix();
			glTranslatef(position.x, position.y - 1.50f, position.z);
			glRotatef(90.0f, 1.0f, 0.0f, 0.0f);
			glutSolidCone(0.05f, 100.0f, 4, 2);
			glPopMatrix();
		    }
		}
	    }
	}
    }

    if (zf_render_system_get_tool_mode())
    {
	/* draw points at repeater sites */
	glDisable(GL_LIGHTING);
	glPointSize(4.0f);
	glColor4f(1.0f, 1.0f, 0.0f, 1.0f);
	glBegin(GL_POINTS);
	for (li = repeater_list; li ; li = li->next)
	{
	    glVertex3fv((GLfloat*)li->data);
	}
	glEnd();
	
	
	/*
	  draw lines between repeaters
	  dont enter the if unless there are at least three repeaters
	*/
	if (repeater_list)
	{
	    if (repeater_list->next)
	    {
		if (repeater_list->next->next)
		{
		    glDisable(GL_LIGHTING);
		    glLineWidth(2.0f);
		    glColor4f(1.0f, 1.0f, 0.0f, 1.0f);
		    glBegin(GL_LINES);
		    for (li = repeater_list->next; li->next->next ; li = li->next)
		    {
			glVertex3fv((GLfloat*)li->data);
			glVertex3fv((GLfloat*)li->next->data);
		    }
		    glEnd();
		}
	    }
	}
    }
    
    glPopAttrib();
}

static void
render_translucent(void)
{
    /* catmull rom requires at least four control points */

    if (!repeater_list)
	return;
    
    if (!repeater_list->next)
	return;

    if (!repeater_list->next->next)
	return;
    
    if (!repeater_list->next->next->next)
	return;

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

    glDisable(GL_LIGHTING);

    glColor4f(1.0f - colour, 0.5f, colour, 1.0f);
    
    clLoadTexture(texture);

    {
	GList* li;
	float scale; /* basically for texture repeat of spectrum */

	scale = -1.0f;

	for (li = repeater_list;
	     li && li->next && li->next->next && li->next->next->next;
	     li = li->next)
	{
	    draw_catmull_rom((CLvertex*)li->data,
			     (CLvertex*)li->next->data,
			     (CLvertex*)li->next->next->data,
			     (CLvertex*)li->next->next->next->data,
			     scale);

	    scale = -scale;
	}
    }

    glPopAttrib();
}

/*!
  \note this will COPY the point and tangent into the control point 
*/
static void
add_repeater(const CLvertex* vertex)
{
    repeater_list = g_list_append(repeater_list,
				  clCopyVertex(clNewVertex(), vertex));

    repeater_frame_list = g_list_append(repeater_frame_list,
					clNewMatrix());
}

/*!
  \todo Optimise call to setup_repeater_frames() to only setup last few
  frames. Only the last few will hae changed when a new repeater is added.
*/
void zf_flux_add_repeater(const CLvertex* vertex)
{
    /* add repeater to repeater list */
    add_repeater(vertex);

    /* setup orientation for ALL repeaters */
    setup_repeater_frames();
}

static void
read(FILE* stream)
{
    unsigned int i;
    int num_vertices;

    fscanf(stream, "ZfFlux\n{\n");
    
    fscanf(stream, "num_repeaters = %d\n", &num_vertices);
    fscanf(stream, "repeaters =\n[\n");

    for (i = 0; i < num_vertices; i++)
    {
	CLvertex vertex;

	fscanf(stream, "vertex = %f %f %f\n",
	       &vertex.x, &vertex.y, &vertex.z);

	add_repeater(&vertex);
    }

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

static void
write(FILE* stream)
{
    unsigned int i;
    CLvertex*    v;
    GList*       li;

    fprintf(stream, "ZfFlux\n{\n");
    
    fprintf(stream, "num_repeaters = %d\n", g_list_length(repeater_list));
    fprintf(stream, "repeaters =\n[\n");

    for (li = repeater_list; li; li = li->next)
    {
	v = (CLvertex*)li->data;

	fprintf(stream, "vertex = %f %f %f\n",
		v->x, v->y, v->z);
    }
    
    fprintf(stream, "]\n");
    fprintf(stream, "}\n");
}

static void
closest_vector_to_line(CLnormal *vector,
		       const CLvertex *v0, 
		       const CLvertex *v1,
		       const CLvertex *pos,
		       float* t) /* nickl - haxor */
{
    CLnormal V1V0, PosmV0, PosmV1, temp;
    GLfloat DdD;

    cluNormalDifference(&V1V0, v1, v0);
    cluNormalDifference(&PosmV0, pos, v0);

    *t = cluNormalDotProduct(&V1V0, &PosmV0);

#if 1 /* sphere-cap ends */
    if(*t <= 0)  /*V0 closest to Pos */
    {
	clCopyNormal(vector,&PosmV0);
	return;
    }
    
    DdD = cluNormalDotProduct(&V1V0, &V1V0);

    if(*t >= DdD) /* V1 closest to Pos */
    {
	cluNormalDifference(&PosmV1, pos, v1);
	clCopyNormal(vector, &PosmV1);
	return;
    }
#endif
    *t /= DdD;

    cluNormalScale(&V1V0, *t);
    clCopyNormal(&temp, (CLnormal*) v0);
    cluNormalAdd(&temp, &V1V0);
    clCopyNormal(vector, (CLnormal*) pos);
    cluNormalSubtract(vector, &temp);

    /* nickl - haxor! */
    cluNormalScale(vector, -1.0f);
}

/* set localised t value and pointers to four control points */
static GLboolean
query_local_catmull_rom(double* t,
			CLvertex** v0,
			CLvertex** v1,
			CLvertex** v2,
			CLvertex** v3)
{
    GList* li;

    //printf("%s() : START : repeater_list has %u elements\n", __FUNCTION__, g_list_length(repeater_list));

    /* ignore requests for t values less than zero */
    if (*t < 0.0)
	return GL_FALSE;

    /* ignore if repeater list is empty */
    if (!(li = repeater_list))
	return GL_FALSE;
    
    /* check for minimum necessary points in repeater, since using catmull-rom */
    if (g_list_length(repeater_list) < 4)
	return GL_FALSE;

    /* ignore first vertex since using catmull-rom */
    if (!(li = li->next))
	return GL_FALSE;
    
    while (*t > 1.0)
    {
	li = li->next;

	if (!(li->next))
	    return GL_FALSE;

	/* ignore last vertex since catmull-from */
	if (!(li->next->next))
	    return GL_FALSE;
	
	*t -= 1.0;
	
	if (*t < 0.0)
	    return GL_FALSE;
    }

    *v0 = (CLvertex*)li->prev->data;
    *v1 = (CLvertex*)li->data;
    *v2 = (CLvertex*)li->next->data;
    *v3 = (CLvertex*)li->next->next->data;

    return GL_TRUE;
}

void
zf_flux_query_start_platform_frame(CLmatrix* frame)
{
    clCopyMatrix(frame, &start_platform_frame);
}

void
zf_flux_query_end_platform_frame(CLmatrix* frame)
{
    clCopyMatrix(frame, &end_platform_frame);
}

void
zf_flux_query_position(CLvertex* point,
		       double t)
{
    CLvertex* v0;
    CLvertex* v1;
    CLvertex* v2;
    CLvertex* v3;

    if (query_local_catmull_rom(&t, &v0, &v1, &v2, &v3))
	solve_catmull_rom(point, v0, v1, v2, v3, t);

    /* else, point is left as is */

    /*
    generate_bezier(&bezier,
		    (ZfRepeater*)li->data,
		    (ZfRepeatery*)li->next->data);
    
    solve_bezier(point, &bezier, t);
    */

}		  

/* each repeater uses the same model, even if we add more types of repeater,
   we should only load one model per type */
static void
init(void)
{
    context = clDefaultContext(clNewContext());

    /* not clean to just exit, but use this until we have a good error
       system */
    if (!(model_repeater = clioLoadModel(context, "../data/models/repeater/repeater.3DS")))
    {
	printf("could not load repeater\n");
	exit(1);
    }

    /* cluModelCentre(model); */
    cluModelScaleUnitCube(model_repeater);
    cluModelScale(model_repeater, 1.6f * RADIUS);

    if (!(model_start_platform = clioLoadModel(context, "../data/models/start_platform/start_platform.3DS")))
    {
	printf("could not load starting platform\n");
	exit(1);
    }

    cluModelScaleUnitCube(model_start_platform);

    if (!(model_end_platform = clioLoadModel(context, "../data/models/end_platform/end_platform.3DS")))
    {
	printf("could not load end platform\n");
	exit(1);
    }

    cluModelScaleUnitCube(model_end_platform);
    cluModelScale(model_end_platform, 3.0f);
    clUpdateContext(context);

    colour = 0.0f;

    power = 1.0f; /* of power */

    repeater_list = 0;
    repeater_frame_list = 0;

    texture = new_texture();
}

static void
close(void)
{
    g_list_foreach(repeater_list, (GFunc)g_free, NULL);
    g_list_free(repeater_list);

    g_list_foreach(repeater_frame_list, (GFunc)g_free, NULL);
    g_list_free(repeater_frame_list);

    if(context)
	clDeleteContext(context);
    if(texture)
	clDeleteTexture(texture);
}

void 
zf_flux_increase_power(float amount)
{
    power += amount;
    if(power > 1.0f)
    {
	power = 1.0f;
    }
}

void 
zf_flux_decrease_power(float amount)
{
    power -= amount;

    if(power < CL_EPSILON)
    {
	power = 0.0f;
    }
}

double
zf_flux_query_power(void)
{
    return power;
}

double
zf_flux_query_max_t(void)
{
    /* Ignore the first and last repeaters */
    return (double)g_list_length(repeater_list) - 2;
}

double
zf_flux_query_next_t_given_distance(double t,
				    float distance)
{
    CLvertex* v0;
    CLvertex* v1;
    CLvertex* v2;
    CLvertex* v3;

    float segment_distance;
    double old_t;
    double new_t;

    old_t = t;
 
    /* initial guess at "new t" */

    /* warning, this changes "t" */
    if (query_local_catmull_rom(&t, &v0, &v1, &v2, &v3))
    {
	CLvertex old_position;
	unsigned int i;

	segment_distance = cluVertexDistance(v1, v2);
	new_t = old_t +  distance / segment_distance; 

	solve_catmull_rom(&old_position, v0, v1, v2, v3, t);
	
	for (i = 0; i < 32; i++)
	{
	    CLvertex new_position;
	    float new_distance;
	    float distance_diff;

	    zf_flux_query_position(&new_position, new_t);

	    new_distance = cluVertexDistance(&old_position, &new_position);
	    
	    distance_diff = distance - new_distance;

/*	    printf("new_t %f %f %f\n", new_t,distance_diff,segment_distance );*/


	    if(fabs(distance_diff) < CL_EPSILON)
		break;
	    else
		new_t += distance_diff/segment_distance;
	}

	/* in case the solution is non-convergent  - happens when two repeaters too close together*/
	if(fabs(new_t - old_t) > 10.0f)
	{
	    /*best approximation we've got*/
	    new_t = old_t +  distance / segment_distance;

	}
	return new_t;
    }


    /* not on curve, return -1 if before, 1 if after */
    if (t < 1.0)
	return 0.0;
    
    else
	return (double)g_list_length(repeater_list);
    
}

double
zf_flux_query_previous_t_given_distance(double t,
				       float distance)
{
    CLvertex* v0;
    CLvertex* v1;
    CLvertex* v2;
    CLvertex* v3;

    float segment_distance;
    double old_t;
    double new_t;

    old_t = t;

    /* initial guess at "new t" */

    /* warning, this changes "t" */
    if (query_local_catmull_rom(&t, &v0, &v1, &v2, &v3))
    {
	CLvertex old_position;
	unsigned int i;

	segment_distance = cluVertexDistance(v1, v2);
	new_t = old_t -  distance / segment_distance; 

	solve_catmull_rom(&old_position, v0, v1, v2, v3, t);
	
	for (i = 0; i < 32; i++)
	{
	    CLvertex new_position;
	    float new_distance;
	    float distance_diff;

	    zf_flux_query_position(&new_position, new_t);

	    new_distance = cluVertexDistance(&new_position, &old_position);
	    
	    distance_diff = distance - new_distance;
	    
	    if(fabs(distance_diff) < CL_EPSILON)
		break;
	    else
		new_t -= distance_diff/segment_distance;
	}

	/* in case the solution is non-convergent  - happens when two repeaters too close together*/
	if(fabs(new_t - old_t) > 10.0f)
	{
	    /*best approximation we've got*/
	    new_t = old_t -  distance / segment_distance;
	    
	}
	return new_t;
    }


    /* not on curve, return -1 if before, 1 if after */
    if (t < 1.0)
	return 0.0;
    
    else
	return (double)g_list_length(repeater_list);
    
}

void
zf_flux_update_frame(CLmatrix* frame,
		     double t)
{
    CLvertex pos;
    CLnormal z;

    CLvertex last_pos;

    zf_flux_query_position(&pos, t);
    
    cluSetVertexMatrixOrigin(&last_pos, frame);

    cluNormalDifference(&z, &last_pos, &pos);
    cluNormalNormalise(&z);

    zf_align_frame_z_vertex_normal(frame, &pos, &z);
}

/*
  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 */

void
zf_flux_init(char* filename)
{
    static ZfSmartPointer smart_pointer;

    smart_pointer.is_valid = is_valid;
    smart_pointer.reference = reference;
    smart_pointer.release = release;

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

    /* haxor! */
    {
	FILE* stream;
	
	stream = fopen(filename, "r");

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

	read(stream);

	fclose(stream);
    }

    /* create correctly oriented frames for each repeater in the list */
    setup_repeater_frames();

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

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

void
zf_flux_close(void)
{
    close();
}

void
zf_flux_nearest_repeater(const CLvertex* vertex,
			 CLvertex* repeater_position)
{
    GList* li;
    float min_distance;
    
    min_distance = MAXFLOAT;

    for (li = repeater_list; li; li = li->next)
    {
	CLvertex* repeater;
	CLnormal difference;
	float distance;

	repeater = (CLvertex*)li->data;

	cluNormalDifference(&difference, repeater, vertex);

	distance = cluNormalMagnitude(&difference);
	if (distance < min_distance)
	{
	    clCopyVertex(repeater_position, repeater);
	    min_distance = distance;
	}
    }
}

/*!
  \warning Assumes t has monotonically increasing integer values at each repeater. Not sure if this is correct - james.
*/
float
zf_flux_nearest_repeater_t(const CLvertex* vertex)
{
    GList*       li;
    float        min_distance;
    unsigned int min_t = 0;
    unsigned int t = 0;

    min_distance = MAXFLOAT;

    for (li = repeater_list; li; li = li->next)
    {
	CLvertex* repeater;
	CLnormal difference;
	float distance;

	repeater = (CLvertex*)li->data;

	cluNormalDifference(&difference, repeater, vertex);

	distance = cluNormalMagnitude(&difference);
	if (distance < min_distance)
	{
	    min_t = t;
	    min_distance = distance;
	}
	
	t++;
    }

    return min_t;
}

void
zf_flux_write(char* filename)
{
    FILE* stream;
    
    printf("%s() : save flux file %s\n", __FUNCTION__, filename);
    
    stream = fopen(filename, "w");
    
    write(stream);
    
    fclose(stream);
}
