//
//	Garmin protocol support
//
//	Paul Nash
//	paul@frcs.alt.za

//
//	$Header: /usr/home/pilot/gypsy-1.6/RCS/garmin.c,v 1.34 1999/08/31 06:03:23 paul Exp $
//
//	$Log: garmin.c,v $
//	Revision 1.34  1999/08/31 06:03:23  paul
//	Ready for release as Gypsy 1.6
//
//	Revision 1.33  1999/08/30 07:03:44  paul
//	added test to skip old-style tracklogs
//
//	Revision 1.32  1999/08/30 06:35:32  paul
//	Added Jaque Andre's patches
//
//	Revision 1.31  1999/06/13 17:32:06  paul
//	fixed a minor bug in the new display model :-)
//
//	Revision 1.30  1999/06/13 17:26:13  paul
//	Changed lat & long edit to use N/S E/W list (button) input, rather
//	than text.  This allows the ':' separator to use ' ' or '-'.
//
//	Revision 1.29  1999/06/12 17:30:52  paul
//	Removed "track to GPS" from menu; confirm clear track log
//
//	Revision 1.28  1999/06/12 14:38:39  paul
//	changed track log to allow cumulative logging.  Not tested yet
//
//	Revision 1.27  1999/05/12 08:48:05  paul
//	Ready for release as Gypsy 1.4.  Major changes include:
//	*  Rudimentary track/route buffer to transfer data between GPS
//	   and PC (a'la gps2p2pc)
//	*  Protocol fix for DLE escaping bug
//	*  Code changes from <andre@drfmc.ceng.cea.fr> to be able to
//	   compile a PalmOS2 version
//	*  Garmin protocol tries a bit harder to recover from comms
//	   errors (but is still *very* dumb)
//
//


#pragma pack(2)

#include "gps.h"
#include <Common.h>
#include <System/SysAll.h>
#include <System/SerialMgr.h>
#include <System/SysEvtMgr.h>
#include <UI/UIAll.h>
#include <UI/Progress.h>
//	This is only needed for the GNU C/C++ 0.5 cross-compiler
#include "callback.h"

#include "globals.h"
#define BUFLEN  256
#define TRK_NAME_LEN 20
#define TRK_SIZE 1024*15
#define TRK_DB_NAME "Gypsy tracks"
#define ROUTE_DB_NAME "Gypsy raw routes"

//
//	Private data structures
//

struct garmin_wpt {
   char ident[GARMIN_NAME_LEN];
   char lat[4];
   char lon[4];
   long unused;
   char cmnt[GARMIN_DESC_LEN];
   char smbl;
   char dspl;
};

typedef struct garmin_trk {
   char code;
   char len;
   char lat[4];
   char lon[4];
   char time[4];
   unsigned char flag;
} garmin_trk ;

typedef struct pilot_trk {
   char ident[TRK_NAME_LEN];
   long lat_min;
   long lat_max;
   long lon_min;
   long lon_max;
   long distance;	 /* not yet implemented */
   long size;
   int  nb_point;
   char flagged;
   char data[1]; 
} pilot_trk ;

typedef union longchar {
   long  l;
   char  c[4];
} longchar;

//
//	Internal function prototypes
//

static void AppendToDB (struct garmin_wpt *wpt);
static void Trim (char *txt);
static void ConvertToGarmin (struct waypoint *wpt, struct garmin_wpt *garmin);
static void CopyAndFill (char *dst, char *src, int len);
static Boolean garminProgressCallback (PrgCallbackData *data);
static void dumpPacket (char *txt, char *buf, int len, int err);
static DmOpenRef openFreshDB (ULong type);
DmOpenRef openAppendDB (ULong type);
static pilot_trk * init_trk(void);
static void add_trkpoint(pilot_trk * trk, garmin_trk * gtrk);
static void save_trk(pilot_trk * trk);
static long garmin2long(char * c);
static void long2garmin(long l, char * c);

//	and internal global variables

static char *rcs_id = "$Id: garmin.c,v 1.34 1999/08/31 06:03:23 paul Exp $";
extern DWord romVersion;


//
//	Retrieve the waypoint list from a GPS, and put all
//	waypoints into the current database.
//

