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

#define BOSS_MASS 1.0f
#define BOSS_RADIUS 25.0f
#define BOSS_CORE_RADIUS 1.0f
#define BOSS_CORE_MASS 1.0f
#define CORE_RECOVERY_TIME 150
#define SHIP_RANGE 100.0f
#define HEART_ROTATE_INCREMENT 0.5f
#define DEATH_THROES 1000
#define BOSS_CORE_SCORE 5000
#define BOSS_BATTLE_OFFSET 3.0f

#define NORMAL_MISSILE_MODE 200
#define FAST_MISSILE_MODE 100
#define NORMAL_NUMBER_OF_MISSILES 1
#define FAST_NUMBER_OF_MISSILES 2

struct ZfBoss
{
    unsigned int ref_count;

    CLvertex position;
    CLvertex launch_position_0;
    CLvertex launch_position_1;
    CLvertex launch_position_2;
    CLvertex launch_position_3;
    CLvertex launch_position_4;
    CLvertex launch_position_5;

    int launch_index_1; /* for determining which launch position */
    int launch_index_2;

    CLmatrix look_frame;
    
    int frames_since_last_fire;
    int core_recovery;
    int core_health;
    int died_time;
    float heart_rotate;

    float start_flux_t;
    bool core_active;

    bool valid;  
    bool active;  /* only active when ship trips trigger at beginning of boss level*/
    bool timer; /* only attack when the timer's moving */

    int valid_targets;  /* when 0, boss core revealed*/
    GList* target_list;

};

static ZfSmartPointer smart_pointer; 
static ZfDynamicCollider dynamic_collider; 

static CLcontext* context;
static CLmodel* model;

static CLcontext* context_heart;
static CLmodel* model_heart;

static int frames_between_firing;

/* Hacky hack - needed to respawn the boss */
static ZfBoss* the_boss;
static char* boss_file; 

static bool
is_valid(const ZfBoss* boss)
{
    return boss->valid;
}

static void
reference(ZfBoss* boss)
{
    boss->ref_count++;
    //printf("boss referenced to %d\n", boss->ref_count);
}

static void
release(ZfBoss* boss)
{
    boss->ref_count--;
    // printf("boss dereferenced to %d\n", boss->ref_count);
    if (boss->ref_count == 0)
    {
	GList* list;

	for(list = boss->target_list; list; list = list->next)
	{
	    ZfBossTarget* target;
	    target = (ZfBossTarget*) list->data;

	    zf_boss_target_destroy(target);
	}
	//	printf("deleting boss\n");
	g_free(boss);
    }
}

/*called when trigger tripped to start boss battle - also activates the targets*/
static void
activate(ZfBoss* boss)
{
    GList* list;
    boss->active = true;
    
    for(list = boss->target_list; list; list = list->next)
    {
	ZfBossTarget* target;
	target = (ZfBossTarget*) list->data;
	
	zf_boss_target_activate(target);
    }
    zf_camera_activate_boss_mode(&boss->position);
}

/*called when trigger tripped to start boss timer*/
static void
start_timer(ZfBoss* boss)
{
    CLvertex vert;
    cluSetVertex(&vert, -16.0f, 0.0f, -35.0f);
    zf_hud_new_message(0.2f, 0, &vert, "boss countdown about to start!");

    boss->timer = true;
    zf_hud_set_boss_mode(1);
}

/* called when valid targets reaches 0 */
static void
kill(ZfBoss* boss)
{
    boss->active = false;
    zf_camera_deactivate_boss_mode();
    zf_hud_set_boss_mode(0);
    zf_ship_set_defeat_boss(true);
}

static void
query_core_position(ZfBoss* boss,
	       CLvertex* position)
{
    cluSetVertexMatrixOrigin(position, &boss->look_frame);

}

