/*
 *  acm : an aerial combat simulator for X
 *  Copyright (C) 1991,1992,1997  Riley Rainey
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; version 2 dated June, 1991.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program;  if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave., Cambridge, MA 02139, USA.
 */

#include <stdlib.h>
#include <string.h>
#include <math.h>

#include "../util/error.h"
#include "../util/memory.h"
#include "../util/units.h"
#include "../wmm/wmm.h"
#include "pm.h"

#define navaid_IMPORT
#include "navaid.h"


/** Double-linked list of known registered NAVAIDs. */
static navaid_Type *navaids;

/** Linked list of recycled NAVAIDs. */
static navaid_Type *pool;


static void navaid_cleanup(void)
{
	navaid_Type *n = navaids, *p;
	while (n) {
		p = n;
		n = n->next;
		memory_dispose(p);
	}
	navaids = NULL;
	
	n = pool;
	while (n) {
		p = n;
		n = n->next;
		memory_dispose(p);
	}
	pool = NULL;
}


static navaid_Type * navaid_new()
{
	navaid_Type *n;
	if( pool != NULL ){
		n = pool;
		pool = pool->next;
	} else {
		n = memory_allocate(sizeof(navaid_Type), NULL);
	}
	n->prev = NULL;
	n->next = navaids;
	if( navaids != NULL )
		navaids->prev = n;
	navaids = n;
	return n;
}


/**
 * Removes navaid from active list an put into the pool.
 */
static void navaid_release(navaid_Type *n)
{
	if( n->prev == NULL )
		navaids = n->next;
	else
		n->prev->next = n->next;
	
	if( n->next != NULL )
		n->next->prev = n->prev;
	
	n->next = pool;
	pool = n;
}


navaid_Type * navaid_reception_check(craft * c, navaid_Channel f)
{
	navaid_Type *n;
	VPoint    p;
	double    range, dist, d;

	// nearest VOR station found:
	navaid_Type *last_found = NULL;
	double    last_dist = 0.0;

	for (n = navaids; n; n = n->next) {
		if (f == n->frequency) {
			if (n->type & navaid_LOC) {
				VTransform(&c->Sg, &n->lt, &p);
				if (
					p.x > 0.0 && fabs(p.y / p.x) < 1.192 /* = 50 DEG */
					&& VMagnitude(&p) < 33e3 /* LOC, typical max range 18 NM */
				) {
					// LOC in range found: return immediately
					return n;
				}
			}
			else if (n->type & (navaid_VOR|navaid_DME)){
				if ( n->frequency < 80 /* 112.00 MHz */ ) {
					/* terminal VOR, range 40 NM */
					range = units_NMtoMETERS(40);
				}
				else {
					/* en-route VOR, range 200 NM */
					range = units_NMtoMETERS(200);
				}

				/*
					Compute aircraft position relative to the station
					in its magnetic north/east/down frame:
				*/
				VTransform(&c->Sg, &n->lt, &p);

				/*
					Ignore station if it can't be received:
				*/
				if( p.z > 0.0 ){
					/*
						Aircraft below horizon of the radio station.
						Reduce radio range due to the Earth curvature.

						The value of the constant K below was derived from
						a pure geometric calculation based on an Earth mean
						radius of 6367 Km, giving a maximum reception range of

							d=K*(sqrt(transmitter_alt)+sqrt(aircraft_alt))

						Other sources suggest a greater value K=4126 with
						all the distances in meters, but I adopted a more
						conservative approach.
					*/
					#define K 3568.0
					d = K*( sqrt(fabs(n->loc.z)) + sqrt(fabs(c->w.z)) ); /* m */
					if( d < range )
						range = d;
				}
				dist = VMagnitude(&p);
				if( dist > range ) /* too far from station */{
					continue;
					printf("%s: too far\n", n->id);
				}

				if ( last_found == NULL || last_dist > dist ) {
					// VOR found: continue looping searching
					// for possible nearest stations with same freq.
					last_found = n;
					last_dist = dist;
				}
			}
			else if(n->type & navaid_NDB){
				if (n->type & (navaid_OMARKER|navaid_MMARKER|navaid_IMARKER)) {
					range = units_NMtoMETERS(20);
				}
				else {
					range = units_NMtoMETERS(100);
				}
				VTransform(&c->Sg, &n->lt, &p);
				dist = VMagnitude(&p);
				if ( dist < range
				&& (last_found == NULL || last_dist > dist) ) {
					// NDB in range found: continue looping searching
					// for possible nearest stations
					last_found = n;
					last_dist = dist;
				}
			}
			else {
				error_internal("unexpected navaid type %d", n->type);
			}
		}
	}

	if( last_found == NULL ){
		// no stations found in range
		return NULL;
	}
	else {
		// nearest in range station found
		return last_found;
	}
}

