/*
 *  acm : an aerial combat simulator for X
 *  Copyright (C) 1991-1994  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 "pm.h"
#include "eng.xbm"
#include "flaps0.xbm"
#include "flaps1.xbm"
#include "flaps2.xbm"
#include "flaps3.xbm"
#include "handleUp.xbm"
#include "handleDn.xbm"
#include "gearUp.xbm"
#include "gearTran.xbm"
#include "gearDown.xbm"
#include <stdio.h>
#include <X11/Xutil.h>
#ifndef hpux
#include <X11/Xos.h>
#endif
#include <X11/cursorfont.h>

#if defined(HAVE_STDLIB_H)
#ifndef bzero
#define bzero(s,n)      memset ((s), 0, (n))
#endif
#endif /* HAVE_STDLIB_H */

extern void resizePlayerWindow ();
extern void resizeChaserWindow();
extern void newExplosion ();
extern void blackBoxKillPlayer ();
extern void printValidAircraft ();
extern void buildEulerMatrix();
extern void transpose();

#define BORDER	1

#define	ARG_FONT		"font"
#define ARG_RADAR_FONT		"radarfont"
#define	ARG_BORDER_COLOR	"bordercolor"
#define ARG_BORDER		"borderwidth"
#define	ARG_GEOMETRY		"geometry"
/* #define DEFAULT_BACKGROUND	"#93bde4"	/* my version of sky blue */
/* #define DEFAULT_BACKGROUND	"#7491ae"	/* my version of sky blue */
#define DEFAULT_BACKGROUND	"#7c99b6"	/* my version of sky blue */
#define DEFAULT_BORDER		"black"
#define DEFAULT_RADAR_FONT	"fixed"

#define SW_BORDER	1
#define SW_BACKGROUND	2
#define SW_HUDFONT	3
#define SW_GEOM		4
#define SW_RADARFONT	5
#define SW_TEAM		6
#define SW_CHASE	7
#define SW_OBSERVE	8
#define SW_DEFAULT_VISUAL 9
#define SW_PLANE	10
#define SW_LIST_PLAYER	11
#define SW_MONOCHROME	12

struct {
	char	*sw;
	int	value;
	}	swt[] = {
	"-bw", SW_BORDER,
	"-skycolor", SW_BACKGROUND,
	"-hudfont",  SW_HUDFONT,
	"-radarfont",SW_RADARFONT,
	"-geometry", SW_GEOM,
	"-team",     SW_TEAM,
	"-chase",    SW_CHASE,
	"-observe",  SW_OBSERVE,
	"-cmap",     SW_DEFAULT_VISUAL,
	"-mono",     SW_MONOCHROME,
	"-plane",    SW_PLANE,
	"-list",     SW_LIST_PLAYER,
	NULL, 0}, *swp;

void
recoverAcmArgv (args, argc, argv)
char	*args;
int	*argc;
char	*argv[];
{

	char *s;

	argv[0] = ACM;
	argv[1] = args;

	if (*args == '\0') {
		*argc = 1;
		argv[1] = (char *) NULL;
		return;
	}
	else
		*argc = 2;

	for (s=args; *s;) {
		if (*s == '|') {
			*s = '\0';
			argv[(*argc)++] = ++s;
		}
		else
			++s;
	}

	argv[*argc] = (char *) NULL;
}

Visual *
get_pseudo_visual (display, screen, depth)
Display *display;
int	screen;
unsigned int *depth;
{

	XVisualInfo	vTemplate;
	XVisualInfo	*visualList;
	int		i, visualsMatched;

/*
 *  Get all Visuals on this screen
 */

	vTemplate.screen = screen;
	visualList = XGetVisualInfo (display, VisualScreenMask,
		&vTemplate, &visualsMatched);

	if (visualsMatched == 0)
		return NULL;

/*
 *  look first for a suitable PseudoColor visual, then a GreyScale visual
 *  if no PseudoColor is present
 */

	for (i=0; i<visualsMatched; ++i) {
		if (visualList[i].visual->class == PseudoColor &&
			visualList[i].depth >= 4) {
			*depth = visualList[i].depth;
			XFree ((char *) visualList);
			return visualList[i].visual;
		}
	}

	for (i=0; i<visualsMatched; ++i) {
		if (visualList[i].visual->class == GrayScale &&
			visualList[i].depth >= 4) {
			*depth = visualList[i].depth;
			XFree ((char *) visualList);
			return visualList[i].visual;
		}
	}

	XFree ((char *) visualList);

	return NULL;
}