static void
collision_core_response(ZfBoss* boss,
			const void* collider_data,
			ZfType collider_type,
			const CLvertex* collision_position,
			const CLnormal* collision_force_perp,
			const CLnormal* collision_force_tan)
{
    if((collider_type & ZF_WEAPON))
    {
	if(boss->valid && boss->core_recovery <= 0 && boss->core_active)
	{
	    CLvertex boss_core_pos;
	    cluSetVertexMatrixOrigin(&boss_core_pos, &boss->look_frame);
	    
	    boss->core_health -= 20;
	    boss->core_recovery = CORE_RECOVERY_TIME;

	    if(boss->core_health <= 0)
	    {
		zf_score_indicator_new(&boss_core_pos, BOSS_CORE_SCORE);
		zf_hud_increase_score(BOSS_CORE_SCORE);
		boss->core_active = false;
		zf_hud_set_boss_mode(2);
		zf_explosion_new(&boss_core_pos, 10.0f);
	    }
	    
	}
    }
}
static void
animate(ZfBoss* boss)
{
    //printf("aniamte %p\n", boss);
    if(boss->active)
    {
	if(boss->core_health > 0)
	{
	    CLnormal direction;
	    CLvertex point;
	
	    /* align axes - watch ship */
	    zf_ship_query_position(&point);
	
	    cluNormalDifference(&direction, &point, &boss->position);
	    cluNormalNormalise(&direction);
	    direction.j = 0.0f;  /* project onto XY plane - ie, no tilt*/
	
	    clDefaultMatrix(&boss->look_frame);
	    zf_align_frame_z_vertex_normal(&boss->look_frame, &boss->position, &direction);

	    if(++boss->frames_since_last_fire >= frames_between_firing && !zf_ship_query_end_state() && boss->timer)
	    {
		CLvertex glob_launch_pos_0,glob_launch_pos_1,glob_launch_pos_2;
		float random1,random2;
		int i;
		int num_missiles;

		if(boss->core_active)
		    num_missiles = FAST_NUMBER_OF_MISSILES;
		else
		    num_missiles = NORMAL_NUMBER_OF_MISSILES;

		clCopyVertex(&glob_launch_pos_0, &boss->launch_position_0);
		cluVertexTransform(&glob_launch_pos_0, &boss->look_frame);
/*
		clCopyVertex(&glob_launch_pos_1, &boss->launch_position_1);
		cluVertexTransform(&glob_launch_pos_1, &boss->look_frame);

		clCopyVertex(&glob_launch_pos_2, &boss->launch_position_2);
		cluVertexTransform(&glob_launch_pos_2, &boss->look_frame);
*/

		for(i = 0; i < num_missiles;i ++)
		{
		    CLvertex* launch;
			
		    random1 = (float)rand() / RAND_MAX;
		    
		    if(random1 < 0.5f)
		    {
			switch(boss->launch_index_1)
			{
			case 1:
			    launch = &boss->launch_position_1;
			    break;
			case 2:
			    launch = &boss->launch_position_2;
			    break;
			case 3:
			    launch = &boss->launch_position_3;
			    break;
			case 4:
			    launch = &boss->launch_position_4;
			    break;
			case 5:
			    launch = &boss->launch_position_5;
			    break;
			}
			zf_enemy_missile_new(&glob_launch_pos_0, 1, launch, boss, &smart_pointer);
			boss->launch_index_1++;
			if(boss->launch_index_1 >= 6)
			    boss->launch_index_1 = 1;
		    }
		    else
		    {
			switch(boss->launch_index_2)
			{
			case 1:
			    launch = &boss->launch_position_1;
			    break;
			case 2:
			    launch = &boss->launch_position_2;
			    break;
			case 3:
			    launch = &boss->launch_position_3;
			    break;
			case 4:
			    launch = &boss->launch_position_4;
			    break;
			case 5:
			    launch = &boss->launch_position_5;
			    break;
			}
			zf_enemy_missile_new(&glob_launch_pos_0, 2,  launch, boss, &smart_pointer);
			boss->launch_index_2--;
			if(boss->launch_index_2 <= 0)
			    boss->launch_index_2 = 5;
		    }
		}
		
		boss->frames_since_last_fire = 0;
	    }
	    if(boss->core_active && boss->core_recovery > 0)
		boss->core_recovery--;

	    boss->heart_rotate += HEART_ROTATE_INCREMENT;
	    
	}
	else /* core is dead */
	{
	    CLvertex exp_pos;
	    /* Mega Asplode*/
	    cluSetVertex(&exp_pos, 
			 boss->position.x + BOSS_RADIUS*2.0f*(float)rand()/RAND_MAX - BOSS_RADIUS,
			 boss->position.y + BOSS_RADIUS*2.0f*(float)rand()/RAND_MAX - BOSS_RADIUS,
			 boss->position.z + BOSS_RADIUS*2.0f*(float)rand()/RAND_MAX - BOSS_RADIUS);
	    zf_explosion_new(&exp_pos, 4.0f*(float)rand()/RAND_MAX);			 
	    
	    /* this sets a delay before the boss disappears. Enjoy the fireworks! */
	    boss->died_time--;
	    if(boss->died_time <= 0)
		kill(boss);
	    else if(boss->died_time == DEATH_THROES / 2) /* debris for boss heart */
	    {
		CLnormal axis, velocity;
	
		cluSetNormal(&axis, 0.0f, 0.0f, 1.0f);
		cluSetNormal(&velocity, 0.0f, 0.0f, 0.0f);
	
		zf_debris_new(&boss->look_frame,
			      &velocity,
			      &axis,
			      0.0f,
			      BOSS_MASS,
			      BOSS_RADIUS,
			      500,
			      model_heart);
	    }
	}
    }
}

