#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <time.h>
#include <string.h>
#include <errno.h>

#include "../util/error.h"
#include "../util/memory.h"
#include "../util/reader.h"
#include "../util/units.h"
#include "navaid.h"
#include "runway.h"

#define zone_IMPORT
#include "zone.h"

/**
 * Schedule zone purge if nobody flies over or near to a zone for so much
 * time (s).
 */
#define zone_EXPIRE_PERIOD_SEC 60

/**
 * A location is close to a zone if inside or whithin this angle range.
 * Note that 4 DEG corresponds to about 240 NM while the max range of a NAVAID
 * under ACM is NM: this guarantees any close scenery be loaded and then any
 * tunable station can be received.
 */
#define zone_CLOSE_ANGLE units_DEGtoRAD(4)


struct zone_Type {
	
	/**
	 * Range of geographic coordinates covered by this zone (RAD).
	 */
	double lat1, lat2, lon1, lon2;
	
	/**
	 * The zone range enlarged by our DELTA margin. Locations inside this
	 * range of latitude and longitude are close to this zone.
	 * Basically,
	 * close_lat1 = lat1 - DELTA,
	 * close_lat2 = lat2 + DELTA,
	 * close_lon1 = lon1 - DELTA,
	 * close_deltaLon = lon2 - lon1 + 2*DELTA,
	 * but special case must be taken near the poles. The longitude range might
	 * be de-normalized outside the canonical [-180,+180] DEG range.
	 */
	double close_lat1, close_lat2, close_lon1, close_deltaLon;
	
	/** Path to the ACM scenery file. */
	char *path;
	
	/** If this scenery is currently loaded in memory. */
	int isLoaded;
	
	/*
	 *  The following fields assigned only if loaded.
	 */
	
	/** Scheduled purge time (s). */
	time_t expire;
	
	/** Team locations read from the scenery. */
	earth_LatLonAlt forceBaseLocations[4];
	
	/** Ground color read from the scenery. */
	char *groundColor;
};


/**
 * Recycled surface objects. Objects removed from global surface objects table
 * stbl goes here.
 */
static craft *stbl_pool;


/**
 * If cleanup function registered in the memory module.
 */
static int cleanup_set;


static void zone_cleanup()
{
	//printf("DEBUG: %s\n", __FUNCTION__);
	while( stbl_pool != NULL ){
		craft *next = stbl_pool->next;
		memory_dispose(stbl_pool);
		stbl_pool = next;
	}
	cleanup_set = 0;
}


/**
 * Returns a brand new craft data structure linked to the surface objects table.
 * @return 
 */
static craft * zone_craftNew()
{
	craft *c;
	if( stbl_pool == NULL ){
		c = memory_allocate(sizeof(craft), NULL);
		memset(c, 0, sizeof(craft));
	} else {
		c = stbl_pool;
		stbl_pool = c->next;
	}
	c->prev = NULL;
	c->next = stbl;
	if( stbl != NULL )
		stbl->prev = c;
	stbl = c;
	return c;
}


/**
 * Removes a craft from the surface objects linked list.
 * @param c
 */
static void zone_craftRelease(craft *c)
{
	if( c->prev == NULL ){
		if( c->next != NULL )
			c->next->prev = NULL;
		stbl = c->next;
	} else {
		c->prev->next = c->next;
		if( c->next != NULL )
			c->next->prev = c->prev;
	}
	memset(c, 0, sizeof(craft));
	c->next = stbl_pool;
	stbl_pool = c;
}


static void zone_destruct(void *p)
{
	zone_Type *this = p;
	//printf("DEBUG: %s %s\n", __FUNCTION__, this->path);
	zone_purge(this, 1);
	memory_dispose(this->path);
}


