/*
 *  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 <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "../util/error.h"
#include "../util/memory.h"
#include "../util/units.h"
#include "../V/Vlib.h"
#include "sounds.h"
#include "box.h"
#include "browse.h"
#include "dis_if.h"
#include "drone.h"
#include "effects.h"
#include "flaps.h"
#include "gear.h"
#include "hsi.h"
#include "hud.h"
#include "instruments.h"
#include "inventory.h"
#include "list.h"
#include "magnetic_compass.h"
#include "patchlevel.h"
#include "planes.h"
#include "pm.h"
#include "prompt.h"
#include "render.h"
#include "terrain.h"
#include "update.h"
#include "viewer.h"
#include "weapon.h"
#include "windows.h"

#define players_IMPORT
#include "players.h"

#define ARG_BORDER_COLOR        "bordercolor"
#define ARG_BORDER              "borderwidth"
#define ARG_GEOMETRY            "geometry"
#define DEFAULT_BACKGROUND      "#7c99b6"
#define DEFAULT_BORDER          "black"


#define SW_BACKGROUND	  2
#define SW_GEOM		      4
#define SW_FORCE		  6

#define SW_PLANE	      9
#define SW_LIST_PLAYER	 10

#define SW_HUD_MODE      12
#define SW_PASSIVE       13
#define SW_LATITUDE      14
#define SW_LONGITUDE     15
#define SW_ALTITUDE      16
#define SW_AIRSPEED_KT   17
#define SW_HEADING       18
#define SW_END_GAME      19
#define SW_NO_SOUND      20
#define SW_FUEL          21
#define SW_PAYLOAD       22
#define SW_LAST 22             /* last entry in list */


static struct {
	char     *sw;
	int       value;
} swt[] = {
	{"-skycolor",  SW_BACKGROUND},
	{"-geometry",  SW_GEOM},
	{"-force",     SW_FORCE},
	{"-stealth",   SW_PASSIVE},
	{"-plane",     SW_PLANE},
	{"-list",      SW_LIST_PLAYER},
	{"-latitude",  SW_LATITUDE},
	{"-longitude", SW_LONGITUDE},
	{"-altitude",  SW_ALTITUDE},
	{"-airspeed-kt", SW_AIRSPEED_KT},
	{"-heading",   SW_HEADING},
	{"-end-game",  SW_END_GAME},
	{"-no-sound",  SW_NO_SOUND},
	{"-fuel",      SW_FUEL},
	{"-payload",   SW_PAYLOAD},
	{"-hud-mode",   SW_HUD_MODE},
	{NULL, 0}
}, *swp;


/**
 * Internal state update call-back, normally set in the craft->update field.
 * @param c Subject craft.
 * @return Reason why this craft should be killed. Normally NULL.
 */
static char *players_update(craft *c)
{
	char *killReason = pm_flightCalculations(c);
	if (killReason != NULL)
		return killReason;
	magnetic_compass_update(c->vl);
	hsi_update(c->vl);
	weapon_update(c);
	flaps_update(c);
	dis_if_updateLocal(c);
	return NULL;
}


void
players_kill(craft *c, char *reason)
{

	viewer   *v;
	int       i;
	VPoint	 vel = { 0, 0, 0 };

/*
 *  Notify to all the viewers the reason why this craft is dead
 */

	if( (reason != NULL)
	&& (c->type == CT_PLANE || c->type == CT_DRONE || c->type == CT_DIS_PLANE )
	){
		char s[1000];
		snprintf(s, sizeof(s), "%s %s force %s: %s",
			c->name, c->cinfo->name, dis_forceToString(c->force), reason);
		prompt_broadcast_print(s);

		/* The owner of the window can see the message, use its terminal: */
		if( c->type == CT_PLANE )
			printf("%s\n", s);
	}

/*
 *  Decrement the player count, iff this is a real person that just got
 *  killed.
 */

	if (c->type == CT_PLANE && (c->flags & FL_BLACK_BOX) == 0) {
		--ptblCount;
	}

/*
 *  Erase our radar emissions
 */

	for (i = 0; i < manifest_MAXPLAYERS; ++i){
		ptbl[i].rval[c->pIndex] = 0.0;
	}

/*
 *  No need to inform the others crafts and missiles we died: the
 *  dis_if module will take care to update c->curRadarTarget field.
 */

/*
 *  Replace the plane with an explosion.
 */

	effects_new_explosion(&(c->Sg), &vel, 30.0, 15.0, 4.0);

/*
 *  Release services tied to the craft
 */

 	memory_dispose(c->aps);
	gear_free(c);

	drone_release_commands(c);

 	pm_hud_strings_free(c);  /* FIXME: HUD data should go in hud.c */

/*
 *  Close all the viewers tied to this craft
 */

	while( c->vl != NULL ){

		v = c->vl;

		/*
		 *  If this was a situation where we had grabbed control in steath
		 *  mode and then died, return to the browsing state.
		 */

		if ( v->viewer_state == ViewerStateNormal
		&& v->watchedCraft != NULL
		&& v->watchedCraft->type == CT_DIS_STEALTH
		) {
			printf ("player killed: returning to stealth browsing mode\n");
			v->viewer_state = ViewerStateBrowsing;

			/*
				FIXME: the current status of the viewer does not match
				that of the watched craft, we should release the services
				(audio, terminal, etc. as in viewer_free()) and then
				initialize them properly.
			*/
			v->c = v->watchedCraft;

			v->watchedCraft = NULL;
			v = NULL;
		}
		else {
			/* vn = c->vl; */
		}

		viewer_free(v);
	}

	if (c->flags & FL_RECORD) {
		--recordCount;
	}

	if (c->flags & FL_BLACK_BOX)
		box_killPlayer(c->pIndex);

	dis_if_entityExit(c->disId);

	c->type = CT_FREE;
}