int
newPlayer(s, display, logname, switches)
int	s;
char	*display;
char	*logname;
char	*switches;
{

    char       *fontName;	/* Name of font for string */
    XSizeHints	xsh;		/* Size hints for window manager */
    Colormap	cmap;
    XGCValues	gcv;
    unsigned long pad;		/* Font size parameters */
    unsigned long bd;		/* Pixel values */
    unsigned long bw;		/* Border width */
    char       *tempstr;	/* Temporary string */
    XColor      color;		/* Temporary color */
    char       *geomSpec;	/* Window geometry string */
    XWMHints	xwmh;		/* Window manager hints */
    Cursor	cursor;
    char	**c;
    char	err[64];
    static char	*background = NULL;
    int		borderWidth = -1;
    int		player;
    viewer	*u;
    craft	*cf;
    int		argc;
    char	*argv[32];
    int		screen;
    char	*hudfont = NULL, *radarfont = NULL;
    int		team = 1;
    char	*plane = NULL;	/* name of plane type */
    int		obsrver = -1;
    int		chaser = -1;
    int		width, height;	/* dimensions of main window */
    double	scale;
    unsigned	depth, mono = 0;
    long	win_attr_mask;
    Visual	*theVisual;
    XSetWindowAttributes window_attributes;
    int		useDefaultVisual = 1;
    Atom	atom[2];

    recoverAcmArgv (switches, &argc, argv);

    geomSpec = NULL;
    u = (viewer *) malloc (sizeof(viewer));

/*
 *  Parse command line
 */

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

			switch (swp->value) {

			case SW_GEOM: 
				geomSpec = *(++c);
				break;

			case SW_BORDER:
				borderWidth = atoi (*(++c));
				break;

			case SW_BACKGROUND:
				background = *(++c);
				break;

			case SW_HUDFONT:
				hudfont = *(++c);
				break;

			case SW_RADARFONT:
				radarfont = *(++c);
				break;

			case SW_TEAM:
				team = atoi (*(++c));
				break;

			case SW_CHASE:
				chaser = atoi (*(++c));
				break;

			case SW_OBSERVE:
				obsrver = atoi (*(++c));
				break;

			case SW_PLANE:
				plane = *(++c);
				break;

			case SW_MONOCHROME:
				mono = 1;
				break;

			case SW_LIST_PLAYER:
				sprintf (err, "\nname\t\tnumber\n");
				write (s, err, strlen(err));
				sprintf (err, "-------------------------\n");
				write (s, err, strlen(err));
				for (team = 0; team < MAXPLAYERS; team++) {
				    if (ptbl[team].type == CT_PLANE) {
					sprintf (err, "%-16s  %d\n",
						ptbl[team].name, team);
					write (s, err, strlen(err));
				    }
				}
				return -1;

			case SW_DEFAULT_VISUAL:
				useDefaultVisual = 0;
				break;
			}
		        break;
		}
	    if (swp->value == 0) {
		free ((char *) u);
		sprintf (err, "%s: invalid switch %s", ACM, *c);
		write (s, err, strlen(err));
		return -1;
	    }
	}
  
     if (chaser != -1) {
	sprintf (err, "You are chasing player %d\n", chaser);
	write (s, err, strlen (err));
	if (!chaseOthers) { }
	switch (player = newChaser (chaser, logname)) {

	case NC_NOT_PLANE:
		sprintf (err, "You have selected unknown player %d\n", chaser);
		write (s, err, strlen (err));
		plane = "chaser";
		return -1;

	case NC_CANNOT_CHASE:
		sprintf (err, "You cannot chase player \"%s\".\n", ptbl[chaser].name);
		write (s, err, strlen (err));
		plane = "chaser";
		return -1;

	}
	ctbl[player].team = chaser;
    }

    if (obsrver != -1) {
	sprintf (err, "You are an observer for player %d\n", obsrver);
	write (s, err, strlen (err));
	switch (newObserver (obsrver)) {
	}
    }

    if (chaser == -1) {

	if (!plane) {
		plane = (team == 1) ? "F-16" : "MiG-29";
	}

	if ((player = newPlane (plane)) < 0) {
		if (player == -1) {
	    		sprintf (err,
		    "Sorry, no room for any more players at this moment.\n");
			write (s, err, strlen(err));
		}
		else {
			sprintf (err,
				"You have selected an unknown plane type.\n");
			write (s, err, strlen(err));
			printValidAircraft(s);
		}
		return -1;
	}

	cf = &ptbl[player];

/*
 *  assign an initial location basewd on the player's team.
 */

	if (team != 2) {
		team = 1;
	}

	cf->Sg.x = teamLoc[team].x;
	cf->Sg.y = teamLoc[team].y;
	/* cf->Sg.z = teamLoc[team].z; */
	cf->curHeading = teamHeading[team];

	buildEulerMatrix (cf->curRoll, cf->curPitch, cf->curHeading,
		&(cf->trihedral));
	transpose (&cf->trihedral, &cf->Itrihedral);
    }
    else {				/* adding a new chaser */
	cf = &ctbl[player];
	cf->type = CT_CHASER;
    }

    if ((u->dpy = XOpenDisplay(display)) == (Display *) NULL) {
	free ((char *) u);
	cf->type = CT_FREE;
	sprintf(err, "%s: can't open %s\n", ACM, display);
	write (s, err, strlen(err));
	return -1;
    }
    screen = DefaultScreen (u->dpy);

    if (radarfont)
	 fontName = radarfont;
    else if ((fontName = XGetDefault(u->dpy, ACM, ARG_RADAR_FONT)) == NULL) {
	fontName = DEFAULT_RADAR_FONT;
    }
    if ((u->rfont = XLoadQueryFont(u->dpy, fontName)) == NULL) {
	XCloseDisplay (u->dpy);
	free ((char *) u);
	cf->type = CT_FREE;
	sprintf(err, "%s: display %s doesn't know font %s\n",
		ACM, display, fontName);
	write (s, err, strlen(err));
	return -1;
    }