zone_Type * zone_new(char *path, double lat1, double lat2, double lon1, double lon2)
{
	if( ! cleanup_set ){
		memory_registerCleanup(zone_cleanup);
		cleanup_set = 1;
	}
	
	zone_Type * this = memory_allocate(sizeof(zone_Type), zone_destruct);
	memset(this, 0, sizeof(*this));
	this->path = memory_strdup(path);
	this->lat1 = lat1;
	this->lat2 = lat2;
	this->lon1 = lon1;
	this->lon2 = lon2;
	this->groundColor = NULL;
	this->isLoaded = 0;
	this->expire = 0;
	
	// Compute the range for locations close to this zone.
	if( lat2 > units_DEGtoRAD(85) ){
		// Close to north pole. Take all dome above lat1.
		this->close_lat1 = lat1 - zone_CLOSE_ANGLE;
		this->close_lat2 = units_DEGtoRAD(90);
		this->close_lon1 = -M_PI;
		this->close_deltaLon = 2*M_PI;
		
	} else if( lat1 < units_DEGtoRAD(-85) ){
		// Close to south pole. Take all dome below lat2.
		this->close_lat1 = units_DEGtoRAD(-90);
		this->close_lat2 = lat2 + zone_CLOSE_ANGLE;
		this->close_lon1 = -M_PI;
		this->close_deltaLon = 2*M_PI;
		
	} else {
		this->close_lat1 = lat1 - zone_CLOSE_ANGLE;
		this->close_lat2 = lat2 + zone_CLOSE_ANGLE;
		// Meridians are very close together near poles, so zone_CLOSE_ANGLE
		// must be adjusted by a 1/cos(lat) factor; take the max:
		double dlon_south = zone_CLOSE_ANGLE / cos(lat1);
		double dlon_north = zone_CLOSE_ANGLE / cos(lat2);
		double dlon;
		if( dlon_north < dlon_south )
			dlon = dlon_south;
		else
			dlon = dlon_north;
		this->close_lon1 = lon1 - dlon;
		this->close_deltaLon = lon2 - lon1 + 2*dlon;
	}
	
	return this;
}

int zone_isLoaded(zone_Type *this)
{
	return this->isLoaded;
}


char * zone_getPath(zone_Type *this)
{
	return this->path;
}


char * zone_getGroundColor(zone_Type *this)
{
	return this->groundColor;
}


earth_LatLonAlt * zone_getForceBaseLocation(zone_Type *this, int force)
{
	assert(0 <= force && force < 4);
	if( ! this->isLoaded )
		return NULL;
	return &this->forceBaseLocations[force];
}


/** Returns true if the zone includes the location. */
int zone_includesLocation(zone_Type *this, earth_LatLonAlt *w)
{
	return this->lat1 <= w->latitude && w->latitude < this->lat2
	&& this->lon1 <= w->longitude && w->longitude < this->lon2;
}


/**
 * Normalize angle difference B-A for a point moving from a longitude A to
 * longitude B walking east. For example, going from longitude 30 to 40 is a
 * walk of 10 DEG; vice-versa, going from 40 to 30 still walking east is a
 * 350 DEG walk.
 */
static double zone_goEast(double x)
{
	while(x < 0)
		x += M_PI;
	return x;
}


int zone_isClose(zone_Type *this, earth_LatLonAlt *p)
{
	return this->close_lat1 <= p->latitude && p->latitude < this->close_lat2
	&& zone_goEast(p->longitude - this->close_lon1) <  this->close_deltaLon;
}


int zone_overlaps(zone_Type *this, zone_Type *other)
{
	return !( (this->lat2 <= other->lat1 || other->lat2 <= this->lat1)
		   || (this->lon2 <= other->lon1 || other->lon2 <= this->lon1) );
}


#define parse_error(in, ...) \
	{ \
		fprintf(stderr, "%s:%d: ", reader_getPath(in), reader_getLineNumber(in)); \
		fprintf(stderr, __VA_ARGS__); \
		fprintf(stderr, "\n"); \
	}


/**
 * Remove some of the detail in an object to reduce the CPU overhead
 * of transforming and clipping it.
 * 
 * FIXME: quite arbitrary algorithm -- to be revised.
 * We might, for example, "linearize" near straight lines.
 * For now, applies only to polygons with more than 9 vertices.
 */
