/*
    Copyright 2007 Mirko Parthey <mirko.parthey@informatik.tu-chemnitz.de>
  
    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, see <http://www.gnu.org/licenses/>.
*/

/* TODO:
 * - check whether negative lat/lon (West/South) are handled correctly, see bin2deg()
 */

#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>
#include <stdint.h>
#include <stdbool.h>
#include <netinet/in.h>
#include <assert.h>
#include <string.h>
#include <time.h>

#define GET_BIGENDIAN_32(p)       ntohl(*(uint32_t *)(p))
#define GET_BIGENDIAN_16(p)       ntohs(*(uint16_t *)(p))
#define GET_BYTE(p)               (*(uint8_t *)(p))

#define PUT_BIGENDIAN_32(p, val)  *(uint32_t *)(p) = htonl(val)
#define PUT_BIGENDIAN_16(p, val)  *(uint16_t *)(p) = htons(val)
#define PUT_BYTE(p, val)          *(uint8_t  *)(p) = (val)

enum dg100_command_id {
	dg100cmd_getconfig     = 0xB7,
	dg100cmd_setconfig     = 0xB8,
	dg100cmd_getfileheader = 0xBB,
	dg100cmd_getfile       = 0xB5,
	dg100cmd_erase         = 0xBA,
	dg100cmd_getid         = 0xBF,
	dg100cmd_setid         = 0xC0,
	dg100cmd_gpsmouse      = 0xBC
};

struct dg100_command {
	int  sendsize;
	int  recvsize;
	int  trailing_bytes;
	bool valid;
};

struct dg100_command  dg100_commands[256] = {
	[dg100cmd_getconfig]     = {  0,   44+2,    2, true },
	[dg100cmd_setconfig]     = { 41,    4+2,    2, true },
	/* getfileheader answer has variable length, -1 is a dummy value */
	[dg100cmd_getfileheader] = {  2,   -1  ,    2, true },
	[dg100cmd_getfile]       = {  2, 1024+2,    2, true },
	[dg100cmd_erase]         = {  2,    4+2,    2, true },
	[dg100cmd_getid]         = {  0,    8+2,    2, true },
	[dg100cmd_setid]         = {  8,    4+2,    2, true },
	[dg100cmd_gpsmouse]      = {  1,    0  ,    0, true }
};

struct loginterval {
	enum {
		li_time = 0,
		li_distance = 1
	} flag;
	uint32_t time;
	uint32_t dist;
};

enum logmode {
	mode_a,
	mode_b,
	mode_c
};

const char *logmode_name[3] = {
	"A",
	"B",
	"C"
};

struct dg100config {
	uint8_t infotype;
	bool    spd_thresh_flag; 
	uint32_t spd_thresh; 
	uint8_t dist_thresh_flag; 
	uint32_t dist_thresh; 
	struct loginterval loginterval[3]; /* indexed by enum logmode */
	uint8_t memfree; 
};

/* maximum frame size observed so far: 1817
 * (this was get_fileheaders() returning 150 entries)
 * get_fileheaders() is the only answer type of variable length */
#define FRAME_MAXLEN 4096

/* maximum number of headers observed so far: 672 */
#define HEADERS_MAX 65536

/* global variables */
struct termios old_tio, new_tio;
int fd;

/* helper functions */
void port_close() {
	tcsetattr(fd, TCSAFLUSH, &old_tio);
	close(fd);
}

void port_open(char port_name[]) {
	char s[256];

	fd = open(port_name, O_RDWR | O_NOCTTY);

	if (fd < 0) {
		snprintf(s, 256, "open %s failed", port_name);
	    	perror(s);
		exit(1);
	}

	if (tcgetattr(fd, &old_tio) < 0) {
		snprintf(s, 256, "tcgetattr %s failed", port_name);
	    	perror(s);
		exit(1);
	}
	//atexit(port_close);

	new_tio = old_tio;
	cfmakeraw(&new_tio); /* this includes setting 8 data bits, no parity */
	new_tio.c_cflag &= ~(CSTOPB); /* set 1 stop bit (not handled by cfmakeraw) */
	cfsetispeed(&new_tio, B115200);
	cfsetospeed(&new_tio, B115200);

	if (tcsetattr(fd, TCSAFLUSH, &new_tio) < 0) {
		snprintf(s, 256, "tcsetattr %s failed", port_name);
	    	perror(s);
		exit(1);
	}

	return;
}

