/*
 *  Copyright (C) 1995  Mats Lofkvist  CelsiusTech Electronics AB
 *
 *  With additions by Riley Rainey, 1995-1998
 *
 *  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 <assert.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <unistd.h>
#include <sys/time.h>

#include "../util/error.h"
#include "../util/memory.h"
#include "../util/prng.h"
#include "../dis/dis/disx.h"
#include "../dis/dis/datum.h"
#include "inventory.h"
#include "pm.h"
#include "update.h"

#define dis_if_IMPORT
#include "dis_if.h"

/*
 * If we may trust DIS timestamps from incoming DIS packets. If true, absolute
 * timestamp from incoming DIS packets is used to build an actual Unix timestamp
 * to mark the state of the entities, performing DR calculations and discarding
 * stale entities (for example, from suddenly disconnected remote hosts). But
 * all this may fail if some remote server is misconfigured.
 * If set to false, DIS timestamp is ignored and out timestamp is assumed instead.
 * 
 * FIXME: it is simpler and safer to simply ignore the DIS timestamp
 * (and in general, we can't trust on any data coming from the net).
 */
#define dis_if_USE_DIS_TIMESTAMP 0

/**
 * Default dead reckoning linear threshold (m). A new state packet is sent if
 * the position of the entity as calculated using the stated DR algorithm applied
 * to the last sent state packet deviates from the current position more than that.
 */
#define DEFAULT_LOCATION_THRESHOLD      2.0

/**
 * Default dead reckoning rotational threshold (DEG). A new state packet is sent if
 * the rotation of the entity as calculated using the stated DR algorithm applied
 * to the last sent state packet deviates from the current rotation angles more
 * than that.
 */
#define DEFAULT_ORIENTATION_THRESHOLD   (3.0 * M_PI / 180.0)

/**
 * Send anyway a fresh state packet within this time interval since last state
 * packet sent (s).
 */
#define dis_if_SEND_TIMEOUT_SECONDS	4.8

/** Entities silent from so much time are stale and then removed (s). */
#define dis_if_RECV_TIMEOUT_SECONDS	12.0

/**
 * Client callback through which ACM is notified new remote entity enters
 * simulation.
 */
static dis_if_EntityEnterCb entityEnterCb;

/**
 * Client callback through which ACM is notified an existing remote entity
 * exits simulation.
 */
static dis_if_DetonationCb detonationCb;

/**
 * Client callback through which ACM is notified a remote cannon burst has been
 * fired.
 */
static dis_if_CannonFireCb cannonFireCb;

static double locationThreshold = DEFAULT_LOCATION_THRESHOLD;
static double orientationThreshold = DEFAULT_ORIENTATION_THRESHOLD;

static int exercise;
static int site;
static int application;

/*
 * Automatic site ID assignment. When the user sets -1 as site ID we generate
 * a random value then we enter in "silent mode" listening all the incoming
 * packets for a few seconds. During this time we look at all the currently
 * used site IDs we see looking for collisions and, in that case, we generate
 * another random site ID.
 * 
 * When a relay server is used, we must also send some dummy initial packet
 * to subscribe our host and start getting routed packets; this subscription
 * has to be periodically renewed because the relay removes silent clients.
 */

/* If randomly generated site ID is still under validation for collisions. */
static int validating_site_id;
/* When validation started (time from time()). */
static int validating_site_id_since;
/* Duration of the validation (s). */
#define VALIDATING_SITE_ID_PERIOD 15
/* If we have a relay server requiring subscription (i.e., a dummy packet) in
 * order to start receiving packets from which read used site IDs. */
static int validating_site_id_relay_have;
/* Last subscription sent to the relay server. Need to renew subscription
 * because the relay server removes stale clients! */
static int validating_site_id_relay_last_pkt;

static disx_ApplicationInfo *app;

static  dis_if_OutstandingRequestInfo *request_chain_head = 0;
static  dis_if_OutstandingRequestInfo *request_chain_tail = 0;

/**
 * If network enabled. If false, does not tries to connect, send or receive
 * anything.
 */
static int network_enabled = 1;

/**
 * We must limit our PDU transmission rate on lower bandwidth connections.
 * 0.0 turns off limiting.
 */
static double bandwidth_bps = 0.0;

static double theTime;
static int absoluteTime = 0;

/**
 * All the allocated entities of type dis_if_Entity managed by this module.
 */
static varray_Type *entities;

/** No. of local entities. */
static int    stats_local_entities_no;
/** No. of remote entities. */
static int    stats_remote_entities_no;
/** Timestamp start counting incoming processed packets. */
static double stats_processed_packets_count_t0;
/** Counter of incoming processed packets. */
static int    stats_processed_packets_count;
/** Current estimated incoming processed packets per second. */
static double stats_processed_packets_per_second;

static int acknowledgePDU (dis_acknowledge_pdu *pdu);
static int transferControlPDU (dis_transfer_control_pdu *pdu);
static int setDataPDU (dis_set_data_pdu *pdu);
static int startPDU (dis_start_pdu *);
static int stopPDU (dis_stop_pdu *);
static int initializeEMInfo ( dis_if_Entity *e );

static dis_if_TransferControlRequestCallback transferControlRequestCallback = 0;

int dis_if_readyToReceive()
{
	return app != NULL;
}

void
dis_if_setTransferControlRequestCallback ( dis_if_TransferControlRequestCallback p )
{
	transferControlRequestCallback = p;
}

static dis_if_OutstandingRequestInfo *
addRequest( dis_request_id request_id )
{
	dis_if_OutstandingRequestInfo *p = memory_allocate(sizeof(dis_if_OutstandingRequestInfo), NULL);

	memset (p, 0, sizeof(dis_if_OutstandingRequestInfo));
	p->request_id = request_id;

	p->next = NULL;
	p->prev = request_chain_tail;
	if ( request_chain_tail ) {
		p->next = p;
	}
	if (request_chain_head == NULL) {
		request_chain_head = p;
	}
	request_chain_tail = p;

	return p;
}

/**
 *  Remove the specified request tracking structure from the
 *  request tracking list.
 */
static void
removeRequest(dis_if_OutstandingRequestInfo *pItem)
{
	dis_if_OutstandingRequestInfo *p = request_chain_head;

	/*
	 * The request list is a mundane doubly linked list.
	 */

	if (pItem) {
		if (pItem->prev == NULL) {
			request_chain_head = p->next;
		}
		else {
			pItem->prev->next = p->next;
		}

		if (pItem->next == NULL) {
			request_chain_tail = p->prev;
		}
		else {
			pItem->next->prev = p->prev;
		}

		memory_dispose( pItem );
	}
}

static dis_if_OutstandingRequestInfo *
findRequestByRequestID ( dis_request_id request_id )
{
	dis_if_OutstandingRequestInfo *p = request_chain_head;

	while ( p ) {
		if (p->request_id == request_id) {
			break;
		}
		p = p->next;
	}

	return p;
}


disx_ApplicationInfo *
dis_if_getApplicationInfo(void)
{
	return app;
}

varray_Type *
dis_if_getEntityTable(void)
{
	return entities;
}

void
dis_if_enableNetwork(int enabled)
{
	network_enabled = enabled;
}

static void
ACMtoDISVelocity(VPoint * in, dis_float_vector * out)
{
	out->x = units_FEETtoMETERS(in->x);
	out->y = units_FEETtoMETERS(in->y);
	out->z = units_FEETtoMETERS(in->z);
}

static void
DIStoACMVelocity(dis_float_vector * in, VPoint * out)
{
	out->x = units_METERStoFEET(in->x);
	out->y = units_METERStoFEET(in->y);
	out->z = units_METERStoFEET(in->z);
}

/**
 * Find the entity with DIS ID id in the local entities table
 * and return its index in the table.
 *
 * The id (handle) is returned on success, 0 is returned on failure.
 */
static int
findEntity(dis_entity_id * id)
{
	int i;
	dis_if_Entity *e;

	for (i = varray_firstHandle(entities); i != 0; i = varray_nextHandle(entities)) {
		e = varray_getValue(entities, i);
		if (e->entityId.entity_id == id->entity_id &&
			e->entityId.sim_id.application_id == id->sim_id.application_id &&
			e->entityId.sim_id.site_id == id->sim_id.site_id) {
			varray_releaseIterator(entities);
			return i;
		}
	}
	varray_releaseIterator(entities);
	return 0;
}

dis_if_Entity *dis_if_findEntityByDISID(dis_entity_id * id)
{
	int eid = findEntity(id);
	if( eid == 0 )
		return NULL;
	return varray_getValue(entities, eid);
}

dis_if_Entity *dis_if_findEntityByID(int eid)
{
	if( varray_isValidHandle(entities, eid) )
		return varray_getValue(entities, eid);
	else
		return NULL;
}

/**
 * Find the local entity with Dis id id in the local entities table
 * and return its index in the table.
 *
 * The id (handle) is returned on success, 0 is returned on failure.
 */
static int
findLocalEntity( const dis_entity_id * id )
{
	int       i;
	dis_if_Entity   *e;

	for (i = varray_firstHandle(entities); i != 0; i = varray_nextHandle(entities)) {
		e = varray_getValue(entities, i);
		if (e->isLocal &&
			e->entityId.entity_id == id->entity_id &&
			e->entityId.sim_id.application_id == id->sim_id.application_id &&
			e->entityId.sim_id.site_id == id->sim_id.site_id) {
			varray_releaseIterator(entities);
			return i;
		}
	}
	varray_releaseIterator(entities);
	return 0;
}


void
dis_if_setBandwidth(double bps)
{
	bandwidth_bps = bps;
}


/**
 * Starts or re-start on collision the randomly generated site ID validation.
 */
static void startSiteIdValidation()
{
	int eid;
	validating_site_id = 1;
	validating_site_id_since = time(NULL);
	site = prng_getIntInRange(1, 65534);
	if( app != NULL )
		app->id.site_id = site;
	/* Update site ID of the local entities: */
	for(eid = varray_firstHandle(entities); eid != dis_if_ID_NONE; eid = varray_nextHandle(entities)){
		dis_if_Entity *e = varray_getValue(entities, eid);
		if( e->isLocal )
			e->entityId.sim_id.site_id = site;
	}
	varray_releaseIterator(entities);
}


