/** 
 *  _   _        _  _                           _             
 * | |_| |  ___ | || |  ___   __ _  _ __  _ __ (_)  ___  _ __ 
 * |  _  | / _ \| || | / __| / _` || '__|| '__|| | / _ \| '__|
 * | | | ||  __/| || || (__ | (_| || |   | |   | ||  __/| |   
 * |_| |_| \___||_||_| \___| \__,_||_|   |_|   |_| \___||_|   
 * 
 *  (c) 2001 Free Lunch Design, Teknisk-IT, AerotechTelub
 *			  http://www.freelunchdesign.com
 * 
 * This source code is released as is under GPL (General Public
 * License). Please refer to license.txt for more information.
 * 
 */
 
#include <stdio.h>
#include <math.h>

#include "allegro.h"
#include "main.h"
#include "mission.h"
#include "object.h"
#include "scenery.h"
#include "building.h"
#include "soldier.h"
#include "vehicle.h"
#include "sub_mission.h"
#include "projectile.h"
#include "smoke.h"
#include "pickup.h"
#include "debris.h"

// constructor
// sets some silly default values
mission::mission(DATAFILE *df) {
	int i;

	sprintf(name, "%s", "<name of mission>");
	sprintf(text, "%s", "<description of mission>");
	sprintf(gfx_file, "%s", "data/def.dat");

	// no objects
	for(i=0;i<MAX_OBJECTS;i++)
		obj[i] = NULL;

	// no noise scenery
	for(i=0;i<MAX_NOISE;i++)
		noise[i] = NULL;

	// no noise projs
	for(i=0;i<MAX_PROJECTILES;i++)
		proj[i] = NULL;

	// no smoke
	for(i=0;i<MAX_SMOKE;i++)
		smk[i] = NULL;

	// no smoke
	for(i=0;i<MAX_DEBRIS;i++)
		dbrs[i] = NULL;

	// no sub_missions
	for(i=0;i<MAX_SUB_MISSIONS;i++)
		sub[i] = NULL;

	// no events triggered
	for(i=0;i<MAX_EVENTS;i++)
		event[i] = 0;

	// no zones
	for(i=0;i<MAX_ZONES;i++)
		zone[i].active = FALSE;

	gfx = NULL;
	password = FALSE;
	gen = df;
	//micon = 0;
	max_object = 0;
	o = NULL;
}

// destructor
mission::~mission() {
	int i;

	// no objects
	for(i=0;i<MAX_OBJECTS;i++) 
		if (obj[i] != NULL) 
			switch(obj[i]->type) {
				// TODO: more types
				case OBJ_SCENERY : 
					scenery *s = (scenery *)obj[i];
					s->~scenery();
			}

	// no noise scenery
	for(i=0;i<MAX_NOISE;i++)
		if (noise[i] != NULL) noise[i]->~scenery();

	for(i=0;i<MAX_SMOKE;i++)
		if (smk[i] != NULL) smk[i]->~smoke();

	for(i=0;i<MAX_DEBRIS;i++)
		if (dbrs[i] != NULL) dbrs[i]->~debris();


		/*
		// no projs
	for(i=0;i<MAX_PROJECTILES;i++)
		if (proj[i] != NULL) proj[i]->~projectile();
		*/

	// no sub_missions
	for(i=0;i<MAX_SUB_MISSIONS;i++)
		if (sub[i] != NULL) sub[i]->~sub_mission();

	if (gfx != NULL) unload_datafile(gfx);
}

// adds an objects to the mission
bool mission::add_object(object *o, bool any) {
	// find free slot
	int i=0;

	if (any) while(obj[i]!=NULL && i<MAX_OBJECTS) i++;
	else i = ++max_object;
	if (i >= MAX_OBJECTS) return FALSE;

	// add object and set some values
	obj[i] = o;
	obj[i]->current_mission = this;
	obj[i]->w = ((BITMAP*)gfx[obj[i]->base_image].dat)->w;
	obj[i]->h = ((BITMAP*)gfx[obj[i]->base_image].dat)->h;
	obj[i]->radius = sqrt(obj[i]->h*obj[i]->h + obj[i]->w*obj[i]->w)/2;
	obj[i]->index = i;
	if (obj[i]->type == OBJ_TANK || obj[i]->type == OBJ_TOWER) {
		vehicle *v = (vehicle *)obj[i];
		v->set_turret_stats(gfx);
	}

	return TRUE;
}

bool mission::add_projectile(projectile *p) {
	// find free slot
	int i=0;
	while(proj[i] != NULL && i<MAX_PROJECTILES) i++;
	if (i==MAX_PROJECTILES) return FALSE;

	// add proj
	proj[i] = p;

	return TRUE;
}

