/*
 * $Id: db.c,v 2.10 90/06/15 21:32:08 tynor Exp $
 *----------------------------------------------------------------------------
 *	FPLAN - Flight Planner
 *	Steve Tynor
 *	tynor@prism.gatech.edu
 *
 *	This program is in the public domain. Permission to copy,
 * distribute, modify this program is hearby given as long as this header
 * remains. If you redistribute this program after modifying it, please
 * document your changes so that I do not take the blame (or credit) for
 * those changes.  If you fix bugs or add features, please send me a
 * patch so that I can keep the 'official' version up-to-date.
 *
 *	Bug reports are welcome and I'll make an attempt to fix those
 * that are reported.
 *
 *	USE AT YOUR OWN RISK! I assume no responsibility for any
 * errors in this program, its database or documentation. I will make an
 * effort to fix bugs, but if you crash and burn because, for example,
 * fuel estimates in this program were inaccurate, it's your own fault
 * for trusting somebody else's code! Remember, as PIC, it's _your_
 * responsibility to do complete preflight planning. Use this program as
 * a flight planning aid, but verify its results before using them.
 *----------------------------------------------------------------------------
 */

#include "mystring.h"
#include <stdio.h>
#include <ctype.h>
#include "wp_info.h"
#include "config.h"

extern char *malloc();
extern char *getenv();
extern int qsort();

static char rcsid[] = "$Id: db.c,v 2.10 90/06/15 21:32:08 tynor Exp $";

/*
 * the database file pointers:
 */
typedef struct {
   FILE  *fp;
   long  rec_size;
   long  num_recs;
} DB_FILE;

DB_FILE pub_airports, pvt_airports, pub_vors, pvt_vors;

#define FSEEK_FROM_BEGINNING 0
#define FSEEK_FROM_CURRENT   1
#define FSEEK_FROM_END       2

/*----------------------------------------------------------------------------*/
static long get_rec_size (buffer)
char *buffer;
{
   long i;

   for (i = 0;; i++) {
      if (buffer[i] == '\n') {
	 return (i + 1L);
      }
   }
}

/*----------------------------------------------------------------------------*/
static long get_num_recs (rec_size, fp, filename)
     long  rec_size;
     FILE **fp;
     char *filename;
{
   long file_size;

   if (fseek (*fp, 0L, FSEEK_FROM_END)) {
      fprintf (stderr, "ERROR: could not seek in db file: %s\n", filename);
      *fp = NULL;
      return (0L);
   }
   file_size = ftell (*fp);

#ifndef MSDOS
   /*
    * can't figure out why MSC doesn't do this properly - maybe it's an 
    * inconsistency in the way fseek works? For now, just assume that 
    * database is in the proper format.
    */
   if ((file_size % rec_size) != 0L) {
      fprintf (stderr, 
	       "ERROR: file size (%ld/%ld) inconsistency in db file: %s\n", 
	       file_size, rec_size, filename);
      *fp = NULL;
      return (0L);
   }
#endif
   return (file_size / rec_size);
}

/*----------------------------------------------------------------------------*/
BOOLEAN open_dbs ()
{
   char *pub_dir, *pvt_dir;
   BOOLEAN ok;

#define BUFSIZE 200
   char filename[BUFSIZE];
   char buffer[BUFSIZE];

#ifdef MSDOS
#define OPEN_MODE "rb"
#else
#define OPEN_MODE "r"
#endif

   if (! (pub_dir = getenv (NAV_PUBLIC)))
      pub_dir = DEFAULT_PUB_DIRECTORY;

   if (! (pvt_dir = getenv (NAV_PRIVATE)))
      pvt_dir = DEFAULT_PVT_DIRECTORY;

#ifndef MSDOS
   if (pvt_dir[0] == '~') {
      strcpy (filename, getenv (HOME));
      strcat (filename, &pvt_dir[1]);
      pvt_dir = strdup (filename);
   }
#endif

   strcpy (filename, pub_dir);
   strcat (filename, AIRPORTS_NAME);
   if (pub_airports.fp = fopen (filename, OPEN_MODE)) {
      fgets (buffer, BUFSIZE, pub_airports.fp);
      pub_airports.rec_size = get_rec_size (buffer);
      pub_airports.num_recs = get_num_recs (pub_airports.rec_size,
					    &pub_airports.fp, filename);
   }

   strcpy (filename, pvt_dir);
   strcat (filename, AIRPORTS_NAME);
   if (pvt_airports.fp = fopen (filename, OPEN_MODE)) {
      fgets (buffer, BUFSIZE, pvt_airports.fp);
      pvt_airports.rec_size = get_rec_size (buffer);
      pvt_airports.num_recs = get_num_recs (pvt_airports.rec_size,
					    &pvt_airports.fp, filename);
   }

   strcpy (filename, pub_dir);
   strcat (filename, VORS_NAME);
   if (pub_vors.fp = fopen (filename, OPEN_MODE)) {
      fgets (buffer, BUFSIZE, pub_vors.fp);
      pub_vors.rec_size = get_rec_size (buffer);
      pub_vors.num_recs = get_num_recs (pub_vors.rec_size,
					&pub_vors.fp, filename);
   }

   strcpy (filename, pvt_dir);
   strcat (filename, VORS_NAME);
   if (pvt_vors.fp = fopen (filename, OPEN_MODE)) {
      fgets (buffer, BUFSIZE, pvt_vors.fp);
      pvt_vors.rec_size = get_rec_size (buffer);
      pvt_vors.num_recs = get_num_recs (pvt_vors.rec_size,
					&pvt_vors.fp, filename);
   }
   ok = TRUE;

   if (!pub_airports.fp && !pvt_airports.fp) {
      ok = FALSE;
      fprintf (stderr, "ERROR: neither public or private airports db found\n");
   }
   if (!pub_vors.fp && !pvt_vors.fp) {
      ok = FALSE;
      fprintf (stderr, "ERROR: neither public or private vors db found\n");
   }
   return (ok);
}