void
dg100_printconfig(const struct dg100config *conf, bool isgetconfig)
{
	const char *s;
	int i;

	printf("%s device configuration:\n", isgetconfig ? "Current" : "New");

	switch (conf->infotype) {
		case 0: s = "position only"; break;
		case 1: s = "position, speed and date/time"; break;
		case 2: s = "position, speed, date/time and altitude"; break;
		default: s = "??? (unknown logging style)"; break;
	}
	printf("Logging %s.\n", s);
	
	if (conf->spd_thresh_flag)
		printf("Don't log when speed is below %d km/h.\n",
				conf->spd_thresh / 100 );

	if (conf->dist_thresh_flag)
		printf("Don't log position changes of less than %d meters.\n",
				conf->dist_thresh );

	for (i=0; i<3; i++) {
		s = logmode_name[i];
		if (conf->loginterval[i].flag == li_time)
			printf("Mode %s: every %d seconds.\n",
					s,
					conf->loginterval[i].time / 1000);
		else
			printf("Mode %s: every %d meters.\n",
					s,
					conf->loginterval[i].dist);
	}

	if (isgetconfig)
		printf("Memory used: %d%%\n", 100 - conf->memfree);
}

void
dg100_decodeconfig(struct dg100config *conf, const uint8_t *data)
{
	int i;

	conf->infotype         = GET_BYTE(data + 0);
	conf->spd_thresh_flag  = GET_BYTE(data + 1);
	conf->spd_thresh       = GET_BIGENDIAN_32(data + 2);
	conf->dist_thresh_flag = GET_BYTE(data + 6);
	conf->dist_thresh      = GET_BIGENDIAN_32(data + 7);

	for (i = 0; i < 3; i++) {
		conf->loginterval[i].flag = GET_BYTE(data + 25 + i);
		conf->loginterval[i].time = GET_BIGENDIAN_32(data + 11 + i * 4);
		conf->loginterval[i].dist = GET_BIGENDIAN_32(data + 28 + i * 4);
	}

	conf->memfree = GET_BYTE(data + 41);

}

void
dg100_encodeconfig(uint8_t *data, const struct dg100config *conf)
{
	int i;

	PUT_BYTE(data + 0, conf->infotype);
	PUT_BYTE(data + 1, conf->spd_thresh_flag);
	PUT_BIGENDIAN_32(data + 2, conf->spd_thresh);
	PUT_BYTE(data + 6, conf->dist_thresh_flag);
	PUT_BIGENDIAN_32(data + 7, conf->dist_thresh);

	for (i = 0; i < 3; i++) {
		PUT_BYTE(data + 25 + i, conf->loginterval[i].flag);
		PUT_BIGENDIAN_32(data + 11 + i * 4, conf->loginterval[i].time);
		PUT_BIGENDIAN_32(data + 28 + i * 4, conf->loginterval[i].dist);
	}

	PUT_BYTE(data + 23, 0);
	PUT_BYTE(data + 24, 0);
	PUT_BYTE(data + 40, 1);
}

time_t
bintime2utc(uint32_t date, uint32_t time) {
	struct tm gpstime;
	char *oldtz;
	time_t caltime;

	gpstime.tm_sec   = time % 100;
	time /= 100;
	gpstime.tm_min   = time % 100;
	time /= 100;

	gpstime.tm_hour  = time + 1;

	/*
	 * GPS year: 2000+; struct tm year: 1900+
	 * GPS month: 1-12, struct tm month: 0-11
	 */
	gpstime.tm_year  = date % 100 + 100;
	date /= 100;
	gpstime.tm_mon   = date % 100 - 1;
	date /= 100;
	gpstime.tm_mday  = date;

	/* save current time zone */
	oldtz = getenv("TZ"), 10;
	/* assume GPS time is in UTC */
	//KEVIN JAAKO: added if statemnt to check if TZ was set.
	if (oldtz)
		setenv("TZ", "Japan", 1);
	
	caltime = mktime(&gpstime);

	/* restore saved time zone */
	if (oldtz)
		setenv("TZ", oldtz, 1);
	else
		unsetenv("TZ");

	return(caltime);
}

