/*--------------------------------------------------------------------
 *	$Id: gmt_mgg.c,v 1.1.1.1.4.2 2002/02/27 17:41:10 pwessel Exp $
 *
 *    Copyright (c) 1991-2002 by P. Wessel and W. H. F. Smith
 *    See README file for copying and redistribution conditions.
 *--------------------------------------------------------------------*/
/*
 * gmt_mgg.c is a collection of functions that are used by several of
 * the mgg GMT-SYSTEM programs. These functions perform:
 *	> Converting time from seconds to dates and back again
 *	> Find right path to a gmt-file
 *
 * Author:	Paul Wessel
 * Date:	13-JUL-1988
 * Updated:	13-JUN-1991 v2.0
 *		10-MAR-1999 v3.2
 *		05-APR-1999 Now knows about DOS delimiters, too
 *		25-JUN-1999 v3.3.1 Moved MGD77 decoding function here
 *			    as well as Carter table reading.
 *
 * List of functions:
 *
 *	gmtmgg_date		- Convert seconds to gmt date.
 *	gmtmgg_init		- Initialize gmt time functions.
 *	gmtmgg_time		- Convert gmt date to seconds.
 *	gmtmggpath_init		- Get paths to gmtfile direcories.
 *	gmtmggpath_func		- Return full path to a leg.
 *	gmtmgg_decode_MGD77	- Convert a MGD77 record to GMT_MGG struct
 *	carter_setup		- Initialize Carter Table lookup
 *	carter_get_bin		- Get Carter 1x1 degree bin
 *	carter_get_zone		- Get Carter zone
 *	carter_depth_from_twt	- Convert twt to depth
 *	carter_twt_from_depth	- Convert depth to twt
 */
 
#define _GMT_MGG_LIB
#include "gmt.h"	/* System dependent files */
#include "gmt_mgg.h"	/* System dependent files */
#include "carter.h"	/* Carter Tables parameters */

char *gmtmgg_path[10];	/* Max 10 directories for now */
int n_gmtmgg_paths = 0;	/* Number of these directories */

BOOLEAN MGD77_first_1900 = FALSE, MGD77_first_2000 = FALSE;

/* GMT function gmtmgg_date computes the date (hr/mi/sec/dd/mm/yy) based
 * on the total time in seconds since the beginning of first_year.
 * The pointer to the GMT structure is passed allong with the other
 * arguments. The Julian day is returned. the yymmddhhmmss is passed
 * through the argument list.
 */
 
int gmtmgg_date (int time, int *year, int *month, int *day, int *hour, int *minute, int *second, struct GMTMGG_TIME *gmt_struct)
{
	int day_time, julian_day;
	day_time = time/86400;
	*month = day_time / 31 + 1;	/* Only approximately, may be smaller */

	while (gmt_struct->daymon[*month +1] <= day_time) (*month)++;
	*year = (*month  - 1) / 12 + gmt_struct->first_year;
	*day = day_time - gmt_struct->daymon[*month] + 1;
	julian_day = (*month > 12) ?
		gmt_struct->daymon[*month] - gmt_struct->daymon[(*month - (*month)%12)] + *day :
		gmt_struct->daymon[*month] + *day;
	*month  = (*month-1)%12 + 1;
	time %= 86400;
	*hour = time / 3600;
	*minute = (time%3600) / 60;
	*second = time - *hour * 3600 - *minute * 60;
	return (julian_day);
}

/*
 *	GMT subroutine gmtmgg_init sets up the structure GMT  which is
 *	used by other gmt routines (gmtmgg_time,gmtmgg_date) to convert
 *	times.  Daymon[month] contains the cumulative number of
 *	days from Jan 1 in first_year through the months PRIOR to the
 *	value of month.  0 <= month <= 60.  month = 0 only occurs
 *	during initializing in this routine. The user must declare
 *	a pointer to the struct GMTMGG_TIME in the main program and pass it
 *	when calling the gmt_* functions. To define the GMT structure,
 *	include the file gmt.h
 *
 *	Paul Wessel
 *	12-JUL-1987
 *
 */
 
