/*
    Access to PsiTrex text files.
    Based on information provided by Ian Cowley.

    Copyright (C) 2003 Mark Bradley, mrcb.gpsb@osps.net

    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; either version 2 of the License, or
    (at your option) any later version.

    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., 59 Temple Place - Suite 330, Boston, MA 02111 USA
 */

#include <stdio.h>
#include <string.h>

#include "defs.h"
#include "garmin_tables.h"
#include <ctype.h>

#define MYNAME "PSITREX" 

typedef enum {
	ltrimEOL = 1 ,		/* skip spaces & tabs to start; ends on EOL */
	EOL,				/* don't skip spaces and tabs to start; end on EOL */
	comma,				/* skip spaces & tabs to start; ends on comma or EOL */
	whitespace,			/* skip spaces & tabs to start; ends on white space or EOL */
	wscomma				/* skip spaces & tabs to start; ends on white space, comma or EOL */
} psit_tokenSep_type;

typedef struct psit_icon_mapping {
	const int	value;
	const char	*icon;
} psit_icon_mapping_t;

static gbfile *psit_file_in;
static gbfile *psit_file_out;
static short_handle mkshort_handle;

/* 2 = not written any tracks out
   1 = change of track to write out track header
   0 = in the middle of writing out track datapoints, so don't write a header */
static int	psit_track_state = 2;

static char psit_current_token[256];

char *snlen;

static
arglist_t psit_args[] = {
/*	{"snlen", &snlen, "Length of generated shortnames", 
 	NULL, ARGTYPE_INT, "1", NULL }, */
	ARG_TERMINATOR
};

/* Taken from PsiTrex 1.13 */
static
const psit_icon_mapping_t psit_icon_value_table[] = {
	{   0x00, "anchor" },
	{   0x06, "dollar" },
	{   0x07, "fish" },
	{   0x08, "fuel" },
	{   0x0a, "house" },
	{   0x0b, "knife" },
	{   0x0d, "mug" },
	{   0x0e, "skull" },
	{   0x12, "wpt_dot" },
	{   0x13, "wreck" },
	{   0x15, "mob" },
	{ 0x0096, "boat_ramp" },
	{ 0x0097, "camp" },
	{ 0x0098, "restrooms" },
	{ 0x0099, "showers" },
	{ 0x009a, "drinking_wtr" },
	{ 0x009b, "phone" },
	{ 0x009c, "1st_aid" },
	{ 0x009d, "info" },
	{ 0x009e, "parking" },
	{ 0x009f, "park" },
	{ 0x00a0, "picnic" },
	{ 0x00a1, "scenic" },
	{ 0x00a2, "skiing" },
	{ 0x00a3, "swimming" },
	{ 0x00a4, "dam" },
	{ 0x00a6, "danger" },
	{ 0x00a9, "ball" },
	{ 0x00aa, "car" },
	{ 0x00ab, "deer" },
	{ 0x00ac, "shpng_cart" },
	{ 0x00ad, "lodging" },
	{ 0x00ae, "mine" },
	{ 0x00af, "trail_head" },
	{ 0x00b0, "truck_stop" },
	{ 0x00b2, "flag" },
	{ 0x2005, "golf" },
	{ 0x2006, "sml_cty" },
	{ 0x2007, "med_cty" },
	{ 0x2008, "lrg_cty" },
	{ 0x200c, "amuse_pk" },
	{ 0x200d, "bowling" },
	{ 0x200e, "car_rental" },
	{ 0x200f, "car_repair" },
	{ 0x2010, "fastfood" },
	{ 0x2011, "fitness" },
	{ 0x2012, "movie" },
	{ 0x2013, "museum" },
	{ 0x2014, "pharmacy" },
	{ 0x2015, "pizza" },		/* how specific does this really need to be? C'mon! */
	{ 0x2016, "post_ofc" },
	{ 0x2017, "rv_park" },
	{ 0x2018, "school" },
	{ 0x2019, "stadium" },
	{ 0x201a, "store" },
	{ 0x201b, "zoo" },
	{ 0x201c, "gas_plus" },
	{ 0x201d, "faces" },
	{ 0x2022, "weigh_sttn" },
	{ 0x2023, "toll_booth" },
	{ 0x2029, "bridge" },
	{ 0x202a, "building" },
	{ 0x202b, "cemetery" },
	{ 0x202c, "church" },
	{ 0x202e, "crossing" },
	{ 0x2032, "oil_field" },
	{ 0x2033, "tunnel" },
	{ 0x2035, "forest" },
	{ 0x2036, "summit" },
	{ 0x203f, "geocache" },
	{ 0x2040, "geocache_fnd" },
	{ 0x4000, "airport" },
	{ 0x4007, "tall_tower" },
	{ 0x4008, "short_tower" },
	{ 0x4009, "glider" },
	{ 0x400a, "ultralight" },
	{ 0x400b, "parachute" },
	{ 0x4012, "seaplane" },
	{     -1, NULL }
};