int GarminGetAll (int mode, UInt num_wpts)
{
   int err;
   struct garmin_wpt *garmin;
   char buf[BUFLEN];
   int len;
   char last_ack = 0;
   char cmd = 0;
   DmOpenRef raw_db = NULL;
   VoidPtr rec;
   VoidHand new_rec;
   int i = 0, num_errs = 0;
   UInt rec_num = 0;
   char msg[64];
   ProgressPtr progress = NULL;
   pilot_trk * trk = NULL;

   if (mode != HOST_WAYPOINT) {
      if ((err = OpenSerialPort ())) {
	 return err;
      }
      switch (mode) {
	 case GPS_WAYPOINT:
	    cmd = 7;
	    break;
	 case GPS_TRACK:
	   if (! (trk = init_trk())) {
	      err = -1;
	   }
	   cmd = 6;
	   break;
	 case GPS_ROUTE:
	   if (! (raw_db = openFreshDB (DB_ROUTE))) {
	      err = -1;
	   }
	   cmd = 4;
	   break;
      }
      if (err) {
	 SerClose (serial_lib);
	 serial_port_open = false;
      }
      num_wpts = 0;
      // Garmin "send xxxx" command
      buf[0] = 10;
      buf[1] = 2;
      buf[2] = cmd;
      buf[3] = 0;
      GarminSendPacket (buf, 4);
   }
   else if ( romVersion >= 0x03000000) {
      StrCopy (msg, "Receiving ~    / ");
      StrIToA (msg + StrLen (msg), num_wpts);
      progress = PrgStartDialog ("Receive from Host", garminProgressCallback);
   }

   while (true) {
      len = BUFLEN;
      err = 0;
      if (GarminGetPacket (buf, &len, &err)) {
	 if (++num_errs > 3) {
	    if (romVersion >= 0x03000000 && progress) {
	       PrgStopDialog (progress, true);
	    }
	    if (mode != HOST_WAYPOINT) {
	       SerClose (serial_lib);
	       serial_port_open = false;
	    }
	    if (raw_db) {
	       DmCloseDatabase (raw_db);
	    }
	    if (trk) {
		MemPtrFree (trk);
		trk = NULL;
	    }
	    FrmCustomAlert (serialAlert, "Receive packet", " ", " ");
	    return(0);
	 }
	 SendAck (buf[1], false);
	 continue;
      }
      num_errs = 0;
      if (buf[1] != 0x06 && buf[1] != 0x15) {
	 if (!err) {
	    SendAck (buf[1], true);
	    last_ack = buf[1];
	 }
	 else {
	    SendAck (buf[1], false);
	 }
      }
      switch (buf[1]) {
	 case 0x1B:		// # of waypoints
	    num_wpts = buf[4] & 0x00FF;
	    num_wpts = num_wpts << 8;
	    num_wpts |= buf[3] & 0x00FF;
 	    if ( romVersion >= 0x03000000) {
 	        StrCopy (msg, "Receiving ~   / ");
 	        StrIToA (msg + StrLen (msg), num_wpts);
 	        progress = PrgStartDialog ("Receive from GPS",
						garminProgressCallback);
 	    }
	    break;
	 case 0x23:		// waypoint record
	    garmin = (struct garmin_wpt *) (buf + 3);
	    AppendToDB (garmin);
 	    if ( romVersion >= 0x03000000) {
 		PrgUpdateDialog (progress, 0, ++i, msg, true);
 	    }
	    break;
	 case 0x1D:		// route ID
	 case 0x1E:		// route waypoint
	    if (! raw_db) {
	       raw_db = openFreshDB (DB_ROUTE);
	    }
	    if (raw_db) {
	       rec_num = DmNumRecords (raw_db) + 1;
	       new_rec = DmNewRecord (raw_db, &rec_num, buf[2] + 2);
	       rec = MemHandleLock (new_rec);
	       DmWrite (rec, 0, buf + 1, buf[2] + 2);
	       MemHandleUnlock (new_rec);
	    }
	    if ( romVersion >= 0x03000000) {
		PrgUpdateDialog (progress, 0, ++i, msg, true);
	    }
	    break;
	 case 0x22:		// track record
	    if (! trk) {
	       trk = init_trk();
	    }
	    if (trk) {
	       add_trkpoint(trk, (garmin_trk *) (buf + 1));
	    }
	    if ( romVersion >= 0x03000000) {
		PrgUpdateDialog (progress, 0, ++i, msg, true);
	    }
	    break;
	 case 0x0C:		// EOF
	    if (mode != HOST_WAYPOINT) {
	       SerClose (serial_lib);
	       serial_port_open = false;
	    }
	    if ( romVersion >= 0x03000000) {
		PrgStopDialog (progress, true);
	    }
	    if (raw_db) {
	       DmCloseDatabase (raw_db);
	    }
	    if (trk) {
	       save_trk(trk);
               MemPtrFree (trk);
	       trk = NULL;
	    }
	    return(0);
	 case 0x06:		// ACK
	    break;
	 case 0x15:		// NAK
	    if (last_ack) {
	       SendAck (last_ack, true);
	    }
	    else {
	       buf[0] = 10;
	       buf[1] = 2;
	       buf[2] = cmd;
	       buf[3] = 0;
	       GarminSendPacket (buf, 4);
	    }
	    break;
	 default:
	    break;
      }
   }
   if ( romVersion >= 0x03000000) {
      PrgStopDialog (progress, true);
   }
   if (mode != HOST_WAYPOINT) {
      SerClose (serial_lib);
      serial_port_open = false;
   }
   if (raw_db) {
      DmCloseDatabase (raw_db);
   }
   if (trk) {
      MemPtrFree (trk);
      trk = NULL;
   }
   return(0);
}


//
//	Send one or more waypoints to the GPS.  This
//	requires us to count the number of qualifying
//	waypoints (flagged, changed or all), then sift 
//	through the database and send them.
//