char *
my_strftime(time_t ti, bool gpx)
{
	static char timestr[256];
	
	if (!gpx) { strftime(timestr, 256, "%Y-%m-%d %H:%M:%S", localtime(&ti)); }
	if (gpx) { strftime(timestr, 256, "%Y-%m-%dT%H:%M:%SZ", localtime(&ti)); }
	return timestr;
}

float
bin2deg(int32_t val)
{
	/* Assume that val prints in decimal digits as "dddmmffff":
	 * ddd:  degrees
	 * mm:   the integer part of minutes
	 * ffff: the fractional part of minutes (decimal fraction 0.ffff)
	 */

	float deg;
	int deg_int, min_scaled;
	bool isneg;
	uint32_t absval;
	
	/* avoid division of negative integers, which has platform-dependent results */
	isneg = (val < 0);
	absval = abs(val);

	deg_int = absval / 1000000;      /* extract dd */
	min_scaled = absval % 1000000;   /* extract mmffff (minutes scaled by 10^4) */
	deg = deg_int + (double) min_scaled / (10000 * 60);

	/* restore the sign */
	deg = isneg ? -deg : deg;
	return(deg);
}

void
print_gpsfile(uint8_t data[], bool gpx)
{
	const uint32_t recordsizes[3] = {8, 20, 32};
	uint32_t first_style, recsize;
	uint32_t lat, lon, utime, udate, style;
	int i, recnum;
	char *s;
	struct logpoint {
		uint32_t style;
		float lat;
		float lon;
		time_t time;
		float speed;
		float altitude;
		uint32_t dummy;
	} p;

	/* the first record of each file is always full-sized; its style field
	 * determines the format of all subsequent records in the file */
	first_style = GET_BIGENDIAN_32(data + 28);
	if (first_style > 2) {
		fprintf(stderr, "unknown GPS record style %d", first_style);
		return;
	}
	recsize = recordsizes[first_style];

	if (!gpx) {
		printf("%4s:%5s-%4s%9s%10s%21s%7s%8s%3s%6s\n",
				"rec#", "from", "to", "lat", "lon", "time", "km/h", "alti/m", "?", "style");
	}
		
	recnum = 0;
	for (i = 0; i <= 2048 - recsize; i += (i == 0) ? 32 : recsize, recnum++) {

		/* look at the data around byte 1024..1025 to check whether the
		 * transition between the pasted frames is handled correctly */
		if (!gpx) { printf("%4d:%5d-%4d", recnum, i, i + recsize - 1); }

		/* always use the style from the first record */
		p.style = first_style;

		lat = GET_BIGENDIAN_32(data + i + 0);
		lon = GET_BIGENDIAN_32(data + i + 4);
		if (lat == UINT32_MAX && lon == UINT32_MAX) {
			if (!gpx) { printf(" (ignoring blank record)\n"); }
			continue;
		}

		p.lat = bin2deg(lat);
		p.lon = bin2deg(lon);
		if (!gpx) { printf("%9.4f%10.4f", p.lat, p.lon); }

		if (first_style >= 1) {
			utime     = GET_BIGENDIAN_32(data + i +  8);
			udate     = GET_BIGENDIAN_32(data + i + 12);
			p.speed   = GET_BIGENDIAN_32(data + i + 16) / 100.0;
			p.time    = bintime2utc(udate, utime);
			s = my_strftime(p.time, gpx);
			if (!gpx) { printf("%21s", s); /* time */  }
			if (!gpx) { printf("%7.1f", p.speed); }
		}

		if (first_style >= 2) {
			p.altitude = GET_BIGENDIAN_32(data + i + 20) / 10000.0;
			p.dummy    = GET_BIGENDIAN_32(data + i + 24);
			style      = GET_BIGENDIAN_32(data + i + 28);
			if (!gpx) { printf("%8.1f%3d%6d", p.altitude, p.dummy, style); }
		}
		if (!gpx) { printf("\n"); }
		if (gpx) {
			
			printf("\t<trkpt lat=\"%.9f\" lon=\"%.9f\">\n", p.lat, p.lon);
		if (first_style >= 1) {
				printf("\t\t<time>%21s</time>\n", s);			
				printf("\t\t<speed>%.1f</speed>\n", p.speed);
			}	
			if (first_style >= 2) {
				printf("\t\t<ele>%.1f</ele>\n", p.altitude);
			}
			printf("\t</trkpt>\n");
			
		}
	}
}

