/* $Id: build_tide_db.c 1157 2006-11-21 02:07:49Z flaterco $ */

#include <stdio.h>
#include <errno.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <tcd.h>

#include "build_tide_db.h"

#ifndef require
#define require(expr) {       \
  int require_expr;           \
  require_expr = (int)(expr); \
  assert (require_expr);      \
}
#endif

#ifdef NDEBUG
#warning NDEBUG is defined.  This configuration is unsupported and discouraged.
#endif


/*****************************************************************************\

                               DISTRIBUTION STATEMENT

     This source file is unclassified, distribution unlimited, public
     domain.  It 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.

\*****************************************************************************/



/*****************************************************************************\

     Program         build_tide_db - builds a harmonic constituent database
                     from XTide format harmonics/offset files and/or NAVO format
                     harmonics files.

     Synopsis        build_tide_db OUTPUT_DB_NAME MASTER_HARMONIC_NAME [more harmonics or xml or navo files]

                     OUTPUT_DB_NAME          database name, the extension .tcd
                                             will be appended to the name if
                                             not already there
                     MASTER_HARMONIC_NAME    ASCII harmonics file that the database will use to create
                     the parameters for the stations
                     [...]                   additional ASCII harmonics or offsets files (in XML format), or
                     NAVO files.

                     Returns         NV_INT32                0 on successful completion, -1 on
                     failure

     Author          Jan C. Depner
     Date            08/01/02

     Modified        David Flater / August Hahn
     See README for details.

\*****************************************************************************/

NV_BOOL parse_xml_station (FILE *fp, TIDE_RECORD *rec);
NV_BOOL parse_line (NV_CHAR *in, NV_CHAR *search, NV_CHAR *result);

/* This never really worked.  Use the new hot comment or XML field,
   or put up with Unknown. */
#if 0
NV_INT32 locate_country(NV_CHAR *in)
{
    /* DWF fixed buffer overflow s[4] 2003-03-19  */
    NV_CHAR country[256], *n, s[5], *sparens;
    NV_INT32 result, i;
    /* H3 AH this is unused until such time as the country names are fixed
    DB_HEADER_PUBLIC db;
    */
    
    strcpy(country, in);   /* copy in to country (we don't want to change in) */

    /* get rid of any (1), (2) etc. */
    for (i = 1; i < 6; i++) {
	sprintf(s, " (%d)", i);
	sparens = strstr(country, s);
	if (sparens != 0x0) {
	    sparens[0] = 0x0;
	    break;
	}
    }

    /*  H2 */
    /*	Had to fix this routine as I had tested it using a modified copy
	of the harmonics and offsets files. Using the official release from
	XTIDE resulted in errors.
    */

    /* locate the country, it'll be after the last ',' */
    n = strrchr(country, ',');

    if (n != 0x0) {
	strcpy(country, n + 2);
	n = strstr(country, " Current");
	if (n != 0x0) {
	    *n = 0x0;
	}	
    } else {
        /* no ',' look for spaces */
	n = strrchr(country, ' ');
	if (n != 0x0) {
	    if (strcmp(n, " Current") == 0) {
		*n = 0x0;
		n = strrchr(country, ' ');
	    }
	    if (n != 0x0) {
		strcpy(country, n + 1);
	    }
	}
    }

    /* here's where we substitute for states -> United States */
    /* Hawaii does not do DST so it's changed to U.S. Makes sense, no? */
    if (strstr(country, "Washington") 		|| strstr(country, "Oregon")		|| strstr(country, "California") ||
	strstr(country, "Alaska") 		|| strstr(country, "Texas")		|| strstr(country, "Louisiana") ||
	strstr(country, "Florida") 		|| strstr(country, "South Carolina") 	|| strstr(country, "Hawaii") ||
	strstr(country, "North Carolina")	|| strstr(country, "New York")		|| strstr(country, "New Jersey") ||
	strstr(country, "Maryland")		|| strstr(country, "Virginia")		|| strstr(country, "Mississippi") ||
	strstr(country, "Rhode Island")		|| strstr(country, "Delaware") 		|| strstr(country, "Massachusetts") ||
	strstr(country, "Georgia")		|| strstr(country, "Maine")		|| strstr(country, "New Hampshire") ||
	strstr(country, "Maryland/Delaware") 	|| strstr(country, "Connecticut") 	|| strstr(country, "Alabama") ||
	strstr(country, "D.C.")			|| strstr(country, "Pennsylvania") 	|| strstr(country, "New England") ||
	strstr(country, "N.Y.")			|| strstr(country, "33 ft. below")	|| strstr(country, "Marianas") ||
	strstr(country, "Pacific Ocean") 	|| strstr(country, "American Samoa") 	|| strstr(country, "Midway Islands") ||
	strstr(country, "Puerto Rico") )
    {
	strcpy(country, "United States");
    };

    /* here's where we substitute for provinces -> Canada */
    if (strstr(country, "British Columbia")	|| strstr(country, "Newfoundland")	|| strstr(country, "Nunavut") ||
	strstr(country, "Nova Scotia")		|| strstr(country, "Québec")		|| strstr(country, "Prince Edward Island") ||
	strstr(country, "Nunavut/NWT")		|| strstr(country, "New Brunswick") 	|| strstr(country, "Labrador") ||
	strstr(country, "Manitoba") 		|| strstr(country, "NWT")		|| strstr(country, "Ontario") )
    {
	strcpy(country, "Canada");
    };

    if (strstr(country, "Kurile Islands"))
	strcpy(country, "Russia");

    if (strstr(country, "Tasmania"))
	strcpy(country, "Australia");

    if (strstr(country, "Faroe Islands"))
	strcpy(country, "Denmark");

    if (strstr(country, "F.S.M."))
	strcpy(country, "Micronesia");

    if (strstr(country, "UK ") || strstr(country, "U.K.")
	|| strstr(country, "Wales")
	|| strstr(country, "Scotland")
	|| strstr(country, "England")
	|| strstr(country, "Northern Ireland")
	|| strstr(country, "Channel Islands")
	|| strstr(country, "Isle of Man"))
      strcpy(country, "Britain (UK)");

    result = find_country(country);

    if (result == -1) {
	/* H3 changed this to NOT add the country. Just report that the country isn't in the list
	otherwise this kept finding countries that didn't exist.

	For this routine to work, I need the station name formatted better; e.g. the country name
	after the last comma would be great!
	
	n = strrchr(country, ',');
	if (n != '\0')
	    n += 2;
	else
	    n = country;

	fprintf(stderr, "   Adding country %s\n", n);

	db = get_tide_db_header();
	add_country(n, &db);

	result = find_country(country);
	*/
	if (strlen(country) > 0)
	  fprintf(stderr, "Missing country %s\n", country);
	else
	  fprintf(stderr, "    Zero-length country string parsed from \"%s\"\n", in);
    }

    return result;
}
#endif