/**
 * Send a dummy empty packet to the relay host to subscribe us and to renew
 * our subscription in order to continue receiving packets from which read
 * the currently used site IDs.
 */
static void subscribeToRelayHost()
{
	dis_pdu pdu;
	memory_zero(&pdu);
	/* Send a PDU type the other clients will ignore: */
	pdu.hdr.pdu_type = PDUTypeOther;
	disx_writePDU(app, &pdu);
}


/**
 * Evaluates a site ID read from the network and set the validation status of
 * our randomly generated site ID accordingly. This function can be called
 * by the initialization function when site ID is set to -1, and once the
 * connection has been fully established with a valid site ID, it is called
 * again for each received packet looking for possible new collisions.
 * If a collision is detected, generates a new site ID and restarts validation.
 * @param in_use_site_id Site ID read from incoming packet, or -1 if no packets
 * received just to update timeouts and renew subscription to the relay server.
 */
static void gotSiteId(int in_use_site_id)
{
	if( in_use_site_id == site ){
		/* Collision detected. Restart generation and validation: */
		startSiteIdValidation();
		return;
	}
	if( validating_site_id ){
		if( time(NULL) - validating_site_id_since > VALIDATING_SITE_ID_PERIOD ){
			/* Randomly generated site ID validated: no collisions. */
			validating_site_id = 0;
		} else if( validating_site_id_relay_have
		&& time(NULL) - validating_site_id_relay_last_pkt > 3.0 ){
			/* Renew subscription to the relay server. */
			subscribeToRelayHost();
			validating_site_id_relay_last_pkt = time(NULL);
		}
	}
}


int dis_if_isValidatingSiteId()
{
	return validating_site_id;
}


int
dis_if_init(char *relay_host, int relay_port,
	int xexercise,
	int xsite,
	int xapplication,
	dis_if_EntityEnterCb xentityEnterCb,
	dis_if_DetonationCb xdetonationCb,
	dis_if_CannonFireCb xcannonFireCb)
{
	dis_Transceiver *xcvr;
	dis_simulation_addr addr;
	
	if( entities )
		error_internal("DIS module already initialized", 0);

	entityEnterCb = xentityEnterCb;
	detonationCb = xdetonationCb;
	cannonFireCb = xcannonFireCb;

	exercise = xexercise;

	entities = varray_new();
	
	stats_local_entities_no = 0;
	stats_remote_entities_no = 0;
	stats_processed_packets_count_t0 = theTime;
	stats_processed_packets_count = 0;
	stats_processed_packets_per_second = 0.0;
	
	/*
	 * Check relay host:
	 */
	if( relay_host != NULL ){
		while(isspace(*relay_host))
			relay_host++;
		if( *relay_host == 0 )
			relay_host = NULL;
	}
	validating_site_id_relay_have = relay_host != NULL;
	validating_site_id_relay_last_pkt = 0;
	
	/*
	 * Check site ID and special values.
	 */
	if( xsite == -1 ){
		if( network_enabled ){
			startSiteIdValidation();
			xsite = site;
		} else {
			xsite = 1; /* harmless, ignored value */
			validating_site_id = 0;
		}
	} else {
		validating_site_id = 0;
	}
	
	/*
	 * Check application ID and special values:
	 */
	if( xapplication == -1 )
		xapplication = getpid();
	
	if( ! network_enabled )
		return 0;

	xcvr = dis_openTransceiver(0, relay_host, relay_port);
	if( xcvr == NULL ){
		return -1;
	}
	app = disx_initializeApplication(xcvr, xexercise, xsite, xapplication);
	if (app == NULL) {
		dis_closeTransceiver(xcvr);
		return -1;
	}

/*
 *  Get the actual simulation address assigned to us.
 */

	disx_getSimulationAddress(app, &addr);
	site = addr.site_id;
	application = addr.application_id;
	return 0;
}

int
dis_if_close(void)
{
	int i;
	
	if (entities) {
		
		/* Release currently live entities: */
		for(i = varray_firstHandle(entities); i != 0; i = varray_nextHandle(entities))
			dis_if_entityExit(i);
		varray_releaseIterator(entities);
		
		/* Dispose spare detached entities: */
		while( (i = varray_getDetachedHandle(entities)) != 0 )
			memory_dispose(varray_getValue(entities, i));
		
		memory_dispose(entities);
		entities = NULL;
	}
	if(app){
		disx_closeApplication(app);
		app = NULL;
	}
	stats_local_entities_no = 0;
	stats_remote_entities_no = 0;
	stats_processed_packets_count_t0 = 0.0;
	stats_processed_packets_count = 0;
	stats_processed_packets_per_second = 0.0;
	network_enabled = 0;
	validating_site_id = 0;
	return 0;
}

void
dis_if_setDRThresholds(double location, double orientation)
{
	locationThreshold = location;
	orientationThreshold = orientation;
}

/** 2^31/3600 bits per second. */
#define timeFactor (596523.235556)

#ifdef WINNT
#define rint(x)	(double)( (int)(x) )
#endif

/**
 *  Convert a DIS timestamp to a double in UNIX format (seconds since 1970).
 *
 *  If the timestamp _and_ the local time both are absolute times,
 *  the timestamp is used for the part of hour. The local time 'theTime'
 *  is used to get the hour part. The returned value will be
 *  the closest possible to 'theTime'.
 *
 *  If either the timestamp or the local time are _not_ absolute,
 *  the local time is returned. This could be improved...
 */
static double
timeDISToDouble(dis_timestamp disTime)
{
#if dis_if_USE_DIS_TIMESTAMP
		double    seconds;			/* seconds into the current hour */
		double    myseconds;		/* ditto for 'theTime' */
		double    diffseconds;
		double    myhour;			/* hour part of 'theTime' */

		/* if either time is not absolute, return the local time */
		if (disTime.type == 0 || absoluteTime == 0)
			return theTime;

		seconds = disTime.time / timeFactor;
		myseconds = fmod(theTime, 3600.0);
		myhour = rint((theTime - myseconds) / 3600.0);

		diffseconds = myseconds - seconds;

		if (diffseconds > 1800.0)
			return 3600.0 * (myhour + 1) + seconds;
		else if (diffseconds < -1800.0)
			return 3600.0 * (myhour - 1) + seconds;
		else
			return 3600.0 * myhour + seconds;
#else
		return theTime;
#endif
}

/**
 *  Convert a double in UNIX format (seconds since 1970) to a DIS timestamp.
 */
static    dis_timestamp
timeDoubleToDIS(double time, int isAbsolute)
{
	uint32_t tmp;
	dis_timestamp res;

	tmp = (uint32_t) (fmod(time, 3600.0) * timeFactor);
	if (tmp > 2147483647L)		/* 2^31 - 1 */
		res.timexxx = 2147483647UL;
	else
		res.timexxx = tmp;
	if( isAbsolute )
		res.timexxx |= 0x8000000UL;
	return res;
}

void
dis_if_setTime(double time)
{
	theTime = time;
	absoluteTime = 0;
}


void
dis_if_setTimeAbsolute(void)
{
	struct timeval tv;

	gettimeofday(&tv, NULL);
	theTime = tv.tv_sec + tv.tv_usec / 1000000.0;
	absoluteTime = 1;
}


/**
 *  Remove the stale or destroyed entity.
 */
static void
entityExit(int eid, char *reason)
{
	dis_if_Entity *e = varray_getValue(entities, eid);
	e->c->kill(e->c, reason);
}

/**
 *  Read in the entity state data from the entity state PDU es
 *  and write it to the local entity with id (index) eid.
 */
static void
getEntityStateData(int eid, dis_entity_state_pdu * es)
{
	dis_if_Entity *e = varray_getValue(entities, eid);
	e->lastTime = timeDISToDouble(es->hdr.time_stamp);
	e->lastLocation[0] = es->pos.x;
	e->lastLocation[1] = es->pos.y;
	e->lastLocation[2] = es->pos.z;
	e->lastVelocity[0] = es->vel.x;
	e->lastVelocity[1] = es->vel.y;
	e->lastVelocity[2] = es->vel.z;
	e->lastOrientation[0] = es->orientation.phi;
	e->lastOrientation[1] = es->orientation.theta;
	e->lastOrientation[2] = es->orientation.psi;
	e->lastLinearAcc[0] = es->dr_parm.linear_acc.x;
	e->lastLinearAcc[1] = es->dr_parm.linear_acc.y;
	e->lastLinearAcc[2] = es->dr_parm.linear_acc.z;
	e->lastAngularVel[0] = es->dr_parm.angular_vel.x;
	e->lastAngularVel[1] = es->dr_parm.angular_vel.y;
	e->lastAngularVel[2] = es->dr_parm.angular_vel.z;

	if (es->marking.charset == DISCharSetASCII)
		memory_strcpy((char *) e->markings, sizeof(e->markings), (char *) es->marking.marking);
	else
		e->markings[0] = '\0';

	dis_processNewDRParameters(es, &e->dr);
}

/**
 * Process the entity state PDU esPDU for a new (currently unknown)
 * entity.
 * @param esPDU
 * @return Entity ID or 0 if not added to the entities list.
 */
static int
entityEnter(dis_entity_state_pdu * esPDU)
{
	int       eid;
	craft    *c = NULL;
	dis_if_Entity *e;

	/* Get new entity and entity handle: */
	eid = varray_getDetachedHandle(entities);
	if( eid == 0 ){
		/* Allocate entity: */
		e = memory_allocate(sizeof(dis_if_Entity), NULL);
		eid = varray_addValue(entities, e);
	} else {
		/* Recycle existing entity block: */
		e = varray_getValue(entities, eid);
	}
	
	/* It's safe to reset everything: */
	memory_zero(e);

	e->isLocal = 0;
	e->state = dis_if_ENTITY_STATE_SIMULATING;
	e->pending_state = dis_if_ENTITY_STATE_NONE;
	e->emit_while_frozen = 0;
	e->forceId = esPDU->force_id;
	e->entityId = esPDU->id;
	e->entityType = esPDU->type;
	e->altEntityType = esPDU->alt_type;
	e->em = NULL;

	/*
     *  We only care about setting the dead reckoning thresholds
	 *  so that we can assume control of an entity.
	 */

	dis_setDRThresholds(&e->dr, dis_if_SEND_TIMEOUT_SECONDS,
			locationThreshold, orientationThreshold);

	getEntityStateData(eid, esPDU);

	/*
	 * Pass entity information to the main ACM code.  Based on the DIS
	 * entity type, it will determine if this is worth tracking.
	 */

	(entityEnterCb) ( eid, &esPDU->type, esPDU->force_id, &c );

	if (c) {
		/* ACM says it's worth tracking */
		e->c = c;
		return eid;
		
	} else {
		/* must not be an entity we care about ... */
		e->c = NULL;
		dis_if_entityExit(eid);
		return 0;
	}
}

