/*
 *  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 <errno.h>
#include <time.h>

#include "../util/error.h"
#include "adf.h"
#include "aps.h"
#include "box.h"
#include "browse.h"
#include "commands.h"
#include "dis_if.h"
#include "drone.h"
#include "flaps.h"
#include "gear.h"
#include "hud.h"
#include "hsi.h"
#include "instruments.h"
#include "joystick.h"
#include "mouse.h"
#include "pm.h"
#include "prompt.h"
#include "radar.h"
#include "render.h"
#include "sounds.h"
#include "terminal.h"
#include "weapon.h"
#include "windows.h"

#define events_IMPORT
#include "events.h"

#define MAX_MAPPED_STRING_LEN	20
#define MAX_POPUP_STRING_LEN	40

#define FLIGHT_STATE_FILE "./acm-flight-state"

/*
	To prevent unwanted quits from the program, it is requested to
	press SHIFT-Q two times within 2 seconds.  Anyway, the user can
	still close the window. Here we store the last time the SHIFT-Q
	was pressed:
*/
static double pressed_XK_Q_time = -5.0;


static void
doButtonEvent(craft * c, viewer * u, gui_Event * ev)
{
	if ( ! u->hasFocus | ! u->hasComm )  return;

	if (ev->code == gui_EVENT_LBUTTONDOWN) {
		weapon_fire(c);
	}
	else if (ev->code == gui_EVENT_MBUTTONDOWN) {
		browse_selectCockpitItem( c, u, 
						   ev->x, 
						   ev->y, 
						   time(NULL) );
	}
	else if (ev->code == gui_EVENT_RBUTTONDOWN) {
		weapon_selectNextAvailable(c);
	}
}


static void
doButtonReleaseEvent(craft * c, viewer * u, gui_Event * ev)
{

	if (ev->code == gui_EVENT_LBUTTONUP)
		weapon_ceaseFire(c);
}