/*
 *  If the player has specified that the want the default Visual, simply
 *  give 'em that along with the default Colormap.
 */

    if (useDefaultVisual) {

	theVisual = DefaultVisual (u->dpy, screen);
	cmap = DefaultColormap (u->dpy, screen);
	depth = DisplayPlanes (u->dpy, screen);

    }

/*
 *  Look for a visual; if we can't find one,
 *  fall back to monochrome mode.
 */

    else {
	if ((theVisual = get_pseudo_visual (u->dpy, screen, &depth)) == NULL) {
		theVisual = DefaultVisual (u->dpy, screen);
		cmap = DefaultColormap (u->dpy, screen);
		depth = DefaultDepth(u->dpy, screen);
	}
	else {
		cmap = XCreateColormap (u->dpy, RootWindow(u->dpy, screen),
			theVisual, AllocNone);
	}
    }

/*
 * Select colors for the border, the window background, and the
 * foreground.  We use the default colormap to allocate the colors in.
 */

    if (background == NULL)
	background = DEFAULT_BACKGROUND;

    if ((tempstr = XGetDefault(u->dpy, ACM, ARG_BORDER_COLOR)) == NULL)
	tempstr = DEFAULT_BORDER;
    if (XParseColor(u->dpy, cmap, tempstr, &color) == 0) {
	XCloseDisplay (u->dpy);
	free ((char *) u);
	cf->type = CT_FREE;
	sprintf (err, "Can't get border color %s\n", tempstr);
	write (s, err, strlen(err));
	return -1;
    }

    if (depth == 1)
	bd = BlackPixel (u->dpy, screen);
    else {
        if (XAllocColor(u->dpy, cmap, &color) == 0) {
	    XCloseDisplay (u->dpy);
	    free ((char *) u);
	    cf->type = CT_FREE;
	    sprintf (err, "Cannot allocate color cells\n");
	    write (s, err, strlen(err));
	    return -1;
	}
	bd = color.pixel;
    }

/*
 * Set the border width of the window, and the gap between the text
 * and the edge of the window, "pad".
 */

    pad = BORDER;
    if (borderWidth >= 0)
	bw = borderWidth;
    else if ((tempstr = XGetDefault(u->dpy, ACM, ARG_BORDER)) == NULL)
	bw = 1;
    else
	bw = atoi(tempstr);

/*
 * Deal with providing the window with an initial position & size.
 * Fill out the XSizeHints struct to inform the window manager.
 */

    if (geomSpec == NULL)
        geomSpec = XGetDefault(u->dpy, ACM, ARG_GEOMETRY);

/*
 * If the defaults database doesn't contain a specification of the
 * initial size & position, locate it in the center of the screen.
 */

    if (geomSpec == NULL) {
	xsh.flags = PPosition | PSize;
	if (chaser == -1) {
	    xsh.height = FS_WINDOW_HEIGHT * DisplayWidth(u->dpy, screen) / 1280;
	    xsh.width =  FS_WINDOW_WIDTH * DisplayWidth(u->dpy, screen) / 1280;
	}
	else {
	    xsh.height = CHASE_WINDOW_HEIGHT * DisplayWidth(u->dpy, screen) / 1280;
	    xsh.width =  CHASE_WINDOW_WIDTH * DisplayWidth(u->dpy, screen) / 1280;
	}
	xsh.x = (DisplayWidth(u->dpy, screen) - xsh.width) / 2;
	xsh.y = (DisplayHeight(u->dpy, screen) - xsh.height) / 2;
    }
    else {
	int         bitmask;

	bzero((char *) &xsh, sizeof(xsh));
	bitmask = XParseGeometry (geomSpec, &xsh.x, &xsh.y,
		&xsh.width, &xsh.height);
	if (bitmask & (XValue | YValue)) {
	    xsh.flags |= USPosition;
	}
	if (bitmask & (WidthValue | HeightValue)) {
	    xsh.flags |= USSize;
	}
    }

    width = xsh.width;
    height = xsh.height;