static const char *
psit_find_desc_from_icon_number(const int icon)
{
	const psit_icon_mapping_t *i;

	for (i = psit_icon_value_table; i->icon; i++) {
		if (icon == i->value) {
			return i->icon;
		}
	}
	return "";
}

static int
psit_find_icon_number_from_desc(const char *desc)
{
	const psit_icon_mapping_t *i;
	int def_icon = 18;

	if (!desc) {
		return def_icon;
	}

	for (i = psit_icon_value_table; i->icon; i++) {
		if (case_ignore_strcmp(desc,i->icon) == 0) {
			return i->value;
		}
	}
	if (atoi(desc) > 0) return atoi(desc);
	return def_icon;
}

static void
psit_rd_init(const char *fname)
{
	psit_file_in = gbfopen(fname, "r", MYNAME);
}

static void
psit_rd_deinit(void)
{
	gbfclose(psit_file_in);
}

static void
psit_wr_init(const char *fname)
{
	psit_file_out = gbfopen(fname, "w", MYNAME);
}

static void
psit_wr_deinit(void)
{
	gbfclose(psit_file_out);
}

/*
 * get characters until and including terminating NULL from psit_file_in 
 * and write into buf.
 */
static void
psit_getToken(gbfile *psit_file, char *buf, size_t sz, psit_tokenSep_type delimType)
{
	int c = -1;

	*buf = 0;

	if (delimType != EOL) {
		while ((c = gbfgetc(psit_file)) != EOF) {
			if (!isspace(c)) break;
		}
	}

	if (gbfeof(psit_file)) return;

	if (delimType == EOL) {
		c = gbfgetc(psit_file);
	}

	if (c == '#') {
		if (gbfgets(buf, sz, psit_file) == NULL) {
			*buf = 0;
			return;
		}
		/* use recursion to skip multiple comment lines or just to return the next token */
		psit_getToken(psit_file, buf, sz, delimType);
		return;
	}

	if ((delimType == EOL) || (delimType == ltrimEOL)) {
		*buf = c;
		buf++;
		gbfgets(buf, sz-1, psit_file);
		return;
	}

	while (sz--) {
		*buf++ = c;
		if ((c = gbfgetc (psit_file)) == EOF) {
			*buf = 0;
			return;
		}
		if (((c == 0) || isspace(c)) &&
			((delimType == whitespace) || (delimType == wscomma)) ) {
				*buf = 0;
				return;
		}
		if (((delimType == comma) || (delimType == wscomma)) &&
			(c == ',')) {
				*buf = 0;
				return;
		}
	}
}


/*
 * test if a token is known 
 * 
 */
static int
psit_isKnownToken(char *buf)
{
	if (strcmp(buf, "Track:") == 0) return 0;
	if (strcmp(buf, "Route:") == 0) return 0;
	if (strcmp(buf, "Waypoint:") == 0) return 0;
	if (strcmp(buf, "Map:") == 0) return 0;
	return 1;
}

/*
 * read in from file a waypoint record
 * MRCB
 */
