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

#include "pm.h"
#ifdef HAVE_DIS
#include "dis.h"
#endif

typedef struct _entry {
	double    time;
	double    min;
	craft    *c;
	VPoint    Sg;
	VPoint    rvel;
	struct _entry *next;
} entry;

extern int mdebug;
extern void lookForCannonImpacts(craft * m);
extern int absorbDamage();
int       isMissileHit(double min, craft * c);

int
inCloud(craft * c)
{
	int       state;

	if (c->w.z > ctop) {
		state = 0;
	}
	else if (c->w.z > cbase) {
		state = 1;
	}
	else {
		state = 2;
	}
	return state;
}

int
fireMissile(craft * c, int ind)
{

	register craft *m;
	register int i;
	VPoint    s, s1;
	VPoint    cY, mX, mY, mZ;
	double    v;

#ifdef HAVE_DIS
	double    disLocation[3];
	double    disVelocity[3];
	double    disZeroVec[3];
	double    disOrientation[3];

#endif

	for ((i = 0, m = &mtbl[0]); i < MAXPROJECTILES; (++i, ++m))
		if (m->type == CT_FREE) {
			m->type = CT_MISSILE;
			break;
		}

	if (i == MAXPROJECTILES)
		return -1;

	m->cinfo = lookupCraft("aim-9m");
	m->fuel = m->cinfo->maxFuel;
	m->curThrust = m->cinfo->maxThrust;
	m->owner = c->pIndex;

	m->gvs_instance = (GVS_OBI) NULL;

/*
 *  Build trihedral based on the launching aircraft's current velocity vector
 *  rather than simply it's current direction vector.
 *
 *      (1)  build a unit velocity vector.
 *      (2)  calculate missiles local Z axis from
 *              plane's-y-axis CROSS missile's-unit-velocity-vector
 *      (3)  calculate missile's Y axis.
 */

	if ((v = mag(c->Cg)) < 1.0) {
		m->trihedral = c->trihedral;
		m->curRoll = c->curRoll;
		m->curPitch = c->curPitch;
		m->curHeading = c->curHeading;
	}
	else {
		mX = c->Cg;
		mX.x /= v;
		mX.y /= v;
		mX.z /= v;
		cY.x = c->trihedral.m[0][1];
		cY.y = c->trihedral.m[1][1];
		cY.z = c->trihedral.m[2][1];

		VCrossProd(&mX, &cY, &mZ);
		VCrossProd(&mZ, &mX, &mY);

		m->trihedral.m[0][0] = mX.x;
		m->trihedral.m[1][0] = mX.y;
		m->trihedral.m[2][0] = mX.z;
		m->trihedral.m[0][1] = mY.x;
		m->trihedral.m[1][1] = mY.y;
		m->trihedral.m[2][1] = mY.z;
		m->trihedral.m[0][2] = mZ.x;
		m->trihedral.m[1][2] = mZ.y;
		m->trihedral.m[2][2] = mZ.z;

		euler(m);
	}

	m->Cg = c->Cg;
	VTransform(&(c->cinfo->wStation[ind]), &(c->trihedral), &s1);
	VReverseTransform_(&s1, &c->XYZtoNED, &s);
	m->Sg.x = c->prevSg.x + FEETtoMETERS(s.x);
	m->Sg.y = c->prevSg.y + FEETtoMETERS(s.y);
	m->Sg.z = c->prevSg.z + FEETtoMETERS(s.z);
	DISGeocentricToWorldCoordinates
		((dis_world_coordinates *) & m->Sg, &m->w);
	m->prevw = m->w;
	GenerateWorldToLocalMatrix(&m->w, &m->XYZtoNED);
	m->armTimer = m->cinfo->armDelay;
	m->flags = FL_HAS_GYRO;
	m->createTime = curTime;

/*
 * kludge
 */

	m->curRadarTarget = c->curRadarTarget;

#ifdef HAVE_DIS

/*
 *  ACM missiles are DIS "tracked munitions", so we are
 *  responsible for sending entity state PDU's for them
 */

	if (disInUse) {
		VPoint    tmp;

		disLocation[0] = m->Sg.x;
		disLocation[1] = m->Sg.y;
		disLocation[2] = m->Sg.z;
		tmp.x = FEETtoMETERS(m->Cg.x);
		tmp.y = FEETtoMETERS(m->Cg.y);
		tmp.z = FEETtoMETERS(m->Cg.z);
		VReverseTransform_(&tmp, &m->XYZtoNED, (VPoint *) & disVelocity[0]);
		disZeroVec[0] = 0.0;
		disZeroVec[1] = 0.0;
		disZeroVec[2] = 0.0;
		disOrientation[0] = m->curHeading;
		disOrientation[1] = m->curPitch;
		disOrientation[2] = m->curRoll;
		dis_entityEnter(c->team, m,
						&m->cinfo->entityType,
						&m->cinfo->altEntityType,
						disLocation, disVelocity,
						disZeroVec, disOrientation,
						disZeroVec, &m->disId);
#ifdef DIS_DEBUG
		printf("Missile Entering m%d %d\n", i, m->disId);
#endif
	}
#endif
	return 0;
}