/*
 * Create the Window with the information in the XSizeHints, the
 * border width,  and the border & background pixels.
 */

    win_attr_mask = CWColormap | CWBitGravity | CWBackPixel;
    window_attributes.colormap = cmap;
    window_attributes.bit_gravity = NorthWestGravity;
    window_attributes.background_pixel = BlackPixel(u->dpy, screen);

    u->win = XCreateWindow (u->dpy, RootWindow (u->dpy, screen),
		xsh.x, xsh.y, xsh.width, xsh.height, bw, depth,
		InputOutput, theVisual, win_attr_mask, &window_attributes);

    scale = (double) xsh.width / (double) FS_WINDOW_WIDTH;

/*
 * Create a pixmap of the engine RPM gauge and flap indicators.
 */

    u->eng = XCreateBitmapFromData (u->dpy, u->win, eng_bits, eng_width,
	eng_height);
    u->flap[0] = XCreateBitmapFromData (u->dpy, u->win, flaps0_bits,
	flaps0_width, flaps0_height);
    u->flap[1] = XCreateBitmapFromData (u->dpy, u->win, flaps1_bits,
	flaps1_width, flaps1_height);
    u->flap[2] = XCreateBitmapFromData (u->dpy, u->win, flaps2_bits,
	flaps2_width, flaps2_height);
    u->flap[3] = XCreateBitmapFromData (u->dpy, u->win, flaps3_bits,
	flaps3_width, flaps3_height);
    u->handle[0] = XCreateBitmapFromData (u->dpy, u->win, handleUp_bits,
	handleUp_width, handleUp_height);
    u->handle[1] = XCreateBitmapFromData (u->dpy, u->win, handleDn_bits,
	handleDn_width, handleDn_height);
    u->gearLight[0] = XCreateBitmapFromData (u->dpy, u->win, gearUp_bits,
	gearUp_width, gearUp_height);
    u->gearLight[1] = XCreateBitmapFromData (u->dpy, u->win, gearTran_bits,
	gearTran_width, gearTran_height);
    u->gearLight[2] = XCreateBitmapFromData (u->dpy, u->win, gearDown_bits,
	gearDown_width, gearDown_height);

/*
 * Set the standard properties and hints for the window managers.
 */

    XSetStandardProperties(u->dpy, u->win, ACM, ACM, None, argv, argc, &xsh);
    xwmh.flags = InputHint | StateHint;
    xwmh.input = True;
    xwmh.initial_state = NormalState;
    XSetWMHints(u->dpy, u->win, &xwmh);
    if (chaser == -1) {
	cursor = XCreateFontCursor (u->dpy, XC_tcross);
	XDefineCursor (u->dpy, u->win, cursor);
    }

/*
 *  Tell the window manager that we'd like to participate in the
 *  WM_CLOSEDOWN and WM_DELETE_WINDOW protocols.
 */

    u->protocolsAtom = XInternAtom (u->dpy, "WM_PROTOCOLS", False);
    atom[0] =
	u->closedownAtom = XInternAtom (u->dpy, "WM_CLOSEDOWN", False);
    atom[1] =
	u->deleteWindowAtom = XInternAtom (u->dpy, "WM_DELETE_WINDOW", False);
    XSetWMProtocols (u->dpy, u->win, atom, 2);

/*
 *  Fill-in the viewer structure
 */
    if (chaser == -1) {
      XSelectInput (u->dpy, u->win, KeyPressMask | ButtonPressMask |
   		  StructureNotifyMask | ButtonReleaseMask | ExposureMask);
      cf->team = team;
    }
    else {
      XSelectInput (u->dpy, u->win, KeyPressMask | StructureNotifyMask |
  		  ExposureMask);
    }

    cf->vl = u;
    strncpy (cf->name, logname, sizeof (cf->name));
    strncpy (cf->display, display, sizeof (cf->display));
    u->next = (viewer *) NULL;