static void
render(ZfBoss* boss)
{
    glPushAttrib(GL_ALL_ATTRIB_BITS);
    if (zf_render_system_get_tool_mode())
    {
	glDisable(GL_COLOR_MATERIAL);
	glDisable(GL_TEXTURE_2D);
	glDisable(GL_LIGHTING);
	
	glColor3f(0.0f, 1.0f, 1.0f);
	
	glPushMatrix();
	glTranslatef(boss->position.x, boss->position.y, boss->position.z);

	/* draw a wire sphere around boss */
	glutWireSphere(BOSS_RADIUS, 16, 16);
	
	glPopMatrix();
    }
    else if(boss->active)
    {
	glPushMatrix();
        
        //glTranslatef(boss->position.x, boss->position.y, boss->position.z);
	glMultMatrixf((GLfloat*) &boss->look_frame);

	if(boss->core_active)
	{
	    glDisable(GL_COLOR_MATERIAL);
	    glDisable(GL_TEXTURE_2D);
	    glDisable(GL_LIGHTING);
	
	    if(boss->core_recovery > 0)
		glColor3f(1.0f, 0.0f, 0.0f);
	    else
		glColor3f(0.0f, 1.0f, 0.0f);


	    glutWireSphere(BOSS_CORE_RADIUS, 16, 16);
	}

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

        //glutWireSphere(BOSS_RADIUS, 16, 16);
	if(!boss->core_active && boss->core_health > 0)
	    clRenderModel(model);

	if(boss->died_time > DEATH_THROES /2)
	{
	    glRotatef(boss->heart_rotate, 0.0f, 0.0f, -1.0f);
	    clRenderModel(model_heart);
	}

        glPopMatrix();
    }
    glPopAttrib();
}

void
zf_boss_decrement_target(ZfBoss* boss)
{
    boss->valid_targets--;
    if(boss->valid_targets == 0)
    {
	CLnormal axis, velocity;
	CLvertex pos;
	/* activate core target */
	boss->core_active = true;
	frames_between_firing = FAST_MISSILE_MODE;
	
	zf_collision_system_add_dynamic(boss,
					&smart_pointer,
					&dynamic_collider,
					ZF_BOSS_TARGET,
					BOSS_CORE_MASS, 
					BOSS_CORE_RADIUS); 
    
	zf_explosion_new(&boss->position, 12.0f);

	cluSetNormal(&axis, 0.0f, 0.0f, 1.0f);
	cluSetNormal(&velocity, 0.0f, 0.0f, 0.0f);
	
	zf_debris_new(&boss->look_frame,
		      &velocity,
		      &axis,
		      0.0f,
		      BOSS_MASS,
		      BOSS_RADIUS,
		      500,
		      model);
    }
}

void
zf_boss_close(void)
{
    clDeleteContext(context);
    clDeleteContext(context_heart);
}