int
killMissile(craft * c, craft * target)
{
#ifdef HAVE_DIS
	double    worldLocation[3], entityLocation[3];
	int       target_eid, missile_eid;
	VPoint    worldVel, tmp;

	if (c->type == CT_MISSILE || c->type == CT_CANNON) {

		if (target == NULL) {
			target_eid = DIS_ID_NONE;
		}
		else {
			target_eid = target->disId;
		}

		if (c->type == CT_CANNON) {
			missile_eid = DIS_ID_NONE;
		}
		else {
			missile_eid = c->disId;
		}

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

/*
 *  killMissile's calling sequence needs to be updated to allow for the
 *  entity detonation location to be passed.
 */

		entityLocation[0] = 0.0;
		entityLocation[1] = 0.0;
		entityLocation[2] = 0.0;

		tmp.x = FEETtoMETERS(c->Cg.x);
		tmp.y = FEETtoMETERS(c->Cg.y);
		tmp.z = FEETtoMETERS(c->Cg.z);
		VReverseTransform_(&tmp, &c->XYZtoNED, &worldVel);

		if (disInUse) {
			dis_detonation(&c->cinfo->entityType,
						   ptbl[c->owner].disId,
						   target_eid,
						   missile_eid,
						   worldLocation,
						   entityLocation,
						   (double *) &worldVel);
		}
	}
#endif
	c->type = CT_FREE;
	return 0;
}