/*----------------------------------------------------------------------------*/
BOOLEAN close_dbs ()
{
   if (pub_airports.fp)
      fclose (pub_airports.fp);
   if (pvt_airports.fp)
      fclose (pvt_airports.fp);
   if (pub_vors.fp)
      fclose (pub_vors.fp);
   if (pvt_vors.fp)
      fclose (pvt_vors.fp);

   return (TRUE);
}


/*----------------------------------------------------------------------------*/
static BOOLEAN lookup_desig_internal (db, desig, buffer_size, buffer)
     DB_FILE db;
     char *desig;
     int  buffer_size;
     char *buffer;
{
   long low, high, mid;
   int  cmp;
   if (! db.fp) 
      return FALSE;

#ifdef NO_BINARY_SEARCH
   rewind (db.fp);   
   while (fgets (buffer, buffer_size, db.fp)) {
      if (!strcmp (desig, strtok (buffer, ":\n")))
	 return TRUE;
   }
   return FALSE;
#else
   low = 0L;
   high = db.num_recs;
   while (low <= high) {
      mid = (low + high) / 2L;
      fseek (db.fp, mid * db.rec_size, FSEEK_FROM_BEGINNING);
      fgets (buffer, buffer_size, db.fp);
      cmp =strcmp (desig, strtok (buffer, ":\n"));
      if (!cmp)
	 return TRUE;
      else if (cmp > 0)
	 low = mid + 1;
      else
	 high = mid - 1;
   }
   return FALSE;
#endif
}

/*----------------------------------------------------------------------------*/
static void decode_common (db)
     DATABASE_INFO *db;
{
   extern double atof ();
   extern double degrees_mins_2_decimal ();
   double d1, d2;

   db->altitude.value =  atof (strtok ((char*)0, ":\n"));
   db->altitude.valid = TRUE;

   d1 = atof (strtok ((char*)0, ":\n"));
   d2 = atof (strtok ((char*)0, ":\n"));
   db->mag_variation = d1 + d2 / 60.0;

   d1 = atof (strtok ((char*)0, ":\n"));
   d2 = atof (strtok ((char*)0, ":\n"));
   db->latitude = d1 + d2 / 60.0;

   d1 = atof (strtok ((char*)0, ":\n"));
   d2 = atof (strtok ((char*)0, ":\n"));
   db->longitude = d1 + d2 / 60.0;
}

/*----------------------------------------------------------------------------*/
static void decode_vor (db)
     DATABASE_INFO *db;
{
   char *tok;

   db->name = strdup (strtok ((char*)0, ":\n"));

   tok = strdup (strtok ((char*)0, ":\n"));   
   if (tok[0] != '\0') {
      db->freq.valid = TRUE;
      db->freq.value = atof (tok);
   } else
      db->freq.valid = FALSE;

   decode_common (db);

   tok = strtok ((char*)0, ":\n");
   if (!strcmp ("NDB", tok))
      db->mode = WP_NDB;
   else if (!strcmp ("VOR", tok))
      db->mode = WP_VOR;
   else if (!strcmp ("DME", tok))
      db->mode = WP_DME;
   else if (!strcmp ("TAC", tok))
      db->mode = WP_TAC;
   else if (!strcmp ("ILS", tok))
      db->mode = WP_ILS;
   else if (!strcmp ("INT", tok))
      db->mode = WP_NAMED_INTERSECTION;
   else if (!strcmp ("WPT", tok))
      db->mode = WP_WPT;
   else if (!strcmp ("LOM", tok))
      db->mode = WP_LOM;
   else if (!strcmp ("LMM", tok))
      db->mode = WP_LMM;
   else 
      db->mode = WP_UNK;

   tok = strtok ((char*)0, ":\n");   
   if (tok)
      db->comment = strdup (tok);
   else
      db->comment = NULL;
}