/*
 *  We use the "redrew" flag to allow Vlib to optimize areas that we'll
 *  be drawing using X, as well.
 */

    u->redrew = 2;

/*
 *  COnnect to the audio server
 */
   
    if (initializeAudio(cf, u, display) != 0) {
    	sprintf (err, "Sound is not available for server %s\n", display);
	write (s, err, strlen(err));
    }
 
#ifdef USE_PIXMAP_ANIMATION
    if (depth != 1)
	_VDefaultWorkContext->usePixmaps = 1;
#endif

    _VDefaultWorkContext->usePixmaps = mono ? 1 : 0;

    if (chaser == -1) {
	u->v = VOpenViewport (u->dpy, screen, u->win, cmap, theVisual,
  			  UNITS_FEET, scale, 0.50,
  			  width * VIEW_WINDOW_WIDTH / FS_WINDOW_WIDTH,
  			  height * VIEW_WINDOW_HEIGHT / FS_WINDOW_HEIGHT);
    }
    else {
	u->v = VOpenViewport (u->dpy, screen, u->win, cmap, theVisual,
  			  UNITS_FEET, scale, 0.50, width, height);
    }

/*
 *  If this is a color-rich visual, then enable depth-cueing mode
 */
 
    if (depth >= 6) {
    	ViewportSetDepthCueing (u->v, 1);
    }

    if (VBindColors (u->v, background) < 0) {
	XCloseDisplay (u->dpy);
	free ((char *) u);
	ptbl[player].type = CT_FREE;
	sprintf (err, "Error in binding colors.\n");
	write (s, err, strlen(err));
	return -1;
    }

    whitePixel = whiteColor->cIndex;
    blackPixel = blackColor->cIndex;
    HUDPixel = HUDColor->cIndex;

/*
 * Create the GC for drawing the picture.
 */

    gcv.graphics_exposures = False;
    u->gc = XCreateGC(u->dpy, u->win, GCGraphicsExposures, &gcv);

    gcv.graphics_exposures = False;
    gcv.foreground = u->v->flags & VPMono ?
		WhitePixel(u->v->dpy, u->v->screen) :
		u->v->pixel[whitePixel];
    gcv.background = u->v->flags & VPMono ?
		BlackPixel(u->v->dpy, u->v->screen) :
		u->v->pixel[blackPixel];
    gcv.line_width = 2;
    u->gauge_gc = XCreateGC(u->dpy, u->win,
		GCGraphicsExposures | GCForeground | GCBackground |
		GCLineWidth, &gcv);

    if (chaser == -1) {
	resizePlayerWindow (cf, u, width, height, 1);
    }
    else {
	resizeChaserWindow (cf, u, width, height, 1);
    }
									       
/*
 * Map the window to make it visible.
 */

    XMapWindow(u->dpy, u->win);
    if (depth != 1) {
#ifdef notdef
	register int curPixel;

    	VExposeBuffer (u->v, u->gc);
	curPixel = *(u->v->pixel);
	XSetForeground (u->dpy, u->gc, curPixel);
	XFillRectangle (u->dpy, u->win, u->gc, 0, 0, u->width, u->height);
#endif
   }

    if (depth == 1)
	XSetWindowBackground (u->dpy, u->win, BlackPixel(u->dpy, screen));
    else
	XSetWindowBackground (u->dpy, u->win, VConstantColor(u->v, blackPixel));

    u->z.color = u->v->pixel[HUDPixel];
    u->z.depth = 1;

    u->rz.color = u->v->pixel[radarColor->cIndex];
    u->rz.depth = 2;

    if (chaser == -1) {
	++ ptblCount;
    }
    else {
	++ ctblCount;
    }

    return 0;

}

int
killPlayer (c)
craft	*c;
{

	viewer	*v, *vn;
	int	i;
	
	playSound(c, SoundCrash);

/*
 *  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<MAXPLAYERS; ++i)
		ptbl[i].rval[c->pIndex] = 0.0;

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

	newExplosion (&(c->Sg), 100.0, 15.0, 4.0);

/*
 *  Free HUD string storage
 */

	if (c->leftHUD[0] != (char *) NULL)
		for (i=0; i<3; ++i) {
			free (c->leftHUD[i]);
			free (c->rightHUD[i]);
		}

/*
 *  Close viewers' display
 */

	for (v=c->vl; v != (viewer *) NULL;) {
		VCloseViewport (v->v);
		XCloseDisplay (v->dpy);
		shutdownAudio (c, v);
		vn = (viewer *) v->next;
		free ((char *) v);
		v = vn;
	}

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

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

	c->type = CT_FREE;
	return 0;
}