int
lookForImpacts(void)
{

	craft    *c, *m;
	entry     p[MAXPLAYERS], *list, *q, *r, *rprev;
	VPoint    v, s0;
	double    t, d, explosion_diameter_meters;
	int       i, j;

	for (m = mtbl, i = 0; i < MAXPROJECTILES; ++i, ++m) {

		if (m->type == CT_CANNON) {
			lookForCannonImpacts(m);
			continue;
		}
		else if (m->type != CT_MISSILE || m->armTimer > 0.0)
			continue;

		list = (entry *) NULL;
		for (c = ptbl, j = 0; j < MAXPLAYERS; ++j, ++c) {

			if (c->type == CT_FREE)
				continue;

/*
 * Reduce the relative motion of this object to a the parametric system
 * of equations:
 *              x(t) = vx * t + s0x
 *              y(t) = vy * t + s0y
 *              z(t) = vz * t + s0z
 *
 * We can then compute the time of perigee (closest pass) along with
 * the associated minimum distance.
 */

			v.x = c->Sg.x - c->prevSg.x - m->Sg.x + m->prevSg.x;
			v.y = c->Sg.y - c->prevSg.y - m->Sg.y + m->prevSg.y;
			v.z = c->Sg.z - c->prevSg.z - m->Sg.z + m->prevSg.z;
			s0.x = c->prevSg.x - m->prevSg.x;
			s0.y = c->prevSg.y - m->prevSg.y;
			s0.z = c->prevSg.z - m->prevSg.z;

/*
 * Compute time of minimum distance between the two objects (note that units
 * here are UPDATE_INTERVAL seconds).
 */

			t = -(v.x * s0.x + v.y * s0.y + v.z * s0.z) /
				(v.x * v.x + v.y * v.y + v.z * v.z);

			if (mdebug)
				printf("perigee in %g seconds with player %d\n",
					   t * deltaT, j);

/*
 *  If the closest pass occurs during this update interval, check for a hit.
 *  We'll build a linked list of all craft that this projectile may strike
 *  during this period, arranged in ascending order by time of "perigee"
 *  (closest pass).  We'll then test for strikes.  If a projectile misses
 *  the first object, then it may have struck subsequent objects in the
 *  list ...
 */

/*
 *  One special case occurs when a target or missile's turn suddenly
 *  changes the perigee time from positive to negative.  If the missile
 *  is within hitting range at t=0 and the time of perigee is negative,
 *  then zap 'em.
 */

			if (t < 0.0) {
				d = sqrt(s0.x * s0.x + s0.y * s0.y +
						 s0.z * s0.z);
				if (isMissileHit(d, c)) {
					t = 0.0;
				}
			}

			if (t >= 0.0 && t <= 1.0) {
				q = &p[j];

				q->Sg = m->prevSg;
				q->Sg.x += (m->Sg.x - m->prevSg.x) * t;
				q->Sg.y += (m->Sg.y - m->prevSg.y) * t;
				q->Sg.z += (m->Sg.z - m->prevSg.z) * t;

				q->rvel = v;

				if (list == (entry *) NULL) {
					q->next = list;
					list = q;
				}
				else if (list->time > t) {
					q->next = list;
					list = q;
				}
				else {
					for (rprev = list, r = list->next; r != (entry *) NULL;) {
						if (r->time > t)
							break;
						rprev = r;
						r = r->next;
					}
					rprev->next = q;
					q->next = r;
				}
				q->time = t;
				q->c = c;
				q->min = sqrt(pow(v.x * t + s0.x, 2.0) +
							  pow(v.y * t + s0.y, 2.0) +
							  pow(v.z * t + s0.z, 2.0));
#ifdef FLIGHTDEBUG
				if (mdebug)
					printf("perigee %g feet; craft %d.\n",
						   q->min, j);
#endif
			}
		}

/*
 *  Now look for missile hits in the list of perigees.
 */

		for (r = list; r != (entry *) NULL; r = r->next)
			if (isMissileHit(r->min, r->c)) {
				m->Sg = r->Sg;	/* Set detonation point for killMissile() */
				killMissile(m, r->c);
#ifdef HAVE_DIS
				/* can only damage local player */
				if (r->c->type != CT_DIS_PLANE)
#endif
					if (absorbDISDamage(r->c, 
										&m->cinfo->entityType, 0, 0, 
										r->min,
										mag(r->rvel),
										&explosion_diameter_meters) == 0) {
						killPlayerEx(r->c,
									 "Your aircraft was destroyed by an air-to-air missile.",
									 "The missile's warhead detonated %.1f meters from your plane.",
									 r->min);
					}
				newExplosion(&(r->Sg), &r->rvel, 5.0, 10.0, 3.0);
				break;
			}
	}

	return (0);
}

/*ARGSUSED */
int
isMissileHit(double min, craft * c)
{

	return (min < 15.0) ? 1 : 0;
}

#define IRMaxRange	FEETtoMETERS(15.0 * NM)

int
isIRVisible(craft * m, craft * c, VPoint * t, double IRScanSlope)
{

	VPoint    relPos, tmp;
	int       cstate, mstate;

	if (c->type == CT_FREE)
		return 0;

/*
 *  If the seeker is in clouds, or the target is not at the same level
 *  (e.g seeker is above clouds, but target is below), then the target is
 *  not IR visible.
 */

	if ((mstate = inCloud(m)) == 1) {
		return 0;
	}

	if ((cstate = inCloud(c)) != mstate) {
		return 0;
	}

	VTransform(&c->prevSg, &m->XYZtoNED, &tmp);
	VReverseTransform_(&tmp, &m->trihedral, t);

	if (sqrt(t->x * t->x + t->y * t->y + t->z * t->z) > IRMaxRange)
		return 0;

	if (t->x <= 0.0)
		return 0;

	relPos.z = t->z / (t->x * IRScanSlope);
	relPos.y = t->y / (t->x * IRScanSlope);

	return (sqrt(relPos.z * relPos.z + relPos.y * relPos.y) > 1.0) ? 0 : 1;
}

int
getIRTarget(craft * c, VPoint * t, double scanSlope)
{

	int       i, n;
	craft    *p;
	VPoint    tNew, tMin;
	double    m1, min;

	if (c->curRadarTarget != -1 &&
		isIRVisible(c, &ptbl[c->curRadarTarget], t, scanSlope))
		return c->curRadarTarget;

/*
 *  Look for a target.  Designate the closest one as a new target.
 */

	min = 1000000.0;
	n = -1;
	for (i = 0, p = ptbl; i < MAXPLAYERS; ++i, ++p) {
		if (p == c)
			continue;
		if (p->type != CT_FREE)
			if (isIRVisible(c, p, &tNew, scanSlope)) {
				m1 = mag(tNew);
				if (m1 < min) {
					n = i;
					min = m1;
					tMin = tNew;
				}
			}
	}

	*t = tMin;
	return n;
}