int GarminSend (Word command)
{
   int num_wpts;
   struct waypoint *wpt;
   struct garmin_wpt *garmin;
   VoidHand rec;
   int rec_num, max, retries;
   char buf[BUFLEN];
   char ack[BUFLEN];
   int len, err, i = 0;
   union {
      int i;
      char c[2];
   } swap;
   char msg[64];
   ProgressPtr progress = NULL;

   int serial_port_already_open;

   max = DmNumRecords (waypoint_db);
   if (command == menuSendAll) {
      num_wpts = max;
   }
   else {			// find out how many records to send
      num_wpts = 0;
      for (rec_num = 0; rec_num < max; ++rec_num) {
	 rec = DmQueryRecord (waypoint_db, rec_num);
	 if (! rec) {
	    continue;
	 }
	 wpt = MemHandleLock (rec);
	 if (command == menuSendChanged && wpt->changed) {
	    ++num_wpts;
	 }
	 if (command == menuSendFlagged && wpt->flagged) {
	    ++num_wpts;
	 }
	 MemHandleUnlock (rec);
      }
   }

   serial_port_already_open = serial_port_open;
   if (! serial_port_open) {
      if ((err = OpenSerialPort ())) {
	 return err;
      }
   }
   buf[0] = 0x1B;
   buf[1] = 0x02;
   swap.i = num_wpts;
   buf[2] = swap.c[1];
   buf[3] = swap.c[0];
   SerReceiveFlush (serial_lib, 0);
    if ( romVersion >= 0x03000000) {
       StrCopy (msg, "Sending ~   / ");
       StrIToA (msg + StrLen (msg), num_wpts);
       if (serial_port_already_open) {
          progress = PrgStartDialog ("Send to Host", garminProgressCallback);
       } else {
          progress = PrgStartDialog ("Send to GPS", garminProgressCallback);
       }
   }
   GarminSendPacket (buf, 4);		// await 'n' records
   len = BUFLEN;
   GarminGetPacket (ack, &len, &err);

   rec_num = 0;
   while ( rec_num < max) {
      rec = DmQueryRecord (waypoint_db, rec_num++);
      if (! rec) {
	 break;
      }
      wpt = MemHandleLock (rec);
      if (command == menuSendChanged && !wpt->changed) {
	 MemHandleUnlock (rec);
	 continue;
      }
      if (command == menuSendFlagged && !wpt->flagged) {
	 MemHandleUnlock (rec);
	 continue;
      }
      buf[0] = 0x23;
      buf[1] = (char) sizeof (struct garmin_wpt);
      garmin = (struct garmin_wpt *) (buf + 2);
      ConvertToGarmin (wpt, garmin);
      MemHandleUnlock (rec);
      SerReceiveFlush (serial_lib, 0);
      if ( romVersion >= 0x03000000) {
         PrgUpdateDialog (progress, 0, ++i, msg, true);
      }
      for (retries = 0; retries < 5; ++retries) {
	 GarminSendPacket (buf, sizeof (struct garmin_wpt) + 2);
	 len = BUFLEN;
	 GarminGetPacket (ack, &len, &err);
	 if (ack[1] == 0x06)
	    break;
      }
//      if (ack[1] != 0x06) {
//	 FrmCustomAlert (serialAlert, "Transmit packet", " ", " ");
//      }
   }
   buf[0] = 0x0C;
   buf[1] = 0x02;
   buf[2] = 0x07;
   buf[3] = 0x00;
   SerReceiveFlush (serial_lib, 0);
   GarminSendPacket (buf, 4);
   len = BUFLEN;
   GarminGetPacket (ack, &len, &err);
   if ( romVersion >= 0x03000000) {
      PrgStopDialog (progress, true);
   }
   if (! serial_port_already_open) {
      SerClose (serial_lib);
      serial_port_open = false;
   }
   return(0);
}


//
//	Convert from internal format to Garmin
//

void ConvertToGarmin (struct waypoint *wpt, struct garmin_wpt *garmin)
{
   char pos[4];

   MemSet (garmin, sizeof (struct garmin_wpt), 0);
   CopyAndFill (garmin->ident, wpt->name, sizeof (garmin->ident));
   CopyAndFill (garmin->cmnt, wpt->descr, sizeof (garmin->cmnt));
   MemMove (pos, &wpt->latitude, sizeof (long));
   garmin->lat[3] = pos[0];
   garmin->lat[2] = pos[1];
   garmin->lat[1] = pos[2];
   garmin->lat[0] = pos[3];
   MemMove (pos, &wpt->longitude, sizeof (long));
   garmin->lon[3] = pos[0];
   garmin->lon[2] = pos[1];
   garmin->lon[1] = pos[2];
   garmin->lon[0] = pos[3];
   garmin->smbl = (char) wpt->symbol;
   garmin->dspl = (char) wpt->display;
//   wpt->changed = false;
}


//
//	The Garmin units have fixed-length fields, padded
//	with spaces.
//

void CopyAndFill (char *dst, char *src, int len)
{
   int i;

   i = StrLen (src);
   if (i > len)
      i = len;
   MemMove (dst, src, i);
   if (i < len) {
      while (i < len) {
	 dst[i++] = ' ';
      }
   }
} 