char *
hexdump(const uint8_t id[], unsigned count)
{
	int i;
	static char s[256];
	bool too_long;

	if (count == 0)
		return("");

	too_long = (count > 64);
	if (too_long)
		count = 64;

	sprintf(s, "%02x", id[0]);
	for (i = 1; i < count; i++) {
		sprintf(s + strlen(s), " %02x", id[i]);
	}

	if (too_long)
		sprintf(s + strlen(s), " [...]");

	return(s);
}

uint16_t
dg100_checksum(uint8_t buf[], int count)
{
	uint16_t sum = 0;
	int i;

	for (i = 0; i < count; i++) {
		sum += buf[i];
	}
	sum &= (1<<15) - 1;

	return(sum);
}

/* communication functions */
size_t
dg100_send(uint8_t cmd, const void *payload, size_t count)
{
	uint8_t frame[FRAME_MAXLEN];
	uint16_t checksum, payload_len;
	size_t framelen, param_len, n;
	
	param_len = count;
	payload_len = 1 + count;
	/* Frame length calculation:
	 * frame start sequence(2), payload length field(2), command id(1),
	 * param(variable length),
	 * checksum(2), frame end sequence(2) */
	framelen = 2 + 2 + 1 + count + 2 + 2;
	assert(framelen <= FRAME_MAXLEN);

	/* create frame head + command */
	PUT_BIGENDIAN_16(frame + 0, 0xA0A2);
	PUT_BIGENDIAN_16(frame + 2, payload_len);
	PUT_BYTE(frame + 4, cmd);

	/* copy payload */
	memcpy(frame + 5, payload, count);

	/* create frame tail */
	checksum = dg100_checksum(frame + 4, framelen - 8);
	PUT_BIGENDIAN_16(frame + framelen - 4, checksum);
	PUT_BIGENDIAN_16(frame + framelen - 2, 0xB0B3);

	n = write(fd, frame, framelen);
	if (n < 0) {
	    	perror("dg_100_send: write failed");
		exit(1);
	}
	return (n);
}

uint8_t
dg100_recv_byte()
{
	uint8_t c;
	ssize_t n;

	n = read(fd, &c, 1);
	if (n < 0) {
		perror("dg100_recv_byte() reading one byte");
		exit(1);
	}
	if (n == 0) {
		fprintf(stderr, "dg100_recv_byte(): read returned zero bytes (EOF)\n");
		exit(1);
	}
	return c;
}

/* payload points into a static buffer (which also contains the framing
 * around the data), so the caller must copy the data before calling
 * dg100_recv_frame() again */
