/*
 *  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 <math.h>

#include "../util/memory.h"
#include "../util/units.h"
#include "../util/zulu.h"
#include "aps.h"
#include "damage.h"
#include "draw.h"
#include "m61a1.h"
#include "pm.h"
#include "vpath.h"
#include "weapon.h"

#define hud_IMPORT
#include "hud.h"

#define DESIGNATOR_SIZE     20

#define HUD(u)     ((hud_Type *)u->hud)

static int blink = 0;
static double blink_toggle_time = 0.0;


typedef struct _hud_data {
	struct _hud_data *next;

	_BOOL  enabled;

	int timer_op;        /* timer operation: 0=do not display, 1=run, 2=stop */
	int timer_freezed;   /* store the time when timer_op is 2 */
	double timer_offset; /* store the offset time when timer_op is 1 or 2 */
} hud_Type;


static hud_Type *free_list = NULL;

static vpath_Type
	* hud_vpath = NULL,
	* fpm_vpath = NULL;


static void hud_cleanup()
{
	hud_Type *hud;

	while( free_list != NULL ){
		hud = free_list;
		free_list = free_list->next;
		memory_dispose(hud);
	}

	memory_dispose(hud_vpath);
	memory_dispose(fpm_vpath);
}


void hud_enable(viewer *u)
{
	hud_Type *hud;

	if( u->hud == NULL ){
		if( free_list == NULL ){
			hud = memory_allocate( sizeof(hud_Type), NULL );
			memory_registerCleanup(hud_cleanup);
		} else {
			hud = free_list;
			free_list = hud->next;
			hud->next = NULL;
		}
		hud->next = NULL;
		hud->timer_op = 0;
		u->hud = hud;
	}
	HUD(u)->enabled = TRUE;
}


void hud_disable(viewer *u)
{
	if( u->hud == NULL )
		return;
	HUD(u)->enabled = FALSE;
}



void
hud_timer_toggle(viewer *u)
/*
	Actually, it cycles between the states timer_op 0, 1 and 2.
*/
{
	if( u->hud == NULL || !HUD(u)->enabled )
		return;

	HUD(u)->timer_op = (HUD(u)->timer_op + 1) % 3;

	if ( HUD(u)->timer_op == 1 ) {
		HUD(u)->timer_offset = curTime;

	} else if ( HUD(u)->timer_op == 2 ) {
		HUD(u)->timer_freezed = (int) (curTime - HUD(u)->timer_offset + 0.5);

	}
}


static void
doBankIndicator(craft * c, viewer * u)
{
	double xscale, yscale, r, r2, r3, r4, cosa, sina, bank,
		x1, y1, x2, y2, x3, y3;
	int x0, y0, a, blink_bank;
	draw_Type *dd;

	xscale = u->xscaleFactor;
	yscale = u->yscaleFactor;
	r = 200.0 * xscale; /* tick mark start radius */
	r2 = 1.05*r; /* tick mark end radious */
	r3 = 0.99*r;
	r4 = 0.94*r;
	x0 = u->v->focus.x;
	y0 = u->v->focus.y + (int) (-150.0*yscale + r);
	dd = draw_new();
	VSetClipRect(u->v, &u->v->rect);

	for( a = -30; a <= 30; a += 10 ){
		cosa = cos( units_DEGtoRAD(a) );
		sina = sin( units_DEGtoRAD(a) );
		draw_segment(dd, x0 + sina*r, y0 - cosa*r, x0 + sina*r2, y0 - cosa*r2);
	}

	bank = c->curRoll;
	blink_bank = 0;
	if( bank < units_DEGtoRAD(-40) ){
		bank = units_DEGtoRAD(-40);
		blink_bank = blink;
	} else if( bank > units_DEGtoRAD(+40) ){
		bank = units_DEGtoRAD(+40);
		blink_bank = blink;
	}
	if( ! blink_bank ){
		x1 = x0 + sin(bank)*r3;
		y1 = y0 - cos(bank)*r3;
		x2 = x0 + sin(bank + units_DEGtoRAD(2))*r4;
		y2 = y0 - cos(bank + units_DEGtoRAD(2))*r4;
		x3 = x0 + sin(bank - units_DEGtoRAD(2))*r4;
		y3 = y0 - cos(bank - units_DEGtoRAD(2))*r4;
		draw_segment(dd, x1, y1, x2, y2);
		draw_segment(dd, x1, y1, x3, y3);
		draw_segment(dd, x2, y2, x3, y3);
	}

	draw_stroke(dd, u->v, HUDColor);
	draw_free(dd);
}