static void zone_undersampleObject (VObject * obj, int rate)
{
    int i, j, k;

    k = 0;

    for (i = 0; i < obj->numPolys; ++i) {
		if( obj->polygon[i]->numVtces < 9 )
			continue;
		for (j = 0; j < obj->polygon[i]->numVtces; j += rate) {
			obj->polygon[i]->vertex[k++] = obj->polygon[i]->vertex[j];
		}
		obj->polygon[i]->numVtces = k;
    }
}


static int zone_parseDouble(reader_Type *in, char *s, double *freq)
{
	char *tail;
	*freq = strtod(s, &tail);
	if( tail == s || *tail != 0 || ! isfinite(*freq) ){
		fprintf(stderr, "%s:%d: invalid decimal number syntax: %s\n",
			reader_getPath(in), reader_getLineNumber(in), s);
		return 0;
	}
	return 1;
}


/**
 * Parses the latitude, longitude and altitude fields.
 * @param in Input file to generate punctual error messages.
 * @param lat Latitude string.
 * @param lon Longitude string.
 * @param alt Altitude (ft).
 * @param w Put result here.
 * @return True if parsing succeeded; false if parsing failed and an error has
 * been sent to standard error.
 */
static int zone_parseLatLonAlt(reader_Type *in, char *lat, char *lon, char *alt, earth_LatLonAlt *w)
{
	if( ! earth_parseLatitude(lat, &w->latitude) ){
		fprintf(stderr, "%s:%d: invalid latitude syntax: %s\n",
			reader_getPath(in), reader_getLineNumber(in), lat);
		return 0;
	}
	if( ! earth_parseLongitude(lon, &w->longitude) ){
		fprintf(stderr, "%s:%d: invalid longitude syntax: %s\n",
			reader_getPath(in), reader_getLineNumber(in), lon);
		return 0;
	}
	if( ! zone_parseDouble(in, alt, &w->z) )
		return 0;
	w->z = units_FEETtoMETERS(w->z);
	return 1;
}


static void zone_parseTeamLocationRecord(zone_Type *this, reader_Type *in, char **argv, int argc, int team_no)
{
	if( argc != 5 ){
		fprintf(stderr, "%s:%d: invalid number of fields in %s record\n",
			reader_getPath(in), reader_getLineNumber(in), argv[0]);
		return;
	}
	if( ! zone_parseLatLonAlt(in, argv[1], argv[2], argv[3], &this->forceBaseLocations[team_no]) )
		return;
	if( ! zone_includesLocation(this, &this->forceBaseLocations[team_no]) )
		fprintf(stderr, "%s:%d: team location outside range of zone %s\n",
			reader_getPath(in), reader_getLineNumber(in), this->path);
	double heading;
	if( ! zone_parseDouble(in, argv[4], &heading) )
		return;
}


/**
 * Parses RWY and RWY2 records and add to the surface list array.
 * @param in
 * @param argv Fields of the scene record.
 * @param argc Number of fields in the record.
 */
