/*
 *  acm : an aerial combat simulator for X
 *  Copyright (C) 1991-1998  Riley Rainey
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; version 2 dated June, 1991.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program;  if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave., Cambridge, MA 02139, USA.
 */

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "../util/error.h"
#include "../util/memory.h"
#include "../util/reader.h"
#include "../util/units.h"
#include "air.h"
#include "box.h"
#include "damage.h"
#include "dis_if.h"
#include "drone.h"
#include "effects.h"
#include "gear.h"
#include "init.h"
#include "joystick.h"
#include "list.h"
#include "m61a1.h"
#include "manifest.h"
#include "mouse.h"
#include "patchlevel.h"
#include "players.h"
#include "pm.h"
#include "render.h"
#include "update.h"

static char *objects = NULL; /* value of the -objects PATHs option */
static char * name = NULL;

// Frame rate (Hz).
static int frame_rate = 20;

static int   use_dis = 0;
static char *dis_relay_name = NULL;
static int   dis_relay_port = 3000;
static int   dis_site = -1;
static int   dis_application = -1;
static int   dis_exercise = 1;

static char *departure_time;


/**
 * Generic kill callback for remote entities.
 * @param c Remote craft/missile/bomb/whatever to remove.
 */
static void disEntityKill(craft *c, char *reason)
{
	int pIndex = c->pIndex;
 	memory_dispose(c->aps);
	gear_free(c);
	drone_release_commands(c);
 	pm_hud_strings_free(c);
	
	if (c->flags & FL_RECORD) {
		--recordCount;
	}

	if (c->flags & FL_BLACK_BOX)
		box_killPlayer(c->pIndex);
	
	dis_if_entityExit(c->disId);
	memory_zero(c);
	
	c->pIndex = pIndex;
	c->type = CT_FREE;
}


/**
 * Entity creation callback to be set in the dis_if module. Invoked when a new
 * remote entity state DIS PDU appears.
 * @param eid Handle the dis_if module assigned to this new entity.
 * @param etype DIS entity type.
 * @param force One of the DISForceXxx constants.
 * @param cptr Here we return NULL if we are not interested to follow this
 * entity. Otherwise, here we set the craft or missile we associate to that
 * new remote entity.
 */
static void
disEntityEnterCb(int eid, dis_entity_type * etype, DISForce force, craft ** cptr)
{
	int       i, top, mtype;
	craftType *p;
	craft    *tbl;

	*cptr = NULL;

	/* Determines which table to insert into: */
	if (etype->kind == DISKindPlatform) {
		tbl = ptbl;
		top = manifest_MAXPLAYERS;
		mtype = CT_DIS_PLANE;
	}
	else if (etype->kind == DISKindMunition) {
		tbl = mtbl;
		top = manifest_MAXPROJECTILES;
		mtype = CT_DIS_MUNITION;
	}
	else {
		return;
	}
	
	/*
	 * If no picture available for the entity, try UFO instead.
	 */
	p = inventory_craftTypeSearchByEntityType(etype);
	if (p == NULL) {
		printf("entering entity %s not found in the inventory\n",
				dis_entityTypeToString(etype));
		p = inventory_craftTypeSearchByZoneAndName(NULL, "UFO");
	}
	if( p == NULL )
		return;

	/* Search a free entry: */
	craft *c = NULL;
	for (i = 0; i < top; ++i) {
		if (tbl[i].type == CT_FREE) {
			c = &tbl[i];
			break;
		}
	}
	if (c == NULL ){
		if( tbl == ptbl )
			fprintf(stderr, "Sorry, cannot store remote craft, table is full: %d\n", manifest_MAXPLAYERS);
		else
			fprintf(stderr, "Sorry, cannot store remote munition, table is full: %d\n", manifest_MAXPROJECTILES);
		return;
	}

	memory_zero(c);
	c->pIndex = i; /* here restores pIndex we just reset :-) */
	c->type = mtype;
	c->force = force;
	c->createTime = curTime;
	c->vl = NULL;
	c->disId = eid;
	c->cinfo = p;
	memory_strcpy(c->name, sizeof(c->name), "DIS");
	c->flags = 0;
	c->radarMode = RM_OFF;
	c->curRadarTarget = -1;
	if( c->type == CT_DIS_PLANE ){
		pm_hud_strings_alloc(c);
		gear_allocate(c);
		gear_up(c);
	}
	c->update = dis_if_updateRemote;
	c->kill = disEntityKill;
	
	*cptr = c;
}