#define TurnAndSlipIndicatorWidth 100


static void
doTurnAndSlipIndicator(craft * c, viewer * u)
/*
	Displays the rate of turn (DEG/s), the lateral acceleration (g)
	and a timer (s).
*/
{
	double xscale, yscale, w;
	int tx, ty, t, blink_turn, blink_w;
	int w_tick;
	int w_t;
	double a;
	draw_Type *dd;
	int h, m, s;
	char buffer[20];
	Alib_Pixel color;

	xscale = u->xscaleFactor;
	yscale = u->yscaleFactor;
	w_t = (int) (xscale * TurnAndSlipIndicatorWidth / 2.0);
	w_tick = (int) (xscale * TurnAndSlipIndicatorWidth / 4.0);
 	tx = u->v->focus.x - (int) (xscale * 160);
 	ty = u->v->focus.y + (int) (yscale * 200);
	dd = draw_new();
	VSetClipRect(u->v, &u->v->rect);
	color = HUDColor;

 	/*
 		Draw scale:
 	*/
	w = 6 * yscale;
	draw_segment(dd, tx-w_t,    ty,  tx+w_t, ty);
	draw_segment(dd, tx-w_t,    ty,  tx-w_t, ty-w);     /* -3.0 DEG/s */
	draw_segment(dd, tx-w_tick, ty,  tx-w_tick, ty-w);  /* -1.5 DEG/s */
	draw_segment(dd, tx,        ty,  tx,     ty-w);     /*  0   DEG/s */
	draw_segment(dd, tx+w_tick, ty,  tx+w_tick, ty-w);  /* +1.5 DEG/s */
	draw_segment(dd, tx+w_t,    ty,  tx+w_t, ty-w);     /* +3.0 DEG/s */

	/*
		Draw turn rate needle:
	*/
	a = units_RADtoDEG( (c->q*sin(c->curRoll) + c->r*cos(c->curRoll))
		/ cos(c->curPitch) );
 	blink_turn = 0;
 	if( a > 3.5 ){
 		a = 3.5;
 		blink_turn = blink;
 	} else if( a < -3.5 ){
 		a = -3.5;
 		blink_turn = blink;
 	}
 	if( ! blink_turn ){
		int x0 = tx + a / 3.0 *  w_t;
		int y0 = ty - 4 * yscale;
		w = 4 * xscale;
		draw_segment(dd, x0 - w, y0, x0 - w - w, y0 - w);
		draw_segment(dd, x0 - w - w, y0 - w, x0 + w + w, y0 - w);
		draw_segment(dd, x0 + w + w, y0 - w, x0 + w, y0);
	}

	/*
		Draw lateral accel. needle:
	*/
	a = c->G.y;
	blink_turn = 0;
	if( a < -0.5 ){
		a = -0.5;
		blink_turn = blink;
	} else if( a > 0.5 ){
		a = 0.5;
		blink_turn = blink;
	}
	if( ! blink_turn ){
		t = tx - (int) rint(2.0*a*w_t);
		draw_segment(dd, t, ty+2*yscale, t, ty+9*yscale);
	}
	if( aps_ac_enabled(c)
	&& ( ! aps_ac_warn(c) || ! blink ) ){
		VDrawStrokeString(u->v,
			tx - w_t, ty + (int) (yscale * 12),
			"AC", 2,
			xscale * 8.0, color);
	}

	draw_stroke(dd, u->v, HUDColor);
	draw_free(dd);

	/*
		Draw AutoTurn status:
	*/
	if ( aps_aw_enabled(c)
	&& ( ! aps_aw_warn(c) || ! blink ) ){

		w = aps_aw_get(c);

		blink_w = FALSE;

		if( w < units_DEGtoRAD(-3.1) ){
			w = units_DEGtoRAD(-3.0);
			blink_w = blink;
		} else if( w > units_DEGtoRAD(3.1) ){
			w = units_DEGtoRAD(3.0);
			blink_w = blink;
		}

		if ( ! blink_w )
			VDrawStrokeString(u->v,
				tx + (int) (w * w_t / units_DEGtoRAD(3.0) - xscale * 6.0 + 0.5),
				ty + (int) (yscale * 4.0 + 0.5),
				"o", 1, xscale * 12.0, color);
	}

	/*
		Draw timer:
	*/

	if ( HUD(u)->timer_op == 0 ) {
		return;

	} else if ( HUD(u)->timer_op == 1 ) {
		t = (int) (curTime - HUD(u)->timer_offset);

	} else {
		t = HUD(u)->timer_freezed;
	}

	if (t >= 3600) {
		h = t / 3600;
		t = t % 3600;
		m = t / 60;
		s = t % 60;
		sprintf(buffer, "%d:%02d:%02d", h, m, s);
	} else if (t >= 60) {
		m = t / 60;
		s = t % 60;
		sprintf(buffer, "%d:%02d", m, s);
	} else {
		sprintf(buffer, "%d", t);
	}

	VDrawStrokeString(u->v, tx - w_t, ty + (int) (yscale * 30.0),
		buffer, strlen(buffer),
		yscale * 12.0, color);
}