static void zone_parseRunwayRecord(zone_Type *this, reader_Type *in, char **argv, int argc)
{
    double heading, mag, length, width;
    int j;
    VPoint fwd, down, local_fwd, local_right, local_down;
    earth_LatLonAlt w, w1, w2;
    VPoint xyz;
    VPolygon *p;

	if (strcmp(argv[0], "RWY") == 0) {
		
		if( argc != 10 ){
			parse_error(in, "invalid number of fields in runway record -- ignoring.");
			return;
		}
		
		if( ! zone_parseDouble(in, argv[4], &length) )
			return;
		length = units_FEETtoMETERS(length);
		
		if( ! zone_parseDouble(in, argv[5], &width) )
			return;
		width = units_FEETtoMETERS(width);

		if( ! zone_parseLatLonAlt(in, argv[6], argv[7], argv[3], &w) )
			return;
		if( ! zone_parseLatLonAlt(in, argv[8], argv[9], argv[3], &w1) )
			return;

		earth_LatLonAltToXYZ(&w, &xyz);
		VPoint xyz1;
		earth_LatLonAltToXYZ(&w1, &xyz1);

		/*
		 *  Calculate runway forward direction:
		 */

		local_fwd.x = xyz1.x - xyz.x;
		local_fwd.y = xyz1.y - xyz.y;
		local_fwd.z = xyz1.z - xyz.z;

		mag = sqrt (
			   local_fwd.x * local_fwd.x +
			   local_fwd.y * local_fwd.y +
			   local_fwd.z * local_fwd.z
		);
		local_fwd.x /= mag;
		local_fwd.y /= mag;
		local_fwd.z /= mag;

		/*
		 *  Average those two points to generate a midpoint that will be the
		 *  origin of a runway coordinate system.
		 */

		xyz.x = (xyz.x + xyz1.x) / 2.0;
		xyz.y = (xyz.y + xyz1.y) / 2.0;
		xyz.z = (xyz.z + xyz1.z) / 2.0;
		earth_XYZToLatLonAlt(&xyz, &w2);
		if( ! zone_includesLocation(this, &w2) && ! zone_includesLocation(this, &w1) )
			fprintf(stderr, "%s:%d: RWY %s %s middle point outside of the zone\n",
				reader_getPath(in), reader_getLineNumber(in), argv[1], argv[2]);
		VMatrix XYZtoNED;
		earth_generateWorldToLocalMatrix(&w2, &XYZtoNED);
		down.z = 1.0;
		down.x = down.y = 0.0;
		VReverseTransform_(&down, &XYZtoNED, &local_down);

		/*
		 * Calculate runway heading, normalized to [0,pi[ and fix the
		 * local_fwd direction accordingly:
		 */

		VPoint q;
		VTransform_(&local_fwd, &XYZtoNED, &q);
		heading = atan2(q.y, q.x);
		if( heading < 0.0 ){
			heading += M_PI;
			local_fwd.x = -local_fwd.x;
			local_fwd.y = -local_fwd.y;
			local_fwd.z = -local_fwd.z;
		}

	} else  { // RWY2

		if( argc != 9 ){
			parse_error(in, "invalid number of fields in runway record -- ignoring.");
			return;
		}

		
		if( ! zone_parseDouble(in, argv[4], &length) )
			return;
		length = units_FEETtoMETERS(length);
		
		if( ! zone_parseDouble(in, argv[5], &width) )
			return;
		width = units_FEETtoMETERS(width);
		
		if( ! zone_parseDouble(in, argv[8], &heading) )
			return;
		heading = units_DEGtoRAD(heading);

		/*
			In order to correctly place the runway numbers, the
			local_fwd vector must be oriented in the range [0,pi[:
		*/

		if( heading >= M_PI )
			heading -= M_PI;

		if( ! zone_parseLatLonAlt(in, argv[6], argv[7], argv[3], &w) )
			return;
		if( ! zone_includesLocation(this, &w) )
			fprintf(stderr, "%s:%d: the middle point of the runway lies outside of the zone\n",
				reader_getPath(in), reader_getLineNumber(in));

		earth_LatLonAltToXYZ(&w, &xyz);

		VMatrix XYZtoNED;
		earth_generateWorldToLocalMatrix(&w, &XYZtoNED);

		fwd.x = cos(heading);
		fwd.y = sin(heading);
		fwd.z = 0.0;
		VReverseTransform_(&fwd, &XYZtoNED, &local_fwd);

		down.z = 1.0;
		down.x = down.y = 0.0;
		VReverseTransform_ (&down, &XYZtoNED, &local_down);
	}


	/*
	 *  Calculate the local right vector in geocentric XYX.
	 *  A basic property of Cross Products: k x i = j
	 */
	VCrossProd(&local_down, &local_fwd, &local_right);

	/*
	 *  Generate a transformation matrix to get from "runway" coordinates to
	 *  Geocentric. The x axis of the runway coord. (local_fwd) points in the
	 *  range [0,180[ DEG of the geographical coords.
	 */
	VMatrix RWYtoXYZ;
	VIdentMatrix(&RWYtoXYZ);

	RWYtoXYZ.m[0][0] = local_fwd.x;
	RWYtoXYZ.m[1][0] = local_fwd.y;
	RWYtoXYZ.m[2][0] = local_fwd.z;

	RWYtoXYZ.m[0][1] = local_right.x;
	RWYtoXYZ.m[1][1] = local_right.y;
	RWYtoXYZ.m[2][1] = local_right.z;

	RWYtoXYZ.m[0][2] = local_down.x;
	RWYtoXYZ.m[1][2] = local_down.y;
	RWYtoXYZ.m[2][2] = local_down.z;

	RWYtoXYZ.m[0][3] = xyz.x;
	RWYtoXYZ.m[1][3] = xyz.y;
	RWYtoXYZ.m[2][3] = xyz.z;

	char *airport = argv[1];
	char *runways_no = argv[2];

	VPolySet *ps = runway_build(runways_no, &RWYtoXYZ, length, width, heading);

	/*
	 * We create a "craft type" for each airport. 
	 * 
	 * Optimize a bit here and put all the runways for a given airport
	 * in the same object, assuming runways are listed per airport.

	 * NOTE. With this optimization, a runway that crosses another one
	 * will hide part of this latter. Otherwise without this
	 * optimization the two overlapping parts are rendered
	 * alternatively one and the other producing a bad flickering
	 * effect, I don't know why.
	 */
	
	VObject *object;
	if( stbl == NULL )
		object = NULL;
	else
		object = stbl->cinfo->object;
	
	if ((object != NULL) && (strcmp(object->name, airport) == 0)) {

		/*
		 * New runway at same airport as the last one. Optimize by adding a
		 * polygon to the last runway.
		 */

		object->polygon = memory_realloc( object->polygon,
			sizeof (VPolygon *) * (object->numPolys + VPolySet_Count(ps)));
		j = 0;
		p = VPolySet_First(ps);
		while( p != NULL ){
			object->polygon[object->numPolys + j] = p;
			p = VPolySet_Next(ps);
			j++;
		}
		object->numPolys += VPolySet_Count(ps);
		VPolySet_Free(ps, 0);
		VComputeObjectExtent (object);
		
	} else {

		/*
		 * New airport.  New object and craftInfo.
		 */
		
		object = memory_allocate(sizeof(VObject), NULL);
		memset(object, 0, sizeof(VObject));
		object->name = memory_strdup(airport);
		object->numPolys = VPolySet_Count(ps);
		object->polygon = memory_allocate( sizeof(VPolygon *) * object->numPolys, NULL );
		p = VPolySet_First(ps);
		j = 0;
		while( p != NULL ){
			object->polygon[j++] = p;
			p = VPolySet_Next(ps);
		}
		VPolySet_Free(ps, 0);
		object->order = NULL;

		VComputeObjectExtent (object);

		craft *c = zone_craftNew();
		c->zone = this;
		c->type = CT_SURFACE;
		c->flags = FL_FIXED_OBJECT;
		c->w = w;
		/*
		 * The terrain module needs the center position of the airport (or of
		 * any nearby runway) to calculate altitude of the terrain.
		 */
		c->Sg.x = xyz.x;
		c->Sg.y = xyz.y;
		c->Sg.z = xyz.z;
		
		/* Create new craft info only if really necessary. */
		c->cinfo = inventory_craftTypeSearchByZoneAndName(this, airport);
		if( c->cinfo == NULL ){
			// FIXME: BUG: these airports are not released along with the zone.
			c->cinfo = inventory_craftTypeNew(this);
			c->cinfo->name = memory_strdup(airport);
			c->cinfo->object = object;
			c->cinfo->placeProc = NULL;
		}
	}

}