/**
 * Process an incoming entity state PDU.
 * Zero is returned on success.
 */
static int
entityStatePDU(dis_entity_state_pdu * esPDU)
{
	int eid = findEntity(&esPDU->id);

	if (esPDU->appearance & DISAppearanceDamageDestroyed) {
		/* deactivated or destroyed entity. if we know about it, exit it */
		if (eid != 0) {
			dis_if_Entity *e = varray_getValue(entities, eid);
			e->c->kill(e->c, "destroyed or deactivated");
		}
		return 0;
	}
	else {
		/* normal entity state PDU. if we know about it, update data,
		   otherwise enter it */
		if (eid != 0) {
			getEntityStateData(eid, esPDU);
			return 0;
		}
		else {
			eid = entityEnter(esPDU);
			if (eid != 0) {
				return 0;
			}
			else
				return -1;
		}
	}
}

 /*
  *  These munition types are renderable with ACM's cannon simulation
  *  code.
  */

static dis_entity_type cannon_types[] =
{
	{2, 2, 225, 2, 1, 0, 0},
	{2, 2, 225, 2, 2, 0, 0},
	{2, 2, 225, 2, 3, 0, 0},
	{2, 2, 225, 2, 4, 0, 0},
	{2, 2, 222, 2, 1, 0, 0},
	{0, 0, 0, 0, 0, 0, 0}
};


/**
 *  Process an incoming fire PDU.
 *
 *  Zero is returned on success.
 */
static int
firePDU(dis_fire_pdu * fPDU)
{
	int       owner, eid;
	dis_entity_type *dp;

	for (dp = cannon_types; dp->kind != 0; ++dp) {
		if (fPDU->burst.munition.kind == dp->kind &&
			fPDU->burst.munition.domain == dp->domain &&
			fPDU->burst.munition.country == dp->country &&
			fPDU->burst.munition.category == dp->category &&
			fPDU->burst.munition.subcategory == dp->subcategory) {
			break;
		}
	}

	/* Not one of the ones that we model?  Then do nothing. */
	if (dp->kind == 0) {
		return 0;
	}

	eid = findEntity(&fPDU->firing_id);
	if (eid != 0) {
		dis_if_Entity *e = varray_getValue(entities, eid);
		owner = e->c->pIndex;
	}
	else {
		owner = 0;
	}

	VPoint pos, vel;
	pos = fPDU->pos;
	DIStoACMVelocity((dis_float_vector *)&fPDU->vel, &vel);

	cannonFireCb(owner, &pos, &vel, fPDU->burst.quantity);

	return 0;
}

/**
 * Process an incoming detonation PDU.
 *
 * Zero is returned on success.
 */
static int
detonationPDU(dis_detonation_pdu * dPDU)
{
	int       munition_eid, ftype;
	double    time, worldLocation[3], entityLocation[3];
	craft    *munition;
	dis_if_Entity *firing, *target;

	if (detonationCb == NULL) {
		munition_eid = findEntity(&dPDU->munition_id);
		if (munition_eid != 0) {
			dis_if_entityExit(munition_eid);
		}
		return 0;
	}
	
	firing = dis_if_findEntityByDISID(&dPDU->firing_id);
	if (firing == NULL)
		return -1;
	
	target = dis_if_findEntityByDISID(&dPDU->target_id);
	if (target == NULL)
		return -2;

	if (dPDU->burst.munition.category == 2)		/* Ballistic */
		ftype = dis_if_FIRE_M61A1;
	else
		ftype = dis_if_FIRE_AIM9M;
	/* FIXME: add AIM-120 */

	time = timeDISToDouble(dPDU->hdr.time_stamp);

	worldLocation[0] = dPDU->pos.x;
	worldLocation[1] = dPDU->pos.y;
	worldLocation[2] = dPDU->pos.z;

	entityLocation[0] = dPDU->loc.x;
	entityLocation[1] = dPDU->loc.y;
	entityLocation[2] = dPDU->loc.z;

	munition_eid = findEntity(&dPDU->munition_id);
	if( munition_eid != 0 && varray_isValidHandle(entities, munition_eid) ){
		dis_if_Entity *e = varray_getValue(entities, munition_eid);
		munition = e->c;
	} else {
		munition = NULL;
	}

	/*
	 * Send detonation event to ACM so that an explosion effect is set and
	 * damage calculations are performed on local aircraft. Note that the
	 * munition itself is not removed from managed entities; this task is up
	 * to an eventual "entity exit" event.
	 */
	detonationCb(ftype, firing->c, target->c, time, worldLocation,
					entityLocation, munition, dPDU);
	return 0;
}


/*
static void printEM(dis_em_emission_pdu *em)
{
	int i;
	assert(em->hdr.pdu_type == PDUTypeEmission);
	printf("  no. of systems: %d\n", em->num_systems);
	if( em->num_systems == 0 )
		return;
	for(i = 0; i < em->num_systems; i++){
		dis_em_system_info *s = &em->system[0];
		printf("   system no. %d: no. of beams %d\n", i, s->num_beams);
	}
}
*/

/**
 * Process an incoming EM emission PDU.
 *
 * Zero is returned on success.
 */
static int
emissionPDU(dis_em_emission_pdu * pdu)
{
	dis_if_Entity   *e;
	int       emitterEid;

	emitterEid = findEntity(&pdu->emitter_id);
	if (emitterEid == 0)
		return -1;
	
	e = varray_getValue(entities, emitterEid);

/*
 *  First emission received?
 */

	if (e->em == NULL){
		initializeEMInfo(e);
	}
/*
 *  Not the first emission.  Free the old PDU variable fields and insert
 *  the new one.
 */

	else {
		dis_freePDUComponents((dis_pdu *) & e->em->em);
	}
	e->em->em = *pdu;
	e->em->lastTime = theTime;
	return 0;
}

static int
dis_if_isLocalEntity (const dis_entity_id *id)
{
	return findLocalEntity(id) != 0;
}


int
dis_if_canSimulate ( int eid )
{
	dis_if_Entity *e;

	if( ! varray_isValidHandle(entities, eid) )
		return 0;
	e = varray_getValue(entities, eid);
	return e->isLocal && e->state == dis_if_ENTITY_STATE_SIMULATING;
}


static void updateProcessedPacketsPerSecond(int processed)
{
	double dt;
	stats_processed_packets_count += processed;
	dt = theTime - stats_processed_packets_count_t0;
	if( dt < 0.0 || dt >= 10.0 ){
		stats_processed_packets_count_t0 = theTime;
		stats_processed_packets_count = 0;
		stats_processed_packets_per_second = 0.0;
	} else if( dt >= 1.0 ){
		stats_processed_packets_per_second =
			0.80 * stats_processed_packets_per_second
			+ 0.20 * stats_processed_packets_count / dt;
		stats_processed_packets_count_t0 = theTime;
		stats_processed_packets_count = 0;
	}
}


static int
messagePDU ( dis_comment_pdu *pdu )
{
	char s[1000];
	int s_len;
	char *c;
	
	if( pdu->num_variable_data != 1
	|| pdu->variable_datum->datum_id != datum_AltDescription )
		return -1;
	s_len = pdu->variable_datum->value_length / 8;
	if( s_len >= sizeof(s) )
		s_len = sizeof(s) - 1;
	// Note that here we really need strncpy() and NOT memory_strcpy() because
	// the first also reset the whole field and NUL-termination is not needed.
	strncpy(s, (char *) pdu->variable_datum->value.ptr_value, s_len);
	s[s_len] = 0;
	c = s;
	while(*c != 0){
		if( !(32 <= *c && *c <= 126) )
			*c = '?';
		c++;
	}
	printf("FIXME: message received: \"%s\"\n", s);
	return 0;
}