static void draw_dashed_line(vpath_Type *p, VPoint *a, VPoint *b, int n)
{
	int i;
	VPoint c, d;

	n = 2*n - 1;
	c = *a;
	for( i = 1; i <= n; i++ ){
		VSetPoint(&d,
			a->x + i*(b->x - a->x)/n,
			a->y + i*(b->y - a->y)/n,
			a->z + i*(b->z - a->z)/n);
		if( (i & 1) == 1 ){
			vpath_moveTo(p, &c);
			vpath_lineTo(p, &d);
		} else {
			c = d;
		}
	}
}


static vpath_Type * build_hud_vpath()
{
	vpath_Type * p;
	int angle, s_len;
	VPoint a, b, c;
	VMatrix m, n;
	char s[10];
	double fw, fh;

	p = vpath_new();

	/**** Bare cross:
	vpath_moveTo(p, &(VPoint){-0.1, 0.0, 1.0});
	vpath_lineTo(p, &(VPoint){+0.1, 0.0, 1.0});

	vpath_moveTo(p, &(VPoint){0.0, 0.1, 1.0});
	vpath_lineTo(p, &(VPoint){0.0, -0.1, 1.0});

	return p;
	****/

/*
	Since the HUD pitch ladder is drawn over a sphere of radius 1.0,
	it is convenient representing all the distances as angles under
	which every item can be seen from the center of the sphere.


	                 .
	                 |
	+----------      .   v...--------+  15
	|                |   D   .       |   .
	|                |   ............|   .
	                 .   ^   .       .   .
	                 |       .       .   .
	                 .<--A-->.       .   .
	                 |               .   .
	                 .<------B------>.   .
	                 |                   .
	                 .<--------D-------->.
	                 |
*/

#define A units_DEGtoRAD(1.0)   /* ladder inner point */
#define B units_DEGtoRAD(2.0)   /* ladder outer point */
#define C units_DEGtoRAD(0.5)   /* ladder "comma" */
#define D units_DEGtoRAD(2.5)   /* label yaw angle */

	/* Draw horizon line, right side: */
	VSetPoint(&a, cos(A), sin(A), 0.0);
	VSetPoint(&b, cos(2*B), sin(2*B), 0.0);
	vpath_moveTo(p, &a);
	vpath_lineTo(p, &b);

	/* Draw horizon line, left side: */
	a.y = -a.y;
	b.y = -b.y;
	vpath_moveTo(p, &a);
	vpath_lineTo(p, &b);

	/* Draw ladder pitch marks, step 5 DEG: */

	fh = sin(units_DEGtoRAD(0.7));
	fw = sin(units_DEGtoRAD(0.4));

	for( angle = -85; angle <= 85; angle += 5 ){

		if( angle == 0 )
			continue;

		VIdentMatrix(&m);
		VRotate(&m, YRotation, units_DEGtoRAD(angle));

		/* Draw the right side of the ladder peg: */
		VSetPoint(&a, cos(A), sin(A), 0.0);
		VSetPoint(&b, cos(B), sin(B), 0.0);
		VSetPoint(&c, cos(B), sin(B), sin(C));
		if( angle < 0 )
			c.z = -c.z;

		VTransform(&a, &m, &a);
		VTransform(&b, &m, &b);
		VTransform(&c, &m, &c);

		if( angle > 0 ){
			vpath_moveTo(p, &a);
			vpath_lineTo(p, &b);
			vpath_lineTo(p, &c);
		} else {
			draw_dashed_line(p, &a, &b, 3);
			vpath_moveTo(p, &b);
			vpath_lineTo(p, &c);
		}

		/* Reverse y, then draw the left side of the peg: */
		a.y = -a.y;
		b.y = -b.y;
		c.y = -c.y;

		if( angle > 0 ){
			vpath_moveTo(p, &a);
			vpath_lineTo(p, &b);
			vpath_lineTo(p, &c);
		} else {
			draw_dashed_line(p, &a, &b, 3);
			vpath_moveTo(p, &b);
			vpath_lineTo(p, &c);
		}

		/* Now the label, right side: */
		sprintf(s, "%d", abs(angle));
		s_len = strlen(s);
		VIdentMatrix(&n);
		VScaleMatrix(&n, fw, fh, 1.0);
		VRotate(&n, XRotation, units_DEGtoRAD(90));
		VRotate(&n, ZRotation, units_DEGtoRAD(90));
		VTranslate(&n, 1.0, sin(D) - 0.5*s_len*fw, 0.5*fh);
		VMatrixMult(&n, &m, &n);
		vpath_draw_string(p, s, s_len, &n);

		/* Label, left side: */
		VTranslate(&n, 0.0, -2.0*sin(D), 0.0);
		vpath_draw_string(p, s, s_len, &n);

	}

	/* -1, -2 and -3 pitch marks: */
	for( angle = -1; angle >= -3; angle-- ){

		VIdentMatrix(&m);
		VRotate(&m, YRotation, units_DEGtoRAD(angle));

		/* Draw the right side of the ladder peg: */
		VSetPoint(&a, cos(A), sin(A), 0.0);
		VSetPoint(&b, cos(B), sin(B), 0.0);
		VTransform(&a, &m, &a);
		VTransform(&b, &m, &b);
		draw_dashed_line(p, &a, &b, 3);

		/* Reverse y, then draw the left side of the peg: */
		a.y = -a.y;
		b.y = -b.y;
		draw_dashed_line(p, &a, &b, 3);
	}

	return p;
}