static void zone_parseNavRecord(zone_Type *this, reader_Type *in, char **argv, int argc)
{
	if( argc != 8 ){
		fprintf(stderr, "%s:%d: invalid number of fields in NAV record\n",
			reader_getPath(in), reader_getLineNumber(in));
		return;
	}
	earth_LatLonAlt w;
	if( ! zone_parseLatLonAlt(in, argv[3], argv[4], argv[5], &w) )
		return;
	if( ! zone_includesLocation(this, &w) )
		fprintf(stderr, "%s:%d: NAV outside range of zone %s\n",
			reader_getPath(in), reader_getLineNumber(in), this->path);
	double freq;
	if( ! zone_parseDouble(in, argv[6], &freq) )
		return;
	navaid_add_vor_dme_ndb(this, argv[1], argv[2], &w, freq);
}


static void zone_parseILSRecord(zone_Type *this, reader_Type *in, char **argv, int argc)
{
	if( argc != 13 ){
		fprintf(stderr, "%s:%d: invalid number of fields in ILS record\n",
			reader_getPath(in), reader_getLineNumber(in));
		return;
	}
	
	earth_LatLonAlt localizer;
	if( ! zone_parseLatLonAlt(in, argv[5], argv[6], argv[9], &localizer) )
		return;
	if( ! zone_includesLocation(this, &localizer) )
		fprintf(stderr, "%s:%d: LOCALIZER outside range of zone %s\n",
			reader_getPath(in), reader_getLineNumber(in), this->path);

	// FIXME: check presence of the GS based on the type of ILS.
	earth_LatLonAlt gs = (earth_LatLonAlt){0, 0, 0};
	double gs_angle = 0;
	if( strcmp(argv[7], "-") != 0 ){
		if( ! zone_parseLatLonAlt(in, argv[7], argv[8], "0", &gs) )
			return;
		if( ! zone_includesLocation(this, &gs) )
			fprintf(stderr, "%s:%d: GS outside range of zone %s\n",
				reader_getPath(in), reader_getLineNumber(in), this->path);
		gs.z = localizer.z;
		if( ! zone_parseDouble(in, argv[12], &gs_angle) )
			return;
	}
	
	double freq;
	if( ! zone_parseDouble(in, argv[4], &freq) )
		return;
	
	double loc_width;
	if( ! zone_parseDouble(in, argv[10], &loc_width) )
		return;
	
	double loc_bearing;
	if( ! zone_parseDouble(in, argv[11], &loc_bearing) )
		return;

	navaid_add_ils(this, argv[3], argv[2], &localizer, &gs, freq,
		loc_width, loc_bearing, gs_angle);
}