static void
psit_waypoint_r(gbfile *psit_file, waypoint **wpt)
{
	int		garmin_icon_num;

	waypoint	*thisWaypoint;

	if (strlen(psit_current_token) > 0) {
		thisWaypoint = waypt_new();

		thisWaypoint->latitude = atof(psit_current_token);

		psit_getToken(psit_file,psit_current_token,sizeof(psit_current_token), comma);
		thisWaypoint->longitude = atof(psit_current_token);

		psit_getToken(psit_file,psit_current_token,sizeof(psit_current_token), comma);
		if (psit_current_token[0] == '*') {
			thisWaypoint->altitude = unknown_alt;
		}
		else {
			thisWaypoint->altitude = atof(psit_current_token);
		}

		/* the name */
		psit_getToken(psit_file,psit_current_token,sizeof(psit_current_token), comma);
		rtrim(psit_current_token);
		thisWaypoint->shortname = xstrdup(psit_current_token);
		thisWaypoint->description = xstrdup("");

		psit_getToken(psit_file,psit_current_token,sizeof(psit_current_token), ltrimEOL);
		rtrim(psit_current_token);
		/* since PsiTrex only deals with Garmins, let's use the "proper" Garmin icon name */
		/* convert the PsiTrex name to the number, which is the PCX one; from there to Garmin desc */
		garmin_icon_num = psit_find_icon_number_from_desc(psit_current_token);
		thisWaypoint->icon_descr = gt_find_desc_from_icon_number(garmin_icon_num, PCX, NULL);

		waypt_add(thisWaypoint);

		psit_getToken(psit_file,psit_current_token,sizeof(psit_current_token), wscomma);
	}
}

/*
 * write out to file a waypoint record
 * MRCB
 */
static void
psit_waypoint_w(gbfile *psit_file, const waypoint *wpt)
{
	int	icon;
    const char *ident;
	char *src = 0;  /* BUGBUG Passed to mkshort */

	gbfprintf(psit_file, "%11.6f,%11.6f,", 
						wpt->latitude,
						wpt->longitude);

	if (wpt->altitude == unknown_alt) 
		gbfprintf(psit_file, "********,");
	else
		gbfprintf(psit_file, "%8.2f,",
						wpt->altitude);

	ident = global_opts.synthesize_shortnames ?
				mkshort(mkshort_handle, src) :
				wpt->shortname;

	gbfprintf(psit_file, " %-6s, ", ident);
	icon = gt_find_icon_number_from_desc(wpt->icon_descr, PCX);

	if (get_cache_icon(wpt) && wpt->icon_descr && (strcmp(wpt->icon_descr, "Geocache Found") != 0)) {
		icon = gt_find_icon_number_from_desc(get_cache_icon(wpt), PCX);
	}

	ident = psit_find_desc_from_icon_number(icon);
	if (strlen(ident) == 0)
		gbfprintf(psit_file, "%1d\n", icon);
	else
		gbfprintf(psit_file, "%s\n", ident);

}

static void
psit_waypoint_w_wrapper(const waypoint *wpt)
{
	psit_waypoint_w(psit_file_out, wpt);
}

/*
 * read in from file a route record
 * MRCB
 */