//
//	Append a record to the current database
//

void AppendToDB (struct garmin_wpt *garmin)
{
   VoidHand rec_handle;
   char pos[4];
   struct waypoint *new_wpt, *wpt;

   current_record = DmNumRecords (waypoint_db) + 1;
   rec_handle = DmNewRecord (waypoint_db, &current_record,
			     sizeof (struct waypoint));
   num_records = DmNumRecords (waypoint_db);
   table_top = num_records - TABLE_ROWS;
   wpt = MemPtrNew (sizeof (struct waypoint));
   MemSet (wpt, sizeof (struct waypoint), 0);
   new_wpt = MemHandleLock (rec_handle);
   StrNCopy (wpt->name, garmin->ident, sizeof (garmin->ident));
   *(wpt->name + sizeof (wpt->name) - 1) = '\0';
   Trim (wpt->name);
   StrNCopy (wpt->descr, garmin->cmnt, sizeof (garmin->cmnt));
   *(wpt->descr + sizeof (wpt->descr) - 1) = '\0';
   Trim (wpt->descr);
   pos[0] = garmin->lat[3];
   pos[1] = garmin->lat[2];
   pos[2] = garmin->lat[1];
   pos[3] = garmin->lat[0];
   MemMove (&wpt->latitude, pos, sizeof (long));
   pos[0] = garmin->lon[3];
   pos[1] = garmin->lon[2];
   pos[2] = garmin->lon[1];
   pos[3] = garmin->lon[0];
   MemMove (&wpt->longitude, pos, sizeof (long));
   wpt->symbol = garmin->smbl;
   wpt->display = garmin->dspl;
   wpt->flagged = wpt->changed = false;
   DmWrite (new_wpt, 0, wpt, sizeof (struct waypoint));
   MemPtrFree (wpt);
   MemHandleUnlock (rec_handle);
   DmReleaseRecord (waypoint_db, current_record, true);
}


//
//	Get the current GPS type
//

//int GarminGetModel ()
//{
//}


//
//	Send a packet, adding the relevant headers, stuffing DLEs and
//	the like ...
//

void GarminSendPacket (char *buf, int len)
{
   int i, j;
   char checksum = 0;
   static char snd_buf[512];
   Word err;

   snd_buf[0] = DLE;
   for (i = j = 0; i < len; ++i) {
      snd_buf[++j] = buf[i];
      checksum -= buf[i];
      if (snd_buf[j] == DLE) {
	 snd_buf[++j] = DLE;
      }
   }
   snd_buf[++j] = checksum;
   if (snd_buf[j] == DLE) {
      snd_buf[++j] = DLE;
   }
   snd_buf[++j] = DLE;
   snd_buf[++j] = ETX;
   ++j;
   SerSend (serial_lib, snd_buf, j, &err);
}


//
//	Open the serial port with some suitable settings
//

int OpenSerialPort ()
{
   Word err = 0;
   SerSettingsType settings;

   if (serial_lib == -1) {
      FrmAlert (gpsSerialAlert);
      return -1;
   }
   err = SerOpen (serial_lib, 0, 9600L);
   if (err == serErrAlreadyOpen) {
      SerClose (serial_lib);
   }
   if (err) {
      FrmAlert (gpsSerialAlert);
      return -1;
   }
   serial_port_open = true;
   settings.baudRate = 9600L;
   settings.flags = serSettingsFlagStopBits1 | serSettingsFlagBitsPerChar8;
   settings.ctsTimeout = 0; // 60*sysTicksPerSecond;
   err = SerSetSettings (serial_lib, &settings);
   return err;
}


//
//	Trim trailing blanks from a waypoint ...
//

void Trim (char *txt)
{
   int j;

   for (j = StrLen (txt) - 1; j >= 0; --j) {
      if (txt[j] == ' ' || txt[j] == '\0') {
	 txt[j] = '\0';
      }
      else {
	 break;
      }
   }
}


//
//	Send an 'ACK' message
//

void SendAck (char type, int ok)
{
   char ack[10];

   if (ok)
      ack[0] = 6;
   else
      ack[0] = 0x15;
   ack[1] = 2;
   ack[2] = type;
   ack[3] = 0;
   GarminSendPacket (ack, 4);
}


//
//	Get a single, well-formed Garmin packet
//