static void zone_parseFeature(zone_Type *this, reader_Type *in, char **argv, int argc)
{
	if( argc != 6 ){
		fprintf(stderr, "%s:%d: invalid number of fields in FEATURE record\n",
			reader_getPath(in), reader_getLineNumber(in));
		return;
	}
	
	earth_LatLonAlt w;
	if( ! zone_parseLatLonAlt(in, argv[2], argv[3], argv[4], &w) )
		return;
	if( ! zone_includesLocation(this, &w) )
		fprintf(stderr, "%s:%d: FEATURE outside range of zone %s\n",
			reader_getPath(in), reader_getLineNumber(in), this->path);
	
	double heading;
	if( ! zone_parseDouble(in, argv[5], &heading) )
		return;
	heading = units_DEGtoRAD(heading);
	
	int depthcue = 1;
	int undersample = 0;
	
	char *fn = argv[1];
	if (*fn == '@') {
		fn++;
		undersample = 1;
	}

	if (*fn == '+') {
		fn++;
		depthcue = 0;
	}

	craft *c = zone_craftNew();
	c->zone = this;
	c->type = CT_SURFACE;
	c->w = w;
	earth_LatLonAltToXYZ(&c->w, &c->Sg);
	c->curHeading = pm_normalize_yaw(heading);
	
	c->cinfo = inventory_craftTypeSearchByZoneAndName(this, fn);
	if ( c->cinfo == NULL ) {
		/* Create new inventory object from this feature. */
		char *fn_resolved = reader_resolveRelativePath(reader_getPath(in), fn);
		char *p = strrchr(fn_resolved, '.');
		FILE *f = fopen(fn_resolved, "r");
		if (f == NULL){
			parse_error(in, "failed opening %s: %s", fn_resolved, strerror(errno));
			memory_dispose(fn_resolved);
			zone_craftRelease(c);
			return;
		}
		c->cinfo = inventory_craftTypeNew(this);
		c->cinfo->name = fn_resolved;
		c->cinfo->object = NULL;
		if (p != NULL && (strcmp(p, ".dxf") == 0 ||
			strcmp(p, ".DXF") == 0)) {
			c->cinfo->object = VReadDepthCueuedDXFObject(f, depthcue);
		} else {
			c->cinfo->object = VReadDepthCueuedObject(f, depthcue);
		}
		if ( c->cinfo->object == NULL ) {
			parse_error(in, "failed reading %s: %s", fn_resolved, strerror(errno));
			fclose(f);
			inventory_craftTypeRelease(c->cinfo);
			zone_craftRelease(c);
			return;
		}
		fclose(f);
		if (undersample)
			zone_undersampleObject(c->cinfo->object, 3);
	}
}