NV_INT32 ProcessHarmonicsFile(FILE* fp, NV_U_INT32 num_constituents)
{
    NV_CHAR                    string[256], tname[100];
    NV_INT32                   i, j, num, comment_pos = 0, cnt = 0;
    NV_BOOL                    got_units;
    TIDE_RECORD                rec;
    int startover = 1;

    /*  XTide format ASCII constituent file.  */

    /*  Skip past the speeds and factors since we already have these.  */
    i = 0;
    while (i < 2)
    {
	fgets (string, sizeof (string), fp);

	if (!strncmp (string, "*END*", 5)) i++;
    }

    /*  Right now this is hard-wired to get past the verbiage at the
	beginning of the constituents.  This is not part of the
	comments for the first record and I need to grab those
	comments.  */
    /* DWF 2004-08-09 changed from counting 27 lines to looking for
       MERCHANTABILITY. */
    /* DWF 2004-08-15 if not found, it's a warning, not an error. */
    do {
      if (!fgets (string, sizeof (string), fp)) {
        fprintf (stderr, "Warning:  no tide records found.\n");
        return 0;
      }
      assert (string[0] == '#');
    } while (!strstr (string, "MERCHANTABILITY"));
    /* Lose extra blank */
    require (fgets (string, sizeof (string), fp));
    assert (!strcmp (string, "#\n"));

    while (fgets (string, sizeof (string), fp))
    {
        if (startover) {
          startover = comment_pos = 0;
          got_units = NVFalse;
          memset (&rec, 0, sizeof (TIDE_RECORD));
          rec.header.record_type = 1;
          rec.header.reference_station = -1;
          rec.confidence = 10;
	  rec.restriction = (NV_U_BYTE) find_or_add_restriction ("Non-commercial use only", NULL);
  	  rec.datum = find_datum ("Mean Lower Low Water");
          rec.max_direction = rec.min_direction = 361;
	  {
	    int r = find_dir_units ("degrees true");
	    assert (r > 0);
	    rec.direction_units = (NV_U_BYTE)r;
	  }
        }

	/*  Comments.  */
	if (string[0] == '#' || strlen (string) < 2)
	{
	    /* FIXME fixup hot comments protocol after X-fields added */
	    if (strstr (string, "BEGIN HOT COMMENTS"))
	      ;
            else if (!strncmp (string, "# country:", 10))
	      rec.country = find_or_add_country (string+10, NULL);
	    else if (!strncmp (string, "# Source:", 9) ||
                     !strncmp (string, "# source:", 9))
	    {
		if ((strlen (&string[9])) < ONELINER_LENGTH)
		{
                  if (string[9] == ' ')
		    strcpy (rec.source, &string[10]);
                  else
		    strcpy (rec.source, &string[9]);
		} else {
		    fprintf (stderr, "libtcd fatal error:  Source '%s' exceeded ONELINER_LENGTH compiled-in limit %d.  Change limit and recompile.\n", string+9, ONELINER_LENGTH);
		    exit (-1);
		}
	    }
            else if (!strncmp (string, "# restriction:", 14))
              rec.restriction = find_or_add_restriction (string+14, NULL);
            /* Space intentionally included */
            else if (!strncmp (string, "# note: ", 8))
              strcat (rec.notes, string+8);
            else if (!strncmp (string, "# station_id_context:", 21)) {
              if ((strlen (&string[21])) < ONELINER_LENGTH)
                strcpy (rec.station_id_context, string+21);
              else {
		    fprintf (stderr, "libtcd fatal error:  Station ID context '%s' exceeded ONELINER_LENGTH compiled-in limit %d.  Change limit and recompile.\n", string+21, ONELINER_LENGTH);
		    exit (-1);
	      }
            } else if (!strncmp (string, "# station_id:", 13)) {
              if ((strlen (&string[13])) < ONELINER_LENGTH)
                strcpy (rec.station_id, string+13);
              else {
		    fprintf (stderr, "libtcd fatal error:  Station ID '%s' exceeded ONELINER_LENGTH compiled-in limit %d.  Change limit and recompile.\n", string+13, ONELINER_LENGTH);
		    exit (-1);
	      }
	    } else if (!strncmp (string, "# date_imported:", 16)) {
              require (sscanf (string+16, "%u", &(rec.date_imported)) == 1);
            } else if (!strncmp (string, "# max_direction:", 16)) {
              require (sscanf (string+16, "%d", &(rec.max_direction)) == 1);
            } else if (!strncmp (string, "# min_direction:", 16)) {
              require (sscanf (string+16, "%d", &(rec.min_direction)) == 1);
            } else if (!strncmp (string, "# Datum:", 8) ||
                     !strncmp (string, "# datum:", 8))
	      rec.datum = find_or_add_datum (&string[8], NULL);
            else if (!strncmp (string, "# expiration:", 13)) {
              require (sscanf (string+13, "%u", &(rec.expiration_date)) == 1);
            } else if (!strncmp (string, "# used_until:", 13)) {
              unsigned y, m, d;
              require (sscanf (string+13, "%u-%u-%u", &y, &m, &d) == 3);
              rec.expiration_date = y*10000 + m*100 + d;
            } else if (!strncmp (string, "# months_on_station:", 20)) {
              unsigned m;
              require (sscanf (string+20, "%u", &m) == 1);
              rec.months_on_station = (NV_U_INT16)m;
            } else if (!strncmp (string, "# last_date_on_station:", 23)) {
              require (sscanf (string+23, "%u", &(rec.last_date_on_station)) == 1);
            } else if (!strncmp (string, "# confidence:", 13)) {
              unsigned i;
              require (sscanf (string+13, "%u", &i) == 1);
              rec.confidence = (NV_U_BYTE)i;
            } else if (!strncmp (string, "# !units:", 9) ||
                     !strncmp (string, "#!units:", 8))
	    {
		/*  Do nothing.  This is superceded by the datum units.  */
	    }
	    else if (!strncmp (string, "# !longitude:", 13) ||
	      !strncmp (string, "#!longitude:", 12))
	    {
		sscanf (&string[13], "%lf", &rec.header.longitude);
	    }
	    else if (!strncmp (string, "# !latitude:", 12) ||
	      !strncmp (string, "#!latitude:", 11))
	    {
		sscanf (&string[12], "%lf", &rec.header.latitude);
	    }
	    else
	    {
              /* FIXME unknown hot comments will become X-fields */
		if (strlen(string) < 3) {
		    /* Blank line */
		    if (comment_pos + 1 >= MONOLOGUE_LENGTH) {
			fprintf (stderr, "libtcd fatal error:  Comments exceeded MONOLOGUE_LENGTH compiled-in limit %d.  Change limit and recompile.\n", MONOLOGUE_LENGTH);
                        exit (-1);
		    }
		    rec.comments[comment_pos++] = 10;
		} else {
		    num = strlen (string+1);
		    assert (num > 0);
		    if (comment_pos + num >= MONOLOGUE_LENGTH) {
			fprintf (stderr, "libtcd fatal error:  Comments containing '%s' exceeded MONOLOGUE_LENGTH compiled-in limit %d.  Change limit and recompile.\n", string+1, MONOLOGUE_LENGTH);
                        exit (-1);
		    } else {
                        /* Compress out leading blank, if present */
                        if (string[1] == ' ') {
			    strncpy (&rec.comments[comment_pos], string+2, num-1);
			    comment_pos += num-1;
                        } else {
			    strncpy (&rec.comments[comment_pos], string+1, num);
			    comment_pos += num;
                        }
		    }
		}
	    }
	}


	/*  Not comments.  */

	else
	{
	    if ((strlen (string)) < ONELINER_LENGTH)
	    {
		if (string[strlen(string) - 1] == '\n')
		    string[strlen(string) - 1] = '\0';
		strcpy (rec.header.name, string);
	    }
	    else
	    {
		fprintf (stderr, "libtcd fatal error:  Name '%s' exceeded ONELINER_LENGTH compiled-in limit %d.  Change limit and recompile.\n", string, ONELINER_LENGTH);
		exit (-1);
	    }

	    fgets (string, sizeof (string), fp);
	    sscanf (string, "%d:%d %s", &i, &j, tname);
	    rec.zone_offset = get_time (string);

	    rec.header.tzfile = find_or_add_tzfile (tname, NULL);

	    fgets (string, sizeof (string), fp);
	    sscanf (string, "%f %s", &rec.datum_offset, tname);
	    i = find_level_units (tname);

	    if (i < 0)
	      got_units = NVFalse;
	    else {
 	      got_units = NVTrue;
	      rec.level_units = i;
	    }

	    for (i = 0 ; i < (int)num_constituents ; i++)
	    {
		fgets (string, sizeof (string), fp);

		sscanf (string, "%s %f %f", tname, &rec.amplitude[i], &rec.epoch[i]);

		/*  We don't need no stinkin' negatives!  */

		if (rec.amplitude[i] < 0.0)
		{
		    rec.amplitude[i] = -rec.amplitude[i];
		    rec.epoch[i] += 180.0;
		}

		while (rec.epoch[i] < 0.0) rec.epoch[i] += 360.0;
		rec.epoch[i] = fmod ((NV_FLOAT64) rec.epoch[i], 360.0);
	    }

	    /*  Add the final 0 to the comments record since we stripped
		it on input.  */

	    rec.comments[comment_pos - 1] = (char)0;

	    if (got_units)
	    {
		add_tide_record (&rec, NULL);
		cnt++;
	    }
	    else
	    {
		fprintf (stderr, "Invalid level units for record - %s\n", rec.header.name);
	    }

	    startover = 1;
	}
    }

    return cnt;
}