struct GMTMGG_TIME *gmtmgg_init (int year1)
{
	struct GMTMGG_TIME *gmt_struct;
	int dm[12];	/* No of days in each month */
	int year, this_year, month, m;
	gmt_struct = (struct GMTMGG_TIME *) GMT_memory (VNULL, (size_t)1, sizeof(struct GMTMGG_TIME), "gmtmgg_init");
	gmt_struct->first_year = year1;
	/* initialize days of the month etc. */
	dm[0] = 0;
	dm[1] = 31;
	dm[2] = 28;
	dm[3] = 31;
	dm[4] = 30;
	dm[5] = 31;
	dm[6] = 30;
	dm[7] = 31;
	dm[8] = 31;
	dm[9] = 30;
	dm[10] = 31;
	dm[11] = 30;
	gmt_struct->daymon[0] = 0;
	for (year = 0, month = 0; year < 5; year++) {
		this_year = gmt_struct->first_year + year;
		if (this_year%4 == 0 && !(this_year%400 == 0)) dm[2] = 29;
		for (m = 1; m <= 12; m++) {
			month++;
			gmt_struct->daymon[month] = gmt_struct->daymon[month - 1] + dm[m - 1];
		}
   		dm[2] = 28;
   		dm[0] = 31;
   	}
	MGD77_first_1900 = MGD77_first_2000 = FALSE;	/* Possibly new file to read */

  	return (gmt_struct);
}

/* GMT function gmtmgg_time returns the number of seconds from
 * first_year calculated from (hr/mi/sc/dd/mm/yy). The pointer
 * to the GMT structure is passed along with the arguments.
 */
 /* MODIFIED 10 July, 1987 by W. Smith  --  I killed a bug in
     month calculation */
 
int gmtmgg_time (int *time, int year, int month, int day, int hour, int minute, int second, struct GMTMGG_TIME *gmt_struct)
{
	int mon, n_days;
	if ((mon = (year - gmt_struct->first_year)) > 4) {
	  fprintf(stderr,"gmtmgg_time:  Year - first_year > 4\n");
	  return(-1);
	}
	mon = mon * 12 + month;
	n_days = gmt_struct->daymon[mon] + day - 1;
	*time = n_days * 86400 + hour * 3600 + minute * 60 + second;
	return (*time);
}
	
/* gmtmggpath_init reads the SHAREDIR/share/mgg/gmtfile_paths file and gets all
 * the gmtfile directories.
 */
 
void gmtmggpath_init (void) {
	int i;
	char file[BUFSIZ], line[BUFSIZ], *SHAREDIR;
	FILE *fp;
	
	if ((SHAREDIR = getenv ("GMTHOME")) == (char *)NULL) {
		fprintf (stderr, "gmtmggpath_init: Environment variable GMTHOME not set!\n");
		exit (EXIT_FAILURE);
	}

	sprintf (file, "%s%cshare%cmgg%cgmtfile_paths", SHAREDIR, DIR_DELIM, DIR_DELIM, DIR_DELIM);
	
	n_gmtmgg_paths = 0;

	if ((fp = fopen (file, "r")) == NULL) {
		fprintf (stderr, "GMT Warning: path file %s for *.gmt files not found\n", file);
		fprintf (stderr, "(Will only look in current directory for such files)\n");
		return;
	}
	
	while (fgets (line, BUFSIZ, fp)) {
		if (line[0] == '#') continue;	/* Comments */
		if (line[0] == ' ' || line[0] == '\0') continue;	/* Blank line */
		gmtmgg_path[n_gmtmgg_paths] = GMT_memory (VNULL, (size_t)1, (size_t)(strlen (line)), "gmtmggpath_init");
		line[strlen (line)-1] = 0;
#if _WIN32
		for (i = 0; line[i]; i++) if (line[i] == '/') line[i] = DIR_DELIM;
#else
		for (i = 0; line[i]; i++) if (line[i] == '\\') line[i] = DIR_DELIM;
#endif
		strcpy (gmtmgg_path[n_gmtmgg_paths], line);
		n_gmtmgg_paths++;
	}
	fclose (fp);
}
	