static vpath_Type * build_fpm_vpath()
{
	#define FPM_A_STEP 45  /* DEG */
	vpath_Type *p;
	int a;

	p = vpath_new();

	vpath_moveTo(p, &(VPoint){0.0, 0.0, 1.0});

	for( a = FPM_A_STEP; a <= 360; a += FPM_A_STEP ){
		vpath_lineTo(p, &(VPoint){0.0, sin(units_DEGtoRAD(a)), cos(units_DEGtoRAD(a))});
	}

	vpath_moveTo(p, &(VPoint){0.0, 0.0, -1.0});
	vpath_lineTo(p, &(VPoint){0.0, 0.0, -2.0});

	vpath_moveTo(p, &(VPoint){0.0, 1.0, 0.0});
	vpath_lineTo(p, &(VPoint){0.0, 2.0, 0.0});

	vpath_moveTo(p, &(VPoint){0.0, -1.0, 0.0});
	vpath_lineTo(p, &(VPoint){0.0, -2.0, 0.0});

	return p;
}


#define LADDER_WIDTH 325.0
#define LADDER_HEIGHT 430.0


static void
doLadder(craft * c, viewer * u, double vel)
{

	_BOOL     plotFPM;
	int       windX, windY, w, tx, ty, x, y;
	VPoint    tmp, tmp1, t1;
	VMatrix   m;
	double    v, d;
	Alib_Rect      rect;
	Alib_Pixel     color;

	tx = (int) (LADDER_WIDTH * u->xscaleFactor * 0.5 + 0.5);
	ty = (int) (LADDER_HEIGHT * u->yscaleFactor + 0.5);
	Alib_setRect(&rect, u->v->focus.x - tx, u->hud_yCenter - 0.37 * ty,
		u->v->focus.x + tx, u->hud_yCenter + 0.63 * ty);
	VSetClipRect(u->v, &rect);
	if( Alib_isEmptyRect(&u->v->rect) )
		return;

	color = HUDColor;
		      
/*
 *  Build a transformation matrix to be used to display the flight
 *  path ladder (artificial horizon).
 *
 *  One thing we do is to keep the ladder centered on the flight path
 *  marker.  The correction angle "v" is calculated to make that happen.
 */

	if (fabs(c->Cg.x) < 5.0 && fabs(c->Cg.y) < 5.0) {
		v = 0.0;
	}
	else {
		t1.x = cos(c->curHeading) * c->Cg.x +
			sin(c->curHeading) * c->Cg.y;
		t1.y = -sin(c->curHeading) * c->Cg.x +
			cos(c->curHeading) * c->Cg.y;
		v = atan2(t1.y, t1.x);
	}

	if( hud_vpath == NULL ){
		hud_vpath = build_hud_vpath();
		fpm_vpath = build_fpm_vpath();
	}
	
	VIdentMatrix(&m);
	VRotate(&m, ZRotation, v);
	VRotate(&m, YRotation, -c->curPitch);
	VRotate(&m, XRotation, -c->curRoll);
	VRotate(&m, ZRotation, units_DEGtoRAD(-90));
	VRotate(&m, XRotation, units_DEGtoRAD(-90));
	vpath_perspective_stroke(hud_vpath, &m, u->v, color);

/*
 *  Determine the location of the flight path marker
 */

	VReverseTransform_(&c->Cg, &c->trihedral, &tmp);


	if (vel < 50.0 || tmp.x == 0.0) {
		plotFPM = ! blink;
		windX = u->v->focus.x;
		windY = u->v->focus.y;
	}
	else if (tmp.x > 0.0) {
		plotFPM = TRUE;
		windX = u->v->focus.x + ((int) (tmp.y * u->v->Scale.x / tmp.x) / 4);
		windY = u->v->focus.y + ((int) (tmp.z * u->v->Scale.y / tmp.x) / 4);
	}
	else {
		plotFPM = FALSE;
		windX = 0.0;
		windY = 0.0;
	}

	if (plotFPM) {

		d = 8.0 * u->xscaleFactor;
		VIdentMatrix(&m);
		VScaleMatrix(&m, d, d, d);
		VRotate(&m, ZRotation, units_DEGtoRAD(-90));
		VRotate(&m, XRotation, units_DEGtoRAD(-90));
		VTranslate(&m, windX, windY, -1.0);
		vpath_stroke(fpm_vpath, &m, u->w, color);

	}

/*
 *  Gather weapon display info (and maybe draw a reticle).
 *
 *  WARNING. This function must be called before drawing the target
 *  designator, as this will also set the m61a1_lcos_last_post global
 *  variable that will be used next.
 */

	weapon_displaySelected(c, u, (int) floor(windX), (int) floor(windY));

/*
 *  Draw a target designator around the current primary radar target.
 */

	if (c->curRadarTarget >= 0) {

		w = (int) (DESIGNATOR_SIZE * u->xscaleFactor);

		VTransform(&ptbl[c->curRadarTarget].Sg, &c->XYZtoNED, &tmp1);
		VReverseTransform_(&tmp1, &c->trihedral, &tmp);

/* radar target is assumed to be ahead of us (tmp.z > 0.0) */

		tx = (u->v->Middl.x + (((int) (tmp.y * u->v->Scale.x / tmp.x)))) >> 2;
		ty = (u->v->Middl.y + (((int) (tmp.z * u->v->Scale.y / tmp.x)))) >> 2;


/*
 *  If the LCOS reticule was drawn by weapon_displaySelected(), then
 *  m61a1_lcos_last_pos contains its position on the screen.  We avoid to
 *  draw the target designator if this latter is too close to the LCOS.
 *
 *  If no LCOS reticle was plotted or if the distance to the LCOS reticle is
 *  sufficient, then plot a radar target designator box.
 */

		if( c->curWeapon == weapon_M61A1 ){
			x = m61a1_lcos_last_pos.x;
			y = m61a1_lcos_last_pos.y;
			d = sqrt((double) ((x - tx) * (x - tx) + (y - ty) * (y - ty)));
		}

		if( c->curWeapon != weapon_M61A1 || d > w * 2.5 ){
			draw_Type *dd;

			dd = draw_new();
			draw_segment(dd, tx - w, ty - w, tx + w, ty - w);
			draw_segment(dd, tx + w, ty - w, tx + w, ty + w);
			draw_segment(dd, tx + w, ty + w, tx - w, ty + w);
			draw_segment(dd, tx - w, ty + w, tx - w, ty - w);
			draw_stroke(dd, u->v, color);
			draw_free(dd);
		}
	}

	/* Bare nose pointer: */
	Alib_drawArc(u->v->w, u->v->focus.x - 2, u->v->focus.y - 2,
		4, 4, 0, 360 * 64, color);

}