int GarminGetPacket (char *buf, int *len, int *proto_err)
{
   Word err;
   char last = '\0';
   int i, l;
   char checksum = 0;

   *proto_err = i  = 0;
   l = *len - 2;
   MemSet (buf, *len, 0);
   while (true) {
      EvtResetAutoOffTimer();
      SerSendWait (serial_lib, sysTicksPerSecond/2);
      err = SerReceiveWait (serial_lib, 1, sysTicksPerSecond);
      if (err == serErrTimeOut) {
	 *len = i;
	 return err;
      }
      if (!err) {
	 SerReceive (serial_lib, buf+i, 1, 0, &err);
      }
      if (err) {
	 SerClearErr (serial_lib);
	 continue;
      }
      if (i == 0 && buf[0] != DLE) {
	 continue;
      }
      if (i == 1 && buf[1] == DLE) {
	 i = 0;
	 continue;
      }
      if (buf[i] == DLE && last == DLE) {
	 last = '\0';
	 continue;
      }
      if (i > 0 && i < l-1) {
	 checksum += buf[i];
      }
      if (i == 2) {
	 if (buf[i] + 5 <= l) {
	    l = buf[i] + 5;
	 }
      }
      if (i == l) {		// && buf[i] == ETX || last == DLE) {
	 *len = i - 5;
	 buf[i+1] = checksum;
	 if (buf[i] == ETX && last == DLE && checksum == 0)
	    *proto_err = 0;
	 else
	    *proto_err = 1;
	 return 0;
      }
      last = buf[i];
      if (++i >= *len) {
	 *len = i;
	 *proto_err = 1;
	 return 0;
      }
   }
   return 0;
}


//
//	Display latitude & longitude in a human-readable and
//	-editable format
//

char *GarminLongToPos (long pos)
{
   static char txt[20];
   int c;
   float dms;
   long sec100, sec, min, deg;

   if (pos < 0) {
      pos *= -1L;
   }
   dms = (float)pos/33.1402;
   sec100 = (long) dms % 100;
   dms /= 100;
   sec = (long) dms % 60;
   dms /= 60;
   min = (long) dms % 60;
   dms /= 60;
   deg = (long) dms;
   StrIToA (txt, deg);
   StrCat (txt, ":");
   c = StrLen (txt);
   StrIToA (txt+c, min);
   StrCat (txt, ":");
   c = StrLen (txt);
   StrIToA (txt+c, sec);
   StrCat (txt, ".");
   c = StrLen (txt);
   StrIToA (txt+c, sec100);
   return txt;
}


//
//	... and convert back again!
//
//	This is actually quite sexy.  It will take input in the form
//	dd:mm:ss.ss NSWE or dd:mm.mmm NSWE or dd.ddddd NSWE or even
//	directly in semicircles.  The *only* proviso is that an entry
//	in degrees must have at least on ':' or '.' in it, as this
//	is how I decide whether the value is in DMS or SC.
//

long GarminPosToLong (char *pos)
{
   long factor = 11930465;
   long result;
   char *b, *c;
   char d;

   //	decide whether this is in DMS or semicircles 
   if (!StrChr (pos, '.') && !StrChr (pos, ':')) {
      return StrAToI (pos);
   }

   //   allow ',' or '!' or 'I' or ' ' or '-' instead of ':'
   for (b = pos; *b != '\0'; ++b) {
      if (*b == ',' || *b == '!' || *b == 'i' || *b == 'I'
		    || *b == ' ' || *b == '-') {
	 *b = ':';
      }
   }

   //	skip through the string, converting
   b = pos;
   result = 0;
   while (b != NULL && *b != '\0') {
      d = '\0';
      c = StrChr (b, ':');
      if (c == NULL)
	 c = StrChr (b, '.');
      if (c != NULL) {
	 d = *c;
	 *c = '\0';
      }
      result += (StrAToI (b) * factor);
      if (c == NULL || d == '\0') {
	 break;
      }
      b = c + 1;
      if (d == ':') {		 // ':' means D->M->S
	 factor /= 60;
	 continue;
      }
      if (d == '.') {		 // '.' means fractions of whatever is current
         while (*b >= '0' && *b <= '9') {
	    factor /= 10;
	    result += ((*b - '0') * factor);
	    ++b;
	 }
      }
      break;			// else end of numbers
   }
   return result;
}


//
//	Callback routine for the progress dialogue box
//

Boolean garminProgressCallback (PrgCallbackData *data)
{
   char *c;

   CALLBACK_PROLOGUE;
   if (data->textLen > 20) {
      StrCopy (data->textP, data->message);
      c = StrChr (data->textP, '~');
   }
   else {
      c = data->textP;
   }
   StrIToA (c, data->stage);
   *(data->textP + StrLen (data->textP)) = ' ';
   data->textChanged = true;
   CALLBACK_EPILOGUE;
   return true;
}

//
//	Get the current location from a GPS
//

int GarminGetPos (long *lat, long *lon)
{
   int err;
   char buf[BUFLEN];
   int len;
   int i = 0;
   double radians;
   char swap[8];

   *lat = *lon = 0L;
   if ((err = OpenSerialPort ())) {
      return err;
   }
   // Garmin "send position" command
   buf[0] = 10;
   buf[1] = 2;
   buf[2] = 2;
   buf[3] = 0;
   GarminSendPacket (buf, 4);
   while (true) {
      len = BUFLEN;
      if (GarminGetPacket (buf, &len, &err)) {
         SerClose (serial_lib);
         serial_port_open = false;
	 FrmCustomAlert (serialAlert, "Receive packet", " ", " ");
	 return 1;
      }
      if (buf[1] != 0x06 && buf[1] != 0x15) {
	 SendAck (buf[1], true);
      }
      if (buf[1] == 0x11) {
//	 dumpPacket (txt, buf, len, err);
	 for (i = 0; i < 8; ++i) {
	    swap[i] = buf[10 - i];
	    MemMove (&radians, swap, sizeof (radians));
	    *lat = (long) (radians * 683565275.576);
	 }
	 for (i = 0; i < 8; ++i) {
	    swap[i] = buf[18 - i];
	    MemMove (&radians, swap, sizeof (radians));
	    *lon = (long) (radians * 683565275.576);
	 }
         SerClose (serial_lib);
         serial_port_open = false;
//	 FrmCustomAlert (serialAlert, txt, " ", " ");
//	 StrIToA (txt, *lon);
//	 StrIToA (buf, *lat);
//	 StrCat (txt, ":");
//	 StrCat (txt, buf);
//	 FrmCustomAlert (serialAlert, txt, " ", " ");
	 return 0;
      }
   }
}