bool mission::add_smoke(smoke *s) {
	// find free slot
	int i=0;
	while(smk[i] != NULL && i<MAX_SMOKE) i++;
	if (i==MAX_SMOKE) {
		//textout_centre(screen, font, "out of smoke error!", 320, 200, 1);
		return FALSE;
	}

	// add proj
	smk[i] = s;

	return TRUE;
}

bool mission::add_debris(debris *d) {
	// find free slot
	int i=0;
	while(dbrs[i] != NULL && i<MAX_DEBRIS) i++;
	if (i==MAX_DEBRIS) {
		//textout_centre(screen, font, "out of smoke error!", 320, 200, 1);
		return FALSE;
	}

	// add proj
	dbrs[i] = d;

	return TRUE;
}

// sets the object of a specified index
void mission::set_object(object *o, int i) {
	if (obj[i] != NULL) obj[i]->~object();

	// add object and set some values
	obj[i] = o;
	obj[i]->w = ((BITMAP*)gfx[obj[i]->base_image].dat)->w;
	obj[i]->h = ((BITMAP*)gfx[obj[i]->base_image].dat)->h;
}



// renders a mission according to camera position
void mission::render(BITMAP *bmp, double cx, double cy, fixed ca) {
	int i,j;
	double hyp, ang;
	double dx, dy;
	object *obj_list[6][MAX_OBJECTS];
	int num_obj[6] = {0,0,0,0,0,0};
	int tot_objects = 0;

	// calc shadow offset
	double na = fixtof(ca)*2*PI/256 + PI/4;
	double sy = cos(na);
	double sx = sin(na);
	int noise_amount = (o->detail_level == DETAIL_LOW ? 0 : (o->detail_level == DETAIL_MED ? MAX_NOISE>>1 : MAX_NOISE));

	//////////////////////
	// draw noise (quickie)
	//////////////////////
	int num_noise = 0;
	for(i=0;i<noise_amount;i++)
		if (noise[i] != NULL) {
			// calculate hypotenusa from object to camera position
			dx = (int)(noise[i]->x - cx)%NOISE_W + NOISE_W/2;
			dy = (int)(noise[i]->y - cy)%NOISE_W + NOISE_W/2;
			hyp = sqrt(dx*dx+dy*dy);
			if (hyp < 600) {
				// calculate angle between camera and object
				ang = atan2(dy, dx) - fixtof(ca)*2*PI/256;
				noise[i]->render_x = HELI_X + hyp * cos(ang);
				if (noise[i]->render_x < SCREEN_W && noise[i]->render_x > 0) {
					noise[i]->render_y = HELI_Y + hyp * sin(ang);
					if (noise[i]->render_y < SCREEN_H && noise[i]->render_y > 0) {
						// draw it
						noise[i]->draw(bmp, gfx, ca, o->shadow);
						num_noise++;
					}
				}
			}
		}
	//////////////////////

	for(i=0;i<MAX_OBJECTS;i++)
		if (obj[i] != NULL) {
			obj[i]->rendered = FALSE;
			if (!obj[i]->inside) {
				// calculate hypotenusa from object to camera position
				dx = (obj[i]->x - cx);
				dy = (obj[i]->y - cy);
				hyp = sqrt(dx*dx+dy*dy);
				// set values for radar rendering
				obj[i]->r_hyp = hyp;
				obj[i]->r_dx = dx;
				obj[i]->r_dy = dy;

				if (hyp < 580 + obj[i]->radius) {		// object is inside screen sphere
					// calculate angle between camera and object
					ang = atan2(dy, dx) - fixtof(ca)*2*PI/256;
					obj[i]->r_angle = ang;
					// calculate render x position
					obj[i]->render_x = HELI_X + hyp * cos(ang);
	
					if (obj[i]->render_x < SCREEN_W + obj[i]->radius && obj[i]->render_x > -obj[i]->radius) {	// object is inside screen (x)
						// calculate render y position
						obj[i]->render_y = HELI_Y + hyp * sin(ang);
						if (obj[i]->render_y < SCREEN_H + obj[i]->radius && obj[i]->render_y > -obj[i]->radius) { // object is inside screen (y)
							obj[i]->rendered = TRUE;
							obj[i]->shax = sx;
							obj[i]->shay = sy;
							// put it in correct list depending on altitude
							if (obj[i]->altitude >= GL_GROUND_FLAT_SCENERY && obj[i]->altitude <= GL_AIR) {
								obj_list[obj[i]->altitude][num_obj[obj[i]->altitude]] = obj[i];
								num_obj[obj[i]->altitude]++;
							}
							else {
								obj_list[5][num_obj[obj[i]->altitude]] = obj[i];
								num_obj[5]++;
							}
						}
					}
				}
			}
			tot_objects ++;
		}


	for(j=0;j<3;j++)
		for(i=0;i<num_obj[j];i++) {
			obj_list[j][i]->draw(bmp, gfx, ca, o->shadow);
			if (obj_list[j][i]->selected) obj_list[j][i]->draw_border(bmp);
		}	


	//////////////////////
	// draw projectiles
	//////////////////////
	int num_proj = 0;
	for(i=0;i<MAX_PROJECTILES;i++)
		if (proj[i] != NULL) {
			// calculate hypotenusa from object to camera position
			dx = (proj[i]->x - cx);
			dy = (proj[i]->y - cy);
			hyp = sqrt(dx*dx+dy*dy);
			proj[i]->r_hyp = hyp;
			if (hyp < 600) {
				// calculate angle between camera and object
				ang = atan2(dy, dx) - fixtof(ca)*2*PI/256;
				proj[i]->render_x = HELI_X + hyp * cos(ang);
				if (proj[i]->render_x < SCREEN_W && proj[i]->render_x > 0) {
					proj[i]->render_y = HELI_Y + hyp * sin(ang);
					if (proj[i]->render_y < SCREEN_H && proj[i]->render_y > 0) {
						// draw it
						proj[i]->draw(bmp, gen, ca, o->shadow);
						num_proj ++;
					}
				}
			}
		}
	//////////////////////


	// draw more objects
	for(j=3;j<6;j++)
		for(i=0;i<num_obj[j];i++) {
			obj_list[j][i]->draw(bmp, gfx, ca, o->shadow);
			if (obj_list[j][i]->selected) obj_list[j][i]->draw_border(bmp);
		}	

	//////////////////////
	// draw debris
	//////////////////////
	for(i=0;i<MAX_DEBRIS;i++)
		if (dbrs[i] != NULL) {
			// calculate hypotenusa from object to camera position
			dx = (dbrs[i]->x - cx);
			dy = (dbrs[i]->y - cy);
			hyp = sqrt(dx*dx+dy*dy);
			if (hyp < 580 + dbrs[i]->size) {
				// calculate angle between camera and object
				ang = atan2(dy, dx) - fixtof(ca)*2*PI/256;
				dbrs[i]->render_x = HELI_X + hyp * cos(ang);
				if (dbrs[i]->render_x < SCREEN_W + dbrs[i]->size && dbrs[i]->render_x > -dbrs[i]->size) {
					dbrs[i]->render_y = HELI_Y + hyp * sin(ang);
					if (dbrs[i]->render_y < SCREEN_H + dbrs[i]->size && dbrs[i]->render_y > -dbrs[i]->size) {
						// draw it
						dbrs[i]->draw(bmp, NULL, 0, o->shadow);
					}
				}
			}
		}


	//////////////////////
	// draw smoke
	//////////////////////
	drawing_mode(DRAW_MODE_TRANS, NULL, 0, 0);
	for(i=0;i<MAX_SMOKE;i++)
		if (smk[i] != NULL) {
			// calculate hypotenusa from object to camera position
			dx = (smk[i]->x - cx);
			dy = (smk[i]->y - cy);
			hyp = sqrt(dx*dx+dy*dy);
			if (hyp < 580 + smk[i]->size) {
				// calculate angle between camera and object
				ang = atan2(dy, dx) - fixtof(ca)*2*PI/256;
				smk[i]->render_x = HELI_X + hyp * cos(ang);
				if (smk[i]->render_x < SCREEN_W + smk[i]->size && smk[i]->render_x > -smk[i]->size) {
					smk[i]->render_y = HELI_Y + hyp * sin(ang);
					if (smk[i]->render_y < SCREEN_H + smk[i]->size && smk[i]->render_y > -smk[i]->size) {
						// draw it
						smk[i]->draw(bmp, NULL, 0, o->shadow);
					}
				}
			}
		}
	//////////////////////
	solid_mode();

	//textprintf_centre(bmp, font, 320, 0, 1, "[%.0f, %.0f]", cx, cy);
	//textprintf(bmp, font, 0, 25, 1, "objects in world:     %d", tot_objects);
	/*
	textprintf(bmp, font, 0, 45, 1, "rendered objects:     %d + %d + %d + %d + %d", num_obj[0], num_obj[1], num_obj[2], num_obj[3], num_obj[4]);
	textprintf(bmp, font, 0, 65, 1, "rendered projectiles: %d", num_proj);
	textprintf(bmp, font, 0, 85, 1, "rendered noise:       %d", num_noise);
	*/
}