NV_INT32 ProcessNavoFile(FILE* fp)
{
    NV_CHAR                    string[256], dum1[3], dum2[5], name[4][12];
    NV_FLOAT32                 amp[4], epo[4], dummy;
    NV_INT32                   i, j, k, num, cnt = 0, year, ilat, ilon, deg, 
                               min;
    TIDE_RECORD                rec;

    memset (&rec, 0, sizeof (TIDE_RECORD));
    while (fgets (string, sizeof (string), fp))
    {
	strcpy (rec.header.name, &string[1]);

	fgets (string, sizeof (string), fp);

	sscanf (string, "%d %d %d %f %f %s %s %d %d", &i, &ilat, &ilon,
	 &dummy, &rec.datum_offset, dum1, dum2, &year, &num);

	deg = abs (ilat) / 100;
	min = abs (ilat) % 100;
	rec.header.latitude = (NV_FLOAT64) deg + (NV_FLOAT64) min / 60.0;
	if (ilat < 0) rec.header.latitude = -rec.header.latitude;

	deg = abs (ilon) / 100;
	min = abs (ilon) % 100;
	rec.header.longitude = (NV_FLOAT64) deg + (NV_FLOAT64) min / 60.0;
	if (ilon < 0) rec.header.longitude = -rec.header.longitude;

	sprintf (rec.comments, "Year of data : %d\n", year);

	rec.last_date_on_station = year * 10000 + 601;


        /*  DOH!  Fixed a really dumb SOD here (I can't reference name[i]).
            JCD */

	for (i = 0 ; i < num ; i++)
	{
	    if (!(k = i % 4))
	    {
		fgets (string, sizeof (string), fp);

		sscanf (string, " %5s %f %f %5s %f %f %5s %f %f %5s %f %f",
                        name[k], &amp[k], &epo[k], name[k + 1], &amp[k + 1],
                        &epo[k + 1], name[k + 2], &amp[k + 2], &epo[k + 2],
                        name[k + 3], &amp[k + 3], &epo[k + 3]);
	    }

	    j = find_constituent (name[k]);

	    if (j == -1)
		fprintf (stderr, "Ignoring unknown constituent : %s\n", name[k]);


	    /*  It appears that NAVO uses 999.9 as a NULL epoch.  */

	    if (epo[k] != 999.9)
	    {
		rec.amplitude[j] = amp[k];
		rec.epoch[j] = epo[k];


		/*  We don't need no stinkin' negatives!  */

		if (rec.amplitude[j] < 0.0)
		{
		    rec.amplitude[j] = -rec.amplitude[j];
		    rec.epoch[j] += 180.0;
		}

		while (rec.epoch[j] < 0.0) rec.epoch[j] += 360.0;
		rec.epoch[j] = fmod ((NV_FLOAT64) rec.epoch[j], 360.0);
	    }
	}

	/*  Defaults (for now).  */

	rec.header.record_type = 1;
	rec.header.reference_station = -1;
	rec.restriction = 1;
	rec.expiration_date = 0;
	rec.months_on_station = 0;
	rec.confidence = 10;
	rec.zone_offset = 0;
        rec.country = 0;
	rec.level_units = find_level_units ("meters");
	rec.header.tzfile = find_or_add_tzfile ("GMT0", NULL);
	rec.datum = find_datum ("Mean Lower Low Water");
	strcpy (rec.source, "IHO/NAVO ihbtides file");

	add_tide_record (&rec, NULL);
	cnt++;
	memset (&rec, 0, sizeof (TIDE_RECORD));
    }

    return cnt;
}