static void
psit_route_r(gbfile *psit_file, route_head **rte)
{
	char rtename[256];
	unsigned int rte_num;

	int		garmin_icon_num;

	route_head *rte_head;
	unsigned int rte_count;

	waypoint	*thisWaypoint;

	psit_getToken(psit_file,psit_current_token,sizeof(psit_current_token), ltrimEOL);

	if (strlen(psit_current_token) == 0) {
		strcpy(rtename, "ROUTE");
	}
	else {
		strcpy(rtename, psit_current_token);
	}

	rtrim(rtename);

	rte_head = route_head_alloc();
	rte_head->rte_name = xstrdup(rtename);
	route_add_head(rte_head);
	*rte = rte_head;

	rte_num = 0;

	rte_count = 0;

	psit_getToken(psit_file,psit_current_token,sizeof(psit_current_token), wscomma);

	while (psit_isKnownToken(psit_current_token) != 0) {
		if (strlen(psit_current_token) > 0) {
			thisWaypoint = waypt_new();

			thisWaypoint->latitude = atof(psit_current_token);

			psit_getToken(psit_file,psit_current_token,sizeof(psit_current_token), comma);
			thisWaypoint->longitude = atof(psit_current_token);

			psit_getToken(psit_file,psit_current_token,sizeof(psit_current_token), comma);
			if (psit_current_token[0] == '*') {
				thisWaypoint->altitude = unknown_alt;
			}
			else {
				thisWaypoint->altitude = atof(psit_current_token);
			}

			/* the name */
			psit_getToken(psit_file,psit_current_token,sizeof(psit_current_token), comma);
			rtrim(psit_current_token);
			thisWaypoint->shortname = xstrdup(psit_current_token);
			thisWaypoint->description = xstrdup("");

			psit_getToken(psit_file,psit_current_token,sizeof(psit_current_token), ltrimEOL);
			rtrim(psit_current_token);
			/* since PsiTrex only deals with Garmins, let's use the "proper" Garmin icon name */
			/* convert the PsiTrex name to the number, which is the PCX one; from there to Garmin desc */
			garmin_icon_num = psit_find_icon_number_from_desc(psit_current_token);
			thisWaypoint->icon_descr = gt_find_desc_from_icon_number(garmin_icon_num, PCX, NULL);

			route_add_wpt(rte_head, thisWaypoint);

			if (gbfeof(psit_file)) break;

			psit_getToken(psit_file,psit_current_token,sizeof(psit_current_token), wscomma);
		}
		else break;
	}
}

/*
 * write out to file a route header
 * MRCB
 */
static void
psit_routehdr_w(gbfile *psit_file, const route_head *rte)
{
	char		hdr[20];
	unsigned int rte_datapoints;
	char		*rname;

	waypoint	*testwpt;
	time_t		uniqueValue = 0;
	int			allWptNameLengths;

	queue *elem, *tmp;

	/* total nodes (waypoints) this route */
	rte_datapoints = 0;
	allWptNameLengths = 0;

	if (rte->waypoint_list.next) {		/* this test doesn't do what I want i.e test if this is a valid route - treat as a placeholder for now */
		char *c;
		
		QUEUE_FOR_EACH(&rte->waypoint_list, elem, tmp) {
			testwpt = (waypoint *)elem;
			if (rte_datapoints == 0) {
				uniqueValue = testwpt->creation_time;
			}
			rte_datapoints++;
		}		

		if (uniqueValue == 0) {
			uniqueValue = current_time();
		}

		/* route name */
		if (!rte->rte_name) {
			sprintf(hdr, "Route%04x", (unsigned) uniqueValue);
			rname = xstrdup(hdr);
		}
		else
			rname = xstrdup(rte->rte_name);

		/* check for psitrex comment sign; replace with '$' */
		while ((c = strchr(rname, '#'))) *c = '$';

		gbfprintf(psit_file, "Route:  %s\n", rname);
		xfree(rname);
	}
}

static void
psit_routehdr_w_wrapper(const route_head *rte)
{
	psit_routehdr_w(psit_file_out, rte);
}


/*
 * read in from file a track record
 * MRCB
 */