#ifdef FIXME_TESTING_CODE_FOR_VLIB
#define DASH_LEN 7

static void draw_dashed_line2(Viewport *v, int x1, int y1, int x2, int y2)
{
	int len, n, i, d, x, y, nx, ny;
	double dx, dy;
	draw_Type *dd;

	len =sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2));
	n = (int) (len / DASH_LEN + 0.5);
	if( (n & 1) == 0 )
		n++;
	
	dx = (x2 - x1)/(double)n;
	dy = (y2 - y1)/(double)n;

	x = x1;  y = y1;

	dd = draw_new();
	d = 1;
	for( i = 1; i <= n; i++ ){
		nx = x1 + (int)(i*dx + 0.5);  ny = y1 + (int)(i*dy + 0.5);
		if( d ){
			draw_segment(dd, x, y, nx, ny);
		}
		x = nx;  y = ny;
		d = !d;
	}
	draw_stroke(dd, v, v->w->pixel[magentaColor]);
	draw_free(dd);
}


static void FIXME_draw_dashed_rect(Viewport *v, Alib_Rect *rr)
{
	Alib_Window *w;
	Alib_Rect r2, *r;


	w = v->w;
	Alib_setClipRect(w, rr);

	r2 = *rr;
	Alib_expandRect(&r2, -1, -1);
	r = &r2;
	//printf("FIXME: r=%d %d %d %d\n", r2.a.x, r2.a.y, r2.b.x, r2.b.y);

	draw_dashed_line2(v, r->a.x, r->a.y, r->b.x-1, r->a.y);
	draw_dashed_line2(v, r->a.x, r->a.y, r->a.x, r->b.y-1);
	draw_dashed_line2(v, r->a.x, r->b.y-1, r->b.x-1, r->b.y-1);
	draw_dashed_line2(v, r->b.x-1, r->a.y, r->b.x-1, r->b.y-1);

	draw_dashed_line2(v, v->focus.x, v->focus.y - 20, v->focus.x, v->focus.y + 20);
	draw_dashed_line2(v, v->focus.x - 20, v->focus.y, v->focus.x + 20, v->focus.y);
}
#endif