NV_INT32 ProcessXMLFile(FILE* fp)
{
    NV_CHAR                    string[256], comments[MONOLOGUE_LENGTH];
    NV_BOOL                    done;
    TIDE_RECORD                rec;
    NV_INT32	cnt = 0; /* DWF added initialization */
    int incomment=0;

    done = NVFalse;

    while (!done)
    {
	fgets (string, sizeof (string), fp);

	if (!strncmp (string, "<document>", 10)) done = NVTrue;
    }

    comments[0] = '\0';
    while (fgets (string, sizeof (string), fp))
    {
      if (!strncmp (string, "<!-- Comments on ", 17))
        incomment=1;
      else if (incomment) {
        if (!strncmp (string, "-->", 3))
          incomment=0;
        else {
          assert (strlen(string) + strlen(comments) + 1 < MONOLOGUE_LENGTH);
          strcat (comments, string);
        }
      } else if (parse_line (string, "<subordinatestation name=", rec.header.name))
	{
	  if (parse_xml_station (fp, &rec))
	    {
	        /* Evil */
  	        /* rec.country = locate_country(rec.header.name); */
                /* Stupid notes workaround */
                if (strlen (rec.comments)) {
                  strcat (comments, "\n");
                  strcat (comments, rec.comments);
                }
                strcpy (rec.comments, comments);
		add_tide_record (&rec, NULL);
		cnt++;
                comments[0] = '\0';
	    }
	}
    }

    return cnt;
}