static void
psit_track_r(gbfile *psit_file, route_head **trk)
{
	char tbuf[100];
	char trkname[256];
	unsigned int trk_num;

	struct tm tmTime;
	time_t	dateTime = 0;
	route_head *track_head = NULL;
	unsigned int trk_count;

	waypoint	*thisWaypoint;

	psit_getToken(psit_file,psit_current_token,sizeof(psit_current_token), ltrimEOL);
	if (strlen(psit_current_token) == 0) {
		strcpy(trkname, "TRACK");
	}
	else {
		strcpy(trkname, psit_current_token);
	}

	rtrim(trkname);

	trk_num = 0;

	trk_count = 0;
	
	track_head = NULL;

	psit_getToken(psit_file,psit_current_token,sizeof(psit_current_token), wscomma);

	while (psit_isKnownToken(psit_current_token) != 0) {
		if (strlen(psit_current_token) > 0) {
			thisWaypoint = waypt_new();

			thisWaypoint->latitude = atof(psit_current_token);

			psit_getToken(psit_file,psit_current_token,sizeof(psit_current_token), comma);
			thisWaypoint->longitude = atof(psit_current_token);

			psit_getToken(psit_file,psit_current_token,sizeof(psit_current_token), comma);
			if (psit_current_token[0] == '*') {
				thisWaypoint->altitude = unknown_alt;
			}
			else {
				thisWaypoint->altitude = atof(psit_current_token);
			}

			/* date portion of the date time DD/MM/YY */
			psit_getToken(psit_file, psit_current_token,
					sizeof(psit_current_token), whitespace);
			sscanf(psit_current_token, "%02d/%02d/%02d", 
				&(tmTime.tm_mday) , &(tmTime.tm_mon), 
				&(tmTime.tm_year));

			/* years are less 1900 in the tm struct */
			tmTime.tm_year += (tmTime.tm_year > 50 ? 0 : 100);		
			/* months are 0 to 11 in the tm struct */
			tmTime.tm_mon--;										
			/* time portion of the date time hh:mm:ss */
			psit_getToken(psit_file,psit_current_token,
					sizeof(psit_current_token), wscomma);
			sscanf(psit_current_token, "%02d:%02d:%02d", 
					&(tmTime.tm_hour) , &(tmTime.tm_min), 
					&(tmTime.tm_sec));

			tmTime.tm_isdst = 0;
			dateTime = mkgmtime(&tmTime);

			psit_getToken(psit_file,psit_current_token,sizeof(psit_current_token), whitespace);

			if ((strcmp(psit_current_token, "1") == 0) || (track_head == NULL)) {
				track_head = route_head_alloc();
				/* Add a number to the track name.  With Garmins, the "first" tracklog is usually ACTIVE LOG
				   the second is ACTIVE LOG001 and so on */
				if (trk_num > 0) {
					sprintf(tbuf, "%s%03d", trkname, trk_num);
					track_head->rte_name = xstrdup(tbuf);
				}
				else {
					track_head->rte_name = xstrdup(trkname);
				}
				trk_num++;
				track_add_head(track_head);
			}

			thisWaypoint->creation_time = dateTime;
			track_add_wpt(track_head, thisWaypoint);

			if (gbfeof(psit_file)) break;

			psit_getToken(psit_file,psit_current_token,sizeof(psit_current_token), wscomma);
		}
		else break;
	}
}

/*
 * write out to file a tracklog header
 * MRCB
 */
static void
psit_trackhdr_w(gbfile *psit_file, const route_head *trk)
{
	char		hdr[30];
	unsigned int trk_datapoints;
	char		*tname;
	waypoint	*testwpt;
	time_t		uniqueValue = 0;

	queue *elem, *tmp;

	if (psit_track_state == 2) {
		/* total nodes (waypoints) this track */
		trk_datapoints = 0;
		if (trk->waypoint_list.next) {	/* this test doesn't do what I want i.e test if this is a valid track - treat as a placeholder for now */
			char *c;
			
			QUEUE_FOR_EACH(&trk->waypoint_list, elem, tmp) {
				if (trk_datapoints == 0) {
					testwpt = (waypoint *)elem;
					uniqueValue = testwpt->creation_time;
				}
				trk_datapoints++;
			}

			if (uniqueValue == 0) {
				uniqueValue = current_time();
			}

			/* track name */
			if (!trk->rte_name) {
				sprintf(hdr, "Track%04x", (unsigned) uniqueValue);
				tname = xstrdup(hdr);
			}
			else
				tname = xstrdup(trk->rte_name);

			/* check for psitrex comment sign; replace with '$' */
			while ((c = strchr(tname, '#'))) *c = '$';
			
			gbfprintf (psit_file, "Track:  %s\n", tname);

			xfree(tname);
		}
	}
	psit_track_state = 1;
}