/*
	Handle key press event. Returns TRUE if player gets killed or player
	asked to terminate the program.
*/
static _BOOL
doKeyEvent(craft * c, viewer * u, int key)
{

	if ( ! u->hasFocus ) {
		return FALSE;
	}

	if ( ! u->hasComm ){
		if ( key == 'd' )
			u->hasComm = TRUE;
		return FALSE;
	}

	if ( terminal_enabled(u) && (key != gui_KEY_F(1)) ){
		terminal_input_char(u, key);
		{
			char line[100];
			if( terminal_read_string(u, line, 100) )
				commands_execute(u, line);
		}
		return FALSE;
	}

	switch (key) {

	case gui_KEY_F(1):
		terminal_toggle(u);
		break;

	case 'd':
		u->hasComm = FALSE;
		break;

	case 'H':
		windows_set_layout(c, u, gui_getWidth(u->gui), gui_getHeight(u->gui), !u->hud_mode);
		break;

	case 'A':
		aps_ap_toggle(c);
		break;

	case 'E':
		aps_rate_control_toggle(c);
		break;

	case 'Z':
		aps_ap_toggle(c);
		if( aps_ap_enabled(c) )
			aps_ap_set_vs(c, 0.0);
		break;

	case 'X':
		aps_ac_toggle(c);
		break;

	case 'T':
		aps_at_toggle(c);
		break;

	case 'N':
		aps_an_toggle(c);
		break;

	case 'L':
		aps_al_toggle(c);
		break;

	case '(':
		aps_bank_max_dec(c);
		break;

	case ')':
		aps_bank_max_inc(c);
		break;

	case 'M':
		/* Toggle MH<->TH display */
		c->showMag = ! c->showMag;
		break;

	case '<':
		aps_an_disable(c);
		aps_al_disable(c);
		aps_aw_set(c, units_DEGtoRAD(-3.0));
		break;

	case '>':
		aps_an_disable(c);
		aps_al_disable(c);
		aps_aw_set(c, units_DEGtoRAD(3.0));
		break;

	case ',':
		aps_an_disable(c);
		aps_al_disable(c);
		aps_aw_set(c, units_DEGtoRAD(-1.5));
		break;

	case '.':
		aps_an_disable(c);
		aps_al_disable(c);
		aps_aw_set(c, units_DEGtoRAD(1.5));
		break;

	case '|':
		aps_an_disable(c);
		aps_al_disable(c);
		aps_aw_set(c, units_DEGtoRAD(0.0));
		break;

	case '/':
		aps_an_disable(c);
		aps_al_disable(c);
		aps_aw_disable(c);
		break;
	
	case '\\':
		dis_if_sendMessage(c->disId, "sending message to all the participants through DIS");
		break;


	case gui_KEY_UP:
		c->pitchComm = c->pitchComm + 0.01;
		if ( c->pitchComm > 1.0 )  c->pitchComm = 1.0;
		break;

	case gui_KEY_DOWN:
		c->pitchComm = c->pitchComm - 0.01;
		if ( c->pitchComm < -1.0 )  c->pitchComm = -1.0;
		break;

	case 'z':
		c->rudderComm = c->rudderComm + 0.01;
		if ( c->rudderComm > 1.0 )  c->rudderComm = 1.0;
		break;

	case 'c':
		c->rudderComm = c->rudderComm - 0.01;
		if ( c->rudderComm < -1.0 )  c->rudderComm = -1.0;
		break;

	case 'x':
		c->rudderComm = 0.0;
		break;

	case gui_KEY_HOME:
		aps_off(c);
		c->SeTrim = 0.0;
		c->SaTrim = 0.0;
		break;

	case 'j':
		/* Adjust Elevator Trim */
		c->SeTrim -= c->pitchComm;
		break;

	case 'y':
		flaps_up(c);
		break;

	case 'h':
		flaps_down(c);
		break;

	case 'w':
		flaps_speed_brakes_retract(c);
		break;

	case 's':
		flaps_speed_brakes_extend(c);
		break;

	case 't':
		if( u->hud_mode )
			hud_timer_toggle(u);
		else
			instruments_timer_toggle(u);
		break;

	case '1':
	case gui_KEY_PGDW:

		aps_al_disable(c);
		aps_an_disable(c);
		aps_ap_disable(c);
		aps_at_disable(c);
		aps_aw_disable(c);

		c->throttleComm = 0.20 * 32768.0;
		break;

	case '2':
	case gui_KEY_PAD_MINUS:
		if ( aps_at_enabled(c) ) {
			aps_at_dec_velocity(c);
		}
		else {
			c->throttleComm -= 512;
			if ( c->throttleComm < 6553 )
				c->throttleComm = 6553;
		}
		break;

	case '3':
	case gui_KEY_PAD_PLUS:
		if ( aps_at_enabled(c) ) {
			aps_at_inc_velocity(c);
		}
		else {
			c->throttleComm += 512;
			if ( c->throttleComm >= 32768 )
				c->throttleComm = 32768;
		}
		break;

	case '4':
	case gui_KEY_PGUP:

		aps_al_disable(c);
		aps_an_disable(c);
		aps_at_disable(c);
		aps_ap_disable(c);
		aps_aw_disable(c);

		c->throttleComm = 32768;
		break;
	
	case '!':
		pm_thrust_reverse_toggle(c);
		break;

	case 'a':
		pm_after_burner_toggle(c);
		break;
	
	case ' ':
		/* Switch NAVn/RNAVn receivers: */
		aps_al_disable(c);
		aps_an_disable(c);
		hsi_switch_mode(u);
		break;

	case '7':
		aps_an_disable(c);
		hsi_obs_inc(u, -1);
		adf_hdg_inc(u, -1);
		break;

	case '8':
		aps_an_disable(c);
		hsi_obs_inc(u, +1);
		adf_hdg_inc(u, +1);
		break;

	case '9':
		aps_al_disable(c);
		aps_an_disable(c);
		hsi_frq_inc(u, -1);
		adf_frq_inc(u, -1);
		break;

	case '0':
		aps_al_disable(c);
		aps_an_disable(c);
		hsi_frq_inc(u, +1);
		adf_frq_inc(u, +1);
		break;

	case gui_KEY_F(3):
		hsi_theta_inc(u, -1);
		break;

	case gui_KEY_F(4):
		hsi_theta_inc(u, +1);
		break;

	case gui_KEY_F(5):
		hsi_rho_inc(u, -1);
		break;

	case gui_KEY_F(6):
		hsi_rho_inc(u, +1);
		break;

	case 'b':
		if( gear_brakesEngaged(c) )
			gear_brakesDisengage(c);
		else
			gear_brakesEngage(c);
		break;

	case 'g':
		gear_handle_toggle(c);
		break;

	case 'l':
		drone_new(c);
		break;

	case gui_KEY_PAD_8:
		render_setOutsideView(c, u, render_VIEW_FORWARD);
		prompt_viewer_print(u, "Front view");
		break;

/* look right */

	case gui_KEY_PAD_6:
		render_setOutsideView(c, u, render_VIEW_RIGHT);
		prompt_viewer_print(u, "Right view");
		break;

/* look left */

	case gui_KEY_PAD_4:
		render_setOutsideView(c, u, render_VIEW_LEFT);
		prompt_viewer_print(u, "Left view");
		break;

/* look back */

	case gui_KEY_PAD_2:
		render_setOutsideView(c, u, render_VIEW_AFT);
		prompt_viewer_print(u, "Rear view");
		break;

/* look up */

	case gui_KEY_PAD_5:
		render_setOutsideView(c, u, render_VIEW_UP);
		prompt_viewer_print(u, "Up view");
		break;

	case gui_KEY_PAD_0:
		render_setOutsideView(c, u, render_VIEW_DOWN);
		prompt_viewer_print(u, "Down view");
		break;

	case 'n':
		if( c->flags & FL_CHASE_VIEW ){
			render_setOutsideView(c, u, render_VIEW_FORWARD);
			prompt_viewer_print(u, "Front view");

		} else if( c->cinfo->object == NULL ){
			prompt_viewer_print(u, "Sorry, no object model available for this plane.");

		} else {
			render_setOutsideView(c, u, render_VIEW_CHASE);
			prompt_viewer_print(u, "Chase view");

		}
		break;

	case 'q':
		c->curRadarTarget = radar_getNewTarget(c);
		dis_if_radarTargetChanged(c);
		break;

	case 'D':
		/* Debug */
		FSPageToggle();
		break;

	case 'r':
		aps_al_disable(c);
		aps_an_disable(c);

		switch (c->radarMode) {

		case RM_OFF:
		case RM_STANDBY:
			if( u->c->cinfo->radarOutput > 0.0 ){
				/* we really have a radar -- enable it */
				radar_setMode(c, RM_NORMAL);
			} else {
				/* no radar -- HSI mode */
				radar_setMode(c, RM_HSI);
				hsi_enable(u);
			}
			break;

		case RM_NORMAL:
		case RM_ACM:
		case RM_STT:
			radar_setMode(c, RM_HSI);
			hsi_enable(u);
			break;

		case RM_HSI:
			hsi_disable(u);
			radar_setMode(c, RM_ADF);
			adf_enable(u);
			break;

		case RM_ADF:
			adf_disable(u);
			radar_setMode(c, RM_OFF);
			break;

		default:
			adf_disable(u);
			radar_setMode(c, RM_OFF);
			hsi_enable(u);
			break;
		}

		break;

/* this is a hack to allow change of beam parameter index
in the outgoing electromagnetic emission pdu's
 */
	case 'R':
		/* Cycle through radar modes: */
		hsi_disable(u);
		adf_disable(u);
		aps_al_disable(c);
		aps_an_disable(c);
		switch (c->radarMode) {
		case RM_OFF:
		case RM_HSI:
		case RM_ADF:
		case RM_STANDBY:
			radar_setMode(c, RM_NORMAL);
			break;
		case RM_NORMAL:
			radar_setMode(c, RM_ACM);
			break;
		case RM_ACM:
			radar_setMode(c, RM_STT);
			break;
		case RM_STT:
			radar_setMode(c, RM_STANDBY);
			break;
		default:
			error_internal("c->radarMode=%d", c->radarMode);
		}
		break;

#ifdef FIXME_INCOMPLETE_OR_BUGGED

	case XK_i:
		fp = fopen(FLIGHT_STATE_FILE, "w");
		if ( fp == NULL ) {
			char s[1000];

			sprintf("Error opening the file " FLIGHT_STATE_FILE
				" for writing: %s", strerror(errno));
			prompt_craft_print(c, s);
		}
		else {
			fwrite((char *) &ptbl[c->pIndex],
				sizeof(craft), 1, fp);
			fclose(fp);
			prompt_craft_print(c, "Flight state saved in file "
				FLIGHT_STATE_FILE);
		}
		break;

	case XK_I:
		fp = fopen(FLIGHT_STATE_FILE, "r");
		if ( fp == NULL ) {
			char s[1000];

			sprintf("Error opening the file " FLIGHT_STATE_FILE
				" for reading: %s", strerror(errno));
			prompt_craft_print(c, s);
		}
		else {
			fread((char *) &pentry, sizeof(craft), 1, fp);
			fclose(fp);

			c->flags = pentry.flags;
			c->Sg = pentry.Sg;
			c->prevSg = pentry.prevSg;
			c->Cg = pentry.Cg;
			c->trihedral = pentry.trihedral;
			c->curHeading = pentry.curHeading;
			c->curPitch = pentry.curPitch;
			c->curRoll = pentry.curRoll;
			c->p = pentry.p;
			c->q = pentry.q;
			c->r = pentry.r;
			c->damageBits = pentry.damageBits;
			c->structurePts = pentry.structurePts;
			c->damageCL = pentry.damageCL;
			c->damageCM = pentry.damageCM;
			c->curThrust = pentry.curThrust;
			c->throttle = pentry.throttle;

			/* FIXME: there are missing fields */

			prompt_craft_print(c, "Flight state restored from file "
				FLIGHT_STATE_FILE);
		}
		break;

	case XK_semicolon:
		debug ^= 1;
		break;
#endif /* FIXME_INCOMPLETE_OR_BUGGED */

	case 27:
		prompt_viewer_print(u, "To exit the program, press SHIFT-Q two times");
		break;
		
	case 'Q':
		if ( pressed_XK_Q_time + 2.0 >= curTime ){
			c->kill(c, NULL);
			return TRUE;
		}
		else {
			pressed_XK_Q_time = curTime;
			prompt_viewer_print(u, "Press SHIFT-Q again within 2 sec. to exit the program");
		}
		break;

#ifndef WINNT
	case 'P':
		system("xwd -name " manifest_ACM " -out /tmp/acm-dump-`date +%s`");
		prompt_viewer_print(u, "Screen dumped in file /tmp/acm-dump-*");
		break;
#endif

#ifdef FIXME_INCOMPLETE_OR_BUGGED

	case XK_braceleft:
		box_startRecording();
		prompt_viewer_print(u, "Black box recording started");
		break;

	case XK_braceright:
		box_endRecording();
		prompt_viewer_print(u, "Black box recording stopped");
		break;

	case XK_bracketleft:
		box_startPlayback();
		prompt_viewer_print(u, "Black box playback started");
		break;

#endif

	case 'K':
		joystick_calibrate();
		break;

	case gui_KEY_F(7):
		instruments_altimeter_correction(u, -1);
		break;

	case gui_KEY_F(8):
		instruments_altimeter_correction(u, +1);
		break;

	case gui_KEY_F(9):
		instruments_attitude_reset(u);
		break;

	case gui_KEY_F(11):
		instruments_attitude_adjust_pitch(u, -units_DEGtoRAD(0.5));
		break;

	case gui_KEY_F(12):
		instruments_attitude_adjust_pitch(u, units_DEGtoRAD(0.5));
		break;

	case '+':
		windows_zoom_in(u);
		break;

	case '-':
		windows_zoom_out(u);
		break;

	case '$':
		if( c->type == CT_PLANE )
			drone_take_commands(c);
		else
			drone_release_commands(c);
		break;
	
	case 'M' - 64: /* ctrl-m */
		sounds_enable(c, ! sounds_isEnabled(c));
		if( sounds_isEnabled(c) )
			prompt_viewer_print(u, "Sounds enabled.");
		else
			prompt_viewer_print(u, "Sounds muted.");
		break;
	
	default:
		prompt_viewer_print(u, "Invalid key.");
		break;

	}

	return FALSE;
}


