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

#define DRONE_RADIUS 0.5f
#define DRONE_ROTATION_RATE 0.3f
#define COLOUR_CHANGE_TIME 100

typedef enum {HEALTH, LIFE, INVINCIBLE} DroneType;

//typedef struct Drone Drone;

struct ZfDrone
{
    unsigned int ref_count;
    CLvertex position;

    DroneType type; 
    
    float roll;
    float r, g, b;     /* colour*/
    bool valid;

    int counter;
    int counter_limit;
};

static ZfSmartPointer smart_pointer; 
static CLcontext* context;
static CLmodel* model_ammo;

static bool
is_valid(const ZfDrone* drone)
{
   return drone->valid;
}

static void
reference(ZfDrone* drone)
{
    drone->ref_count++;
}

static void
release(ZfDrone* drone)
{
    drone->ref_count--;

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


static void
animate(ZfDrone* drone)
{
    switch(drone->type)
    {
    case HEALTH:
	/*change colour */
	drone->counter++;
	drone->roll -= DRONE_ROTATION_RATE;
	if(drone->counter > drone->counter_limit)
	{
	    drone->counter = 0;
	    if(drone->r == 1.0f)
	    {
		drone->r = 0.0f;
		drone->b = 1.0f;
	    }
	    else
	    {
		drone->r = 1.0f;
		drone->b = 0.0f;
	    }
	}
	break;
    case LIFE:
	drone->roll += DRONE_ROTATION_RATE * 2.0f;
	break;

    case INVINCIBLE:
	drone->roll += DRONE_ROTATION_RATE * 3.0f;
	break;

    default:
	break;

    }
}


static void
drone_read(FILE* stream)
{
    CLvertex position;
    int type;

    fscanf(stream, "ZfDrone\n{\n");
    fscanf(stream, "type = %d\n", &type);
    fscanf(stream, "position = %f %f %f\n",&position.x, &position.y, &position.z);
    fscanf(stream, "}\n");

    zf_drone_new(type, &position);
}


static void
render_opaque(ZfDrone* drone)
{
    /* render the model here?*/
    glPushAttrib(GL_ALL_ATTRIB_BITS);
    glEnable(GL_LIGHTING);
    glEnable(GL_TEXTURE_2D);

    glEnable(GL_COLOR_MATERIAL);
    glColor3f(drone->r, 
	      drone->g,
	      drone->b);
    glPushMatrix();
    glTranslatef(drone->position.x,
		 drone->position.y,
		 drone->position.z);
   
    /* HACK */
    glRotatef(-90.0f, 1.0f, 0.0f, 0.0f);
  
    glRotatef(drone->roll, 0.0f, 0.0f, 1.0f);
    /* must be drawn in opaque to be hit by lasers.... */
    clRenderModel(model_ammo);

    glPopMatrix();

    glPopAttrib();
}

static void
render_translucent(ZfDrone* drone)
{
    /*
      glPushAttrib(GL_ALL_ATTRIB_BITS);
      glDisable(GL_LIGHTING);

      glColor4f(drone->r, 
      drone->g,
      drone->b, 
      0.6f);
      glPushMatrix();
      glTranslatef(drone->position.x,
      drone->position.y,
      drone->position.z);
      glRotatef(drone->roll, 0.0f, 1.0f, 0.0f);
      glutSolidSphere(DRONE_RADIUS, 8, 8);
      glPopMatrix();

      glPopAttrib();
*/
}

static bool
query_collision(ZfDrone* drone,
		const CLUsphere* sphere,
		const CLnormal* force,
		ZfType collider_type,
		CLvertex* collision_position,
		CLnormal* collision_force_perp,
		CLnormal* collision_force_tan)
{
    if((collider_type & ZF_WEAPON))
    {
	float dist;
	
	/* First check if in sphere */
	dist = cluVertexDistance(&sphere->origin, &drone->position);

	if(dist < sphere->radius + (DRONE_RADIUS*2.0f))
	{
	    drone->valid = false;
	    zf_explosion_new(&drone->position, 2.0f);

	    switch(drone->type)
	    {
	    case HEALTH:
		/* Determine which type of health pack to "drop" */
		if(drone->r == 1.0f)
		{
		    zf_drone_pickup_new(0, &drone->position);
		}
		else
		{
		    zf_drone_pickup_new(1, &drone->position);
		}
		break;
		
	    case LIFE:
		zf_drone_pickup_new(2, &drone->position);
		break;
		
	    case INVINCIBLE:
		zf_drone_pickup_new(3, &drone->position);
		break;
	    }

	    return true;
	}
    }
    return false;
}

/*!
  \todo Move read code to dedicated read() function.
*/
void
zf_drone_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_drones;
	
	FILE* stream;

	stream = fopen(filename, "r");

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

	/* read */
	fscanf(stream, "ZfDrones\n{\n");
	fscanf(stream, "num_drones = %d\n", &num_drones);

	//printf("%s() : num_drones = %u\n", __FUNCTION__, num_drones);
	
	fscanf(stream, "drones =\n[\n");
	
	for (i = 0; i < num_drones; i++)
	{
	    drone_read(stream);   
	}

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

    context = clDefaultContext(clNewContext());
    model_ammo = clioLoadModel(context, "../data/models/ammo_drone/ammo_drone.3DS");

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

    cluModelCentre(model_ammo); 
    cluModelScaleUnitCube(model_ammo);
    clUpdateContext(context);
}

