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

static const float gravity = 0.0098f;

static CLimage*   particle_image;
static CLtexture* particle_texture;

typedef struct Particle Particle;

struct Particle
{
    CLvertex position;
    CLvertex last_position;
    
    float density;
    CLcolour colour;

    unsigned int lifespan; /* in game steps */
    unsigned int age;

    float        size;     /* of the rendered particle */
};

static GList* particle_list;

static void
decay_particles(void)
{
    GList* li;

    li = particle_list;

    while (li)
    {
	Particle* particle;

	particle = (Particle*)li->data;
	
	particle->age++;
	
	if (particle->age > particle->lifespan)
	{
	    GList* li_next;
	    
	    li_next = li->next;
	    
	    /* remove particle from list */
	    particle_list = g_list_delete_link(particle_list, li);

	    /* release particle */
	    g_free(particle);

	    li = li_next;
	}
	else
	    li = li->next;
    }
}

static void
animate(void* nothing)
{
    GList* li;

    /* age particles, remove dead particles */
    decay_particles();

    /* verlet integration */
    for (li = particle_list; li; li = li->next)
    {
	Particle* particle;
	CLnormal velocity;
	CLnormal acceleration;

	particle = (Particle*)li->data;
	    
	/* velocity from last step */
	cluNormalDifference(&velocity,
			    &particle->position, &particle->last_position);
	
	/* store last position */
	clCopyVertex(&particle->last_position, &particle->position);
	
	/* acceleration due to gravity */
	cluSetNormal(&acceleration, 0.0f, -gravity * particle->density, 0.0f);

	/* update velocity with acceleration */
	cluNormalAdd(&velocity, &acceleration);
	
	/* update position with velocity */
	cluVertexAdd(&particle->position, &velocity);
    }
}

/* variables for rendering particles */
static CLmatrix modelview;
static CLnormal right;
static CLnormal up;

static inline void
render_particle(Particle* particle)
{
  float half_size;
  CLnormal sright;
  CLnormal sup;
  CLvertex centre0;
  CLvertex centre1;
  CLnormal offset;
  CLvertex corner0;
  CLvertex corner1;
  CLvertex corner2;
  CLvertex corner3;

  half_size = 0.02f *  particle->size / 2.0f; //WARNING: 0.02f is a h4x0r!
    
  clCopyNormal(&sright, &right);
  cluNormalScale(&sright, half_size);
  clCopyNormal(&sup, &up);
  cluNormalScale(&sup, half_size);
  
  if (particle->position.y <= particle->last_position.y)
  {      
      clCopyVertex(&centre0, &particle->position);
      clCopyVertex(&centre1, &particle->last_position);
  }
  else
  {
      clCopyVertex(&centre0, &particle->last_position);
      clCopyVertex(&centre1, &particle->position);
  }
  
  cluSetNormal(&offset, 0.0f, half_size, 0.0f);
  cluVertexAdd(&centre0, &offset);
  cluVertexAdd(&centre1, &offset);
  
  clCopyVertex(&corner0, &centre0);
  clCopyVertex(&corner1, &centre0);
  clCopyVertex(&corner2, &centre1);
  clCopyVertex(&corner3, &centre1);
  
  /* bottom left */
  cluVertexSubtract(&corner0, &sright);
  cluVertexSubtract(&corner0, &sup);
  
  /* bottom right */
  cluVertexAdd(&corner1, &sright);
  cluVertexSubtract(&corner1, &sup);
  
  /* top right */
  cluVertexAdd(&corner2, &sright);
  cluVertexAdd(&corner2, &sup);
  
  /* top left */
  cluVertexSubtract(&corner3, &sright);
  cluVertexAdd(&corner3, &sup);
  
  glColor4f(particle->colour.r,
	    particle->colour.g,
	    particle->colour.b,
	    1.0f - (float)particle->age/(float)particle->lifespan);

  glTexCoord2f( 0.0f, 0.0f );
  glVertex3fv((GLfloat*)&corner0);
  
  glTexCoord2f( 1.0f, 0.0f );
  glVertex3fv((GLfloat*)&corner1);
  
  glTexCoord2f( 1.0f, 1.0f);
  glVertex3fv((GLfloat*)&corner2);
  
  glTexCoord2f( 0.0f, 1.0f );
  glVertex3fv((GLfloat*)&corner3);
}

static void
render(void* nothing)
{
    glPushAttrib(GL_ALL_ATTRIB_BITS);

    glDisable(GL_LIGHTING);

    /* glColor3f(1.0f, 0.0f, 0.0f); */
    glEnable(GL_TEXTURE_2D);
    clLoadTexture(particle_texture);

    /* load modelview matrix for billboarding (once per particle system render call) */
    glGetFloatv(GL_MODELVIEW_MATRIX, (GLfloat*)&modelview);  
    cluSetNormal(&right, modelview.m00, modelview.m10, modelview.m20);
    cluSetNormal(&up, modelview.m01, modelview.m11, modelview.m21);

    glBegin(GL_QUADS);
    g_list_foreach(particle_list, (GFunc)render_particle, NULL);
    glEnd();

    glPopAttrib();
}

/*
  NEW STUFF
*/
/* filler methods to meet interface (in Ci would use void implements...) */
static bool
is_valid(const void* nothing)
{
    return true;
}

static void
reference(void* nothing)
{
}

static void
release(void* nothing)
{
}

void
zf_particle_system_init(void)
{
    static ZfSmartPointer smart_pointer;

    smart_pointer.is_valid = is_valid;
    smart_pointer.reference = reference;
    smart_pointer.release = release;
    
    zf_render_system_add_translucent(0, /* null */
				     &smart_pointer,
				     (ZfRender*)render);
    
    zf_animation_system_add(0, /* null */
			    &smart_pointer,
			    (ZfAnimate*)animate);

    particle_list = 0;

    /* load a texture */
    particle_image = clioLoadImage("../data/textures/particle/particle_explosion_invert.png");
    particle_texture = cluSetTextureImage(clDefaultTexture(clNewTexture()), particle_image);
}

void
zf_particle_system_close(void)
{
    g_list_foreach(particle_list, (GFunc)g_free, NULL);
    g_list_free(particle_list);
}

void
zf_particle_system_add_particle(CLvertex* position,
				CLnormal* velocity, /* per game step */
				float density,
				CLcolour* colour,
				unsigned int lifespan,
				float size)
{
    Particle* particle;

    particle = g_new(Particle, 1);
    
    clCopyVertex(&particle->position, position);

    clCopyVertex(&particle->last_position, position);
    cluVertexSubtract(&particle->last_position, velocity);

    particle->density = density;
    clCopyColour(&particle->colour, colour);

    particle->lifespan = lifespan;
    particle->age = 0;

    particle->size = size;

    particle_list = g_list_prepend(particle_list, particle);
}

void
zf_particle_system_print(void)
{
    printf("%s()\n", __FUNCTION__);
    printf("particle list has %u elements\n",
	   g_list_length(particle_list));
}

unsigned int
zf_particle_system_get_particle_count(void)
{
    return g_list_length(particle_list);
}