/* gmtpath takes a legid as argument and returns the full path
 * to where this data file can be found.  gmtmggpath_init must be
 * called first
 */
 
int gmtmggpath_func (char *leg_path, char *leg)
{
	int id;
	char geo_path[BUFSIZ];
	
	/* First look in current directory */
	
	sprintf (geo_path, "%s.gmt", leg);
	if (!access(geo_path, R_OK)) {
		strcpy(leg_path, geo_path);
		return (0);
	}
	
	/* Then look elsewhere */
	
	for (id = 0; id < n_gmtmgg_paths; id++) {
		sprintf (geo_path, "%s%c%s.gmt", gmtmgg_path[id], DIR_DELIM, leg);
		if (!access (geo_path, R_OK)) {
			strcpy (leg_path, geo_path);
			return (0);
		}
	}
	return(1);
}
	
int gmtmgg_decode_MGD77 (char *string, int tflag, struct GMTMGG_REC *record, struct GMTMGG_TIME **gmt)
{
	int year, month, day, hour, min, sec, l_mag, l_top, test, bin, zone, l_twt;
	short int twt;
	double tz, fmin;
	char s_tz[5], s_yr[5], s_mo[3], s_dy[3], s_hr[3], s_mi[6], s_lat[8], s_lon[9];
	char s_faa[5], s_mag[6], s_top[7], s_top_twt[7];
	BOOLEAN version_Y2K;

	version_Y2K = (string[0] == '5');	/* New format that is Y2K compliant */

	/* First decode Time Zone */

	if (version_Y2K) {	/* TZ is in hours */
		strncpy (s_tz, &string[10], (size_t) 2);	s_tz[2] = 0;
		tz = atof (s_tz);
	}
	else {	/* Original MGD77 format in 1/100 of hour */
		strncpy (s_tz, &string[10], (size_t) 4);	s_tz[4] = 0;
		tz = atof (s_tz) * 0.01;
	}
	if (string[9] == '-') tz = -tz;
	
	/* Then decode Time */
	
	if (!tflag) {	/* Time information provided */

		if (version_Y2K) {	/* 4-digit year */
			strncpy (s_yr, &string[12], (size_t) 4);	s_yr[4] = 0;
			year = atoi (s_yr);
		}
		else {	/* 2-digit year, apply Y2K kludge */

			/* Y2K KLUDGE FIX: Assume 2-digit year xx < 39 means 20xx, else 19xx */

			strncpy (s_yr, &string[14], (size_t) 2);	s_yr[2] = 0;
			year = atoi (s_yr);
			if (year < NGDC_OLDEST_YY) {	/* Presumably 20xx */
				if (MGD77_first_2000) fprintf (stderr, "mgd77togmt: Warning: 2-digit year %d assumed to be 20%d\n", year, year);
				year += 2000;
				MGD77_first_2000 = FALSE;
			}
			else {
				if (MGD77_first_1900) fprintf (stderr, "mgd77togmt: Warning: 2-digit year %d assumed to be 19%d\n", year, year);
				year += 1900;
				MGD77_first_1900 = FALSE;
			}
		}

		strncpy (s_mo, &string[16], (size_t) 2);	s_mo[2] = 0;
		month = atoi (s_mo);
	
		strncpy (s_dy, &string[18], (size_t) 2);	s_dy[2] = 0;
		day = atoi (s_dy);
	
		strncpy (s_hr, &string[20], (size_t) 2);	s_hr[2] = 0;
		hour = atoi (s_hr);
	
		strncpy (s_mi, &string[22], (size_t) 5);	s_mi[5] = 0;
		fmin = atof (s_mi) * 0.001;
		min = (int) floor (fmin);
		sec = irint (60.0 * (fmin - min));

		if (!(*gmt)) {	/* If not set, now is the time */
			*gmt = gmtmgg_init (year);
			fprintf (stderr, "gmtmgg_decode_MGD77: No start year set, using year = %d from 1st data record\n", year);
		}
		test = gmtmgg_time (&(record->time), year, month, day, hour, min, sec, *gmt);
		if (test < 0) return (1);
		record->time += irint (tz * 3600.0);
	}
	
	/* Get lat lon, return error if outside domain */
	
	strncpy (s_lat, &string[28], (size_t) 7);	s_lat[7] = 0;
	record->lat = 10 * atoi (s_lat);
	if (string[27] == '-') record->lat = -record->lat;
	if (abs(record->lat) > 90000000) return (1);
	
	strncpy (s_lon, &string[36], (size_t) 8);	s_lon[8] = 0;
	record->lon = 10 * atoi (s_lon);
	if (string[35] == '-') record->lon = -record->lon;
	if ((record->lon) < 0) record->lon += 360000000;
	if (abs (record->lon) > 360000000) return (1);
	
	/* Get gravity */
	
	strncpy (s_faa, &string[104], (size_t) 4);	s_faa[4] = 0;
	record->gmt[0] = atoi (s_faa);
	if (record->gmt[0] == 9999 || (s_faa[0] == ' ' && s_faa[1] == ' ' && s_faa[2] == ' ' && s_faa[3] == ' '))
		record->gmt[0] = GMTMGG_NODATA;
	else if (string[103] == '-')
		record->gmt[0] = -record->gmt[0];
		
	/* Get magnetics */
	
	strncpy (s_mag, &string[73], (size_t) 5);	s_mag[5] = 0;
	l_mag = atoi (s_mag);
	if (l_mag == 99999 || (s_mag[0] == ' ' && s_mag[1] == ' ' && s_mag[2] == ' ' && s_mag[3] == ' ' && s_mag[4] == ' '))
		l_mag = GMTMGG_NODATA;
	else if (string[72] == '-')
		l_mag = -l_mag;
	if (l_mag != GMTMGG_NODATA) l_mag = irint (0.1 * l_mag);
	record->gmt[1] = l_mag;
	
	/* Get Bathymetry */
	strncpy (s_top_twt, &string[45], (size_t) 6);	s_top_twt[6] = '\0';
	l_twt = atoi(s_top_twt);
	if (l_twt == 999999 || (s_top_twt[0] == ' ' && s_top_twt[1] == ' ' && s_top_twt[2] == ' ' &&
		s_top_twt[3] == ' ' && s_top_twt[4] == ' '&& s_top_twt[5] == ' ')) {
			/* No two-way time was given.  See if there is a corrected depth:  */

		strncpy (s_top, &string[51], (size_t) 6);	s_top[6] = 0;
		l_top = atoi (s_top);
		if (l_top == 999999 || (s_top[0] == ' ' && s_top[1] == ' ' && s_top[2] == ' ' &&
			s_top[3] == ' ' && s_top[4] == ' '&& s_top[5] == ' '))
			l_top = GMTMGG_NODATA;
		else
			l_top = -irint (0.1 * l_top);
		record->gmt[2] = l_top;
	}
	else {
		twt = irint (0.1 * l_twt);
		/* Convert the twt to depth  */
		if ((carter_get_bin (record->lat, record->lon, &bin)) || (carter_get_zone (bin, &zone)) || (carter_depth_from_twt (zone, twt, &(record->gmt[2]))) ) {
			fprintf (stderr, "mgd77togmt:  ERROR in Carter correction system.\n");
			record->gmt[2] = GMTMGG_NODATA;
		}
		else {
			record->gmt[2] = -record->gmt[2];
		}
	}
	return (0);
}