/*----------------------------------------------------------------------------*/
static void decode_airport (db)
     DATABASE_INFO *db;
{
   char *tok;

   db->mode = WP_AIRPORT;
   db->city = strdup (strtok ((char*)0, ":\n"));   
   db->name = strdup (strtok ((char*)0, ":\n"));   

   decode_common (db);

   tok = strdup (strtok ((char*)0, ":\n"));   

   if (tok[0] != '\0') {
      db->freq.valid = TRUE;
      db->freq.value = atof (tok);
   } else
      db->freq.valid = FALSE;

   tok = strtok ((char*)0, ":\n");   
   if (tok)
      db->comment = strdup (tok);
   else
      db->comment = NULL;
}

/*----------------------------------------------------------------------------*/
static void str_upcase (str)
     char *str;
{
   int i;

   for (i = 0; str[i] != '\0'; i++)
      if (islower (str[i]))
	  str[i] = toupper (str[i]);
}

/*----------------------------------------------------------------------------*/
static BOOLEAN in_cache (mode, desig)
     WAYPOINT_MODE mode;
     char *desig;
{
   int i;

   for (i = 0; i < num_cached; i++) {
      if ((!strcmp (desig, cache[i]->desig)) && 
	  (mode == cache[i]->mode)) {
	 return TRUE;
      }
   }
   return FALSE;
}

/*----------------------------------------------------------------------------*/
BOOLEAN lookup_desig (kind, desig, db)
     WAYPOINT_KIND kind;
     char          *desig;
     DATABASE_INFO **db;
{
#undef BUFSIZE
#define BUFSIZE 200
   char buffer [BUFSIZE];
   DB_FILE fps[4];
   int i;
   BOOLEAN found;
   extern yyerror();
   enum {VOR, AIRPORT} db_kind;

   str_upcase (desig);

   if (kind == WP_VIA) {
      /* 
       * prefer the VOR databases
       */
      fps[0] = pvt_vors;
      fps[1] = pub_vors;
      fps[2] = pvt_airports;
      fps[3] = pub_airports;
   } else {
      /* 
       * prefer the AIRPORT databases 
       */
      fps[0] = pvt_airports;
      fps[1] = pub_airports;
      fps[2] = pvt_vors;
      fps[3] = pub_vors;
   }

   for (i = 0; i < 4; i++) {
      if (found = lookup_desig_internal (fps[i], desig, BUFSIZE, buffer)) {
#if 0
	 printf ("%s found w/db %d[%ld] (%s)\n",desig,i,fps[i].fp,
		 ((fps[i].fp == pub_airports.fp) ||
		  (fps[i].fp == pvt_airports.fp)) ? "APT" : "VOR");
#endif
	 if ((fps[i].fp == pub_airports.fp) || (fps[i].fp == pvt_airports.fp))
	    db_kind = AIRPORT;
	 else
	    db_kind = VOR;
	 break;
      }
   }
   if (! found) {
      fprintf (stderr, "ERROR: could not find %s in any database\n", desig);
      return FALSE;
   }

   /*
    * now, convert it into a db record...
    */

   *db = (DATABASE_INFO*) malloc (sizeof (DATABASE_INFO));
   if (! *db)
      yyerror ("unable to allocate space for database element");

   (*db)->desig = desig;	/* no need to strdup - the parser already did */

   if (db_kind == VOR)
      decode_vor (*db);
   else
      decode_airport (*db);

   /*
    * append to the cache: (NOTE: it might already be there if we didn't grab
    * it out of the cach because of incompatible modes - e.g. the cached wp is
    * an airport, but this is a via node - we forced a recheck in case there was
    * a navaid with the same desig)
    */
   if (num_cached < CACHE_SIZE)
      if (! in_cache ((*db)->mode, desig)) {
	 cache[num_cached++] = *db;
      }
   return TRUE;
}

/*----------------------------------------------------------------------------*/
static int dbcmp (db1, db2)
     DATABASE_INFO **db1, **db2;
{
   return (strcmp ((*db1)->desig, (*db2)->desig));
}

/*----------------------------------------------------------------------------*/
void put_db_summary (out_fp)
     FILE *out_fp;
{
   int i;

   /*
    * first, sort the cache 
    */
   qsort ((char*) &cache[0], num_cached, sizeof(DATABASE_INFO*), dbcmp);

   /*
    * now, print it out.
    */
   fprintf (out_fp, "\f\n");
   for (i = 0; i < num_cached; i++)
      put_db (out_fp, cache[i]);
}


/*----------------------------------------------------------------------------*/
void min_max_lat_long (min_lat, max_lat, min_long, max_long)
     double *min_lat, *max_lat, *min_long, *max_long;
{
   int i;

   *min_lat  =  190.0;
   *max_lat  = -190.0;
   *min_long =  190.0;
   *max_long = -190.0;

   for (i = 0; i < num_cached; i++) {
      *min_lat = MIN (cache[i]->latitude, *min_lat);
      *max_lat = MAX (cache[i]->latitude, *max_lat);
      *min_long = MIN (cache[i]->longitude, *min_long);
      *max_long = MAX (cache[i]->longitude, *max_long);
   }
}