int
dis_if_receive(void)
{
	int       err, free_needed;
	dis_pdu   pdu;
	int       processed = 0;
	

	if ( ! network_enabled || ! app )
		return 0;

	err = 0;
	while ( disx_readPDU(app, &pdu) ) {

		/* Some handling funcs store the PDU: do not release! */
		free_needed = 1;

		/* ignore other exercises */
		if (pdu.hdr.exercise_id != exercise)
			goto free_pdu;

		switch (pdu.hdr.pdu_type) {
			
		case PDUTypeOther:
			/* Clients in validation state may send this type of PDU to subscribe
			 * to the relay server. We ignore it. */
			break;
			
		case PDUTypeEntityState:
			/* don't read our own broadcasts */
			if (dis_if_isLocalEntity(&pdu.entity_state.id))
				goto free_pdu;
			if( validating_site_id )
				gotSiteId(pdu.entity_state.id.sim_id.site_id);
			err = entityStatePDU(&pdu.entity_state);
			processed++;
			break;

		case PDUTypeFire:
			/* don't read our own broadcasts */
			if (dis_if_isLocalEntity(&pdu.fire.firing_id))
				goto free_pdu;
			if( validating_site_id )
				gotSiteId(pdu.fire.firing_id.sim_id.site_id);
			err = firePDU(&pdu.fire);
			processed++;
			break;

		case PDUTypeDetonation:
			/* don't read our own broadcasts */
			if (dis_if_isLocalEntity(&pdu.detonation.firing_id))
				goto free_pdu;
			if( validating_site_id )
				gotSiteId(pdu.detonation.firing_id.sim_id.site_id);
			err = detonationPDU(&pdu.detonation);
			processed++;
			break;

		case PDUTypeEmission:
			/* don't read our own broadcasts */
			if (dis_if_isLocalEntity(&pdu.em_emission.emitter_id))
				goto free_pdu;
			if( validating_site_id )
				gotSiteId(pdu.em_emission.emitter_id.sim_id.site_id);
			err = emissionPDU(&pdu.em_emission);
			free_needed = err != 0;
			processed++;
			break;

		case PDUTypeSetData:
			/* don't read our own broadcasts */
			if (dis_if_isLocalEntity(&pdu.set_data.orig_id))
				goto free_pdu;
			if( validating_site_id )
				gotSiteId(pdu.set_data.orig_id.sim_id.site_id);
			err = setDataPDU( &pdu.set_data );
			free_needed = err != 0;
			processed++;
			break;

		case PDUTypeStopFreeze:
			/* don't read our own broadcasts */
			if (dis_if_isLocalEntity(&pdu.stop.orig_id))
				goto free_pdu;
			if( validating_site_id )
				gotSiteId(pdu.stop.orig_id.sim_id.site_id);
			err = stopPDU( &pdu.stop );
			free_needed = err != 0;
			processed++;
			break;

		case PDUTypeStartResume:
			/* don't read our own broadcasts */
			if (dis_if_isLocalEntity(&pdu.start.orig_id))
				goto free_pdu;
			if( validating_site_id )
				gotSiteId(pdu.start.orig_id.sim_id.site_id);
			err = startPDU( &pdu.start );
			free_needed = err != 0;
			processed++;
			break;

		case PDUTypeTransferControl:
			/* don't read our own broadcasts */
			if (dis_if_isLocalEntity(&pdu.transfer_control.orig_id))
				goto free_pdu;
			if( validating_site_id )
				gotSiteId(pdu.transfer_control.orig_id.sim_id.site_id);
			err = transferControlPDU( &pdu.transfer_control );
			free_needed = err != 0;
			processed++;
			break;

		case PDUTypeAcknowledge:
			/* don't read our own broadcasts */
			if (dis_if_isLocalEntity(&pdu.acknowledge.orig_id))
				goto free_pdu;
			if( validating_site_id )
				gotSiteId(pdu.acknowledge.orig_id.sim_id.site_id);
			err = acknowledgePDU( &pdu.acknowledge );
			free_needed = err != 0;
			processed++;
			break;

		case PDUTypeComment:
			/* don't read our own broadcasts */
			if (dis_if_isLocalEntity(&pdu.message.orig_id))
				goto free_pdu;
			if( validating_site_id )
				gotSiteId(pdu.message.orig_id.sim_id.site_id);
			err = messagePDU( &pdu.message );
			free_needed = 1;
			processed++;
			break;

		default:
			fprintf(stderr, "Ignoring unexpected/unsupported DIS PDU type %d\n", pdu.hdr.pdu_type);
			err = 0;
			break;
		}

/*
 *  Free any dynamically allocated variable components that are part of this
 *  PDU.
 */

	  free_pdu:
		if (free_needed) {
			dis_freePDUComponents(&pdu);
		}

	}
	
	updateProcessedPacketsPerSecond(processed);
	
	if( processed == 0 && validating_site_id ){
		/*
		 * No packets received. We still need to update timeouts and renew the
		 * subscription to the relay server:
		 */
		gotSiteId(-1);
	}

/*
 *  Check for timeouts on remote entities and look for pending state changes.
 */

	stats_local_entities_no = 0;
	stats_remote_entities_no = 0;
	int i;
	for (i = varray_firstHandle(entities); i != 0; i = varray_nextHandle(entities)) {
		dis_if_Entity *e = varray_getValue(entities, i);
		if( e->isLocal )
			stats_local_entities_no++;
		else
			stats_remote_entities_no++;
		if ( ! e->isLocal && theTime - e->lastTime > dis_if_RECV_TIMEOUT_SECONDS) {
			char s[99];
			sprintf(s, "stale -- no packets received since %.0f s",
					theTime - e->lastTime);
			entityExit(i, s);
			
		} else if ( e->pending_state != dis_if_ENTITY_STATE_NONE ) {
			if (theTime >= e->pending_time) {
				e->state = e->pending_state;
				e->pending_state = dis_if_ENTITY_STATE_NONE;
			}
		}
	}
	varray_releaseIterator(entities);

	return err != 0;
}


int
dis_if_snoop ( int millisec )
{
	int interval_millisec = 500;
	
	if( ! network_enabled || ! app )
		return 0;

	if (dis_if_haveAbsoluteTime)
		dis_if_setTimeAbsolute();
	else
		dis_if_setTime(curTime);
	
	dis_if_receive();

	while ( millisec > 0 ) {
		if ( millisec < interval_millisec ) {
			interval_millisec = millisec;
		}
#ifdef WINNT
		Sleep(interval_millisec);
#else
		usleep ( interval_millisec * 1000 );
#endif

		update_simulationTime ();

		if (dis_if_haveAbsoluteTime)
			dis_if_setTimeAbsolute();
		else
			dis_if_setTime(curTime);
		dis_if_receive();
			
		millisec -= interval_millisec;
	}

	return 0;
}


int dis_if_transferControlRequestHandler(dis_if_Entity *e, dis_transfer_control_pdu *pdu)
{
	int result = dis_if_RESULT_UNABLE;
	craftType *cinfo;

	switch (pdu->transfer_type) {

	/*
	 *  Someone would like use to take control of an entity
     *
     *  If it is an aircraft we can model, then make it a drone.
     */

	case DISTransferTypeEntityControllerRequest:
		cinfo = inventory_craftTypeSearchByEntityType( &e->entityType );
		if ( cinfo != NULL ) {
			e->c->type = CT_DRONE;
			e->c->cinfo = cinfo;
			/* TODO: provision the aircraft; landing gear, etc */
			result = dis_if_RESULT_REQUEST_OK;
		}
		break;

	/*
	 *  Control of this entity is requested by someone else.
     *
     *  Change type to DIS aircraft and we're done.
	 */

	case DISTransferTypeEntityRequest:
		result = dis_if_RESULT_REQUEST_OK;
		e->c->type = CT_DIS_PLANE;
	}

	return result;
}


/**
 *  Determines if an entity should emit entity state PDUs based on 
 *  protocol rules
 */
static int
dis_shouldTransmitPDUs ( dis_if_Entity *e )
{
	int result = 0;

	if (e->isLocal) {
		if ( e->state == dis_if_ENTITY_STATE_SIMULATING ) {
			result = 1;
		}
		else if ( e->state == dis_if_ENTITY_STATE_STOPPED && 
				  e->emit_while_frozen ) {
			result = 1;
		}
	}

	return result;
}

/**
 *  Send an entity state PDU for the local entity with id (index) eid.
 *
 *  Zero is returned on success.
 */
static int
sendEntityState(dis_if_Entity  *e)
{
	dis_entity_state_pdu pdu, *esPDU = &pdu;
	int       i;

	if ( ! network_enabled || ! app || validating_site_id )
		return 0;

	if  (dis_shouldTransmitPDUs ( e ) == 0) {
		return 0;
	}

	esPDU->hdr.pdu_type = PDUTypeEntityState;
	esPDU->hdr.time_stamp = timeDoubleToDIS(theTime, absoluteTime);
	esPDU->id = e->entityId;
	esPDU->force_id = e->forceId;
	esPDU->art_parm_count = 0;
	esPDU->type = e->entityType;
	esPDU->alt_type = e->altEntityType;
	esPDU->pos.x = e->location[0];
	esPDU->pos.y = e->location[1];
	esPDU->pos.z = e->location[2];
	esPDU->vel.x = (float) e->velocity[0];
	esPDU->vel.y = (float) e->velocity[1];
	esPDU->vel.z = (float) e->velocity[2];
	esPDU->orientation.phi = (float) e->orientation[0];
	esPDU->orientation.theta = (float) e->orientation[1];
	esPDU->orientation.psi = (float) e->orientation[2];
	esPDU->appearance = e->appearance;
	esPDU->dr_parm.algorithm = DISDRMethodRVW;
	esPDU->dr_parm.linear_acc.x = (float) e->linearAcceleration[0];
	esPDU->dr_parm.linear_acc.y = (float) e->linearAcceleration[1];
	esPDU->dr_parm.linear_acc.z = (float) e->linearAcceleration[2];
	esPDU->dr_parm.angular_vel.x = (float) e->angularVelocity[0];
	esPDU->dr_parm.angular_vel.y = (float) e->angularVelocity[1];
	esPDU->dr_parm.angular_vel.z = (float) e->angularVelocity[2];
	esPDU->marking.charset = DISCharSetASCII;
	memset(esPDU->marking.marking, 0, sizeof(esPDU->marking.marking));
	// We really need strncpy() here, NOT memory_strcpy():
	strncpy((char *) esPDU->marking.marking, (char *) e->markings, sizeof(esPDU->marking.marking));
	esPDU->capabilities = 0;
	esPDU->art_parm = NULL;

	if ( ! disx_writePDU(app, (dis_pdu *) esPDU) ) {
		return -2;
	}
	else {
		dis_processNewDRParameters(esPDU, &e->dr);
		e->lastTime = theTime;
		for (i = 0; i < 3; i++) {
			e->lastLocation[i] = e->location[i];
			e->lastVelocity[i] = e->velocity[i];
			e->lastLinearAcc[i] = e->linearAcceleration[i];
			e->lastOrientation[i] = e->orientation[i];
			e->lastAngularVel[i] = e->angularVelocity[i];
		}

		return 0;
	}
}

/**
 *  Set the position data of the entity with id (index) eid to the given
 *  values.
 */
static void
setPosData(int eid, double loc[3], double vel[3], double linAcc[3],
		   double ori[3], double angVel[3])
{
	int       i;
	dis_if_Entity *e = varray_getValue(entities, eid);
	for (i = 0; i < 3; i++) {
		e->location[i] = loc[i];
		e->velocity[i] = vel[i];
		e->linearAcceleration[i] = linAcc[i];
		e->orientation[i] = ori[i];
		e->angularVelocity[i] = angVel[i];
	}
}