//
//	dump packet contents into a buffer
//

void dumpPacket (char *txt, char *buf, int len, int err)
{
   char tmp[16];
   int i;

   StrIToA (txt, len);
   StrCat (txt, ":");
   StrIToA (tmp, err);
   StrCat (txt, tmp);
   StrCat (txt, " 0x");
   for (i = 0; i < 30; ++i) {
      if (i > 0) {
	 StrCat (txt, " ");
      }
      StrIToH (tmp, buf[i]);
      StrCat (txt, tmp + 6);
   }
}


//
//	send raw buffered data (either route or track)
//

int GarminSendRaw (Word command)
{
   int num_wpts;
   VoidHand rec;
   DmOpenRef raw_db = NULL;
   char *raw;
   int rec_num, retries;
   char buf[BUFLEN];
   char ack[BUFLEN];
   int len, err, i = 0;
   union {
      int i;
      char c[2];
   } swap;
   char cmd = 0;
   char msg[64];
   ProgressPtr progress = NULL;
   int serial_port_already_open;

   serial_port_already_open = serial_port_open;
   switch (command) {
      case menuXferTrackOut:
	 raw_db = DmOpenDatabaseByTypeCreator (DB_TRACK, DB_APPID, dmModeReadWrite);
	 cmd = 6;
	 break;
      case menuXferRouteOut:
	 raw_db = DmOpenDatabaseByTypeCreator (DB_ROUTE, DB_APPID, dmModeReadWrite);
	 cmd = 4;
	 break;
   }
   if (! raw_db) {
      return 0;
   }
   num_wpts = DmNumRecords (raw_db);
   if (! serial_port_open) {
      if ((err = OpenSerialPort ())) {
	 return err;
      }
   }
   buf[0] = 0x1B;
   buf[1] = 0x02;
   swap.i = num_wpts;
   buf[2] = swap.c[1];
   buf[3] = swap.c[0];
   SerReceiveFlush (serial_lib, 0);
    if ( romVersion >= 0x03000000) {
       StrCopy (msg, "Sending ~   / ");
       StrIToA (msg + StrLen (msg), num_wpts);
       if (serial_port_already_open) {
          progress = PrgStartDialog ("Send to Host", garminProgressCallback);
       }
       else {
          progress = PrgStartDialog ("Send to GPS", garminProgressCallback);
       }
   }
   GarminSendPacket (buf, 4);		// await 'n' records
   len = BUFLEN;
   GarminGetPacket (ack, &len, &err);

   rec_num = 0;
   while ( rec_num < num_wpts) {
      rec = DmQueryRecord (raw_db, rec_num++);
      if (! rec) {
	 break;
      }
      raw = MemHandleLock (rec);
      MemMove (buf, raw, raw[1] + 2);
      MemHandleUnlock (rec);
      SerReceiveFlush (serial_lib, 0);
      if ( romVersion >= 0x03000000) {
         PrgUpdateDialog (progress, 0, ++i, msg, true);
      }
      for (retries = 0; retries < 5; ++retries) {
	 GarminSendPacket (buf, buf[1] + 2);
	 len = BUFLEN;
	 GarminGetPacket (ack, &len, &err);
	 if (ack[1] == 0x06)
	    break;
      }
//      if (ack[1] != 0x06) {
//	 FrmCustomAlert (serialAlert, "Transmit packet", " ", " ");
//      }
   }
   buf[0] = 0x0C;
   buf[1] = 0x02;
   buf[2] = cmd;
   buf[3] = 0x00;
   SerReceiveFlush (serial_lib, 0);
   GarminSendPacket (buf, 4);
   len = BUFLEN;
   GarminGetPacket (ack, &len, &err);
   if ( romVersion >= 0x03000000) {
      PrgStopDialog (progress, true);
   }
   if (! serial_port_already_open) {
      SerClose (serial_lib);
      serial_port_open = false;
   }
   if (raw_db) {
      DmCloseDatabase (raw_db);
   }
   return(0);
}

//
//	send track data 
//