int carter_setup (void)
{
	/* This routine must be called once before using carter table stuff.
	It reads the carter.d file and loads the appropriate arrays.
	It sets carter_not_initialized = FALSE upon successful completion
	and returns 0.  If failure occurrs, it returns -1.  */

	FILE *fp = NULL;
	char buffer [BUFSIZ], *SHAREDIR;
	int  i;

	carter_not_initialized = TRUE;

	/* Read the correction table:  */

	if ((SHAREDIR = getenv ("GMTHOME")) == (char *)NULL) {
		fprintf (stderr, "carter_setup: Environment variable GMTHOME not set!\n");
                return (-1);
	}

	sprintf (buffer, "%s%cshare%cmgg%ccarter.d", SHAREDIR, DIR_DELIM, DIR_DELIM, DIR_DELIM);
	if ( (fp = fopen (buffer, "r")) == NULL) {
                fprintf (stderr,"carter_setup:  Cannot open r %s\n", buffer);
                return (-1);
        }

	for (i = 0; i < 4; i++) fgets (buffer, BUFSIZ, fp);	/* Skip 4 headers */
	fgets (buffer, BUFSIZ, fp);

	if ((i = atoi (buffer)) != N_CARTER_CORRECTIONS) {
		fprintf (stderr, "carter_setup:  Incorrect correction key (%d), should be %d\n", i, N_CARTER_CORRECTIONS);
                return(-1);
	}

        for (i = 0; i < N_CARTER_CORRECTIONS; i++) {
                if (!fgets (buffer, BUFSIZ, fp)) {
			fprintf (stderr, "carter_setup:  Could not read correction # %d\n", i);
			return (-1);
		}
                carter_correction[i] = atoi (buffer);
        }

	/* Read the offset table:  */

	fgets (buffer, BUFSIZ, fp);	/* Skip header */
	fgets (buffer, BUFSIZ, fp);

	if ((i = atoi (buffer)) != N_CARTER_OFFSETS) {
		fprintf (stderr, "carter_setup:  Incorrect offset key (%d), should be %d\n", i, N_CARTER_OFFSETS);
                return (-1);
	}

        for (i = 0; i < N_CARTER_OFFSETS; i++) {
                 if (!fgets (buffer, BUFSIZ, fp)) {
			fprintf (stderr, "carter_setup:  Could not read offset # %d\n", i);
			return (-1);
		}
                carter_offset[i] = atoi (buffer);
        }

	/* Read the zone table:  */

	fgets (buffer, BUFSIZ, fp);	/* Skip header */
	fgets (buffer, BUFSIZ, fp);

	if ((i = atoi (buffer)) != N_CARTER_BINS) {
		fprintf (stderr, "carter_setup:  Incorrect zone key (%d), should be %d\n", i, N_CARTER_BINS);
                return (-1);
	}

        for (i = 0; i < N_CARTER_BINS; i++) {
                 if (!fgets (buffer, BUFSIZ, fp)) {
			fprintf (stderr, "carter_setup:  Could not read offset # %d\n", i);
			return (-1);
		}
                carter_zone[i] = atoi (buffer);
        }
        fclose (fp);

	/* Get here when all is well.  */

	carter_not_initialized = FALSE;
	return (0);
}