/*
 *  Construct an EM emission PDU that reflects the current state of our
 *  radar set. We set 1 radar system with 0 or 1 beams.
 */
static int
constructEmissionPDU(dis_if_Entity *e, int mode, int update)
{
	dis_em_emission_pdu *em;
	dis_em_system_info *s;
	dis_beam_info b;
	int num_beams = 0;
	dis_track_info target;
	
	if( e->em == NULL ){
		e->em = (dis_if_EntityEM *) memory_allocate(sizeof(dis_if_EntityEM), NULL);
		memory_zero(e->em);
		e->em->em.hdr.pdu_type = PDUTypeEmission;
	}

	e->em->mode = mode;
	em = &e->em->em;

	if( network_enabled && ! validating_site_id )
		disx_issueEventID(app, &em->event);
	else
		memory_zero(&em->event);
	
	// Creates 1 radar system:
	if( em->num_systems == 0 ){
		em->system = memory_allocate(sizeof(dis_em_system_info), NULL);
		memory_zero(em->system);
	}
	em->num_systems = 1;
	s = em->system;
	
	em->state_update = update;

	s->location.x = 0.0f;
	s->location.y = 0.0f;
	s->location.z = 0.0f;
	s->emitter_system.name = 0;
	s->emitter_system.function = DISEmitterFuncAirborneFireControl;
	s->emitter_system.id = 1;
	
	// Set 0 or 1 beams:
	memory_zero(&b);
	switch (mode) {
		
	case 0:
		num_beams = 0;
		break;

/*
 *  Three-bar track while scan mode
 */

	case 1:
		num_beams = 1;
		b.beam_id = 1;
		b.beam_parm_index = 0;
		b.beam_function = DISBeamFuncAcquisitionAndTracking;
		b.fundamental.freq = 9000.0f;
		b.fundamental.erp = 100.0f;
		b.fundamental.prf = 18000.0f;
		b.fundamental.pulse_width = 1.0f;
		b.fundamental.beam_azimuth_center = 0.0f;
		b.fundamental.beam_azimuth_sweep = units_DEGtoRAD(60);
		b.fundamental.beam_elev_center = 0.0f;
		b.fundamental.beam_elev_sweep = units_DEGtoRAD(60);
		b.fundamental.beam_sweep_sync = 0.0f;
		b.pad = 0;
		b.jamming_mode = 0;
		break;

/*
 *  Four bar 20 x 30 ACM mode
 */

	case 2:
		num_beams = 1;
		b.beam_id = 1;
		b.beam_parm_index = 1;
		b.beam_function = DISBeamFuncAcquisitionAndTracking;
		b.fundamental.freq = 9000.0f;
		b.fundamental.erp = 100.0f;
		b.fundamental.prf = 18000.0f;
		b.fundamental.pulse_width = 1.0f;
		b.fundamental.beam_azimuth_center = 0.0f;
		b.fundamental.beam_azimuth_sweep = units_DEGtoRAD(30);
		b.fundamental.beam_elev_center = 0.0f;
		b.fundamental.beam_elev_sweep = units_DEGtoRAD(20);
		b.fundamental.beam_sweep_sync = 0.0f;
		b.pad = 0;
		b.jamming_mode = 0;
		break;

/*
 *  Single target track
 */

	case 3:
		num_beams = 1;
		b.beam_id = 1;
		b.beam_parm_index = 2;
		b.beam_function = DISBeamFuncAcquisitionAndTracking;
		b.fundamental.freq = 9000.0f;
		b.fundamental.erp = 100.0f;
		b.fundamental.prf = 18000.0f;
		b.fundamental.pulse_width = 1.0f;
		b.fundamental.beam_azimuth_center = 0.0f;	/* wrong, don't care */
		b.fundamental.beam_azimuth_sweep = 0.0f;
		b.fundamental.beam_elev_center = 0.0f;		/* wrong, don't care */
		b.fundamental.beam_elev_sweep = 0.0f;
		b.fundamental.beam_sweep_sync = 0.0f;
		b.pad = 0;
		b.jamming_mode = 0;
		break;
	
	default:
		error_internal("invalid radar mode: %d", mode);
	}

	// Set tracked target:
	int num_targets = 0;
	if( num_beams > 0 ){
		e->em->cur_target = e->c->curRadarTarget;

		craft *c = e->c;
		if( c->curRadarTarget >= 0
		&& ! varray_isValidHandle(entities, ptbl[c->curRadarTarget].disId)
		){
			/*
			 * Target missing or already killed and removed. Update craft/missile
			 * target:
			 */
			c->curRadarTarget = -1;
		}

		if (c->curRadarTarget >= 0) {
			num_targets = 1;
			dis_if_Entity *curRadarTarget = varray_getValue(entities, ptbl[c->curRadarTarget].disId);
			target.target = curRadarTarget->entityId;
			target.emitter_id = 1;
			target.beam_id = 1;
		}
	}
	
	/*
	 * Set beam in the radar system avoiding dynamic memory allocation as much
	 * as possible by using the existing data structure already available in the
	 * emission PDU. We must set the system, the beam in the system, and the
	 * target in the beam.
	 */
	if( num_beams == 0 ){
		// No beams and no targets. Dispose any existing beam data:
		if( s->num_beams > 0 ){
			int i;
			for(i = 0; i < s->num_beams; i++){
				if( s->beam[0].num_targets > 0 ){
					memory_dispose(s->beam[0].tracked_target);
				}
				memory_dispose(s->beam);
			}
			s->beam = NULL;
		}
		s->num_beams = 0;
		
	} else {
		// One beam to set.
		if( s->num_beams == 0 ){
			if( num_targets > 0 ){
				b.num_targets = 1;
				b.tracked_target = memory_allocate(sizeof(dis_track_info), NULL);
				b.tracked_target[0] = target;
			}
			s->beam = memory_allocate(sizeof(dis_beam_info), NULL);
		} else {
			if( s->beam[0].num_targets == 0 ){
				if( num_targets > 0 ){
					b.num_targets = 1;
					b.tracked_target = memory_allocate(sizeof(dis_track_info), NULL);
					b.tracked_target[0] = target;
				}
			} else {
				if( num_targets == 0 ){
					memory_dispose(s->beam[0].tracked_target);
				} else {
					b.num_targets = 1;
					b.tracked_target = s->beam[0].tracked_target;
					b.tracked_target[0] = target;
				}
			}
		}
		s->num_beams = 1;
		s->beam[0] = b;
	}
	
	return 0;
}

void
dis_if_entityEnter(DISForce force, 
				craft * c, 
				dis_entity_type * e1, 
				dis_entity_type * e2,
				double loc[3], 
				double vel[3],
				double linAcc[3], 
				double ori[3], 
				double angVel[3],
				int *neid)
{
	int eid;
	dis_if_Entity *e;
	
	/* Get new entity and entity handle: */
	eid = varray_getDetachedHandle(entities);
	if( eid == 0 ){
		/* Allocate entity: */
		e = memory_allocate(sizeof(dis_if_Entity), NULL);
		eid = varray_addValue(entities, e);
	} else {
		/* Recycle existing entity block: */
		e = varray_getValue(entities, eid);
	}
	
	/* It's safe to reset everything: */
	memory_zero(e);

	e->isLocal = 1;
	e->c = c;
	e->state = dis_if_ENTITY_STATE_SIMULATING;
	e->pending_state = dis_if_ENTITY_STATE_NONE;
	e->emit_while_frozen = 0;

	if( network_enabled ){
		disx_issueEntityID(app, &e->entityId);
	} else {
		memory_zero(&e->entityId); /* FIXME: ???? */
	}

	e->forceId = force;
	e->entityType = *e1;
	e->altEntityType = *e2;

	e->markings[0] = '\0';
	e->appearance = 0;
	setPosData(eid, loc, vel, linAcc, ori, angVel);

	*neid = eid;
	
	constructEmissionPDU(e, 0, 0);

	dis_setDRThresholds(&e->dr, dis_if_SEND_TIMEOUT_SECONDS,
					   locationThreshold, orientationThreshold);
}

void
dis_if_setEntityMarkings(int eid, char *markings)
{
	dis_if_Entity *e = varray_getValue(entities, eid);
	// Note that here we really need strncpy() and NOT memory_strcpy() because
	// the first also reset the whole field and NUL-termination is not needed.
	strncpy((char *) e->markings, markings, MARKINGS_LEN);
}

void
dis_if_getEntityMarkings(int eid, char *markings, int max)
{
	dis_if_Entity *e = varray_getValue(entities, eid);
	// Note that here we really need strncpy() and NOT memory_strcpy() because
	// the first also reset the whole field and NUL-termination is not needed.
	strncpy(markings, (char *) e->markings, max);
}

void
dis_if_setEntityAppearance(int eid, dis_entity_appearance x)
{
	dis_if_Entity *e = varray_getValue(entities, eid);
	e->appearance = x;
}

dis_entity_appearance
dis_if_getEntityAppearance(int eid)
{
	dis_if_Entity *e = varray_getValue(entities, eid);
	return e->appearance;
}


void
dis_if_entityExit(int eid)
{
	dis_if_Entity *e;
	
	e = varray_getValue(entities, eid);
	
	e->appearance = DISAppearanceDamageDestroyed;
	sendEntityState(e);

	if (e->em) {
		dis_freePDUComponents((dis_pdu *) &e->em->em);
		memory_dispose(e->em);
		e->em = NULL;
	}
	varray_detach(entities, eid);
}