void mission::save(char *file_name) {
	int tmp,i,t,j=-1, k=1;
	PACKFILE *fp = pack_fopen(file_name, F_WRITE_PACKED);

	// prep. objects
	// remove all essentials
	for(i=0;i<MAX_OBJECTS;i++)
		if (obj[i] != NULL) obj[i]->essential = FALSE;
	// check all sub targets
	for(i=0;i<MAX_SUB_MISSIONS;i++) 
		if (sub[i] != NULL) 
			for(t=0;t<MAX_TARGETS;t++)
				if (sub[i]->target[t] != -1) 
					if (obj[sub[i]->target[t]] != NULL)
						obj[sub[i]->target[t]]->essential = TRUE;


	// save header
	pack_fwrite(name, sizeof(name), fp);
	pack_fwrite(text, sizeof(text), fp);
	pack_fwrite(gfx_file, sizeof(gfx_file), fp);
	
	// stuff
	pack_fwrite(zone, sizeof(zone), fp);
	pack_fwrite(&password, sizeof(password), fp);
	pack_fwrite(&micon, sizeof(micon), fp);

	// save sub_missions
	for(i=0;i<MAX_SUB_MISSIONS;i++) {
		if (sub[i] != NULL) {
			pack_fwrite(&k, sizeof(k), fp);
			sub[i]->save(fp);
		}
		else pack_fwrite(&j, sizeof(j), fp);
	}
		
	// write objects
	tmp = sizeof(obj)/4;	// max number of objects
	pack_fwrite(&tmp, sizeof(tmp), fp);
	pack_fwrite(obj, sizeof(obj), fp);
	for(i=0;i<tmp;i++)
		if (obj[i] != NULL) {
			pack_fwrite(&obj[i]->type, sizeof(obj[i]->type), fp);
			obj[i]->save(fp);
		}
		else pack_fwrite(&j, sizeof(obj[i]->type), fp);

	pack_fclose(fp);
}