void CreateTCDFromHarmonics(NV_CHAR* dbname, FILE* fp)
{
    NV_CHAR	string[256], tname[100], **constituent;
    NV_FLOAT64	*speed, temp[10];
    NV_FLOAT32	**equilibrium, **node_factor;
    NV_INT32	i, constituents, start_year, num_years, num, j, end, k;
    NV_BOOL	done;

    /*  Get the number of constituents.  */

    done = NVFalse;
    while (!done)
    {
	require (fgets (string, sizeof (string), fp));

	if (string[0] != '#')
        {
	    sscanf (string, "%d", &constituents);
	    done = NVTrue;
	}
    }

    assert (constituents > 0);
    if (constituents > MAX_CONSTITUENTS) {
	fprintf (stderr, "libtcd fatal error:  exceeded MAX_CONSTITUENTS compiled-in limit %d.  Change limit and recompile.\n", MAX_CONSTITUENTS);
	exit (-1);
    }

    if ((speed = (NV_FLOAT64 *) malloc (constituents * sizeof (NV_FLOAT64))) == NULL)
    {
	perror ("Allocating speed memory");
	exit (-1);
    }

    if ((constituent = (NV_CHAR **) malloc (constituents * sizeof (NV_CHAR *))) == NULL)
    {
	perror ("Allocating name memory");
	exit (-1);
    }

    /*  Get the constituent names and speeds.  */

    done = NVFalse;
    while (!done)
    {
	fgets (string, sizeof (string), fp);

	if (string[0] != '#')
	{
	    for (i = 0 ; i < constituents ; i++)
	    {
		sscanf (string, "%s %lf", tname, &speed[i]);

		constituent[i] = (NV_CHAR *) malloc (strlen (tname) + 1);
		strcpy (constituent[i], tname);

		fgets (string, sizeof (string), fp);
	    }
	    done = NVTrue;
	}
    }

    /*  Get the start year.  */

    done = NVFalse;
    while (!done)
    {
	fgets (string, sizeof (string), fp);

	if (string[0] != '#')
	{
	    sscanf (string, "%d", &start_year);
	    done = NVTrue;
	}
    }

    /*  Get the number of years.  */

    done = NVFalse;
    while (!done)
    {
	fgets (string, sizeof (string), fp);

	if (string[0] != '#')
	{
	    sscanf (string, "%d", &num_years);
	    done = NVTrue;
	}
    }

    /*  Get the equilibrium arguments.  */

    equilibrium = (NV_FLOAT32 **) malloc (constituents * sizeof (NV_FLOAT32 *));

    done = NVFalse;
    while (!done)
    {
	fgets (string, sizeof (string), fp);

	if (string[0] != '#')
	{
	    for (i = 0 ; i < constituents ; i++)
	    {
		/*  Check the constituent name just for grins.  */

		if (strncmp (string, constituent[i], strlen ( constituent[i])))
		{
		    fprintf (stderr, "Constituent names don't match!\n");
		    fprintf (stderr, "%s %s\n", string, constituent[i]);
		    exit (-1);
		}

		equilibrium[i] = (NV_FLOAT32 *) malloc (num_years * sizeof (NV_FLOAT32));

		num = num_years / 10;
		if (num_years % 10) num++;

		for (j = 0 ; j < num ; j++)
		{
		    fgets (string, sizeof (string), fp);
		    sscanf (string, "%lf %lf %lf %lf %lf %lf %lf %lf %lf %lf",
	      &temp[0], &temp[1], &temp[2], &temp[3],
	      &temp[4], &temp[5], &temp[6], &temp[7],
	      &temp[8], &temp[9]);

		    end = j * 10 + 10;
		    if (end > num_years) end = num_years;
		    for (k = j * 10 ; k < end ; k++)
		    {
			equilibrium[i][k] = temp[k - j * 10];
		    }
		}
		fgets (string, sizeof (string), fp);
	    }
	    done = NVTrue;
	}
    }

    /*  Get the middle of year node factors.  */

    node_factor = (NV_FLOAT32 **) malloc (constituents * sizeof (NV_FLOAT32 *));

    done = NVFalse;
    while (!done)
    {
	fgets (string, sizeof (string), fp);

	if (string[0] != '#' && string[0] != '*')
	{
	    /*  Check the number of years, I don't know what to do if
	    they don't match.  */

	    sscanf (string, "%d", &num);
	    if (num != num_years)
	    {
		fprintf (stderr, "Number of years don't match!\n");
		fprintf (stderr, "%d %d\n", num, num_years);
		exit (-1);
	    }

	    /*  Read the first constituent name.  */
	    fgets (string, sizeof (string), fp);

	    for (i = 0 ; i < constituents ; i++)
	    {
		/*  Check the constituent name just for grins.  */

		if (strncmp (string, constituent[i], strlen ( constituent[i])))
		{
		    fprintf (stderr, "Constituent names don't match!\n");
		    fprintf (stderr, "%s %s\n", string, constituent[i]);
		    exit (-1);
		}

		node_factor[i] = (NV_FLOAT32 *) malloc (num_years * sizeof (NV_FLOAT32));

		num = num_years / 10;
		if (num_years % 10) num++;
		for (j = 0 ; j < num ; j++)
		{
		    fgets (string, sizeof (string), fp);
		    sscanf (string, "%lf %lf %lf %lf %lf %lf %lf %lf %lf %lf",
                            &temp[0], &temp[1], &temp[2], &temp[3],
                            &temp[4], &temp[5], &temp[6], &temp[7],
                            &temp[8], &temp[9]);

		    end = j * 10 + 10;
		    if (end > num_years) end = num_years;
		    for (k = j * 10 ; k < end ; k++)
		    {
			node_factor[i][k] = temp[k - j * 10];
		    }
		}
		fgets (string, sizeof (string), fp);
	    }
	    done = NVTrue;
	}
    }

    /*  Create the database.  */

    if (!create_tide_db (dbname, constituents, constituent, speed,
			 start_year, num_years, equilibrium, node_factor))
    {
	fprintf (stderr, "Error creating data base file.\n");
	exit (-1);
    }

    /*  Right now this is hard-wired to get past the verbiage at the
	beginning of the constituents.  This is not part of the
	comments for the first record and I need to grab those
	comments.  */

    /* DWF 2004-08-12: Discovered this second occurrence of the
       hard-coded line count *after* committing the change to
       duplicate code in ProcessHarmonicsFile on 2004-08-09.  However,
       the only place this function is used is in main, which closes
       the file immediately afterward.  No harm, no foul.  Since I
       don't know why we are bothering to skip forward, I am now
       commenting out this code.  That way, it's possible to convert
       congen output and get a TCD file containing only the
       constituents (zero tide records).  fp officially left in open
       but otherwise unknown state. */
#if 0
    for (i = 0 ; i < 27 ; i++)
	fgets (string, sizeof (string), fp);
#endif

    /*  Free the memory (free the Chicago seven, free Huey [and Dewey
	and Louie]).  */

    /* FIXME bogus null checking */

    for (i = 0 ; i < constituents ; i++)
    {
	if (constituent[i] != NULL) free (constituent[i]);
    }
    if (constituent != NULL) free (constituent);

    if (speed != NULL) free (speed);


    for (i = 0 ; i < constituents ; i++)
    {
	if (equilibrium[i] != NULL) free (equilibrium[i]);
    }
    if (equilibrium != NULL) free (equilibrium);


    for (i = 0 ; i < constituents ; i++)
    {
	if (node_factor[i] != NULL) free (node_factor[i]);
    }
    if (node_factor != NULL) free (node_factor);
}