int
dis_if_setEntityState(int eid, double loc[3], double vel[3],
				double linAcc[3],
				double ori[3], double angVel[3])
{
	if ( ! network_enabled || ! app || validating_site_id )
		return 0;
	
	double    delta, min_delta;
	int       sendESPDU = 0, sendEMPDU = 0, i, j;
	dis_euler_angles ori_e;
	dis_if_Entity *e = varray_getValue(entities, eid);
	
	assert(e->isLocal);
	
	setPosData(eid, loc, vel, linAcc, ori, angVel);

/*
 *  EM emission PDU possibly needed ?
 */

	delta = theTime - e->lastTime;

	if (e->em && e->em->mode > 0
	&& (delta > dis_if_SEND_TIMEOUT_SECONDS || e->em->em.state_update) ) {
		sendEMPDU = 1;
		for (i = 0; i < e->em->em.num_systems; ++i) {
			for (j = 0; j < e->em->em.system[0].num_beams; ++j) {
				e->em->em.system[i].beam[j].fundamental.beam_sweep_sync += (float) delta;
			}
		}
		e->em->lastTime = theTime;
		e->em->em.state_update = 0;
	}

	ori_e.phi   = ori[0];
	ori_e.theta = ori[1];
	ori_e.psi   = ori[2];

	sendESPDU = dis_testDRThresholds(&e->dr, delta, (VPoint *) loc, &ori_e);

	/*
	 *  Are we limiting PDU transmissions? 
	 *
	 *  If so, ensure enough time has passed since
	 *  our last entity state transmission.
	 */

	if (bandwidth_bps > 0.0) {
		min_delta = 1440.0 * varray_length(entities) / bandwidth_bps;
		if (delta < min_delta) {
			sendESPDU = 0;
		}
	}

	if (sendESPDU != 0) {
		sendEntityState(e);
	}

	if ( sendEMPDU && e->state == dis_if_ENTITY_STATE_SIMULATING ) {
		e->em->em.hdr.time_stamp = timeDoubleToDIS(theTime, absoluteTime);
		if( network_enabled && ! validating_site_id ){
			// Set (or refresh because of the site ID validation) our ID:
			e->em->em.emitter_id = e->entityId;
			disx_writePDU(app, (dis_pdu *) & e->em->em);
		}
	}
	return sendESPDU || sendEMPDU;
}


int
dis_if_getEntityState(int eid, double loc[3], double vel[3], double ori[3])
{
	int       i;
	VMatrix   orientation;
	dis_linear_vel_vector drvel;
	if( ! varray_isValidHandle(entities, eid) )
		return 0;
	dis_if_Entity *e = varray_getValue(entities, eid);
	assert( ! e->isLocal );
	if( ! e->isLocal ){
		/* Remote entity. */
		dis_computeDRPosition(&e->dr,
			theTime - e->lastTime,
			(VPoint *) & e->location,
			&drvel,
			&orientation);

		e->velocity[0] = drvel.x;
		e->velocity[1] = drvel.y;
		e->velocity[2] = drvel.z;

		VMatrixToEuler(&orientation,
			&e->orientation[0],
			&e->orientation[1],
			&e->orientation[2]);

		for (i = 0; i < 3; i++) {
			loc[i] = e->location[i];
			vel[i] = e->velocity[i];
			ori[i] = e->orientation[i];
		}

	} else {
		/* Local entity. */
		for (i = 0; i < 3; i++) {
			loc[i] = e->location[i];
			vel[i] = 0.0;
			ori[i] = e->orientation[i];
		}

	}
	return 1;
}

int
dis_if_fire(int ftype, int firingEid, int targetEid, int rounds,
		 double location[3], double velocity[3], double range,
		 int *eventId, int *missileEid)
{
	/* FIXME: TODO */
	*eventId = 0;
	*missileEid = 0;

	return 0;
}


static dis_entity_id null_id = {{0, 0}, 0};

int
dis_if_fireCannon(craft * c, VPoint * pos, VPoint * vel, int quantity, int rate)
{
	dis_fire_pdu fire;
	int       status;
	dis_if_Entity *e;

	if ( ! network_enabled || ! app || validating_site_id)
		return 0;

	fire.hdr.pdu_type = PDUTypeFire;
	
	e = varray_getValue(entities, c->disId);
	fire.firing_id = e->entityId;
	fire.target_id = null_id;
	fire.munition_id = null_id;

	disx_issueEventID(app, &fire.event);

	fire.fire_mission_index = 0;	/* NO_FIRE_MISSION */
	fire.pos = *pos;
	fire.burst.munition = cannon_types[1];
	fire.burst.warhead = 0;
	fire.burst.fuze = 0;
	fire.burst.quantity = quantity;
	fire.burst.rate = rate;

	ACMtoDISVelocity(vel, (void *) &fire.vel);

	fire.range = 0.0f;

	fire.hdr.time_stamp = timeDoubleToDIS(theTime, absoluteTime);

	status = disx_writePDU(app, (dis_pdu *) & fire);
	return (status == 0) ? 0 : -1;
}


int
dis_if_detonation(dis_entity_type * munition_type,
			   int firingEid, int targetEid, int munitionEid,
			   double worldLocation[3], double entityLocation[3],
			   double vel[3])
{
	dis_detonation_pdu pdu;
	int       status;
	dis_if_Entity *firing, *target, *munition;

	if ( ! network_enabled || ! app || validating_site_id )
		return 0;

	pdu.hdr.pdu_type = PDUTypeDetonation;

	if( ! varray_isValidHandle(entities, firingEid) ){
		/* only local entities may set a detonation, should never happen */
		return -3;
	}
	firing = varray_getValue(entities, firingEid);
	if ( ! firing->isLocal ) {
		/* should never happen, only local entities may set detonation */
		return -1;
	}
	
	if( targetEid != dis_if_ID_NONE && varray_isValidHandle(entities, targetEid) )
		/* Target entity available. */
		target = varray_getValue(entities, targetEid);
	else
		/* Either no target set, or targeted entity not available. */
		target = NULL;
	
	if (targetEid != dis_if_ID_NONE && target == NULL) {
		/* Set target isn't available anymore in our table. */
		return -2;
	}
	pdu.firing_id = firing->entityId;
	if (target != NULL) {
		pdu.target_id = target->entityId;
	}
	else {
		pdu.target_id = null_id;
	}
	
	if (munitionEid != dis_if_ID_NONE)
		munition = varray_getValue(entities, munitionEid);
	else
		munition = NULL;

	if (munition != NULL) {
		pdu.munition_id = munition->entityId;
	}
	else {
		pdu.munition_id = null_id;
	}

	disx_issueEventID(app, &pdu.event);

	pdu.vel.x = (float) vel[0];
	pdu.vel.y = (float) vel[1];
	pdu.vel.z = (float) vel[2];

	pdu.pos.x = worldLocation[0];
	pdu.pos.y = worldLocation[1];
	pdu.pos.z = worldLocation[2];

	pdu.burst.munition = *munition_type;

/*
 *  This code will need some extra work ...
 */

	if (pdu.burst.munition.category == 2) {
		pdu.burst.warhead = DISWarheadKinetic;
		pdu.burst.fuze = DISFuzeContact;
		pdu.burst.quantity = 1;
		pdu.burst.rate = 0;
		pdu.result = DISDetonationResultEntityImpact;
	}
	else {
		pdu.burst.warhead = DISWarheadHEFragment;
		pdu.burst.fuze = DISFuzeProximity;
		pdu.burst.quantity = 1;
		pdu.burst.rate = 0;
		pdu.result = DISDetonationResultDetonation;
	}

	pdu.loc.x = (float) entityLocation[0];
	pdu.loc.y = (float) entityLocation[1];
	pdu.loc.z = (float) entityLocation[2];

	pdu.result = 0;
	pdu.num_art_parms = 0;
	pdu.art_parm = NULL;

	pdu.hdr.time_stamp = timeDoubleToDIS(theTime, absoluteTime);

	status = disx_writePDU(app, (dis_pdu *) & pdu);
	return (status == 0) ? 0 : -1;
}

/**
 * Initialize EM info data structures.  Returns zero on success, -1 on error.
 */
static int
initializeEMInfo ( dis_if_Entity *e )
{
	int result = 0;

	if (e->em == NULL) {

/*
 *  Allocate  EM emission information structure and initialize it to reflect
 *  that this is an external entity (mode == -1).
 */

		e->em = (dis_if_EntityEM *) memory_allocate(sizeof(dis_if_EntityEM), NULL);
		memory_zero(e->em);
		e->em->mode = -1;
		e->em->cur_target = -1;

	}
	return result;
}


int
dis_if_setRadarMode(craft * c, int mode, int update)
{
	int       status;
	dis_if_Entity  *e;
	
	e = varray_getValue(entities, c->disId);

	if ( e->em == NULL) {
		initializeEMInfo ( e );
	}

	if ( e->em->mode == mode) {
		return 0;
	}

	constructEmissionPDU(e, mode, 1);
	e->em->em.hdr.time_stamp = timeDoubleToDIS(theTime, absoluteTime);
	e->em->lastTime = theTime;
	if ( ! network_enabled || ! app || validating_site_id )
		return 0;
	status = disx_writePDU(app, (dis_pdu *) & e->em->em);
	return (status == 0) ? 0 : -1;
}


int
dis_if_radarTargetChanged(craft * c)
{
	int       status;
	dis_if_Entity *e;

	e = varray_getValue(entities, c->disId);
	if ( e->em == NULL) {
		initializeEMInfo(e);
	}

	if (e->em->cur_target == c->curRadarTarget) {
		return 0;
	}
	constructEmissionPDU(e, e->em->mode, 1);
	e->em->em.hdr.time_stamp = timeDoubleToDIS(theTime, absoluteTime);
	e->em->lastTime = theTime;
	if ( ! network_enabled || ! app || validating_site_id )
		return 0;
	status = disx_writePDU(app, (dis_pdu *) & e->em->em);
	return (status == 0) ? 0 : -1;
}


int
dis_if_getBeamCount(craft * c)
{
	dis_if_Entity *e = varray_getValue(entities, c->disId);
	if (e->em && e->em->em.num_systems > 0) {
		return e->em->em.system[0].num_beams;
	}
	return 0;
}


void
dis_if_getRadarParameters(craft * c, int j, double *az_center, double *az_width,
					   double *el_center, double *el_width, double *erp)
{
	dis_if_Entity *e = varray_getValue(entities, c->disId);
	dis_beam_info *b = &e->em->em.system[0].beam[j];
	*az_center = b->fundamental.beam_azimuth_center;
	*az_width  = b->fundamental.beam_azimuth_sweep;
	*el_center = b->fundamental.beam_elev_center;
	*el_width  = b->fundamental.beam_elev_sweep;
	*erp       = b->fundamental.erp;
}