void
zf_boss_init(char* filename)
{
    unsigned int i;
    int num_targets;
    float flux_t;
    CLvertex boss_pos;
    CLvertex* target_pos;
    
    boss_file = filename;

    FILE* stream;
    
    smart_pointer.is_valid = (ZfIsValid*) is_valid;
    smart_pointer.reference = (ZfReference*) reference;
    smart_pointer.release = (ZfRelease*) release;
    
    dynamic_collider.query_position = (ZfQueryPosition*) query_core_position;
    dynamic_collider.collision_response = (ZfCollisionResponse*) collision_core_response;
    
    frames_between_firing = NORMAL_MISSILE_MODE;
    
    stream = fopen(filename, "r");
    
    /* check fopen */
    if (!stream)
    {	
	printf("%s() : ERROR : can't load boss file %s\n", __FUNCTION__, filename);
	exit(1);
    }

    fscanf(stream, "ZfBoss\n{\n");
    fscanf(stream, "flux_t = %f\n", &flux_t);
    fscanf(stream, "position = %f %f %f\n",
	   &boss_pos.x, &boss_pos.y, &boss_pos.z);
    fscanf(stream, "num_targets = %d\n", &num_targets);
    
    target_pos = g_new(CLvertex, num_targets);
    
    for (i = 0; i < num_targets; i++)
    {
	fscanf(stream, "target = %f %f %f\n",
	       &target_pos[i].x, &target_pos[i].y, &target_pos[i].z);
    }
    
    fscanf(stream, "}\n");
    fclose(stream);

    if(num_targets != 0)
    {
	CLvertex vert;	
	zf_boss_new(&boss_pos, flux_t, num_targets, &target_pos[0]);
 
	/* multiple messages to make it stay longer. Better than changing hud message life, as it affects others*/
	cluSetVertex(&vert, -10.0f, 2.0f, -25.0f);
	zf_hud_new_message(flux_t, 1, &vert, "warning - warning");
	zf_hud_new_message(flux_t+0.1f, 1, &vert, "warning - warning");
	zf_hud_new_message(flux_t+0.2f, 1, &vert, "warning - warning");
	zf_hud_new_message(flux_t+0.3f, 1, &vert, "warning - warning");
	cluSetVertex(&vert, -11.0f, -2.0f, -25.0f);
	zf_hud_new_message(flux_t, 1, &vert, "danger approaching!");
	zf_hud_new_message(flux_t+0.1f, 1, &vert, "danger approaching!");
	zf_hud_new_message(flux_t+0.2f, 1, &vert, "danger approaching!");
	zf_hud_new_message(flux_t+0.3f, 1, &vert, "danger approaching!");
    }
    
    
    context = clDefaultContext(clNewContext());
    model = clioLoadModel(context, "../data/models/boss/boss1.3ds");
    if (!model)
    {
	printf("could not load boss frame model\n");
	exit(1);
    }
    cluModelScaleUnitCube(model);
    cluModelScale(model, BOSS_RADIUS*2); /* needs to be slightly bigger? */
    clUpdateContext(model->context);

    context_heart = clDefaultContext(clNewContext());
    model_heart = clioLoadModel(context_heart, "../data/models/boss/boss1_heart.3ds");
    if (!model_heart)
    {
	printf("could not load boss heart model\n");
	exit(1);
    }
    cluModelScaleUnitCube(model_heart);
    cluModelScale(model_heart, BOSS_RADIUS*2);
    clUpdateContext(model_heart->context);

}