int carter_get_bin (int lat, int lon, int *bin)
{
	/* Given signed long ints in the 1.0e06 times decimal degree range, 
	   -90000000 <= lat < 90000000, 0 <= lon < 360000000, set bin
	   number.  Returns 0 if OK, -1 if error.  */

	int latdeg, londeg;

	if (lat < -90000000 || lat > 90000000) {
		fprintf (stderr, "getbin_6:  Latitude domain error.\n");
		return (-1);
	}
	if (lon < 0 || lon > 360000000) {
		fprintf (stderr, "getbin_6:  Longitude domain error.\n");
		return (-1);
	}
	latdeg = (lat + 90000000)/1000000;
	if (latdeg == 180) latdeg = 179;	/* Map north pole to previous row  */

	londeg = lon/1000000;
	*bin = 360 * latdeg + londeg;

	return (0);
}

int carter_get_zone (int bin, int *zone)
{
	/* Sets value pointed to by zone to the Carter zone corresponding to
		the bin "bin".  Returns 0 if successful, -1 if bin out of
		range.  */

	if (carter_not_initialized && carter_setup() ) {
		fprintf (stderr,"carter_get_zone:  Initialization failure.\n");
		return (-1);
	}

	if (bin < 0 || bin >= N_CARTER_BINS) {
		fprintf (stderr,"carter_get_zone:  bin out of range.\n");
		return (-1);
	}
	*zone = carter_zone[bin];
	return (0);
}