static void
psit_trackhdr_w_wrapper(const route_head *trk)
{
	psit_trackhdr_w(psit_file_out, trk);
}


/*
 * write out to file a tracklog datapoint
 * MRCB
 */
static void
psit_trackdatapoint_w(gbfile *psit_file, const waypoint *wpt)
{
	time_t	t = wpt->creation_time;
	struct tm *tmTime = gmtime(&t);

	gbfprintf(psit_file, "%11.6f,%11.6f,", 
						wpt->latitude,
						wpt->longitude);

	if (wpt->altitude == unknown_alt) 
		gbfprintf(psit_file, "********, ");
	else
		gbfprintf(psit_file, "%8.2f, ",
						wpt->altitude);

	/* Following date time format is fixed and reveals the origin of PsiTrex (i.e. the UK) */
	gbfprintf(psit_file, "%02d/%02d/%02d %02d:%02d:%02d,",
						tmTime->tm_mday,
						tmTime->tm_mon+1,
						tmTime->tm_year % 100,
						tmTime->tm_hour,
						tmTime->tm_min,
						tmTime->tm_sec);

	gbfprintf(psit_file," %d\n", psit_track_state);
	psit_track_state = 0;
}

static void
psit_trackdatapoint_w_wrapper(const waypoint *wpt)
{
	psit_trackdatapoint_w(psit_file_out, wpt);
}


static void
psit_read(void)
{
	waypoint	*wpt;
	route_head	*rte;
	route_head	*trk;

#ifdef DUMP_ICON_TABLE
	printf("static icon_mapping_t icon_table[] = {\n");
#endif

	psit_getToken(psit_file_in, psit_current_token, sizeof(psit_current_token), whitespace);

	do {
		if (strlen(psit_current_token) == 0) break;

		if (strcmp(psit_current_token, "Track:") == 0) {
			if (global_opts.objective == trkdata) {
				psit_track_r(psit_file_in, &trk);
			}
			else break;		/* psitrex files only have one format in; */
							/* if this is a track file and we don't want them, them bail out */
		}
		else if (strcmp(psit_current_token, "Route:") == 0) {
			if (global_opts.objective == rtedata) {
				psit_route_r(psit_file_in, &rte);
			}
			else break;		/* ditto, but for routes */
		}
		else {
			/* Must be waypoints in this file */
			if (global_opts.objective == wptdata) {
				psit_waypoint_r(psit_file_in, &wpt);
#ifdef DUMP_ICON_TABLE
				printf("\t{  %4u, \"%s\" },\n", icon, wpt->shortname);
#endif

			}
			else break;
		}
	} while (!gbfeof(psit_file_in));

	return;

#ifdef DUMP_ICON_TABLE
		printf("\t{ -1, NULL },\n");
		printf("};\n");
#endif
}

static void 
psit_noop(const route_head *wp)
{
	/* no-op */
}

void
psit_write(void)
{
	int short_length;

	if (snlen)
		short_length = atoi(snlen);
	else
		short_length = 10;

	mkshort_handle = mkshort_new_handle();

	setshort_length(mkshort_handle, short_length);
	setshort_whitespace_ok(mkshort_handle, 0);

	psit_track_state = 2;

	if (global_opts.objective == wptdata) {
		waypt_disp_all(psit_waypoint_w_wrapper);
	}
	if (global_opts.objective == rtedata) {
		route_disp_all(psit_routehdr_w_wrapper, psit_noop, psit_waypoint_w_wrapper);
	}
	if (global_opts.objective == trkdata) {
		track_disp_all(psit_trackhdr_w_wrapper, psit_noop, psit_trackdatapoint_w_wrapper);
	}

	mkshort_del_handle(&mkshort_handle);

}

ff_vecs_t psit_vecs = {
	ff_type_file,
	FF_CAP_RW_ALL,
	psit_rd_init,
	psit_wr_init,
	psit_rd_deinit,
	psit_wr_deinit,
	psit_read,
	psit_write,
	NULL, 
	psit_args,
	CET_CHARSET_ASCII, 0	/* CET-REVIEW */
};