int GarminSendTrack (void)
{
   static long lat, lon, t;
   short dlat, dlon;
   int num_wpts, num_recs, flag;
   VoidHand rec;
   DmOpenRef trk_db = NULL;
   unsigned char *ptr;
   int rec_num, retries;
   char buf[BUFLEN];
   char ack[BUFLEN];
   int len, err, i = 0, j;
   union {
      int i;
      char c[2];
   } swap;
   char cmd = 0;
   char msg[64];
   ProgressPtr progress = NULL;
   int serial_port_already_open;
   pilot_trk * trk;
   garmin_trk gtrk;

   serial_port_already_open = serial_port_open;
	 trk_db = DmOpenDatabaseByTypeCreator (DB_TRACK, DB_APPID, dmModeReadWrite);
	 cmd = 6;
   if (! trk_db) {
      return 0;
   }
   num_recs = DmNumRecords (trk_db);
   num_wpts = 0;
   for (rec_num = 0; rec_num < num_recs; ++rec_num) {
      rec = DmQueryRecord (trk_db, rec_num);
      if (! rec) {
	 DmCloseDatabase (trk_db);
	 return(0);
      }
      if (MemHandleSize (rec) < sizeof (struct pilot_trk) + 12) { 
	 continue; 		//  old style track record
      }
      trk = MemHandleLock (rec);
      num_wpts += trk -> nb_point;
      MemHandleUnlock (rec);
   }
   if (! serial_port_open) {
      if ((err = OpenSerialPort ())) {
	 return err;
      }
   }
   buf[0] = 0x1B;
   buf[1] = 0x02;
   swap.i = num_wpts;
   buf[2] = swap.c[1];
   buf[3] = swap.c[0];
   SerReceiveFlush (serial_lib, 0);
    if ( romVersion >= 0x03000000) {
       StrCopy (msg, "Sending ~   / ");
       StrIToA (msg + StrLen (msg), num_wpts);
       if (serial_port_already_open) {
          progress = PrgStartDialog ("Send to Host", garminProgressCallback);
       }
       else {
          progress = PrgStartDialog ("Send to GPS", garminProgressCallback);
       }
   }
   GarminSendPacket (buf, 4);		// await 'n' records
   len = BUFLEN;
   GarminGetPacket (ack, &len, &err);

   gtrk.code = 0x22;
   gtrk.len = 13;
   rec_num = 0;
   while ( rec_num < num_recs) {
      rec = DmQueryRecord (trk_db, rec_num++);
      if (! rec) {
	 break;
      }
      if (MemHandleSize (rec) < sizeof (struct pilot_trk) + 12) { 
	 continue; 		//  old style track record
      }
      trk = MemHandleLock (rec);
      ptr = trk -> data;
      for(j = 0; j < trk -> nb_point; j++) {
         flag = *ptr++;
	 if (flag > 253) {
	    gtrk.flag = flag - 254;
            MemMove(&lat, ptr, 4);
            MemMove(&lon, ptr+4, 4);
            MemMove(&t, ptr+8, 4);
	    ptr += 12;
	 } else {
	    gtrk.flag = 0;
	    if ( flag > 127 ) {
	       t += flag - 128;
	       MemMove(&dlat, ptr, 2);
	       MemMove(&dlon, ptr + 2, 2);
	       ptr +=4;
	    } else {
	       t += flag;
	       dlat = *((char *) ptr++);
	       dlon = *((char *) ptr++);
	    }
	    lat += ((long) dlat << 6);
	    lon += ((long) dlon << 6);
	 }
	 long2garmin(lat, gtrk.lat);
	 long2garmin(lon, gtrk.lon);
	 long2garmin(t, gtrk.time);
	 
	 SerReceiveFlush (serial_lib, 0);
         if ( romVersion >= 0x03000000) {
            PrgUpdateDialog (progress, 0, ++i, msg, true);
         }
	 MemMove(buf,(char *) &gtrk, 15);
	 for (retries = 0; retries < 5; ++retries) {
	    GarminSendPacket (buf, 15);
	    len = BUFLEN;
	    GarminGetPacket (ack, &len, &err);
	    if (ack[1] == 0x06)
	       break;
         }
//       if (ack[1] != 0x06) {
//	    FrmCustomAlert (serialAlert, "Transmit packet", " ", " ");
//       }
	 
      }
      MemHandleUnlock (rec);
   }
   buf[0] = 0x0C;
   buf[1] = 0x02;
   buf[2] = cmd;
   buf[3] = 0x00;
   SerReceiveFlush (serial_lib, 0);
   GarminSendPacket (buf, 4);
   len = BUFLEN;
   GarminGetPacket (ack, &len, &err);
   if ( romVersion >= 0x03000000) {
      PrgStopDialog (progress, true);
   }
   if (! serial_port_already_open) {
      SerClose (serial_lib);
      serial_port_open = false;
   }
   if (trk_db) {
      DmCloseDatabase (trk_db);
   }
   return(0);
}


//
//	Open a fresh raw database of whatever type is requested
//

DmOpenRef openFreshDB (ULong type) 
{
   DmSearchStateType state;
   UInt card;
   LocalID dbID;
   char *name = TRK_DB_NAME;

   while (! DmGetNextDatabaseByTypeCreator (true, &state, type, DB_APPID,
					    false, &card, &dbID)) {
      if (DmDeleteDatabase (card, dbID)) {
	 break;
      }
   }
   if (type == DB_ROUTE) {
      name = ROUTE_DB_NAME;
   }
   DmCreateDatabase (0, name, DB_APPID, type, false);
   return DmOpenDatabaseByTypeCreator (type, DB_APPID, dmModeReadWrite);
}