/*!
  \todo free model later maybe?
*/
void
zf_drone_close(void)
{
    clDeleteContext(context);
}

void
zf_drone_write(char* filename, GList* drone_list)
{
    FILE* stream; 
    GList*  li;
    ZfDrone* drone;
    
    printf("%s() : save drones file %s\n", __FUNCTION__, filename);
    
    stream = fopen(filename, "w");
    
    fprintf(stream, "ZfDrones\n{\n");
    fprintf(stream, "num_drones = %d\n",  g_list_length(drone_list));
    fprintf(stream, "drones =\n[\n");

    for (li = drone_list; li; li = li->next)
    {
	drone = (ZfDrone*)li->data;

	fprintf(stream, "ZfDrone\n{\n");
	fprintf(stream, "type = %d\n",drone->type);
	fprintf(stream, "position = %f %f %f\n",
		drone->position.x,
		drone->position.y,
		drone->position.z);
	fprintf(stream, "}\n");
    }

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

    fclose(stream);

}

ZfDrone*
zf_drone_new(unsigned int type, const CLvertex* position)
{
    ZfDrone* drone;
    
    drone = g_new(ZfDrone, 1);
    
    drone->ref_count = 0;
    drone->valid = true;
    clCopyVertex(&drone->position, position);

    drone->roll = 0.0f;
    drone->counter = 0;

    switch(type)
    {
    case 0:
	drone->type = HEALTH;
	drone->r = 1.0f;
	drone->g = 0.0f;
	drone->b = 0.0f;
	drone->counter_limit = COLOUR_CHANGE_TIME;
	break;

    case 1:
	drone->type = LIFE;
	drone->r = 0.4f;
	drone->g = 0.0f;
	drone->b = 0.4f;
	break;
	
    case 2:
	drone->type = INVINCIBLE;
	drone->r = 0.0f;
	drone->g = 0.5f;
	drone->b = 0.3f;
	break;
    default:
	printf("%s() : ERROR : unknown drone type\n", 
	       __FUNCTION__);
    }
    
    
    zf_animation_system_add(drone,
			    &smart_pointer,
			    (ZfAnimate*) animate);
    
    zf_render_system_add_opaque(drone,
				&smart_pointer,
				(ZfRender*) render_opaque);
/*
    zf_render_system_add_translucent(drone,
				     &smart_pointer,
				     (ZfRender*) render_translucent);
*/
    zf_collision_system_add_static(drone,
				   &smart_pointer,
				   (ZfQueryCollision*)query_collision,
				   ZF_DRONE);

    return drone;
}

bool
zf_drone_query_ray_intercept(const CLUray* ray, 
			     void* data,
			     CLvertex** position)
{
    CLUsphere sphere;
    float t;              /* intersection time along ray */
    unsigned int i;

    ZfDrone *drone;
    drone = data;

    sphere.radius = DRONE_RADIUS * 2; /* make it easier to hit */

    for(i = 0; i < 6; i++)
    {
	clCopyVertex(&sphere.origin, &drone->position);
	
	if(cluRayIntersectSphere(&t, ray, &sphere))
	{
	    *position = &drone->position;
	    return true;
	}
    }
    return false;
}