ssize_t
dg100_recv_frame(uint8_t *answer_id, uint8_t **payload)
{
	static uint8_t buf[FRAME_MAXLEN];
	uint16_t frame_start_seq, payload_len_field;
	uint16_t payload_end_seq, payload_checksum, frame_end_seq;
	uint16_t frame_head, sum, numheaders;
	uint8_t c, cmd;
	int i, param_len, frame_len;

	/* consume input until frame head sequence 0xA0A2 was received */
	frame_head = 0;
	do {
		c = dg100_recv_byte();
		frame_head <<= 8;
		frame_head |= c;

	} while (frame_head != 0xA0A2);

	PUT_BIGENDIAN_16(buf + 0, frame_head);

	/* To read the remaining data, we need to know how long the frame is.
	 *
	 * The obvious source of this information would be the payload length
	 * field, but the spec says that this field should be ignored in answers.
	 * Indeed, its value differs from the actual payload length.
	 *
	 * We could scan for the frame end sequences,
	 * but there is no guarantee that they do not appear within valid data.
	 *
	 * This means we can only calculate the length using information from
	 * the beginning of the frame, other than the payload length.
	 *
	 * The solution implemented here is to derive the frame length from the
	 * Command ID field, which is more of an answer ID. This is possible
	 * since for each answer ID, the frame length is either constant or it
	 * can be derived from the first two bytes of payload data.
	 */

	/* read Payload Length, Command ID, and two further bytes */
	for (i = 2; i < 7; i++) {
		buf[i] = dg100_recv_byte();
	}

	payload_len_field = GET_BIGENDIAN_16(buf + 2);
	cmd = GET_BYTE(buf + 4);

	/*
	 * getconfig/setconfig have the same answer ID -
	 * this seems to be a firmware bug we must work around.
	 * Distinguish them by the (otherwise ignored) Payload Len field,
	 * which was observed as 53 for getconfig and 5 for setconfig.
	 */
	if (cmd == dg100cmd_getconfig && payload_len_field <= 20) {
		cmd = dg100cmd_setconfig;
	}

	if (!dg100_commands[cmd].valid) {
		/* TODO: consume data until frame end signature,
		 * then report failure to the caller */
		fprintf(stderr, "unknown answer ID %d\n", cmd);
		exit(1);
	}

	param_len = dg100_commands[cmd].recvsize;

	/*
	 * the getfileheader answer has a varying param_len,
	 * we need to calculate it
	 */
	if (cmd == dg100cmd_getfileheader) {
		numheaders = GET_BIGENDIAN_16(buf + 5);
		param_len = 2 + 2 + 12 * numheaders + 2;
	}

	/* Frame length calculation:
	 * frame start sequence(2), payload length field(2), command id(1),
	 * param(variable length),
	 * payload end seqence(2), checksum(2), frame end sequence(2) */
	frame_len = 2 + 2 + 1 + param_len + 2 + 2 + 2;
	 
	if (frame_len > FRAME_MAXLEN) {
		fprintf(stderr, "frame too large (frame_len=%d, FRAME_MAXLEN=%d)\n",
				frame_len, FRAME_MAXLEN);
		exit(1);
	}

	/* TODO: We could try to read the rest of the frame at once; */
	/* must set tcsetattr(c_cc[VMIN] = ????) for this to work */
	for (i = 7; i < frame_len; i++) {
		buf[i] = dg100_recv_byte();
	}

	frame_start_seq   = GET_BIGENDIAN_16(buf + 0);
	payload_len_field = GET_BIGENDIAN_16(buf + 2);
	payload_end_seq   = GET_BIGENDIAN_16(buf + frame_len - 6);
	payload_checksum  = GET_BIGENDIAN_16(buf + frame_len - 4);
	frame_end_seq     = GET_BIGENDIAN_16(buf + frame_len - 2);

	/* TODO: check signatures and checksums,
	 * flush input on failure or scan for end sequence */

#ifdef DEBUG
	/* calculate checksum */
	sum = dg100_checksum(buf + 4, frame_len - 8);

	printf("frame_start_seq  = %04x (%s)\n", frame_start_seq,  frame_start_seq  == 0xA0A2 ? "ok" : "FAILED");
	printf("payload_len_field= %d\n",        payload_len_field);
	printf("payload_end_seq  = %04x (%s)\n", payload_end_seq,  payload_end_seq  == 0x0D00 ? "ok" : "FAILED");
	printf("payload_checksum = %04x (%s)\n", payload_checksum, payload_checksum == sum    ? "ok" : "FAILED");
	printf("frame_end_seq    = %04x (%s)\n", frame_end_seq,    frame_end_seq    == 0xB0B3 ? "ok" : "FAILED");
	printf("  calculated checksum=%04x\n", sum);
	printf("  calculated parameter length=%d\n", param_len);
	printf("  calculated frame length=%d\n", frame_len);
#endif

#ifdef DEBUG2
	/* print frame contents */
	for (i = 0; i < frame_len; i++) {
		printf("[%4d]=%02x ", i, buf[i]);
	}
	printf("\n");
	printf("------------------------------------------\n");
#endif

	*answer_id = cmd;
	*payload = buf + 5;
	return(param_len);
}

/* return value: number of bytes copied into buf, -1 on error */
ssize_t
dg100_recv(uint8_t expected_id, void *buf, unsigned len)
{
	int n;
	uint8_t id, *data;
	int copysize, trailing_bytes;

	n = dg100_recv_frame(&id, &data);

	/* check whether the received frame matches the expected answer type */
	if (id != expected_id) {
		fprintf(stderr, "ERROR: answer type %02x, expecting %02x", id, expected_id);
		return -1;
	}

	trailing_bytes = dg100_commands[id].trailing_bytes;
	copysize = n - trailing_bytes;

#ifdef DEBUG
	if (trailing_bytes) {
		printf("trailing bytes in answer: (%s)\n" ,
				hexdump(data + copysize, trailing_bytes));
	}
#endif

	/* check for buffer overflow */
	if (len < copysize) {
		fprintf(stderr, "ERROR: buffer too small, size=%d, need=%d", len, copysize);
		return -1;
	}

	memcpy(buf, data, copysize);
	return(copysize);
}