/**
 * Detonation callback to be set in the dis_if module and invoked by the
 * dis_module when a DIS detonation PDU is received. Establishes the damage of
 * the target and possibly kills the target. Also creates an explosion effect.
 * @param ftype dis_if_FIRE_M61A1 or dis_if_FIRE_AIM9M.
 * @param firing Firing entity.
 * @param target Target craft.
 * @param time Ignored.
 * @param worldLocation Point of impact, that is where the missile where when
 *        it exploded.
 * @param entityLocation Location of the detonation in the reference system of
 * the target (m). Used for damage assessment of missiles and bombs.
 * @param munition The munition that hit the target (missile, bomb, cannon shell).
 * @param dpdu Detonation DIS PDU.
 */
static void
disDetonationCb(int ftype, 
				craft *firing, 
				craft *target, 
				double time, 
				double *worldLocation, 
				double *entityLocation, 
				craft * munition, 
				dis_detonation_pdu *dpdu)
{
	VPoint   Sg, rvel, tmp;
	double   exp_diameter, dist_meters, vel_meters_per_sec;
	char     reason[1000];

	Sg.x = worldLocation[0];
	Sg.y = worldLocation[1];
	Sg.z = worldLocation[2];

	/* If the target is a local player, damage him: */
	if (target != NULL && (target->type == CT_PLANE || target->type == CT_DRONE) ){
		
		/* impact distance from C.G. */
		dist_meters = VMagnitude((VPoint *)entityLocation);

		/* impact velocity */
		tmp.x = units_FEETtoMETERS(target->Cg.x);
		tmp.y = units_FEETtoMETERS(target->Cg.y);
		tmp.z = units_FEETtoMETERS(target->Cg.z);
		VReverseTransform_(&tmp, &target->XYZtoNED, &rvel);
		rvel.x = dpdu->vel.x - rvel.x;
		rvel.y = dpdu->vel.y - rvel.y;
		rvel.z = dpdu->vel.z - rvel.z;
		vel_meters_per_sec = VMagnitude(&rvel);
		if( damage_absorbDISDamage(target, 
							&dpdu->burst.munition,
							dpdu->burst.warhead, 
							dpdu->burst.fuze,
							dist_meters,
							vel_meters_per_sec,
							&exp_diameter) == 0) {
			snprintf(reason, sizeof(reason), "%s fired by %s",
				ftype == dis_if_FIRE_M61A1 ?
					"cannon shells" :
					"something (presumably a missile)",
				firing->name
			);
			target->kill(target, reason);
			
		} else {
			/*
			 * Ouch, damage_absorbDISDamage() not invoked, no explosion diameter.
			 * Here we have either to query the munition table, or set an
			 * arbitrary "average explosion diameter":
			 */
			exp_diameter = 10.0;
		}
	}
	
	/* Set a generic explosion effect: */
	effects_new_explosion(&Sg, &(VPoint){0.0, 0.0, 0.0}, exp_diameter, 15.0, 1.0);
}

static int
disInit(void)
{
	int       err;
	
	if( !(0 <= dis_exercise && dis_exercise <= 255) )
		error_external("exercise ID must be in [0,255], %d given", dis_exercise);
	if( !(-1 <= dis_site && dis_site <= 65535) )
		error_external("site ID must be in [-1,65535], %d given", dis_site);
	if( !(-1 <= dis_application && dis_application <= 65535) )
		error_external("application ID must be in [-1,65535], %d given", dis_application);

	err = dis_if_init(dis_relay_name, dis_relay_port,
		dis_exercise, dis_site, dis_application,
		disEntityEnterCb, disDetonationCb, m61a1_DISFire);
	dis_if_setDRThresholds(manifest_DIS_LOCATION_THRESHOLD, manifest_DIS_ORIENTATION_THRESHOLD);

	return err;
}


static void
help()
{
	printf("ACM %s\n", patchlevel_REVISION_STRING);
	printf(
"Copyright (C) 1991-1998  Riley Rainey (rrainey@ix.netcom.com)\n"
"Updated and modified by Umberto Salsi (salsi@icosaedro.it)\n"
"ACM comes with ABSOLUTELY NO WARRANTY.\n"
"This is free software, and you are welcome to distribute it under the\n"
"conditions described in the COPYING file.\n");
}