static int
transferControlPDU ( dis_transfer_control_pdu *pdu )
{
	dis_if_Entity     *e;
	int          eid;
	dis_acknowledge_pdu reply_pdu;
	int status = 0;
	int error_return_needed = 0;
	
	if( app == NULL || validating_site_id )
		return -1;

	reply_pdu.hdr            = pdu->hdr;
	reply_pdu.hdr.pdu_type   = PDUTypeAcknowledge;
	reply_pdu.hdr.time_stamp = timeDoubleToDIS( theTime, absoluteTime );

	reply_pdu.orig_id      = pdu->recv_id;
	reply_pdu.recv_id      = pdu->orig_id;
	reply_pdu.request_id   = pdu->request_id;

	/*
	 *  Request applies to one of our entities?
	 */

	eid = findLocalEntity( &pdu->target_id );
	if ( eid != 0 ) {

		e = varray_getValue(entities, eid);

		if (pdu->recv_id.sim_id.site_id == site &&
			( pdu->recv_id.sim_id.application_id == ALL_APPLIC ||
			  pdu->recv_id.sim_id.application_id == application )) {
	
			reply_pdu.acknowledge_flag   = 36;	/* per CALSPAN */
			reply_pdu.resp_flag    = 1;			/* per CALSPAN */

			switch (pdu->transfer_type) {

			/*
			 *  Someone wants us to take control of an entity
			 */

			case DISTransferTypeEntityControllerRequest:

				/*
				 *  the transferControlRequestCallback function is 
				 *  responsible for determining if we can feasibly take 
				 *  control of the entity.
				 */

				if ( (transferControlRequestCallback != NULL) &
					((*transferControlRequestCallback)(e, pdu) == 0) ) {

					status = disx_writePDU( app, (dis_pdu *) & reply_pdu );
					status = (status == 0) ? 0 : -1;

					if (status == 0) {
						e->isLocal = 1;
					}
				}
				else {
					error_return_needed = 1;
				}
				break;

		    /*
			 *  Control of this entity is requested by someone else.
			 */

			case DISTransferTypeEntityRequest:

				if (transferControlRequestCallback != NULL &&
					(*transferControlRequestCallback)(e, pdu) == 0) {

					status = disx_writePDU( app, (dis_pdu *) & reply_pdu );
					status = (status == 0) ? 0 : -1;

					if (status == 0) {
						e->isLocal = 0;
					}
				}
				else {
					error_return_needed = 1;
				}
				break;
			}

		}
	}
	else {
		/*
		 *  The target entity was not local to us.  Still, the PDU might 
         *  look like it is destined for us.  In this case, return an 
         *  error reply.
		 */

		if (pdu->recv_id.sim_id.site_id == site &&
			( pdu->recv_id.sim_id.application_id == ALL_APPLIC ||
			  pdu->recv_id.sim_id.application_id == application )) {

			error_return_needed = 1;
		}
	}

	if (error_return_needed) {

		reply_pdu.acknowledge_flag   = 36;	/* per CALSPAN */
		reply_pdu.resp_flag  = 5;			/* error state, per CALSPAN */

		status = disx_writePDU(app, (dis_pdu *) & reply_pdu);
		status = (status == 0) ? 0 : -1;
	}

	return status;
}

static int
acknowledgePDU ( dis_acknowledge_pdu *pdu )
{
	dis_if_Entity   *e;
	dis_if_OutstandingRequestInfo *preq;

	preq = findRequestByRequestID ( pdu->request_id );

	if ( preq != NULL && 
		 preq->request_type == OUTSTANDING_REQUEST_TRANSFER_CONTROL ) {

		e = preq->e;

		/*
		 * no longer need to track the request
		 */

		removeRequest ( preq );

		/*
		 * Were we expecting to be granted control? If not, there is some sort
         * of error.
		 *
		 * This is a non-standard exchange defined by CALSPAN. We are 
         * processing a response to a control request.  The request was 
         * originated by us, the response we just received comes from the 
		 * controlling application.
		 */

		if ( pdu->acknowledge_flag == 36 ) {

			/*
			 * response flag set to "1" to indicate a success
			 *                      "5" indicates a failure
			 */

			switch (pdu->resp_flag) {
			case 1:
				if (e->controlRequestCallback) {
					(e->controlRequestCallback)( (dis_pdu *) pdu, 
												 e->callbackData );
				}
				e->isLocal = 1;
				e->state = dis_if_ENTITY_STATE_SIMULATING;
				e->pending_state = dis_if_ENTITY_STATE_NONE;
				break;

			case 5:
				if (e->controlRequestCallback) {
					(e->controlRequestCallback)( (dis_pdu *) pdu, 
												 e->callbackData );
				}
				break;

			default:
				printf("Unrecognized response flag in Acknowledge DIS PDU: %d\n",
						pdu->resp_flag );
				break;
			}

			e->controlRequestCallback = NULL;
			e->callbackData = NULL;
		}
	}

	return 0;
}


int
dis_if_sendMessage(int senderEid, char *s)
{
	dis_comment_pdu pdu, *msgPDU = &pdu;
	dis_variable_datum datum;
	
	dis_if_Entity  *sender = varray_getValue(entities, senderEid);

	if ( ! network_enabled || ! app || validating_site_id )
		return 0;

	msgPDU->hdr.pdu_type = PDUTypeComment;
	msgPDU->hdr.time_stamp = timeDoubleToDIS(theTime, absoluteTime);
	msgPDU->orig_id = sender->entityId;
	msgPDU->recv_id.entity_id = 0; // ?
	msgPDU->num_variable_data = 1;
	msgPDU->variable_datum = &datum;
	datum.datum_id = datum_AltDescription; // ?
	datum.value_length = 8*strlen(s);
	datum.value.ptr_value = (u_char *) s;
	
	if (disx_writePDU(app, (dis_pdu *) msgPDU) != 0) {
		return -1;
	}
	return 0;
}

int
dis_if_requestControl (dis_if_Entity *e, 
					int (*callbackFn)(dis_pdu*, void *), 
					void *arg)
{
	dis_pdu pdu;
	int status;
	dis_if_OutstandingRequestInfo *preq;


	if ( app == NULL || validating_site_id || e->isLocal)
		return -1;

	memset ( &pdu, 0, sizeof(pdu) );
	e->callbackData = arg;
	e->controlRequestCallback = callbackFn;

	pdu.hdr.pdu_type = PDUTypeTransferControl;
	pdu.hdr.time_stamp = timeDoubleToDIS(theTime, absoluteTime);

	pdu.transfer_control.request_id = disx_issueRequestID ( app );

	pdu.transfer_control.orig_id.sim_id.site_id = site;
	pdu.transfer_control.orig_id.sim_id.application_id = application;
	pdu.transfer_control.orig_id.entity_id = NO_ENTITY;

	pdu.transfer_control.recv_id.sim_id = e->entityId.sim_id;
	pdu.transfer_control.recv_id.entity_id = NO_ENTITY;

	pdu.transfer_control.target_id = e->entityId;

	pdu.transfer_control.reliability_service = 1;
	pdu.transfer_control.num_record_sets = 0;
	pdu.transfer_control.transfer_type = DISTransferTypeEntityRequest;

	/*
	 *  Add tracking information so that we know about this
	 *  outstanding request.
	 */

	preq = addRequest ( pdu.transfer_control.request_id );
	if (preq) {
		preq->request_type = OUTSTANDING_REQUEST_TRANSFER_CONTROL;
		preq->e = e;
		/* requests can timeout, but we don't track that, yet */
		preq->timeout_time = theTime + 5.0;
	}

	status = disx_writePDU( app, & pdu );
	return (status == 0) ? 0 : -1;
}

static int
setDataPDU (dis_set_data_pdu *pdu)
{
	int          eid;
	dis_data_pdu reply_pdu;
	unsigned int i;
	int status;
	
	if( app == NULL )
		return -1;

	reply_pdu.hdr     = pdu->hdr;
	reply_pdu.hdr.pdu_type   = PDUTypeData;
	reply_pdu.hdr.time_stamp = timeDoubleToDIS( theTime, absoluteTime );

	reply_pdu.orig_id = pdu->recv_id;
	reply_pdu.recv_id = pdu->orig_id;
	reply_pdu.request_id  = pdu->request_id;

	disx_initializeDatumInfo (&reply_pdu.datum_info);

	eid = findEntity(&pdu->recv_id);
	if (eid == 0) {
		return -1;
	}
	/*
	else {
		e = &entities[eid];
	}
	*/

	for (i=0; i<pdu->datum_info.num_fixed_data; ++i) {
		switch ( pdu->datum_info.variable_datum[i].datum_id ) {
		case datum_OrientationX:
			break;
		case datum_OrientationY:
			break;
		case datum_OrientationZ:
			break;

		case datum_XVelocity:
			break;
		case datum_YVelocity:
			break;
		case datum_ZVelocity:
			break;
		}
	}

	for (i=0; i<pdu->datum_info.num_variable_data; ++i) {
		switch ( pdu->datum_info.variable_datum[i].datum_id ) {
		case datum_GeocentricCoordinatesX:
			break;
		case datum_GeocentricCoordinatesY:
			break;
		case datum_GeocentricCoordinatesZ:
			break;
		}
	}

	/*
	 * Send reply
	 */

	status = disx_writePDU(app, (dis_pdu *) & reply_pdu);
	return (status == 0) ? 0 : -1;
}