void
zf_boss_new(const CLvertex* position, 
	    const float flux_t, 
	    const int num_targets, 
	    const CLvertex* target_positions)
{
    ZfBoss* boss;
    int counter;
    CLvertex temp;

    boss = g_new(ZfBoss, 1);

    boss->ref_count = 0;
    boss->frames_since_last_fire = 0;
    boss->core_recovery = 0;
    boss->core_health = 100;
    boss->valid_targets = num_targets;
    boss->valid = true;
    boss->heart_rotate = 0.0f;
    boss->start_flux_t = flux_t;
    boss->died_time = DEATH_THROES;
    boss->target_list = 0;

    clCopyVertex(&boss->position, position);
    /* local coords */
    cluSetVertex(&boss->launch_position_0, 0.0f, 0.0f, BOSS_RADIUS + 3.0f);
    cluSetVertex(&boss->launch_position_1, -5.0f, 0.0f, BOSS_RADIUS + 3.0f);
    cluSetVertex(&boss->launch_position_2, -5.0f, 5.0f, BOSS_RADIUS + 3.0f);
    cluSetVertex(&boss->launch_position_3, 0.0f, 5.0f, BOSS_RADIUS + 3.0f);
    cluSetVertex(&boss->launch_position_4, 5.0f, 5.0f, BOSS_RADIUS + 3.0f);
    cluSetVertex(&boss->launch_position_5, 5.0f, 0.0f, BOSS_RADIUS + 3.0f);

    boss->launch_index_1 = 1;
    boss->launch_index_2 = 5; /* 5 launch positions*/

    clDefaultMatrix(&boss->look_frame);
    cluSetMatrixPosition(&boss->look_frame, position); 

    boss->active = false;
    boss->core_active = false;
    boss->timer = false;

    for(counter = 0;counter < num_targets; counter++)
    {
	ZfBossTarget* target;
	target = zf_boss_target_new(&target_positions[counter], flux_t, counter, boss, &smart_pointer);

	boss->target_list = g_list_append(boss->target_list, target);
    }

    the_boss = boss;  /* hack to get reference to boss. Not like there are going to be more than one boss in a level */
    

    zf_trigger_system_add_trigger(flux_t,
				  boss,
				  &smart_pointer,
				  (ZfSpawn*)activate); 

    zf_trigger_system_add_trigger(flux_t+BOSS_BATTLE_OFFSET,
				  boss,
				  &smart_pointer,
				  (ZfSpawn*)start_timer); 

    zf_render_system_add_opaque(boss,
				&smart_pointer,
				(ZfRender*) render);

    zf_animation_system_add(boss,
			    &smart_pointer,
			    (ZfAnimate*) animate);
}

void
zf_boss_restart(void)
{
    if(the_boss)
    {
	CLvertex vert;	
	GList* list;

	the_boss->active = false;
	the_boss->frames_since_last_fire = 0;
	the_boss->core_recovery = 0;
	the_boss->core_health = 100;
	the_boss->core_active = false;
	the_boss->heart_rotate = 0.0f;
	the_boss->timer = false;

	the_boss->valid_targets = g_list_length(the_boss->target_list);
	//printf("reseting target number to %d\n", the_boss->valid_targets);

	cluSetVertex(&vert, -10.0f, 2.0f, -25.0f);
	zf_hud_new_message(the_boss->start_flux_t, 1, &vert, "warning - warning");
	zf_hud_new_message(the_boss->start_flux_t+0.1f, 1, &vert, "warning - warning");
	zf_hud_new_message(the_boss->start_flux_t+0.2f, 1, &vert, "warning - warning");
	zf_hud_new_message(the_boss->start_flux_t+0.3f, 1, &vert, "warning - warning");
	cluSetVertex(&vert, -11.0f, -2.0f, -25.0f);
	zf_hud_new_message(the_boss->start_flux_t, 1, &vert, "danger approaching!");
	zf_hud_new_message(the_boss->start_flux_t+0.1f, 1, &vert, "danger approaching!");
	zf_hud_new_message(the_boss->start_flux_t+0.2f, 1, &vert, "danger approaching!");
	zf_hud_new_message(the_boss->start_flux_t+0.3f, 1, &vert, "danger approaching!");


	zf_trigger_system_add_trigger(the_boss->start_flux_t,
				      the_boss,
				      &smart_pointer,
				      (ZfSpawn*)activate); 
	
	zf_trigger_system_add_trigger(the_boss->start_flux_t+BOSS_BATTLE_OFFSET,
				      the_boss,
				      &smart_pointer,
				      (ZfSpawn*)start_timer); 

	for(list = the_boss->target_list; list; list = list->next)
	{
	    ZfBossTarget* target;
	    target = (ZfBossTarget*) list->data;
	    
	    zf_boss_target_reset(target);
	}
    }
}

CLmatrix*
zf_boss_query_frame(ZfBoss* boss)
{
    return &boss->look_frame;
}

bool
zf_boss_query_dying(ZfBoss* boss)
{
    return (boss->died_time < DEATH_THROES);
}

/* To see if the timer battle has started */
bool
zf_boss_query_battle(ZfBoss* boss)
{
    return boss->timer;
}


void
zf_boss_launch_mega_attack(void)
{
    if(the_boss)
    {
	CLvertex boss_pos;
	cluSetVertexMatrixOrigin(&boss_pos, &the_boss->look_frame);

	the_boss->core_recovery = 100000;
	zf_boss_bomb_new(&boss_pos);
    }
}