int carter_depth_from_twt (int zone, short int twt_in_msec, short int *depth_in_corr_m)
{
	/* Given two-way travel time of echosounder in milliseconds, and
		Carter Zone number, finds depth in Carter corrected meters.
		Returns (0) if OK, -1 if error condition.  */

	int	i, nominal_z1500, low_hundred, part_in_100;

	if (carter_not_initialized && carter_setup() ) {
		fprintf (stderr,"carter_depth_from_twt:  Initialization failure.\n");
		return (-1);
	}
	if (zone < 1 || zone > N_CARTER_ZONES) {
		fprintf (stderr,"carter_depth_from_twt:  Zone out of range.\n");
		return (-1);
	}
	if (twt_in_msec < 0) {
		fprintf (stderr,"carter_depth_from_twt:  Negative twt.\n");
		return (-1);
	}

	nominal_z1500 = irint (0.75 * twt_in_msec);

	if (nominal_z1500 <= 100) {
		/* There is no correction in water this shallow.  */
		*depth_in_corr_m = nominal_z1500;
		return (0);
	}

	low_hundred = nominal_z1500 / 100;
	i = carter_offset[zone-1] + low_hundred - 1;	/* -1 'cause .f indices */
	
	if (i >= (carter_offset[zone] - 1) ) {
		fprintf (stderr, "carter_depth_from_twt:  twt too big.\n");
		return (-1);
	}

	part_in_100 = nominal_z1500%100;

	if (part_in_100) {	/* We have to interpolate the table  */

		if ( i == (carter_offset[zone] - 2) ) {
			fprintf (stderr, "carter_depth_from_twt:  twt too big.\n");
			return (-1);
		}

		*depth_in_corr_m = (short) irint (carter_correction[i] +
			0.01 * part_in_100 * (carter_correction[i+1]
				- carter_correction[i]) );
		return (0);
	}
	else {
		*depth_in_corr_m = carter_correction[i];
		return (0);
	}
}


int carter_twt_from_depth (int zone, short int depth_in_corr_m, short int *twt_in_msec)
{
	/*  Given Carter zone and depth in Carter corrected meters,
	finds the two-way travel time of the echosounder in milliseconds.
	Returns -1 upon error, 0 upon success.  */

	int	min, max, guess;
	double	fraction;

	if (carter_not_initialized && carter_setup() ) {
		fprintf(stderr,"carter_twt_from_depth:  Initialization failure.\n");
		return (-1);
	}
	if (zone < 1 || zone > N_CARTER_ZONES) {
		fprintf (stderr,"carter_twt_from_depth:  Zone out of range.\n");
		return (-1);
	}
	if (depth_in_corr_m < 0) {
		fprintf(stderr,"carter_twt_from_depth:  Negative depth.\n");
		return(-1);
	}

	if (depth_in_corr_m <= 100) {	/* No correction applies.  */
		*twt_in_msec = (short int)irint (1.33333 * depth_in_corr_m);
		return (0);
	}

	max = carter_offset[zone] - 2;
	min = carter_offset[zone-1] - 1;

	if (depth_in_corr_m > carter_correction[max]) {
		fprintf (stderr, "carter_twt_from_depth:  Depth too big.\n");
		return (-1);
	}

	if (depth_in_corr_m == carter_correction[max]) {
		/* Hit last entry in table exactly  */
		*twt_in_msec = (short int)irint (133.333 * (max - min) );
		return (0);
	}


	guess = (depth_in_corr_m / 100) + min;
	if (guess > max) guess = max;
	while (guess < max && carter_correction[guess] < depth_in_corr_m) guess++;
	while (guess > min && carter_correction[guess] > depth_in_corr_m) guess--;

	if (depth_in_corr_m == carter_correction[guess]) {
		/* Hit a table value exactly  */
		*twt_in_msec = (short int)irint (133.333 * (guess - min) );
		return (0);
	}
	fraction = ((double)(depth_in_corr_m - carter_correction[guess])
			/ (double)(carter_correction[guess+1] - carter_correction[guess]));
	*twt_in_msec = (short int)irint (133.333 * (guess - min + fraction) );
	return (0);
}