bool mission::load(char *file_name) {
	int num_objects, i, type;
	char path[64];

	PACKFILE *fp = pack_fopen(file_name, F_READ_PACKED);
	if (fp == NULL) return FALSE;

	// get header
	pack_fread(name, sizeof(name), fp);
	pack_fread(text, sizeof(text), fp);
	pack_fread(gfx_file, sizeof(gfx_file), fp);

	// stuff
	pack_fread(zone, sizeof(zone), fp);
	pack_fread(&password, sizeof(password), fp);
	pack_fread(&micon, sizeof(micon), fp);

	// read sub_missions
	for(i=0;i<MAX_SUB_MISSIONS;i++) {
		sub[i] = NULL;
		pack_fread(&type, sizeof(type), fp);			
		if (type != -1) { 
			sub[i] = new sub_mission();
			sub[i]->load(fp);
		}
	}
		
	// read objects
	pack_fread(&num_objects, sizeof(num_objects), fp);	// number of objects to read
	pack_fread(obj, sizeof(obj), fp);
	
	for(i=0;i<num_objects;i++) {
		obj[i] = NULL;
		pack_fread(&type, sizeof(type), fp);			
		if (type == OBJ_BUILDING) { 
			building *o = new building(); o->load(fp); obj[i] = o; 
			if (obj[i]->base_image == 138)  /* sigh, this is so ugly I'm almost crying */ obj[i]->shadow_dist = 5;
		}
		if (type == OBJ_SCENERY) { scenery *o = new scenery(); o->load(fp); obj[i] = o; }
		if (type == OBJ_PICKUP) { pickup *o = new pickup(1); o->load(fp); obj[i] = o; }
		if (type == OBJ_SOLDIER || type == OBJ_FOOT_SOLDIER || type == OBJ_BAZOOKA_SOLDIER) { 
			soldier *o = new soldier(type); o->load(fp); obj[i] = o; 
			sprintf(path, "data/objects/%s", obj[i]->file);
			obj[i]->set_from_file(path);
		}
		if (type == OBJ_TANK || type == OBJ_TRUCK || type == OBJ_JEEP || type == OBJ_HELI || type == OBJ_TOWER) { 
			vehicle *o = new vehicle(type); o->load(fp); obj[i] = o; 
			sprintf(path, "data/objects/%s", obj[i]->file);
			obj[i]->set_from_file(path);
		}
		if (obj[i] != NULL) max_object = i;
	}

	pack_fclose(fp);

	packfile_password(get_pwd());
	gfx = load_datafile(gfx_file);
	packfile_password(NULL);

	// setup/update objects (partially for sector space)
	int x1 = obj[0]->x;
	int y1 = obj[0]->y;
	int x2 = x1, y2 = y1;
	for(i=0;i<num_objects;i++) 
		if (obj[i] != NULL) {
			obj[i]->reset();
			obj[i]->current_mission = this;
			obj[i]->w = ((BITMAP *)gfx[obj[i]->base_image].dat)->w;
			obj[i]->h = ((BITMAP *)gfx[obj[i]->base_image].dat)->h;
			obj[i]->radius = sqrt(obj[i]->h * obj[i]->h + obj[i]->w * obj[i]->w) / 2;

			if (obj[i]->x < x1) x1 = obj[i]->x;
			if (obj[i]->x > x2) x2 = obj[i]->x;
			if (obj[i]->y < y1) y1 = obj[i]->y;
			if (obj[i]->y > y2) y2 = obj[i]->y;
		}


	x1 -= SECTOR_W*2;	x2 += SECTOR_W*2;	y1 -= SECTOR_W*2;	y2 += SECTOR_W*2;

	// calc size of sector space
	int w = (x2 - x1) / SECTOR_W;
	int h = (y2 - y1) / SECTOR_H;

	// setup sectors 
	space = new sector_space(x1, y1, w, h);

	// add objects to sectors
	for(i=0;i<num_objects;i++) 
		if (obj[i] != NULL)
			if (obj[i]->altitude >= GL_GROUND_SCENERY)
				space->add_object(obj[i]);

	// fix zones
	for(i=0;i<MAX_ZONES;i++) {
		zone2[i].cx = zone[i].x1 + (zone[i].x2 - zone[i].x1)/2;
		zone2[i].cy = zone[i].y1 + (zone[i].y2 - zone[i].y1)/2;
	}

	return TRUE;
}