//
//	Open a raw database of whatever type is requested
//

DmOpenRef openAppendDB (ULong type) 
{
   DmSearchStateType state;
   UInt card;
   LocalID dbID;
   char *name = TRK_DB_NAME;

   if (type == DB_ROUTE) {
      name = ROUTE_DB_NAME;
   }
   if (DmGetNextDatabaseByTypeCreator (true, &state, type, DB_APPID,
					    false, &card, &dbID)) {
      DmCreateDatabase (0, name, DB_APPID, type, false);
   }
   return DmOpenDatabaseByTypeCreator (type, DB_APPID, dmModeReadWrite);
}


//
//	Trash any track logs
//

void GarminClearTrackLog (void)
{
   DmSearchStateType state;
   UInt card;
   LocalID dbID;

   while (! DmGetNextDatabaseByTypeCreator (true, &state, DB_TRACK, DB_APPID,
					    false, &card, &dbID)) {
      if (DmDeleteDatabase (card, dbID)) {
	 break;
      }
   }
}

//
//	Initialize a new track ptr
//

static pilot_trk * init_trk(void) {
   pilot_trk * trk; 

   if( (trk = MemPtrNew (sizeof (pilot_trk) + TRK_SIZE)) ) {
      trk -> nb_point = 0;
      trk -> size = 0;
      trk -> distance = 0;
   }
   return(trk);
}

//
//	add a point to the current track
//

static void add_trkpoint(pilot_trk * trk, garmin_trk * gtrk) {
   static long lat0, lon0, t0;
   unsigned char * ptr;
   long lat, lon, t, flag, dlat, dlon, dt;
   short s;

   if(trk -> size + 13 > TRK_SIZE) {
      return;
   } 
   lat = garmin2long(gtrk -> lat);
   lon = garmin2long(gtrk -> lon);
   t = garmin2long(gtrk -> time);
   flag = gtrk -> flag;
   
   if ( trk -> nb_point == 0 ) {
      trk -> lat_min = lat;
      trk -> lat_max = lat;
      trk -> lon_min = lon;
      trk -> lon_max = lon;
      flag = 1;
   } else { 
      if (lat < trk -> lat_min ) trk -> lat_min = lat;
      if (lat > trk -> lat_max ) trk -> lat_max = lat;
      if (lon < trk -> lon_min ) trk -> lon_min = lon;
      if (lon > trk -> lon_max ) trk -> lon_max = lon;
   }
   ptr = trk -> data + trk -> size;
   if(flag) {
      *ptr++ = 255;
      MemMove(ptr, &lat, 4);
      MemMove(ptr+4, &lon, 4);
      MemMove(ptr+8, &t, 4);
      trk -> size += 13;
   } else {
      dlat=(lat - lat0)>>6;
      dlon=(lon - lon0)>>6;
      dt=t - t0;
      if (abs(dlat) < 127 && abs(dlon) < 127 && dt < 126) {
         *ptr++ = dt;
	 *ptr++ = dlat;
	 *ptr++ = dlon;
         trk -> size += 3;
      } else if (abs(dlat) < 32767 && abs(dlon) < 32767 && dt < 126 ) {
         *ptr++ = dt + 128;
	 s = dlat;
         MemMove(ptr, &s, 2);
	 s = dlon;
         MemMove(ptr+2, &s, 2);
         trk -> size += 5;
      } else {
         *ptr++ = 254;
         MemMove(ptr, &lat, 4);
         MemMove(ptr+4, &lon, 4);
         MemMove(ptr+8, &t, 4);
         trk -> size += 13;
      }    
   }
   lat0 = lat;
   lon0 = lon;
   t0 = t;
   trk -> nb_point += 1;
}


//
//	save the current track in tracks database 
//

static void save_trk(pilot_trk * trk) {
   DmOpenRef trk_db;
   long record_size;
   VoidPtr rec;
   VoidHand new_rec;
   UInt rec_num;

   record_size = trk -> size + sizeof (pilot_trk);
   if ( (trk_db = openAppendDB (DB_TRACK)) ) {
      rec_num = DmNumRecords (trk_db) + 1;
      new_rec = DmNewRecord (trk_db, &rec_num, record_size);
      rec = MemHandleLock (new_rec);
      DmWrite (rec, 0, trk, record_size);
      MemHandleUnlock (new_rec);
      DmCloseDatabase (trk_db);
   }
}
 

//
// garmin2long
//
static long garmin2long(char * c) {
   longchar lc;

   lc.c[0] = c[3];
   lc.c[1] = c[2];
   lc.c[2] = c[1];
   lc.c[3] = c[0];
   return(lc.l);
}

//
// long2garmin
//
static void long2garmin(long l, char * c) {
   longchar lc;   

   lc.l = l;
   c[0] = lc.c[3];
   c[1] = lc.c[2];
   c[2] = lc.c[1];
   c[3] = lc.c[0];
}