static void parseGeometry(char *geometry, int *width, int *height)
{
	int w, h;
	*width = 400;
	*height = 300;
	if( geometry == NULL )
		return;
	if( sscanf(geometry, "%dx%d", &w, &h) < 2 || w < 1 || h < 1 ){
		fprintf(stderr, "invalid geometry string: %s\n", geometry);
		return;
	};
	*width = w;
	*height = h;
}


int players_new(char *logname, list_Type *switches)
{
	int       argc;
	char    **argv;
	char     *geomSpec;			/* Window geometry string */
	char     *c;
	static char *background = NULL;
	int       player;
	viewer   *u;
	craft    *cf;
	int       i;
	DISForce  force = DISForceOther;
	char     *plane = "C-172";  /* name of plane type */
	int       width, height;    /* size of the main window */
	int       passive = 0;
	_BOOL     hud_mode;
	int       overrides[SW_LAST+1];
	char     *overrideLatitude = NULL;
	char     *overrideLongitude = NULL;
	double    overrideAltitude = 0.0,
	          overrideHeading_rad = 0.0, overrideAirspeed_fps = 0.0,
	          overrideFuel = 0.0,
			  overridePayload = 0.0;
	int       end_game = 0;
	int       no_sound = 0;
	double    disLocation[3];
	double    disZeroVec[3];
	double    disOrientation[3];

/*
 *  Parse command line
 */

	memset(overrides, 0, sizeof(overrides));
	geomSpec = NULL;
	hud_mode = FALSE;

 	if( switches == NULL ){
		argc = 0;
		argv = NULL;  /* make happy gcc -Wall */
	} else {
		argc = switches->n;
		argv = switches->arr;
	}

	for( i=0; i<argc; i++ ){
		c = argv[i];
		if( *c == '-' ){
			for (swp = &swt[0]; swp->value != 0; ++swp) {
				if (strcmp(swp->sw, c) == 0) {

					switch (swp->value) {

					case SW_GEOM:
						i++;
						if( i < argc )
							geomSpec = argv[i];
						break;

					case SW_END_GAME:
						end_game = 1;
						break;

/*
					case SW_BORDER:
						i++;
						if( i < argc )
							borderWidth = atoi( argv[i] );
						break;
*/

					case SW_BACKGROUND:
						i++;
						if( i < argc )
							background = argv[i];
						break;

					case SW_FORCE:
						i++;
						if( i >= argc )
							error_external("missing argument for -force");
						force = dis_parseForce(argv[i]);
						if( force < 0 )
							error_external("unknown DIS force: %s", argv[i]);
						break;

					case SW_PASSIVE:
					    passive = 1;
						break;

					case SW_PLANE:
						i++;
						if( i < argc )
							plane = argv[i];
						break;

					case SW_LATITUDE:
						i++;
						if( i < argc ){
							overrideLatitude = argv[i];
							overrides[SW_LATITUDE] = 1;
						}
						break;

					case SW_LONGITUDE:
						i++;
						if( i < argc ){
							overrideLongitude = argv[i];
							overrides[SW_LONGITUDE] = 1;
						}
						break;

					case SW_ALTITUDE:
						i++;
						if( i < argc ){
							overrideAltitude = units_FEETtoMETERS(atof( argv[i] ));
							overrides[SW_ALTITUDE] = 1;
						}
						break;

					case SW_AIRSPEED_KT:
						i++;
						if( i < argc ){
							overrideAirspeed_fps = units_KTtoFPS(atof( argv[i] ));
							if (overrideAirspeed_fps > units_KTtoFPS(2500.0)) {
								printf ("You really should slow down.\n");
								printf ("At least to less than 2500 knots.\n");
							}
							overrides[SW_AIRSPEED_KT] = 1;
						}
						break;

					case SW_HEADING:
						i++;
						if( i < argc ){
							overrideHeading_rad = units_DEGtoRAD(atof( argv[i] ));
							overrides[SW_HEADING] = 1;
						}
						break;

					case SW_LIST_PLAYER: {
						printf("\nname\t\tnumber\n");
						printf("-------------------------\n");
						int i;
						for (i = 0; i < manifest_MAXPLAYERS; i++) {
							if (ptbl[i].type == CT_PLANE) {
								printf("%-16s  %d\n", ptbl[i].name, i);
							}
						}
						return -1;
					}

/*
					case SW_DEFAULT_VISUAL:
						useDefaultVisual = 0;
						break;
*/

					case SW_NO_SOUND:
						no_sound = 1;
						break;

					case SW_FUEL:
						i++;
						if( i < argc ){
							overrideFuel = atof( argv[i] );
							overrides[SW_FUEL] = 1;
						}
						break;

					case SW_PAYLOAD:
						i++;
						if( i < argc ){
							overridePayload = atof( argv[i] );
							overrides[SW_PAYLOAD] = 1;
						}
						break;

					case SW_HUD_MODE:
						hud_mode = TRUE;
						break;

					default:
						error_internal("swp->value=%d", swp->value);
					}
					break;
				}
			}

			if (swp->value == 0) {
				error_external("invalid switch %s", argv[i]);
				return -1;
			}
		}
	}

	player = planes_newPlane(plane);

	if (player < 0) {
		if (player == -1) {
			printf("Sorry, no room for any more players at this moment.\n");
		}
		else {
			printf("You have selected an unknown plane type. Choose one among these types:\n");
			inventory_printValidAircraft();
		}
		return -1;
	}

	cf = &ptbl[player];

	/*
	 * Set fuel and payload
	 */

	if (overrides[SW_FUEL]) {
		if (overrideFuel > cf->cinfo->maxFuel) {
			printf("Too much fuel for this plane. Maximum is %.0f lb.\n",
				cf->cinfo->maxFuel);
			cf->fuel = cf->cinfo->maxFuel;
		} else {
			cf->fuel = overrideFuel;
		}
	} else {
		cf->fuel = cf->cinfo->maxFuel;
	}

	if (overrides[SW_PAYLOAD]) {
		cf->payload = overridePayload;
	} else {
		cf->payload = 150.0;  /* weight of the pilot (lb) */
	}

	// Default initial position: 0N 0E, sea level!
	cf->w = (earth_LatLonAlt){0,0,0};

	// Set position according to command line parameters:
	if ( overrides[SW_LATITUDE]
	&& ! earth_parseLatitude(overrideLatitude, &cf->w.latitude) )
			error_external("invalid starting latitude: ", overrideLatitude);

	if ( overrides[SW_LONGITUDE]
	&& ! earth_parseLongitude(overrideLongitude, &cf->w.longitude) )
			error_external("invalid starting latitude: ", overrideLatitude);
	
	/* Forces terrain altitude recalculation: */
	cf->terrain_altitude_timeout = 0.0;

	earth_LatLonAltToXYZ(&cf->w, &cf->Sg);
	
	/*
	 * Forces loading of the scenery below the aircraft. If the starting zone
	 * defines our force base location, retrieve that location for the resupply
	 * procedure.
	 */
	cf->zone = zones_load(zones, &cf->w, NULL, 1);
	if( cf->zone != NULL && zone_isLoaded(cf->zone) )
		forceBaseLocation[cf->force] = *zone_getForceBaseLocation(cf->zone, cf->force);
		
	if (overrides[SW_ALTITUDE]) {
		cf->w.z = overrideAltitude;
		cf->curPitch = units_DEGtoRAD(2.0);  /* ensure some initial lift */
		gear_up(cf);
	} else {
		double h;
		gear_ground_altitude_pitch(cf, &h, &cf->curPitch);
		/* WARNING. terrain_local_altitude() requires cf->Sg and zone be already set. */
		cf->w.z = terrain_localAltitude(cf) + units_FEETtoMETERS(h);
		earth_LatLonAltToXYZ(&cf->w, &cf->Sg);
		gear_down(cf);
	}

	// Set magnetic variation based on the actual position.
	cf->updActualMagneticField = curTime - 1.0;  /* force VAR recalculation */
	/* ignore = */ pm_mag_heading(cf); /* set cf->actualLocalVAR */
	cf->indicatedLocalVAR = cf->actualLocalVAR;
	cf->updIndicatedMagneticField = curTime + 2.0;
	
	cf->curHeading = 0;
	if( overrides[SW_HEADING] )
		cf->curHeading = pm_normalize_yaw( overrideHeading_rad - cf->actualLocalVAR );
	
	cf->prevSg = cf->Sg;
	cf->curRoll = 0.0;
	cf->r = cf->q = cf->p = 0.0;

	// Generate trihedral and world-to-aircraft matrices
	VEulerToMatrix(cf->curRoll, cf->curPitch, cf->curHeading, &(cf->trihedral));
	earth_generateWorldToLocalMatrix(&cf->w, &cf->XYZtoNED);

	// Set airspeed.
	if (overrides[SW_AIRSPEED_KT]) {
		VMatrix turn;
		VPoint v = { overrideAirspeed_fps, 0.0, 0.0 };
		VIdentMatrix(&turn);
		VRotate(&turn, ZRotation, cf->curHeading);
		VTransform_(&v, &turn, &cf->Cg );
	} else {
		VSetPoint(&cf->Cg, 0, 0, 0);
	}
	
	cf->update = players_update;
	cf->kill   = players_kill;

	// Open a new viewer.

	cf->vl = NULL;

	u = viewer_new(cf);

	u->hud_mode = hud_mode;

	/*
	 *  If we're passive (stealth mode), wipe out aircraft information;
	 *  this entry will become a placeholder for our browsing view.
	 */

	if ( passive ) {

		u->viewer_state = ViewerStateBrowsing;
		cf->type = CT_DIS_STEALTH;
		cf->cinfo = NULL;
		cf->radarMode = RM_DIS_BROWSE;

		if ( end_game ) {
			end_game_mode = 1;
		}

		/*
		 *  Stealth a specific entity?  Go ahead and set it as the subject.
		 *
		 *  This requires snooping for entities in the PDU stream prior
		 *  to looking for the entity in the table.
		 */

		if ( subjectEntitySpecified ) {
			dis_if_Entity *e;
			craft *c;

			printf ("Building entity database ... ");
			fflush ( stdout );

			update_simulationTime ();
			dis_if_snoop ( 5500 );

			printf ("done.\n");

			e = dis_if_findEntityByDISID( & subjectEntityID );
			if( e == NULL )
				c = e->c;
			else
				c = NULL;
			if ( c ) {

				browse_stealthCraft ( c, u, -1, 1);

			}
		}

	}
	else {

		if ( end_game ) {
			printf("The -end-game switch is only valid when used " );
			printf("with stealth mode.\n" );
			printf("The switch will be ignored.\n\n" );
		}

		if ( subjectEntitySpecified ) {
			printf("The -subject-entity-id switch is only valid when used " );
			printf("with stealth mode.\n" );
			printf("The switch will be ignored.\n\n" );
		}

	}
	
	parseGeometry(geomSpec, &width, &height);
	u->gui = gui_new("ACM-" patchlevel_REVISION_STRING, width, height);

	render_setOutsideView(u->c, u, render_VIEW_FORWARD);

	if (background == NULL)
		background = DEFAULT_BACKGROUND;

	cf->force = force;

	memory_strcpy(cf->name, sizeof(cf->name), logname);
	
	sounds_enable(cf, ! no_sound);

	u->w = Alib_new(u->gui);

	Alib_setDepthCueing(u->w, 1);

	windows_set_layout(cf, u, width, height, hud_mode);

	if( hud_mode ){
		hud_enable(u);
	} else {
		instruments_enable(u);
	}

	++ptblCount;

	/*
	 *  Transmit initial DIS entity state PDU
	 */
	
	if ( cf->type != CT_DIS_STEALTH ) {
		earth_LatLonAltToXYZ(&cf->w, (VPoint *) disLocation);
		disZeroVec[0] = 0.0;
		disZeroVec[1] = 0.0;
		disZeroVec[2] = 0.0;
		disOrientation[0] = cf->curHeading;
		disOrientation[1] = 0.0;
		disOrientation[2] = 0.0;

		dis_if_entityEnter(force, cf,
				&cf->cinfo->entityType,
				&cf->cinfo->altEntityType,
				disLocation, disZeroVec, disZeroVec,
				disOrientation, disZeroVec, &cf->disId);
	}

	cf->showMag = TRUE;

	magnetic_compass_enable(u);

	cf->radarMode = RM_HSI;
	hsi_enable(u);

	return 0;
}