void mission::set_obj_image(int o_id, int im) {
	obj[o_id]->base_image  = im;
	obj[o_id]->w = ((BITMAP*)gfx[im].dat)->w;
	obj[o_id]->h = ((BITMAP*)gfx[im].dat)->h;
}

void mission::random_noise(int first, int last) {
	int scope = last-first+1;
	for(int i=0;i<MAX_NOISE;i++) {
		if (noise[i] != NULL) noise[i]->~scenery();
		noise[i] = new scenery();
		noise[i]->altitude = -1;
		noise[i]->base_image = rand()%scope + first;
		noise[i]->x = rand()%NOISE_W;
		noise[i]->y = rand()%NOISE_W;
		BITMAP *bmp = (BITMAP *)gfx[noise[i]->base_image].dat;
		noise[i]->w = bmp->w;
		noise[i]->h = bmp->h;
	}
}

bool mission::add_zone(int ax1, int ay1, int ax2, int ay2) {
	int i=1;
	while (zone[i].active && i<MAX_ZONES) i++;
	if (i==MAX_ZONES) return FALSE;
	zone[i].x1 = ax1;
	zone[i].x2 = ax2;
	zone[i].y1 = ay1;
	zone[i].y2 = ay2;
	zone[i].active = TRUE;
	return TRUE;
}

int mission::get_zone(int x, int y, bool top_right) {
	for(int i=1;i<MAX_ZONES;i++) 
		if (top_right) {
			if (x<=zone[i].x2 && x>=zone[i].x2-16 && y>=zone[i].y1 && y<=zone[i].y1+16) return i;
		}
		else {
			if (x>=zone[i].x1 && x<=zone[i].x2 && y>=zone[i].y1 && y<=zone[i].y2) return i;
		}

	return -1;
}

void mission::draw_zone(BITMAP *bmp, Tzone *z, int cx, int cy, int col, int num) {
	rect(bmp, z->x1-cx+HELI_X, z->y1-cy+HELI_Y, z->x2-cx+HELI_X, z->y2-cy+HELI_Y, col);
	rect(bmp, z->x2-cx+HELI_X, z->y1-cy+HELI_Y, z->x2-cx+HELI_X-15, z->y1-cy+HELI_Y+15, col);
	line(bmp, z->x2-cx+HELI_X, z->y1-cy+HELI_Y+15, z->x2-cx+HELI_X-15, z->y1-cy+HELI_Y, col);
	line(bmp, z->x2-cx+HELI_X, z->y1-cy+HELI_Y, z->x2-cx+HELI_X-15, z->y1-cy+HELI_Y+15, col);
	if (num) textprintf(bmp, font, z->x1-cx+HELI_X+2, z->y1-cy+HELI_Y+2, col, "%d", num);
}