void navaid_add_vor_dme_ndb(zone_Type *zone, char *ident, char *type, earth_LatLonAlt * w, double freq)
{
	// Automatic module initialization:
	if( navaids == NULL && pool == NULL )
		memory_registerCleanup(navaid_cleanup);
	
	navaid_Type *n = navaid_new();
	
	n->zone = zone;
	memory_strcpy(n->id, sizeof(n->id), ident);
	
	n->loc = *w;
	
	if (strcmp(type, "VORTAC") == 0) {
		n->type = navaid_VOR | navaid_DME;
	}
	else if (strcmp(type, "TACAN") == 0) {
		n->type = navaid_VOR | navaid_DME;
	}
	else if (strcmp(type, "VOR/DME") == 0) {
		n->type = navaid_VOR | navaid_DME;
	}
	else if (strcmp(type, "VOR") == 0) {
		n->type = navaid_VOR;
	}
	else if (strcmp(type, "DME") == 0) {
		n->type = navaid_DME;
	}
	else if (strcmp(type, "NDB") == 0) {
		n->type = navaid_NDB;
	}
	else if (strcmp(type, "OMARKER") == 0) {
		n->type = navaid_NDB | navaid_OMARKER;
	}
	else if (strcmp(type, "OMARKER/COMLO") == 0) {
		n->type = navaid_NDB | navaid_OMARKER;
	}
	else if (strcmp(type, "MMARKER") == 0) {
		n->type = navaid_NDB | navaid_MMARKER;
	}
	else if (strcmp(type, "IMARKER") == 0) {
		n->type = navaid_NDB | navaid_IMARKER;
	}
	else if (strcmp(type, "NDB") == 0) {
		n->type = navaid_NDB;
	}
	else {
		fprintf(stderr, "%s: %s: unexpected NAV of the type `%s'. Ignore.\n",
			zone_getPath(zone), ident, type);
		navaid_release(n);
		return;
	}
	
	earth_generateWorldToLocalMatrix(w, &n->lt);
	if( n->type & navaid_VOR ){
		wmm_MagneticField mf;
		wmm_getMagneticField(0.0, w->latitude, w->longitude, w->z, &mf);
		n->bearing = - mf.Decl;
		VRotate(&n->lt, ZRotation, n->bearing);
	} else {
		n->bearing = 0;
	}
	
	earth_LatLonAltToXYZ(w, &n->Sg);

	if (n->type & (navaid_VOR | navaid_DME)) {
		n->frequency = navaid_VOR_CHANNEL_MIN + (int) ((freq - 108.00) * 20.0 + 0.5);
		if( n->frequency < navaid_VOR_CHANNEL_MIN
		|| n->frequency > navaid_VOR_CHANNEL_MAX ){
			fprintf(stderr, "%s: %s VOR/DME: frequency %g out of range. Ignore.\n",
				zone_getPath(zone), ident, freq);
			navaid_release(n);
			return;
		}
	}
	else {
		n->frequency = (int) freq;
		if( n->frequency < navaid_NDB_CHANNEL_MIN
		|| n->frequency > navaid_NDB_CHANNEL_MAX ){
			fprintf(stderr, "%s: %s: frequency out of range %g for NDB station. Ignore.\n",
				zone_getPath(zone), ident, freq);
			navaid_release(n);
			return;
		}
	}
}

void navaid_add_ils(zone_Type *zone, char *ident, char *type, earth_LatLonAlt * w,
	earth_LatLonAlt * gsw, double freq, double loc_width,
	double loc_bearing, double gs_angle)
{
	// Automatic module initialization:
	if( navaids == NULL && pool == NULL )
		memory_registerCleanup(navaid_cleanup);
	
	navaid_Type *n = navaid_new();
	n->zone = zone;
	memory_strcpy(n->id, sizeof(n->id), ident);
	n->bearing = units_DEGtoRAD(loc_bearing);
	n->loc = *w;
	earth_generateWorldToLocalMatrix(w, &n->lt);
	VRotate(&n->lt, ZRotation, -n->bearing - M_PI);
	earth_LatLonAltToXYZ(w, &n->Sg);

	n->gs_loc = *gsw;
	earth_generateWorldToLocalMatrix(gsw, &n->gst);
	VRotate(&n->gst, ZRotation, -n->bearing - M_PI);

	n->slope = units_DEGtoRAD(gs_angle);
	n->beam_width = units_DEGtoRAD(loc_width);

	if (strcmp(type, "ILS") == 0
	|| strcmp(type, "LOC/GS") == 0 ) {
		n->type = navaid_LOC | navaid_GS;
	}
	else if (strcmp(type, "ILS/DME") == 0) {
		n->type = navaid_LOC | navaid_GS | navaid_DME;
	}
	else if (strcmp(type, "LOCALIZER") == 0
	|| strcmp(type, "LDA") == 0
	|| strcmp(type, "SDF") == 0 ) {
		n->type = navaid_LOC;
	}
	else if (strcmp(type, "LOC/DME") == 0
	|| strcmp(type, "LDA/DME") == 0
	|| strcmp(type, "SDF/DME") == 0 ) {
		n->type = navaid_LOC | navaid_DME;
	}
	else {
		fprintf(stderr, "%s: %s: unknown ILS type `%s'. Ignore.\n",
			zone_getPath(zone), ident, type);
		navaid_release(n);
		return;
	}
	
	if( (n->type & navaid_GS) && !(1 <= gs_angle && gs_angle <= 5) ){
		fprintf(stderr, "%s: %s: invalid slope angle: %g DEG. Ignore.\n",
			zone_getPath(zone), ident, gs_angle);
		navaid_release(n);
		return;
	}

	n->frequency = navaid_VOR_CHANNEL_MIN + (int) ((freq - 108.00) * 20.0 + 0.5);
	if( n->frequency < navaid_VOR_CHANNEL_MIN
	|| n->frequency > navaid_VOR_CHANNEL_MAX ){
		fprintf(stderr, "%s: %s ILS: frequency %g out of the range. Ignore.\n",
			zone_getPath(zone), ident, freq);
		navaid_release(n);
		return;
	}

	if( navaids == NULL )
		memory_registerCleanup(navaid_cleanup);
}


void navaid_purgeZone(zone_Type *zone)
{
	navaid_Type *n = navaids;
	while( n != NULL ){
		if( n->zone == zone ){
			navaid_Type *q = n;
			n = n->next;
			navaid_release(q);
		} else {
			n = n->next;
		}
	}
}
