#include <string.h>
#include "air.h"
#include "damage.h"
#include "dis_if.h"
#include "draw.h"
#include "effects.h"
#include "init.h"
#include "inventory.h"
#include "../util/memory.h"
#include "pm.h"
#include "terrain.h"
#include "weapon.h"
#include "gear.h"
#include "../util/units.h"

#define ccip_IMPORT
#include "ccip.h"

static int select_mk82(craft *);
static int display_mk82(craft *, craftType *, viewer *, int, int);
static int update_mk82(craft *);
static int press_mk82(craft *);
static int release_mk82(craft *);

static weapon_Type mk82Desc =
{
	select_mk82,				/* select */
	update_mk82,				/* update */
	display_mk82,				/* display procedure */
	press_mk82,				    /* fire */
	release_mk82,				/* fire button release */
};

static char * ccip_update(craft * c);
static void ccip_kill(craft * c, char *reason);


static void
computeImpactPoint(craft *c, earth_LatLonAlt *ip)
{
	double t_sec, ground_speed_fps, loft_distance_feet;
	double T1_sec, T2_sec;
	double A, B, C, S4AC;
	double sin_course, cos_course;

	/*
	 * Local terrain skin location
	 */

	*ip = c->w;
	ip->z = terrain_localAltitude(c);

	A = - 0.5 * units_earth_g;
	B = - c->Cg.z;
	C = units_METERStoFEET ( c->w.z - ip->z );

	S4AC = sqrt ( B * B - 4.0 * A * C );

	/*
	 *  Compute time to impact using the solution to the quadratic formula:
	 *
	 *     x = 0.5 * ( - earth_g ) * t^2  +  v0 * t  +  x0
	 *
	 *   v0 = vertical velocity (up positive, feet-per-second)
	 *   x0 = height above surface (feet)
	 */

	if (S4AC >= 0.0) {

		T1_sec = ( - B + S4AC ) / ( 2.0 * A );
		T2_sec = ( - B - S4AC ) / ( 2.0 * A );

	}
	else {
		/* complex root(s) */
		T1_sec = T2_sec = 10.0; /* FIXME: ??? */
	}

	t_sec = (T1_sec > T2_sec) ? T1_sec : T2_sec;

	ground_speed_fps = sqrt ( c->Cg.x * c->Cg.x + c->Cg.y * c->Cg.y );

	loft_distance_feet = ground_speed_fps * t_sec;

	/*
	 *  Based on instantaneous velocity, not aircraft orientation
	 */

	cos_course = c->Cg.x / ground_speed_fps;
	sin_course = c->Cg.y / ground_speed_fps;

	earth_updateLatLon(ip, cos_course, sin_course, units_FEETtoMETERS ( loft_distance_feet ) );

}


static int count[manifest_MAXPLAYERS];
static int hasFired[manifest_MAXPLAYERS];

static int
select_mk82( craft * c )
{
	hasFired[c->pIndex] = 0;
	count[c->pIndex] = weapon_countOrdinance(c, weapon_MK82);
	return 1;
}

weapon_Type *
ccip_new(void)
{

	craftType *c;
	FILE     *f;
	dis_entity_type em1 = {2, 9, 225, 2, 73, 0, 0};
	dis_entity_type em2 = {0, 0, 0, 0, 0, 0, 0};

	c = inventory_craftTypeNew(NULL);
	c->name = memory_strdup( weapon_idToName(weapon_MK82) );

	mk82Desc.w = c;

	c->entityType = em1;
	c->altEntityType = em2;

	/*
	 *  Unverified parameters calculated using DATCOM recommendations
	 */

	c->CDOrigin = 0.081;
	c->CDFactor = 0.0;
	c->CDBOrigin = 0.0;
	c->CDBFactor = 0.0;
	c->emptyWeight = 510.0;
	c->wingS = 1.0; /* FIXME */
	c->maxFuel = 0.0;
	c->maxThrust = 0.0;
	c->spFuelConsump = 0.0;

	/*
	 * dCL/da = 4.09
	 * dCm,cg/da = -21.4
	 */

	f = init_fopen("missiles/sa10.obv", "r");  /* FIXME */
	c->object = VReadObject(f);
	fclose(f);

	return &mk82Desc;
}