/* the number of bytes to be sent is determined by cmd,
 * count is the size of recvbuf */
ssize_t
dg100_request(uint8_t cmd, const void *sendbuf, void *recvbuf, size_t count)
{
	ssize_t sendsize;
	int n, i, frames, fill;
	uint8_t *buf;

	sendsize = dg100_commands[cmd].sendsize;
	dg100_send(cmd, sendbuf, sendsize);

	/* the number of frames the answer will comprise */
	frames = (cmd == dg100cmd_getfile) ? 2 : 1;
	buf = recvbuf;
	fill = 0;
	for (i = 0; i < frames; i++) {
		n = dg100_recv(cmd, buf + fill, count - fill);
		if (n < 0)
			return(-1);
		fill += n;
	}
	return(fill);
}

/* public functions */
ssize_t
dg100_getconfig(struct dg100config *conf)
{
	uint8_t answer[44];

	printf("### dg100_getconfig()\n");
	dg100_request(dg100cmd_getconfig, NULL, answer, sizeof(answer));
	dg100_decodeconfig(conf, answer);
	dg100_printconfig(conf, true);
	return(0);
}

ssize_t
dg100_setconfig(const struct dg100config *conf)
{
	uint8_t request[41], answer[4];

	printf("### dg100_setconfig()\n");
	dg100_printconfig(conf, false);
	dg100_encodeconfig(request, conf);
	dg100_request(dg100cmd_setconfig, request, answer, sizeof(answer));
	if (GET_BIGENDIAN_32(answer) != 1) {
		fprintf(stderr, "dg100_setconfig() FAILED\n");
		return(-1);
	}
	return(0);
}

/* mouse commands from http://www.qbik.ch/usb/devices/showdev.php?id=4051
 * FIXME: enabling the GPS mouse mode does not always work;
 * need to get a USB trace from the Globalsat software on Windows */
void
dg100_mouse(bool on)
{
	uint8_t request[1];

	request[0] = on ? 1 : 0;
	printf("### dg100_mouse(%02x)\n", request[0]);
	dg100_request(dg100cmd_gpsmouse, request, NULL, 0);
}

int
dg100_getfileheaders(uint16_t headers[], bool gpx)
{
	uint8_t request[2];
	uint8_t answer[FRAME_MAXLEN];
	uint32_t time, date, seqnum;
	time_t ti;
	uint16_t numheaders, nextheader;
	int i, h, headers_total;

	if (!gpx) { printf("### dg100_getfileheaders()\n"); }
	nextheader = 0;
	headers_total = 0;
	do {
		if (!gpx) { printf("nextheader=%d\n", nextheader); }

		/* request the next batch of headers */
		PUT_BIGENDIAN_16(request, nextheader);
		dg100_request(dg100cmd_getfileheader, request, answer, sizeof(answer));

		/* process the answer */
		numheaders = GET_BIGENDIAN_16(answer);
		nextheader = GET_BIGENDIAN_16(answer + 2);

		if (!gpx) { printf("numheaders=%d\n", numheaders); }
		for (i = 0; i < numheaders; i++) {
			h = 4 + i * 12;
			time   = GET_BIGENDIAN_32(answer + h);
			date   = GET_BIGENDIAN_32(answer + h + 4);
			seqnum = GET_BIGENDIAN_32(answer + h + 8);
			ti = bintime2utc(date, time);
			if (!gpx) { printf("%3d: %s\n", seqnum, my_strftime(ti, gpx)); }
			headers[headers_total++] = seqnum;
		}
	} while (numheaders != 0);
	
	return(headers_total);
}

void
dg100_getfile(uint16_t num, bool gpx)
{
	uint8_t request[2];
	uint8_t answer[2048];

	if (!gpx) { printf("### dg100_getfile(%d)\n", num); }
	PUT_BIGENDIAN_16(request, num);
	dg100_request(dg100cmd_getfile, request, answer, sizeof(answer));
	print_gpsfile(answer, gpx);
}