/*
 *  Track target using proportional navigation guidance (N = 4).
 */

#define AIM9SLOPE	0.57735

void
trackTarget(craft * c)
{

	VMatrix   mtx, mtx1;
	VPoint    t, t1, v, vrel, zeroVec = {0,0,0};
	double    h, maxTurn, omegap, omegay;
	double    hs;
	double    deltaRoll, deltaPitch, deltaYaw;
	craft    *target;

/*
 *  Now let's get to target tracking;  the missile won't start tracking until
 *  0.60 seconds has elapsed.  Then, if we don't already have a target
 *  designated, get one.
 */

	if (curTime - c->createTime < 0.60) {
		deltaPitch = 0.0;
		deltaYaw = 0.0;
		goto change;
	}
	else if ((c->curRadarTarget = getIRTarget(c, &t, AIM9SLOPE)) == -1) {

/*
 *  Not target; missile goes ballistic
 */

		deltaPitch = 0.0;
		deltaYaw = 0.0;
		goto change;

#ifdef FLIGHTDEBUG
		if (mdebug)
			printf("Missile elects to self-destruct\n");
#endif
		newExplosion(&(c->Sg), &zeroVec, 5.0, 10.0, 3.0);
		killMissile(c, (craft *) NULL);
		return;
	}

/*
 *  We'll steer towards the target at a rate proportional to the
 *  rate of change of the target's position in the missile's XZ (pitch)
 *  and XY (yaw) planes.
 */

	target = &ptbl[c->curRadarTarget];

	v.x = target->Cg.x - c->Cg.x;
	v.y = target->Cg.y - c->Cg.y;
	v.z = target->Cg.z - c->Cg.z;

	t.x = METERStoFEET(t.x);
	t.y = METERStoFEET(t.y);
	t.z = METERStoFEET(t.z);

	VReverseTransform_( &v, &c->trihedral, &vrel );

	hs = t.x * t.x + t.y * t.y;

	omegay = (vrel.y * t.x - vrel.x * t.y) / hs;

	omegap = (vrel.z * hs - t.z * (vrel.x * t.x + vrel.y * t.y)) /
		(sqrt(hs) * (hs + t.z * t.z));

	deltaPitch = omegap * 4.0 * deltaT;
	deltaYaw   = omegay * 4.0 * deltaT;

	h = sqrt( deltaPitch * deltaPitch + deltaYaw * deltaYaw );

/*
 *  We'll constrain missile turns to about 20 degree/second unless it's velocity
 *  would make that greater than a 25g load factor.
 */

	if ( c->VT > 0.0 ) {
		maxTurn = (earth_g / c->VT) * sqrt( 25.0 * 25.0 - 1.0 );
	}
	else {
		maxTurn = 0.0;
	}

	if (maxTurn > DEGtoRAD(20.0)) {
		maxTurn = DEGtoRAD(20.0);
	}
	maxTurn *= deltaT;

#ifdef FLIGHTDEBUG
	if (mdebug)
		printf("\nturn rate = %g; maxTurn = %g\n", h, maxTurn);
#endif

	if (h > maxTurn) {
		deltaPitch *= maxTurn / h;
		deltaYaw *= maxTurn / h;
	}

/*
 *  Re-orient the missile and velocity vector.
 */

  change:

	deltaRoll = 0.0;

#ifdef FLIGHTDEBUG
	if (mdebug) {
		printf("Missile changes: pitch/yaw: %g %g (deg).\n",
			   RADtoDEG(deltaPitch), RADtoDEG(deltaYaw));
		printf("position [%g, %g, %g]\n", t.x, t.y, t.z);
		printf("target pitch/yaw rates: %g, %g (deg/sec)\n",
			   RADtoDEG(omegap), RADtoDEG(omegay));
	}
#endif

	buildEulerMatrix(deltaRoll, -deltaPitch, deltaYaw, &mtx);

	VReverseTransform_(&c->Cg, &c->trihedral, &t);
	VTransform(&t, &mtx, &t1);
	VTransform(&t1, &c->trihedral, &c->Cg);

	VMatrixMultByRank(&mtx, &c->trihedral, &mtx1, 3);
	c->trihedral = mtx1;
	euler(c);

}