static int
stopPDU (dis_stop_pdu *pdu)
{
	dis_if_Entity     *e;
	int          eid;
	int          status = 0;
	int          need_reply = 1;
	int          all_local_entities = 0;
	dis_acknowledge_pdu reply_pdu;
	struct timeval tv;
	double changeTime;

	if( app == NULL || validating_site_id )
		return -1;
	reply_pdu.hdr     = pdu->hdr;
	reply_pdu.hdr.pdu_type   = PDUTypeAcknowledge;
	reply_pdu.hdr.time_stamp = timeDoubleToDIS( theTime, absoluteTime );

	reply_pdu.orig_id = pdu->recv_id;
	reply_pdu.recv_id = pdu->orig_id;

	reply_pdu.request_id  = pdu->request_id;
	reply_pdu.acknowledge_flag = DISAcknowledgeFlagStop;
	reply_pdu.resp_flag = DISRequestStatusComplete;

	if (pdu->recv_id.sim_id.site_id == ALL_SITES) {
		all_local_entities = 1;
	}
	else if (pdu->recv_id.sim_id.site_id == site &&
		     pdu->recv_id.sim_id.application_id == ALL_APPLIC) {
		all_local_entities = 1;
	}
	else if (pdu->recv_id.sim_id.site_id == site &&
		     pdu->recv_id.sim_id.application_id == application &&
			 pdu->recv_id.entity_id == ALL_ENTITIES) {
		all_local_entities = 1;
	}

	dis_timeToTimeval( &pdu->real_time, &tv );
	changeTime = tv.tv_sec + tv.tv_usec / 1000000.0;

	if ( all_local_entities ) {
		int i;
		
		for(i = varray_firstHandle(entities); i != 0; i = varray_nextHandle(entities)){
			e = varray_getValue(entities, i);
			
			if ( ! e->isLocal ) {
				continue;
			}
			
			if ( changeTime <= theTime ) {
				e->state = dis_if_ENTITY_STATE_STOPPED;
			}
			else {
				e->pending_state = dis_if_ENTITY_STATE_STOPPED;
				e->pending_time = changeTime;
			}
			
			if ( pdu->behavior & DISFrozenBehaviorRunClock ) {
			}
		
			if ( pdu->behavior & DISFrozenBehaviorTransmit ) {
				e->emit_while_frozen = 1;
			}
		
			if ( pdu->behavior & DISFrozenBehaviorReceive ) {
			}
		}
		varray_releaseIterator(entities);
	}
	else {

		eid = findEntity(&pdu->recv_id);
		if (eid == 0) {
			/* cannot comply, entity not found */
			reply_pdu.orig_id.entity_id = NO_ENTITY;
			reply_pdu.resp_flag = DISRequestStatusOther;
		}
		else {
			e = varray_getValue(entities, eid);

			if (e->isLocal) {

				if ( changeTime <= theTime ) {
					e->state = dis_if_ENTITY_STATE_STOPPED;
				}
				else {
					e->pending_state = dis_if_ENTITY_STATE_STOPPED;
					e->pending_time = changeTime;
				}
			
				if ( pdu->behavior & DISFrozenBehaviorRunClock ) {
				}
			
				if ( pdu->behavior & DISFrozenBehaviorTransmit ) {
					e->emit_while_frozen = 1;
				}
			
				if ( pdu->behavior & DISFrozenBehaviorReceive ) {
				}
			}
			else {
				need_reply = 0;
			}
		}
	}
	
	/*
	 * Send reply
	 */
	if (need_reply) {
		status = disx_writePDU(app, (dis_pdu *) & reply_pdu);
	}
	return (status == 0) ? 0 : -1;
}

static int
startPDU (dis_start_pdu *pdu)
{
	dis_if_Entity     *e;
	int          eid;
	int          status = 0;
	int          need_reply = 1;
	int          all_local_entities = 0;
	dis_acknowledge_pdu reply_pdu;
	struct timeval tv;
	double changeTime;

	if( app == NULL || validating_site_id )
		return -1;
	reply_pdu.hdr     = pdu->hdr;
	reply_pdu.hdr.pdu_type   = PDUTypeAcknowledge;
	reply_pdu.hdr.time_stamp = timeDoubleToDIS( theTime, absoluteTime );

	reply_pdu.orig_id = pdu->recv_id;
	reply_pdu.recv_id = pdu->orig_id;

	reply_pdu.request_id  = pdu->request_id;
	reply_pdu.acknowledge_flag = DISAcknowledgeFlagStop;
	reply_pdu.resp_flag = DISRequestStatusComplete;

	if (pdu->recv_id.sim_id.site_id == ALL_SITES) {
		all_local_entities = 1;
	}
	else if (pdu->recv_id.sim_id.site_id == site &&
		     pdu->recv_id.sim_id.application_id == ALL_APPLIC) {
		all_local_entities = 1;
	}
	else if (pdu->recv_id.sim_id.site_id == site &&
		     pdu->recv_id.sim_id.application_id == application &&
			 pdu->recv_id.entity_id == ALL_ENTITIES) {
		all_local_entities = 1;
	}

	dis_timeToTimeval( &pdu->real_time, &tv );
	changeTime = tv.tv_sec + tv.tv_usec / 1000000.0;

	if ( all_local_entities ) {
		int i;

		for(i = varray_firstHandle(entities); i != 0; i = varray_nextHandle(entities)){
			e = varray_getValue(entities, i);

			if ( ! e->isLocal ) {
				continue;
			}

			if ( changeTime <= theTime ) {
				e->state = dis_if_ENTITY_STATE_SIMULATING;
			}
			else {
				e->pending_state = dis_if_ENTITY_STATE_SIMULATING;
				e->pending_time = changeTime;
			}

			e->emit_while_frozen = 0;
		}
		varray_releaseIterator(entities);
	}
	else {
		eid = findEntity( &pdu->recv_id );
		if (eid == 0) {
			/* cannot comply. entity not found */
			reply_pdu.orig_id.entity_id = NO_ENTITY;
			reply_pdu.resp_flag = DISRequestStatusOther;
		}
		else {
			e = varray_getValue(entities, eid);

			/*
			 * Is entity local ?
			 */

			if ( e->isLocal ) {

				if ( changeTime <= theTime ) {
					e->state = dis_if_ENTITY_STATE_SIMULATING;
				}
				else {
					e->pending_state = dis_if_ENTITY_STATE_SIMULATING;
					e->pending_time = changeTime;
				}

				e->emit_while_frozen = 0;
			}
			else {
				need_reply = 0;
			}
		}
	}

	/*
	 * Send reply
	 */

	if (need_reply) {
		status = disx_writePDU(app, (dis_pdu *) & reply_pdu);
	}
	return (status == 0) ? 0 : -1;
}

int dis_if_getNumberOfLocalEntities()
{
	return stats_local_entities_no;
}

int dis_if_getNumberOfRemoteEntities()
{
	return stats_remote_entities_no;
}

double dis_if_getProcessedPacketsPerSecond()
{
	return stats_processed_packets_per_second;
}


char *dis_if_updateRemote(craft *c)
{
	double    location[3];
	double    velocity[3];
	double    orientation[3];
	VPoint    tmp;
	VMatrix   ABCtoXYZ;

	if( ! dis_if_getEntityState(c->disId, location, velocity, orientation) )
		return "entity not tracked anymore";

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

	earth_XYZToLatLonAlt(&c->Sg, &c->w);
	earth_generateWorldToLocalMatrix(&c->w, &c->XYZtoNED);

	tmp.x = units_METERStoFEET(velocity[0]);
	tmp.y = units_METERStoFEET(velocity[1]);
	tmp.z = units_METERStoFEET(velocity[2]);
	VTransform_(&tmp, &c->XYZtoNED, &c->Cg);

	/* Compute the "ABCtoNED" trihedral from the DIS Euler angles: */
	c->curRoll = pm_normalize_roll( orientation[0] );
	c->curPitch = pm_normalize_pitch( orientation[1] );
	c->curHeading = pm_normalize_yaw( orientation[2] );

	VEulerToMatrix(c->curRoll, c->curPitch, c->curHeading,
					 &ABCtoXYZ);
	VMatrixMultByRank(&ABCtoXYZ, &c->XYZtoNED, &c->trihedral, 3);

	/* Now derive NED heading, pitch and roll from adjusted trihedral: */
	pm_euler(c);
	
	return NULL;
}


void dis_if_updateLocal(craft *c)
{
	VPoint    velocity, linearAcceleration;
	double    location[3], disVelocity[3], disLinearAcceleration[3];
	double    orientation[3], angularVelocity[3];
	VPoint    tmp;
	VMatrix   ABCtoXYZ, NEDtoXYZ;
	static double base = -1;

	if (base < 0)
		base = curTime;

/*
 *  Well, this is a bit strange, but ACM's coordinate system for positions
 *  is meters in the Geocentric; for velocities are expressed as feet per
 *  second in the local NED [north-east-down] system.
 */

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

	tmp.x = units_FEETtoMETERS(c->Cg.x);
	tmp.y = units_FEETtoMETERS(c->Cg.y);
	tmp.z = units_FEETtoMETERS(c->Cg.z);
	VReverseTransform_(&tmp, &c->XYZtoNED, &velocity);
	disVelocity[0] = velocity.x;
	disVelocity[1] = velocity.y;
	disVelocity[2] = velocity.z;

/*
 *  Derive ECI [Geocentric] heading, pitch, roll
 */
	VMatrixTranspose(&c->XYZtoNED, &NEDtoXYZ);
	/* the trihedral is an "ABCtoNED" transformation */
	VMatrixMultByRank(&c->trihedral, &NEDtoXYZ, &ABCtoXYZ, 3);
	VMatrixToEuler(&ABCtoXYZ, &orientation[0], &orientation[1], &orientation[2]);
	
	/*
	 *  Body frame angular velocities.
	 */

	angularVelocity[0] = c->p;	/* x-axis */
	angularVelocity[1] = c->q;	/* y-axis */
	angularVelocity[2] = c->r;	/* z-axis */

	/*
	 *  Transform linear acceleration vector
	 *  from body coordinates to ECI system
	 */

	tmp = c->linAcc;
	tmp.x = units_FEETtoMETERS(tmp.x);
	tmp.y = units_FEETtoMETERS(tmp.y);
	tmp.z = units_FEETtoMETERS(tmp.z);
	VTransform_(&tmp, &ABCtoXYZ, &linearAcceleration);
	disLinearAcceleration[0] = linearAcceleration.x;
	disLinearAcceleration[1] = linearAcceleration.y;
	disLinearAcceleration[2] = linearAcceleration.z;
	
	dis_if_setEntityState(c->disId, location, disVelocity,
		disLinearAcceleration, orientation, angularVelocity);
}