void
dg100_getallfiles(bool gpx)
{
	int i, filenum, numheaders;
	uint16_t headers[HEADERS_MAX];

	numheaders = dg100_getfileheaders(headers, gpx);
	if (!gpx) {
		printf("### dg100_getfiles(), numheaders=%d\n", numheaders); 
	}
	
	if (gpx) {
		printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
		printf("<gpx\n version=\"1.0\"\n creator=\"DG100.c by Mirko Parthey. GPX output by Kevin Jaako.\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xmlns=\"http://www.topografix.com/GPX/1/0\""); 
		printf(" xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd\">\n");
		printf("\t<trk>\n\t\t<name>GPS Data</name>\n<trkseg>\n");		
	}	
	
	
	for (i = 0; i < numheaders; i++) {
		filenum = headers[i];
		dg100_getfile(filenum, gpx);
	} 
	
	if (gpx) {
		printf("\n</trkseg>\n</trk>\n</gpx>\n");
	}
}

//KJ ADDED THESE
//FUNCTIONS to wrap getallfiles to switch between regular & GPX formatted data

void
dg100_getfiles()
{
	dg100_getallfiles(0);
}

void
dg100_printgpx()
{
	dg100_getallfiles(1);	
}


void
dg100_getid()
{
	uint8_t answer[8];

	printf("### dg100_getid\n");
	dg100_request(dg100cmd_getid, NULL, answer, sizeof(answer));
	printf("id=(%s)\n", hexdump(answer, 8));
}

int
dg100_setid(const uint8_t id[8])
{
	uint8_t answer[4];

	printf("### dg100_setid(%s)\n", hexdump(id, 8));
	dg100_request(dg100cmd_setid, id, answer, sizeof(answer));
	if (GET_BIGENDIAN_32(answer) != 1) {
		fprintf(stderr, "dg100_setid() FAILED\n");
		return(-1);
	}
	return(0);
}

int
dg100_erase()
{
	uint8_t request[2] = { 0xFF, 0xFF };
	uint8_t answer[4];

	printf("### dg100_erase()\n");
	dg100_request(dg100cmd_erase, request, answer, sizeof(answer));
	if (GET_BIGENDIAN_32(answer) != 1) {
		fprintf(stderr, "dg100_erase() FAILED\n");
		return(-1);
	}
	return(0);
}

int main() {
	struct dg100config conf;

	const struct dg100config desiredconf = {
		.infotype = 2,
		.spd_thresh_flag = 0,
		.spd_thresh = 0 * 100,
		.dist_thresh_flag = 0,
		.dist_thresh = 0 * 1,
		.loginterval = {
			[mode_a] = {
				.flag = li_time,
				.time = 60 * 1000
			},
			[mode_b] = {
				.flag = li_time,
				.time = 5 * 1000
			},
			[mode_c] = {
				.flag = li_time,
				.time = 1 * 1000
			},
		},
		.memfree = 0
	};

	const struct dg100config testconf = {
		.infotype = 2,
		.spd_thresh_flag = 1,
		.spd_thresh = 5 * 100,
		.dist_thresh_flag = 1,
		.dist_thresh = 5 * 1,
		.loginterval = {
			[mode_a] = {
				.flag = li_time,
				.time = 30 * 1000
			},
			[mode_b] = {
				.flag = li_time,
				.time = 3 * 1000
			},
			[mode_c] = {
				.flag = li_time,
				.time = 1 * 1000
			},
		},
		.memfree = 0
	};

	const uint8_t testid[8] = {
		0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38
	};

	//CHANGED PORT to match prolific's serial device name
	//NOTE: prolific gives a different device name depending on which USB port you use!
	
	port_open("/dev/cu.PL2303-3B1");
	//port_open("/dev/ttyUSB01");
	memset(&conf, 0, sizeof(conf));

	//dg100_getconfig(&conf);
	//dg100_setconfig(&testconf);
	//dg100_getconfig(&conf);
	//dg100_setconfig(&desiredconf);
	//dg100_getconfig(&conf);

	//dg100_mouse(false);

	//dg100_getid();
	//dg100_setid(testid);
	//dg100_getid();

	//dg100_getfiles();

	//KJ added this function to output GPX data...
	dg100_printgpx();

	//dg100_erase();
	//dg100_getconfig(&conf);
	port_close();

	return 0;
}