void
hud_draw(viewer * u)
{
	craft    *c;
	int       i, x, x1, y, fth, xscale1, yscale1;
	double    xscale, yscale, fontH, vel;
	char      buffer[80];
	Alib_Pixel     color;

	//FIXME_draw_dashed_rect(u->v, &u->v->rect);

	if( u->hud == NULL || !HUD(u)->enabled )
		return;
	
	c = u->c;

	if( ! damage_isFunctioning(c, SYS_HUD) )
		return;

	xscale = u->xscaleFactor;
	yscale = u->yscaleFactor;
	color = HUDColor;

	/* Stall warning */
	if( ! blink && c->damageBits & FLAG_STALL_WARN ){
		fontH = (int) (24 * yscale);
		int fontW = fontH;
		x = u->v->focus.x - 5 * fontW / 2;
		y = u->v->focus.y + 4 * fontH;
		strcpy(buffer, "STALL");
		VDrawStrokeString(u->v, x, y, buffer, strlen(buffer), fontH, color);
	}

	xscale1 = (int) (xscale * 2048.0);
	yscale1 = (int) (yscale * 2048.0);
	fontH = (int) (12.0 * yscale + 0.5);
	fth = (int) (18.0 * yscale + 0.5);

	if( curTime >= blink_toggle_time ){
		blink = ! blink;
		if( blink )
			blink_toggle_time = curTime + 0.05;
		else
			blink_toggle_time = curTime + 0.5;
	}

	if (u->viewDirection.x < 0.90)
		return;

	/* Angle of Attack */
	if ((vel = VMagnitude(&c->Cg)) < 50.0)
		sprintf(buffer, "a=0.0");
	else
		sprintf(buffer, "a=%.1f", units_RADtoDEG(c->alpha));
	x = u->v->focus.x + 150 * xscale1 / 2048;
	y = u->v->focus.y - u->velScale.length / 2 - (int) (yscale * 50);
	VDrawStrokeString(u->v, x, y, buffer, strlen(buffer), fontH, color);

	/* Accelerometer */
	x1 = u->v->focus.x - 220 * xscale1 / 2048;
	sprintf(buffer, "%4.1fg", - c->G.z);
	VDrawStrokeString(u->v, x1, y, buffer, strlen(buffer), fontH, color);

	/*
		APS lights:
	*/

	x = u->v->focus.x + 100.0*xscale;
	y = u->v->focus.y - 90.0*yscale;
	i = fontH * 2;  /* interline */

	/* Rate control law: */
	if( aps_rate_control_enabled(c) ){
		VDrawStrokeString(u->v, x, y, "Rate", 4, fontH, color);
	}

	/* Auto Pilot */
	if( aps_ap_enabled(c) && ( ! aps_ap_warn(c) || ! blink ) ){
		VDrawStrokeString(u->v, x, y, "VS/ALT", 6, fontH, color);
	}

	/* Auto Landing */
	y += i;
	if( aps_al_enabled(c) && ( ! aps_al_warn(c) || ! blink ) ){
		VDrawStrokeString(u->v, x, y, "Land", 4, fontH, color);
	}

	/* Auto Nav */
	y += i;
	if ( aps_an_enabled(c) && ( ! aps_an_warn(c) || ! blink ) ){
		VDrawStrokeString(u->v, x, y, "NAV", 3, fontH, color);
	}

	/* Auto Throttle */
	y += i;
	if ( aps_at_enabled(c) && ( ! aps_at_warn(c) || ! blink ) ){
		VDrawStrokeString(u->v, x, y, "THR", 3, fontH, color);
	}

	/* Auto Turn */
	y += i;
	if ( aps_aw_enabled(c) && ( ! aps_aw_warn(c) || ! blink ) ){
		VDrawStrokeString(u->v, x, y, "Turn", 4, fontH, color);
	}

	/* Auto Coord */
	y += i;
	if ( aps_ac_enabled(c) && ( ! aps_ac_warn(c) || ! blink ) ){
		VDrawStrokeString(u->v, x, y, "Coord", 5, fontH, color);
	}

	/* Vertical velocity */
	x = u->v->focus.x + 130 * xscale1 / 2048;
	y = u->velScale.yorg + (int) (2 * yscale * fontH);
	sprintf(buffer, "%+6d0", (int) (-c->Cg.z * 6.0));
	VDrawStrokeString(u->v, x, y, buffer, strlen(buffer), fontH, color);

	/*  Compass, altitude and airspeed cards */

	scale_drawCompass(u->v, &(u->hdgScale),
		units_RADtoDEG(c->showMag? pm_mag_heading(c) : c->curHeading) * 100.0);

	scale_draw(u->v, &(u->altScale), units_METERStoFEET(c->w.z));

	/* TAS: */
	//vel = c->VT;
	/* Airspeed along the x axis (the Pitot tube indicates this): */
	vel = c->VT * cos(c->alpha) * cos(c->beta);
	if( vel < 0.0 )  vel = 0.0;
	scale_draw(u->v, &(u->velScale), units_FPStoKT(vel));

	/*
		AutoThrottle
	*/
	if ( aps_at_enabled(c)
	&& ( ! aps_at_warn(c) || ! blink ) ){
		x = u->v->focus.x - 150 * xscale1 / 2048;
		y = u->hud_yCenter - 10 * yscale1 / 2048;
		sprintf(buffer, "AT%3d IAS", (int) (units_FPStoKT( aps_at_get_velocity(c) ) + 0.5));
		VDrawStrokeString(u->v, x, y,
			buffer, strlen(buffer), fontH, color);
	}

	/* Mach number */
	if (c->mach >= 0.20) {
		sprintf(c->leftHUD[1], "   %4.2f", c->mach);
	}
	else {
		strcpy(c->leftHUD[1], "");
	}

	x = u->v->focus.x - 220 * xscale1 / 2048;
	y = u->hud_yCenter - 200 * yscale1 / 2048;

	for (i = 0; i < 5; ++i) {
		VDrawStrokeString(u->v, x, y, c->leftHUD[i],
			strlen(c->leftHUD[i]), fontH, color);
		y += fth;
	}

	x = u->v->focus.x + 160 * xscale1 / 2048;
	y = u->hud_yCenter - 200 * yscale1 / 2048;

	for (i = 0; i < 5; ++i) {
		VDrawStrokeString(u->v, x, y, c->rightHUD[i],
			strlen(c->rightHUD[i]), fontH, color);
		y += fth;
	}

	doLadder(c, u, vel);

	doBankIndicator(c, u);

	doTurnAndSlipIndicator(c, u);
}