static list_Type *
read_switches_from_file(const char *fname)
{
	FILE *f;
	char commands[4096];
	char *argv[100];
	int  argc;
	int  n, err, i;
	list_Type *l;

	argv[0] = NULL;
	argc = 1;

	f = fopen (fname, "r");
	if( f == NULL ){
		fprintf(stderr, "ERROR: failed to read file `%s'\n", fname);
		return NULL;
	}
	n = fread(commands, 1, sizeof(commands) - 1, f);
	err = ferror(f);
	fclose(f);
	if( err != 0 ){
		fprintf(stderr, "ERROR: failed to read file `%s'\n", fname);
		return NULL;
	}
	if( n == sizeof(commands) - 1 ){
		fprintf(stderr, "ERROR: file `%s' too long\n", fname);
		return NULL;
	}

	if( n < 1 )
		return NULL;

	if( ! reader_split(commands, &argc, argv, 100) ){
		fprintf(stderr, "ERROR: too many arguments in file `%s'\n", fname);
		return NULL;
	}
	if( argc == 0 )
		return NULL;

	l = list_new();
	for( i = 0; i < argc; i++ )
		list_add_elem(l, argv[i]);
	return l;
}


static list_Type *
processCommandSwitches(list_Type *args)
{
	int i;
	char *a;
	list_Type *switches;

	switches = list_new();

	for (i = 0; i < args->n; ++i) {

		a = args->arr[i];

		if (strcmp(a, "-h") == 0 || strcmp(a, "-help") == 0
		|| strcmp(a, "--help") == 0 || strcmp(a, "-version") == 0
		|| strcmp(a, "-copyright") == 0) {
			help();
			exit(0);
		}
		else if (strcmp(a, "-objects") == 0 && ++i < args->n) {
			memory_dispose(objects);
			objects = memory_strdup(args->arr[i]);
		}
		else if (strcmp(a, "-no-dis") == 0) {
			/* 
			 * FIXME: the -no-dis option now useless.
			 * Networking is enabled if any -dis-* option is set.
			 */
			use_dis = 0;
		}
		else if (strcmp(a, "-dis-relay-name") == 0 && ++i < args->n) {
			memory_dispose(dis_relay_name);
			dis_relay_name = memory_strdup(args->arr[i]);
			use_dis = 1;
		}
		else if (strcmp(a, "-dis-relay-port") == 0 && ++i < args->n) {
			dis_relay_port = strtol(args->arr[i], (char **) NULL, 0);
			use_dis = 1;
		}
		else if (strcmp(a, "-dis-site") == 0 && ++i < args->n) {
			dis_site = strtol(args->arr[i], (char **) NULL, 0);
			use_dis = 1;
		}
		else if (strcmp(a, "-dis-appl") == 0 && ++i < args->n) {
			dis_application = strtol(args->arr[i], (char **) NULL, 0);
			use_dis = 1;
		}
		else if (strcmp(a, "-dis-exercise") == 0 && ++i < args->n) {
			dis_exercise = strtol(args->arr[i], (char **) NULL, 0);
			use_dis = 1;
		}
		else if (strcmp(a, "-dis-absolute-time") == 0) {
			dis_if_haveAbsoluteTime = 1;
			use_dis = 1;
		}
		else if (strncmp(a, "-dis", 4) == 0) {
			fprintf(stderr, "Acm DIS arguments:\n"
				" -dis-relay-name <host> (default: use broadcasting)\n"
				" -dis-relay-port <udp-port> (default: 3000)\n"
				" -dis-exercise <exercise number> (default %d)\n"
				" -dis-site <site number> (default %d)\n"
				" -dis-appl <application number> (default %d)\n"
				" -dis-absolute-time\n",
				dis_exercise, dis_site, dis_application);
		}
		else if (strcmp(a, "-init") == 0 && ++i < args->n) {
			list_Type *q;

		    q = read_switches_from_file(args->arr[i]);
			list_add_list(switches, q);
			memory_dispose(q);
		}
		else if (strcmp(a, "-arcade") == 0) {
			arcadeMode = 1;
		}
		else if (strcmp(a, "-da") == 0 && ++i < args->n) {
			drone_set_aggressiveness( atof(args->arr[i]) );
		}
		else if (strcmp(a, "-drone-mode") == 0 && ++i < args->n) {
			if( strcmp(args->arr[i], "DOG_FIGHT") == 0 )
				drone_set_mode(drone_DOG_FIGHT_MODE);
			else if( strcmp(args->arr[i], "HUNTING") == 0 )
				drone_set_mode(drone_HUNTING_MODE);
			else
				error_external("invalid argument for -drone-mode option, must be DOG_FIGHT or HUNTING: %s", args->arr[i]);
		}
		else if (strcmp(a, "-display") == 0 && ++i < args->n) {
			setenv("DISPLAY", args->arr[i], 1);
		}
		else if (strcmp(a, "-geometry") == 0 && ++i < args->n) {
			list_add_elem(switches, "-geometry");
			list_add_elem(switches, args->arr[i]);
		}
		else if (strcmp(a, "-eye_to_screen_cm") == 0 && ++i < args->n) {
			eye_to_screen_cm = atof(args->arr[i]);
		}
		else if (strcmp(a, "-downward_view_angle_deg") == 0 && ++i < args->n) {
			downward_view_angle_rad = units_DEGtoRAD( atof(args->arr[i]) );
		}
		else if (strcmp(a, "-js") == 0 && i+1 < args->n) {
			if (args->arr[i + 1][0] != '-') {
				joystick_setPort(args->arr[++i]);
			}
			else {
				joystick_setPort("/dev/cua0");
			}
		}
		else if (strcmp(a, "-name") == 0 && ++i < args->n) {
			memory_dispose(name);
			name = memory_strdup(args->arr[i]);
		}
		else if (strcmp(a, "-plane") == 0 && ++i < args->n) {
			list_add_elem(switches, "-plane");
			list_add_elem(switches, args->arr[i]);
		}
		else if (strcmp(a, "-fuel") == 0 && ++i < args->n) {
			list_add_elem(switches, "-fuel");
			list_add_elem(switches, args->arr[i]);
		}
		else if (strcmp(a, "-payload") == 0 && ++i < args->n) {
			list_add_elem(switches, "-payload");
			list_add_elem(switches, args->arr[i]);
		}
		else if (strcmp(a, "-stealth") == 0) {
			list_add_elem(switches, "-stealth");
		}
		else if (strcmp(a, "-end-game") == 0) {
			list_add_elem(switches, "-end-game");
		}
		else if (strcmp(a, "-threshold-range") == 0 && ++i < args->n) {
			double end_game_threshold_nm;

			end_game_threshold_nm = atof( args->arr[i] );
			if (end_game_threshold_nm < 1.0) {
				end_game_threshold_nm = 1.0;
			}
			end_game_threshold_meters = units_FEETtoMETERS(end_game_threshold_nm*units_NmToFeetFactor);
		}
		else if (strcmp(a, "-force") == 0 && ++i < args->n) {
			list_add_elem(switches, "-force");
			list_add_elem(switches, args->arr[i]);
		}
		else if (strcmp(a, "-departure-time") == 0 && ++i < args->n) {
			memory_dispose(departure_time);
			departure_time = memory_strdup(args->arr[i]);
		}
		else if (strcmp(a, "-latitude") == 0 && ++i < args->n) {
			list_add_elem(switches, "-latitude");
			list_add_elem(switches, args->arr[i]);
		}
		else if (strcmp(a, "-longitude") == 0 && ++i < args->n) {
			list_add_elem(switches, "-longitude");
			list_add_elem(switches, args->arr[i]);
		}
		else if (strcmp(a, "-altitude") == 0 && ++i < args->n) {
			list_add_elem(switches, "-altitude");
			list_add_elem(switches, args->arr[i]);
		}
		else if (strcmp(a, "-heading") == 0 && ++i < args->n) {
			list_add_elem(switches, "-heading");
			list_add_elem(switches, args->arr[i]);
		}
		else if (strcmp(a, "-airspeed-kt") == 0 && ++i < args->n) {
			list_add_elem(switches, "-airspeed-kt");
			list_add_elem(switches, args->arr[i]);
		}
		else if (strcmp(a, "-visibility") == 0 && ++i < args->n) {
			render_setVisibility( units_NMtoMETERS(atof(args->arr[i]) ) );
		}
		else if( strcmp(a, "-clouds-range") == 0 && i+2 < args->n ){
			render_setClouds( units_FEETtoMETERS(atof(args->arr[i+1])),
				units_FEETtoMETERS(atof(args->arr[i+2])));
			i += 2;
		}
		else if (strcmp(a, "-ground-mode") == 0 && ++i < args->n ){
			a = args->arr[i];
			if( strcmp(a, "flat") == 0 )
				render_setGroundDepth(render_GROUND_FLAT);
			else if( strcmp(a, "tiled") == 0 )
				render_setGroundDepth(render_GROUND_TILED);
			else
				error_external("invalid -render-ground-mode: must be flat or tiled, %s given", a);
		}
		else if (strcmp(a, "-wind") == 0 && ++i < args->n) {
			double wd, wv;
			if( sscanf(args->arr[i], "%lf/%lf", &wd, &wv) != 2 )
				error_external("invalid parameter for -wind. Expected DIRECTION/VELOCITY");
			air_set_wind(wd, wv);
		}
		else if (strcmp(a, "-gust") == 0 && ++i < args->n) {
			air_set_gust( atof(args->arr[i]) );
		}
		else if (strcmp(a, "-no-sound") == 0 ) {
			list_add_elem(switches, "-no-sound");
		}
		else if (strcmp(a, "-frame-rate") == 0 && ++i < args->n) {
		    frame_rate = atoi(args->arr[i]);
			if( frame_rate < 1 )
				frame_rate = 1;
	    }
		else if (strcmp(a, "-transfer-entity-mode") == 0 && ++i < args->n) {
			transferEntityIdBits = strtol ( args->arr[i], NULL, 0 );
		}
		else if (strcmp(a, "-subject-entity-id") == 0 && ++i < args->n) {
			dis_entity_id id;
			if (dis_parseEntityID ( &id, args->arr[i], 
								   strlen(args->arr[i])+1, ":/." ) == 0) {
				subjectEntityID = id;
				subjectEntitySpecified = 1;
			}
			else {
				error_external("invalid entity ID \"%s\"", args->arr[i]);
			}
		}
		else if (strcmp(a, "-mouse-mode") == 0 && ++i < args->n) {
			a = args->arr[i];
			if( strcmp(a, "fast") == 0 )
				mouse_stick_mode = mouse_FAST;
			else if( strcmp(a, "normal") == 0 )
				mouse_stick_mode = mouse_NORMAL;
			else if( strcmp(a, "precise") == 0 )
				mouse_stick_mode = mouse_PRECISE;
			else 
				error_external("invalid mouse mode \"%s\", must be fast|normal|precise", a);
		}
		else if (strcmp(a, "-hud-mode") == 0 ) {
			list_add_elem(switches, "-hud-mode");
		}
		else {
			error_external("unknown command line option `%s' or missing mandatory value of the option", args->arr[i]);
		}
	}
	
	return switches;
}