void CreateTCDFromNavo(NV_CHAR* dbname)
{
    NV_FLOAT32	**equilibrium, **node_factor;
    NV_INT32	i, j, constituents, start_year, num_years;

    /*  NAVO format.  */
    /*  Use defaults for speeds, etc.  */

    constituents = DEFAULT_CONSTITUENTS;
    start_year = DEFAULT_START_YEAR;
    num_years = DEFAULT_NUM_YEARS;

    equilibrium = (NV_FLOAT32 **) malloc (constituents * sizeof (NV_FLOAT32 *));
    node_factor = (NV_FLOAT32 **) malloc (constituents * sizeof (NV_FLOAT32 *));

    for (i = 0 ; i < constituents ; i++)
    {
	equilibrium[i] = (NV_FLOAT32 *) malloc (num_years * sizeof (NV_FLOAT32));
	node_factor[i] = (NV_FLOAT32 *) malloc (num_years * sizeof (NV_FLOAT32));
	for (j = 0 ; j < num_years ; j++)
	{
	    equilibrium[i][j] = default_equilibrium[i][j];
	    node_factor[i][j] = default_node_factor[i][j];
	}
    }

    /*  Create the database.  */

    if (!create_tide_db (dbname, constituents, default_constituent,
			 default_speed, start_year, num_years, equilibrium, node_factor))
    {
	fprintf (stderr, "Error creating data base file.\n");
	exit (-1);
    }

    /* FIXME bogus null checking */

    for (i = 0 ; i < constituents ; i++)
    {
	if (equilibrium[i] != NULL) free (equilibrium[i]);
    }
    if (equilibrium != NULL) free (equilibrium);


    for (i = 0 ; i < constituents ; i++)
    {
	if (node_factor[i] != NULL) free (node_factor[i]);
    }
    if (node_factor != NULL) free (node_factor);
}