void zone_load(zone_Type *this)
{
	this->expire = time(NULL) + zone_EXPIRE_PERIOD_SEC;
	if( this->isLoaded )
		return;
	//printf("DEBUG: loading zone %s\n", this->path);
	this->isLoaded = 1;
	this->groundColor = memory_strdup("#305030");
	
    /*
     *  The units used in the objects we've created are expressed in FEET.
     *  Internally, graphics objects should be METERS.
     */
    VPoint scale;
    scale.x = scale.y = scale.z = units_FEETtoMETERS (1.0);
    VSetReadObjectScale (&scale);

    reader_Type *in = reader_new(this->path);
    char *argv[16], line[1000];
    int argc;
    while( reader_getLine(in, line, sizeof(line)) ){

		// Ignore empty and comment lines.
		if( line[0] == '\0' || line[0] == '#' )
			continue;

		if( ! reader_split(line, &argc, argv, 32) ){
			parse_error(in, "too many fields -- ignoring line");
			
		} else if ( strcmp(argv[0], "TEAM1_LOC") == 0 ) {
			zone_parseTeamLocationRecord(this, in, argv, argc, 1);

		} else if( strcmp(argv[0], "TEAM2_LOC") == 0 ){
			zone_parseTeamLocationRecord(this, in, argv, argc, 2);

		} else if( strcmp(argv[0], "GROUND_COLOR") == 0 ){
			if( argc == 2 ){
				memory_dispose(this->groundColor);
				this->groundColor = memory_strdup(argv[1]);
			} else {
				parse_error(in, "invalid number of fields in GROUND_COLOR record");
			}

		} else if (strcmp(argv[0], "RWY") == 0) {
			zone_parseRunwayRecord(this, in, argv, argc);

		} else if (strcmp(argv[0], "RWY2") == 0) {
			zone_parseRunwayRecord(this, in, argv, argc);

		} else if (strcmp(argv[0], "NAV") == 0) {
			zone_parseNavRecord(this, in, argv, argc);
			
		} else if (strcmp(argv[0], "ILS") == 0) {
			zone_parseILSRecord(this, in, argv, argc);

		} else if (strcmp(argv[0], "FEATURE") == 0) {
			zone_parseFeature(this, in, argv, argc);
			
		} else {
			parse_error(in, "unexpected record `%s' -- ignoring.", argv[0]);
		}

    }
	if( reader_getError(in) != NULL )
		error_external("%s: %s", reader_getPath(in), reader_getError(in));
    memory_dispose(in);
}


void zone_purge(zone_Type *this, int forced)
{
	if( ! this->isLoaded )
		return;
	if( ! forced && time(NULL) < this->expire )
	this->isLoaded = 0;
	//printf("DEBUG: releasing zone %s\n", this->path);
	navaid_purgeZone(this);
	memory_dispose(this->groundColor);
	this->groundColor = NULL;
	craft *c = stbl;
	while( c != NULL ){
		craft *next = c->next;
		if( c->zone == this )
			zone_craftRelease(c);
		c = next;
	}
	inventory_purgeZone(this);
}