int
main(int argc, char **argv)
{
	list_Type      *args, *newPlayerSwitches;
	int       i;
	
	error_init("ACM-" patchlevel_REVISION_STRING);

	curTime = 0.0;
	mouse_stick_mode = mouse_NORMAL;
	eye_to_screen_cm = 50.0;
	downward_view_angle_rad = units_DEGtoRAD(15.0);

	/*
	 * An endGameThreshold of -1.0 means "use the radar lock range for
	 * the current aircraft.
	 */

	end_game_threshold_meters = -1.0;
	end_game_mode = 0;

	/*
	 *  When accepting control of an entity, the default is to use our
	 *  site ID and keep everything else the same.
	 */

	transferEntityIdBits = 0x4;

	dis_if_haveAbsoluteTime = 0;

	ptblCount = ctblCount = 0;

	/* Parse command line arguments. */
	args = list_new();
	for ( i = 1; i < argc; i++ )
		list_add_elem(args, argv[i]);
	newPlayerSwitches = processCommandSwitches(args);
	memory_dispose(args);
	if( name == NULL )
		name = memory_strdup("Anonymous");
	
	/* Init DIS module interface. */
	/* Define handler for DIS transfer control requests. */
	dis_if_setTransferControlRequestCallback ( dis_if_transferControlRequestHandler );
	dis_if_enableNetwork(use_dis);
	if( disInit() != 0 )
		error_external("DIS protocol initialization failed");
	
	/* Init ACM global variables. */
	init_init(objects, departure_time);
	
	/* Create 1 player. */
	if( ! players_new(name, newPlayerSwitches) == 0 )
		return 1;

	/* Do simulation until player dies or quits. */
	update_loop(frame_rate);
	
	/* Release all global stuff. */
	init_term();

	memory_dispose(newPlayerSwitches);
	memory_dispose(name);
	memory_dispose(objects);
	memory_dispose(dis_relay_name);
	memory_dispose(departure_time);
	return memory_report();
}