NV_INT32 main (NV_INT32 argc, char *argv[])
{
    FILE                       *fp;
    NV_CHAR                    string[256], dbname[512];
    NV_INT32                   i, cnt = 0;
    NV_BOOL                    xml, navo;
    DB_HEADER_PUBLIC           db;

    if (argc < 3)
    {
        /* usage instead of this! */
	fprintf(stderr, "usage: build_tide_db output_tcd_filename input_master_harm [input_child_harm...] [input_xml...] [input_nvo...]\n");
	exit(-1);

    } else {
	/*  Add the .tcd extension if it's not there.  */
	strcpy (dbname, argv[1]);
	if (!strstr (dbname, ".tcd")) strcat (dbname, ".tcd");
    }

    xml = NVFalse;
    navo = NVFalse;

    /* check first for an XML file at argv[2], exit if it's there. */
    if ((fp = fopen (argv[2], "r")) == NULL)
    {
	perror (argv[2]);
	exit (-1);
    } else {
	/*  Check for XML input file.  */
	fgets (string, sizeof (string), fp);

	if (string[0] == '<')
	{
	    fprintf (stderr, "Cannot use an XML file as the first input file!\n");
	    exit (-1);
	}
	fclose(fp);
    }

    /*  Check to see if the data base file already exists.  If not, we'll create it.  */
    if ((fp = fopen (dbname, "rb+")) == NULL)
    {
	fprintf(stderr, "\nCreating TCD file from %s\n", argv[2]);

	/*  Check for old NAVO format file.  */
        if (strstr (argv[2], ".nvo")) navo = NVTrue;

        /*  NAVO format.  */
        if (navo)
        {
	  CreateTCDFromNavo(dbname);  /* CreateTCDFromNavo doesn't need a file! */
        } else {
	    /*  XTide format.  */
	    /*  Open the input file.  */
	    if ((fp = fopen (argv[2], "r")) == NULL)
	    {
		perror (argv[2]);
		exit (-1);
	    }
	    CreateTCDFromHarmonics(dbname, fp);
	    fclose(fp);
	}
    }
    else
    {
	/*  Database already existed so we're going to append data.
	This can be dangerous, as there is no checking to see if the stations are
	already in the .tcd file... */
        fclose (fp);

        if (!open_tide_db (dbname))
        {
            fprintf (stderr, "Unable to read harmonics database: %s\n", dbname);
            exit (-1);
        }
    }

    db = get_tide_db_header();

    for (i = 2; i < argc; i++)
    {
	fprintf(stderr, "\nProcessing file %s\n", argv[i]);

	navo = NVFalse;
	xml =  NVFalse;

	if ((fp = fopen (argv[i], "r")) == NULL)
	{
	    perror (argv[i]);
	    exit (-1);
	}

        /*  Check for old NAVO format file.  */
        if (strstr (argv[i], ".nvo")) {
	    navo = NVTrue;
	} else {
	    /*  Check for XML input file.  */
	    fgets (string, sizeof (string), fp);

	    if (string[0] == '<') xml = NVTrue;

	    fseek (fp, 0, SEEK_SET);
	}

	/*  XTide format XML offset file (subordinate stations).  */
	if (xml)
	{
	    cnt += ProcessXMLFile(fp);
	}

	/*  NAVO format constituent file.  */
	else if (navo)
	{
	    cnt += ProcessNavoFile(fp);
	}

	/*  XTide constituent file.  */
	else
	{
	    cnt += ProcessHarmonicsFile(fp, db.constituents);
	}
	fclose(fp);
    }

    close_tide_db ();

    fprintf (stderr, "%d records written\n", cnt);

    return (0);
    
}