static int fspage_show = 0; /* FIXME: put in data struct tied to the viewer */

static void TermPrintLn(viewer * u, char *s)
{
	static int x = 0, y = 0, h = 0, dy = 0;

	if( s == NULL ){
		if( u->hud_mode )
			h = RectHeight(u->v->rect) / 40;
		else
			h = RectHeight(u->v->rect) / 25;
		if( h > 9 )
			h = 9;
		dy = (int) (h * 1.5 + 0.5);
		x = dy;
		y = dy;
		return;
	}

	VDrawStrokeString(u->v, x, y, s, strlen(s), h, whiteColor);
	y += dy;
}


void hud_free(viewer *u)
{
	if( u->hud == NULL )
		return;
	
	HUD(u)->next = free_list;
	free_list = u->hud;
	u->hud = NULL;
}


void   FSPageToggle()
{
	fspage_show = ! fspage_show;
}


static void
doFSPage(craft * c, viewer * u)
{

	double VAR;
	char buf[256], buf1[256];

	VSetClipRect(u->v, &u->v->rect);

	TermPrintLn(u, NULL);

	TermPrintLn(u, c->cinfo->name);
	TermPrintLn(u, c->cinfo->description);
	
	zulu_Date d;
	zulu_timestampToDate(departure_timestamp + curTime, &d);
	sprintf(buf, "%04d-%02d-%02dT%02d:%02d", d.year, d.month, d.day,
		d.hour, d.minutes);
	TermPrintLn(u, buf);

	sprintf (buf, "G: %+.2f %+.2f %+.2f", c->G.x, c->G.y, c->G.z);
	TermPrintLn(u, buf);

	sprintf (buf, "Ail/Elev/Rdr: %+.3f %+.3f %+.3f", c->Sa, c->Se, c->Sr);
	TermPrintLn(u, buf);

	sprintf (buf, "Euler: %+.1f %+.1f %+.1f",
		units_RADtoDEG(c->curRoll),
		units_RADtoDEG(c->curPitch),
		units_RADtoDEG(c->curHeading));
	TermPrintLn(u, buf);

	sprintf (buf, "Euler rates: %+.2f %+.2f %+.2f",
		units_RADtoDEG(c->p + c->q*tan(c->curPitch)*sin(c->curRoll) + c->r*tan(c->curPitch)*cos(c->curRoll)),
		units_RADtoDEG(c->q*cos(c->curRoll) - c->r*sin(c->curRoll)),
		units_RADtoDEG(c->q*sin(c->curRoll)/cos(c->curPitch) + c->r*cos(c->curRoll)/cos(c->curPitch)));
	TermPrintLn(u, buf);

	sprintf (buf, "Omega: %+.2f %+.2f %+.2f",
		units_RADtoDEG(c->p), units_RADtoDEG(c->q), units_RADtoDEG(c->r));
	TermPrintLn(u, buf);

	earth_latitudeToString(buf, sizeof(buf), c->w.latitude, earth_LLM_DMS);
	TermPrintLn(u, buf);

	earth_longitudeToString(buf, sizeof(buf), c->w.longitude, earth_LLM_DMS);
	strcat (buf, "   ");
	sprintf (buf1, "%d m", (int)(c->w.z+0.5));
	strcat (buf, buf1);
	TermPrintLn(u, buf);

	if (c->showMag) {
		VAR = units_RADtoDEG( c->indicatedLocalVAR );
		sprintf(buf, "VAR %.1f%c", fabs(VAR), VAR < 0 ? 'E' : 'W');
		TermPrintLn(u, buf);
	}

	sprintf(buf, "T = %.0f C", units_RankineToCelsius(c->air.t));
	TermPrintLn(u, buf);
	sprintf(buf, "P = %.0f hPa", c->air.p * units_LbToKgFactor * units_earth_g
			/ units_FootToMeterFactor * 0.01);
	TermPrintLn(u, buf);
	sprintf(buf, "rho = %.3f Kg/m^3", units_SlugToKg(c->air.rho)
			/ (units_FootToMeterFactor*units_FootToMeterFactor*units_FootToMeterFactor));
	TermPrintLn(u, buf);
	sprintf(buf, "c = %.0f m/s", c->air.mach1 * units_FootToMeterFactor);
	TermPrintLn(u, buf);
}


void
doFlightStatusPage(craft * c, viewer * u)
{

	if ( ! fspage_show )
		return;

	doFSPage(c, u);
	return;

}