/**
 * Process all events from this viewer.
 * 
 * Return TRUE on:
 * 
 * - close request of the window: in this case the viewer u gets closed
 *   and released
 * 
 * - player gets killed or terminated: in this case the viewer u and all
 *   the other viewers attached to the craft c gets closed and the craft c
 *   gets released
 */
static _BOOL
doWindowEvent(viewer * u)
{
	gui_Event ev;
	craft    *c;

	c = u->c;

	while( gui_getEvent(u->gui, &ev) ) {

		switch (ev.code) {

		case gui_EVENT_KEYDOWN:
/*
			if (u->viewer_state == ViewerStateBrowsing ||
				u->viewer_state == ViewerStatePiggyback) {
				if (browse_keyEvent( c, u, &ev, 1)) {
					return FALSE;
				}
			}
			else {
*/
				if( doKeyEvent(c, u, ev.key) ){
					return TRUE;
				}
/*
			}
*/
			break;

		case gui_EVENT_LBUTTONDOWN:
		case gui_EVENT_MBUTTONDOWN:
		case gui_EVENT_RBUTTONDOWN:
			doButtonEvent(c, u, &ev);
			break;

		case gui_EVENT_LBUTTONUP:
		case gui_EVENT_MBUTTONUP:
		case gui_EVENT_RBUTTONUP:
			doButtonReleaseEvent(c, u, &ev);
			break;

		case gui_EVENT_WINDOW_SIZE_CHANGE:
			windows_set_layout(c, u, ev.x, ev.y, u->hud_mode);
			break;

		case gui_EVENT_WINDOW_FOCUS_IN:
			u->hasFocus = TRUE;
			break;

		case gui_EVENT_WINDOW_FOCUS_OUT:
			u->hasFocus = FALSE;
			u->hasComm = FALSE;
			break;

		case gui_EVENT_WINDOW_EXPOSE:
			Alib_invalidate(u->w);
			break;
		
		case gui_EVENT_WINDOW_CLOSE:
			c->kill(c, NULL);
			return TRUE;

		default:
			break;
		}
	}

	return FALSE;  /* done with all pending events for this viewer */

}


/*
	Process events for each viewer
*/
void
events_window_keyb_buttons(void)
{
	viewer   *u;

	u = vl_head;

	while( u != NULL ){

		mouse_getPosition(u->c, u);

		if( doWindowEvent(u) ){

			/*
			Viewer or craft terminated. We can't
			continue scanning the linked list of
			viewers because we don't know how many
			viewers were attached to this craft, so
			we have to restart from the first
			viewer:
			*/

			u = vl_head;

		} else {

			u = u->vl_next;
		}
	}
}


static int last_switches = 0;


int
events_joystick(craft * c, viewer * u, double throttle, int switches)
{

	int       switch_xor = switches ^ last_switches;

/*
 *  Change in state of any buttons ?
 */

	if (switch_xor != 0) {

		if (switch_xor & 1) {

/*
 *  Button 1 press
 */
			if (switches & 1) {
				weapon_fire(c);
			}

/*
 *  Button 1 release
 */

			else {
				weapon_ceaseFire(c);
			}
		}

/*
 * Button 2 press
 */

		if (switch_xor & 2) {
			if (switches & 2) {
				weapon_selectNextAvailable(c);
			}
		}

	}

	last_switches = switches;

	return 0;
}