static int
dropOrdinance ( craft *c, int ind )
{
	craft *m;
	int i;
	VPoint    s, s1;
	VPoint    cY, mX, mY, mZ;
	double    v;
	double    disLocation[3];
	VPoint    velocity;
	double    disVelocity[3];
	double    disZeroVec[3];
	double    disOrientation[3];

	/*
	 *  Find an empty projectile entry
	 */

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

	if (i == manifest_MAXPROJECTILES)
		return -1;

	m->cinfo = inventory_craftTypeSearchByZoneAndName(NULL, "MK82");
	m->fuel = 0.0;
	m->curThrust = 0.0;
	m->owner = c->pIndex;
	m->payload = 0.0;
	m->armTimer = 0.0;
	m->prevSg = c->Sg;

/*
 *  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 bomb local Z axis from
 *              plane's-y-axis CROSS bomb's-unit-velocity-vector
 *      (3)  calculate bomb's Y axis.
 */

	if ((v = VMagnitude(&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;

		pm_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 + units_FEETtoMETERS(s.x);
	m->Sg.y = c->prevSg.y + units_FEETtoMETERS(s.y);
	m->Sg.z = c->prevSg.z + units_FEETtoMETERS(s.z);
	earth_XYZToLatLonAlt(&m->Sg, &m->w);
	earth_generateWorldToLocalMatrix(&m->w, &m->XYZtoNED);
	m->armTimer = m->cinfo->armDelay;
	m->flags = 0;
	m->createTime = curTime;

	m->curRadarTarget = -1;
	m->update = ccip_update;
	m->kill   = ccip_kill;

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

	{
		VPoint    tmp;

		disLocation[0] = m->Sg.x;
		disLocation[1] = m->Sg.y;
		disLocation[2] = m->Sg.z;
		tmp.x = units_FEETtoMETERS(m->Cg.x);
		tmp.y = units_FEETtoMETERS(m->Cg.y);
		tmp.z = units_FEETtoMETERS(m->Cg.z);
		VReverseTransform_(&tmp, &m->XYZtoNED, &velocity);
		disVelocity[0] = velocity.x;
		disVelocity[1] = velocity.y;
		disVelocity[2] = velocity.z;
		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_if_entityEnter(c->force, m,
						&m->cinfo->entityType,
						&m->cinfo->altEntityType,
						disLocation, disVelocity,
						disZeroVec, disOrientation,
						disZeroVec, &m->disId);
	}
	return 0;
}


static int
update_mk82(craft * c)
{
	int i;

	if( ! hasFired[c->pIndex] )
		return 1;
	
	hasFired[c->pIndex] = 0;  /* reset fire request */

	/*
	 *  Bombs won't drop if we have "Weight on wheels"
	 *  or if we run out of ammunition.
	 */

	if( gear_someWheelGroundContact(c)
	|| count[c->pIndex] <= 0 )
		return 1;

	/* Get station from which to drop the bomb: */
	i = weapon_getReadyStation(c, weapon_MK82);
	if (i < 0){
		fprintf(stderr, "Oops. Can't find an MK82\n");
		return 1;
	}

	/*
	 *  Decrement bombs counter.
	 *  In arcade mode, we never run out of ammunition
	 */

	if ( ! arcadeMode ) {
		c->station[i].id = -1;
		count[c->pIndex]--;
	}

	/* Drop bomb from station i: */
	dropOrdinance (c, i);
	/* playSound(c, SoundBombDrop); */

	return 1;
}

static int
display_mk82(craft *c, craftType *ct, viewer *u, int fpm_x, int fpm_y)
{
	char      s[16];
	earth_LatLonAlt ip_wc;  /* impact point (world) */
	VPoint    ip_gc;         /* impact point (geocentric XYZ coords) */
	VPoint    ip_ned;        /* impact point (NED coords) */
	VPoint    ip_eye;        /* impact point (eye space) */
	double    x0, y0;        /* cross center (viewport coords, pixels) */
	double    xscale, yscale;  /* world-to-viewport scale factors */

/*
 *  Update HUD display strings.
 */

	sprintf(s, "%d %s", count[c->pIndex], weapon_idToName(weapon_MK82));
	strcpy(c->leftHUD[3], s);
	strcpy(c->leftHUD[2], "");
	strcpy(c->leftHUD[4], "");

	/* Compute impact point ip_wc (world coords): */
 	computeImpactPoint(c, &ip_wc);

	/* Compute impact point ip_gc (geocentric coords XYZ): */
	earth_LatLonAltToXYZ(&ip_wc, &ip_gc);

	/* Compute impact point ip_eye (pilot's eye coords): */
	VTransform(&ip_gc, &c->XYZtoNED, &ip_ned);
	VReverseTransform(&ip_ned, &c->AXYZtoNED, &ip_eye);
	/* Pilot's head position correction: */
	ip_eye.x -= units_FEETtoMETERS(c->cinfo->viewPoint.x);
	ip_eye.y -= units_FEETtoMETERS(c->cinfo->viewPoint.y);
	ip_eye.z -= units_FEETtoMETERS(c->cinfo->viewPoint.z);

	/*
		Display CCIP circle only if the impact point is ahead of us and
		closer than 45 DEG from HUD center:
	*/

	if( ip_eye.x > 0.0
	&& ip_eye.x * ip_eye.x > ip_eye.y * ip_eye.y + ip_eye.z * ip_eye.z ){

		xscale = u->v->xres * u->v->dist / ip_eye.x;
		yscale = u->v->yres * u->v->dist / ip_eye.x;

		/* Center of the CCIP circle: */
		x0 = u->v->focus.x + xscale * ip_eye.y;
		y0 = u->v->focus.y + yscale * ip_eye.z;

		draw_Type *dd = draw_new();

		/* Draw CCIP circle, 5 mm radius: */
		double r = 0.005 /* m */ * u->v->xres * u->zoom/100.0;
		draw_circle(dd, x0, y0, r);

		/* Draw bomb fall line: */
		double nx = x0 - fpm_x;
		double ny = y0 - fpm_y;
		double n = sqrt(nx * nx + ny * ny);
		double fpm_r = 8.0 * u->xscaleFactor; /* FPM circle radius */
		/* Draw BFL only if FPM and CCIP are far enough: */
		if( n > fpm_r + r ){
			nx /= n;
			ny /= n;
			draw_segment(dd,
				fpm_x + nx * fpm_r,   fpm_y + ny * fpm_r,
				fpm_x + nx * (n - r), fpm_y + ny * (n - r));
		}

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

/*
 *  Return TRUE if we are recommending a bomb drop.
 */

	return 0;
}

static int
press_mk82(craft *c)
/* Fire! */
{
	hasFired[c->pIndex] = 1;
	return 1;
}

static int
release_mk82(craft *c)
{
	return 0;
}


/**
 * Gravity bomb dynamics update.
 * @param c
 * @return "Exploded on the ground" or NULL if still falling down.
 */
static char *
ccip_update(craft * c)
{
	double    q;
	double    FDrag, FWeight;
	double    dNorth, dEast, dmag;
	VPoint    F, Fg;

	if (c->w.z < terrain_localAltitude(c)) {
		c->Cg = (VPoint) {0,0,0};
		c->w.z = terrain_localAltitude(c);
		dis_if_updateLocal(c);
		return "exploded on the ground";
	}

/*
 *  Compute the resultant force vector on the bomb.
 */

	air_update(&c->air, units_METERStoFEET(c->w.z));
	c->prevSg = c->Sg;
	c->VT = VMagnitude(&c->Cg);
	q = c->air.rho * c->cinfo->wingS * c->VT * c->VT * 0.5;
	FDrag = c->cinfo->CDOrigin * q;

	F.x = c->curThrust - FDrag;
	F.y = 0.0;
	F.z = 0.0;

/*
 *  Now calculate changes in position (Sg) and velocity (Cg).
 */

	VTransform(&F, &c->AXYZtoNED, &Fg);
	FWeight = c->cinfo->emptyWeight;
	Fg.z += FWeight;
	pm_calcGForces(c, &Fg, FWeight);

/*
 *  Update the bomb's position and velocity.
 */

	dNorth = units_FEETtoMETERS(c->Cg.x * deltaT + Fg.x / FWeight
						  * units_earth_g * halfDeltaTSquared);
	dEast = units_FEETtoMETERS(c->Cg.y * deltaT + Fg.y / FWeight
						 * units_earth_g * halfDeltaTSquared);
	c->w.z -= units_FEETtoMETERS(c->Cg.z * deltaT + Fg.z / FWeight
						   * units_earth_g * halfDeltaTSquared);

	dmag = sqrt(dNorth * dNorth + dEast * dEast);

	earth_updateLatLon(&c->w, dNorth / dmag, dEast / dmag, dmag);
	earth_LatLonAltToXYZ(&c->w, &c->Sg);
	earth_generateWorldToLocalMatrix(&c->w, &c->XYZtoNED);

	c->Cg.x += Fg.x / FWeight * units_earth_g * deltaT;
	c->Cg.y += Fg.y / FWeight * units_earth_g * deltaT;
	c->Cg.z += Fg.z / FWeight * units_earth_g * deltaT;
	
	dis_if_updateLocal(c);

	return NULL;
}


/**
 * Returns the distance between the two points.
 * Return a negative value if the distance is greater then the range.
 */
static double distance(VPoint * a, VPoint * b, double range)
{
	double dx = a->x - b->x;
	if( fabs(dx) > range )  return -1.0;
	
	double dy = a->y - b->y;
	if( fabs(dy) > range )  return -1.0;
	
	double dz = a->z - b->z;
	if( fabs(dz) > range )  return -1.0;
	
	double r = sqrt( dx * dx + dy * dy + dz * dz );

	if( r > range )
		return -1.0;

	return r;
}


/**
 * Gravity bomb explodes and gets removed from simulation.
 * Test for damage of any local craft.
 * Send explosion DIS event toward any remote craft that might be involved;
 * if no potential remote target is involved, send explosion DIS event anyway
 * but without target, just to display an explosion on any remote client.
 * @param c
 * @param reason
 */
static void ccip_kill(craft * bomb, char *reason)
{
	double    worldLocation[3], entityLocation[3];
	VPoint    worldVel, localVel;
	int i;
	craft *target;
	double r, explosion_diameter_meters;
	
	worldLocation[0] = bomb->Sg.x;
	worldLocation[1] = bomb->Sg.y;
	worldLocation[2] = bomb->Sg.z;

/*
 *  killBomb'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;
*/
	
	entityLocation[0] = bomb->Sg.x;
	entityLocation[1] = bomb->Sg.y;
	entityLocation[2] = bomb->Sg.z;

	localVel.x = units_FEETtoMETERS(bomb->Cg.x);
	localVel.y = units_FEETtoMETERS(bomb->Cg.y);
	localVel.z = units_FEETtoMETERS(bomb->Cg.z);
	VReverseTransform_(&localVel, &bomb->XYZtoNED, &worldVel);
	
	/* Look for damaged targets: */
	for( i = 0; i < manifest_MAXPLAYERS; i++ ){
		target = &ptbl[i];

		/* Ignores free entries in players table: */
		if( target->type == CT_FREE )
			continue;

		/* Distance of the blast from this craft (m): */
		r = distance(&target->Sg, &bomb->Sg, 900.0 /* m */);
		if( r < 0.0 )
			continue;
		
		if( target->type == CT_PLANE || target->type == CT_DRONE ){
			
			/* Local craft. Absorb damage: */
			if( damage_absorbDISDamage(target, 
				&bomb->cinfo->entityType, 0, 0, 
				r,
				0.0 /* dummy */,
				&explosion_diameter_meters) == 0
			){
				target->kill(target, "Mark 82 bomb");
			}
		
		} else if( target->type == CT_DIS_PLANE ){
		
			/* Remote craft. Send detonation event, but don't remove bomb yet: */
			dis_if_detonation(&bomb->cinfo->entityType,
					ptbl[bomb->owner].disId,
					target->disId,
					dis_if_ID_NONE, // don't remove remote bomb
					worldLocation,
					entityLocation,
					(double *) &worldVel);
		}
	}
	
	/* Send explosion to the remote clients and remove their bomb entry: */
	dis_if_detonation(&bomb->cinfo->entityType,
			ptbl[bomb->owner].disId,
			dis_if_ID_NONE,
			bomb->disId,
			worldLocation,
			entityLocation,
			(double *) &worldVel);
	
	/* Set local explosion effect: */
	effects_new_explosion(&bomb->Sg, &(VPoint){0.0, 0.0, 0.0}, 50.0, 15.0, 1.0);
	
	/* Remove bomb: */
	dis_if_entityExit(bomb->disId);
	pm_hud_strings_free(bomb);
	bomb->type = CT_FREE;
}

