/*
    Communicate Thales/Magellan serial protocol.

    Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007,
      2008, 2010  Robert Lipe, robertlipe@usa.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 <ctype.h>
#include <time.h>

#include "defs.h"
#include "magellan.h"
#include "gbser.h"
#include "explorist_ini.h"

#if HAVE_GLOB
#include <glob.h>
#endif

static int bitrate = 4800;
static int wptcmtcnt;
static int wptcmtcnt_max;
static int explorist;
static int broken_sportrak;
#define MYNAME "MAGPROTO"
#define MAXCMTCT 200

#define debug_serial  (global_opts.debug_level > 1)

static char* termread(char* ibuf, int size);
static void termwrite(char* obuf, int size);
static void mag_readmsg(gpsdata_type objective);
static void mag_handon(void);
static void mag_handoff(void);
static short_handle mkshort_handle = NULL;
static char* deficon = NULL;
static char* bs = NULL;
static char* cmts = NULL;
static char* noack = NULL;
static char* nukewpt = NULL;
static int route_out_count;
static int waypoint_read_count;
static int wpt_len = 8;
static const char* curfname;
static int extension_hint;
// For Explorist GC/510/610/710 familes, bludgeon in GPX support.
// (This has nothing to do with the Explorist 100...600 products.)
static ff_vecs_t* gpx_vec;
static mag_info* explorist_info;
static char** os_gpx_files(const char* dirname);

/*
 * Magellan's firmware is *horribly* slow to send the next packet after
 * we turn around an ack while we are reading from the device.  It's
 * quite spiffy when we're writing to the device.   Since we're *way*
 * less likely to lose data while reading from it than it is to lose data
 * when we write to it, we turn off the acks when we are predominatly
 *  reading.
 */
static int suppress_ack;

typedef enum {
  mrs_handoff = 0,
  mrs_handon,
  mrs_awaiting_ack
} mag_rxstate;

/*
 *   An individual element of a route.
 */
typedef struct mag_rte_elem {
  queue Q;	 		/* My link pointers */
  char* wpt_name;
  char* wpt_icon;
} mag_rte_elem;

/*
 *  A header of a route.  Related elements of a route belong to this.
 */
typedef struct mag_rte_head_ {
  queue Q;			/* Queue head for child rte_elems */
  char* rte_name;
  int nelems;
} mag_rte_head;

static queue rte_wpt_tmp; /* temporary PGMNWPL msgs for routes */

static gbfile* magfile_h;
static mag_rxstate magrxstate;
static int mag_error;
static unsigned int last_rx_csum;
static int found_done;
static int got_version;
static int is_file = 0;
static route_head* trk_head;
static int ignore_unable;

static waypoint* mag_wptparse(char*);
typedef char* (cleanse_fn)(char*);
static cleanse_fn* mag_cleanse;
static const char** os_get_magellan_mountpoints();

static icon_mapping_t gps315_icon_table[] = {
  { "a", "filled circle" },
  { "b", "box" },
  { "c", "red buoy" },
  { "d", "green buoy" },
  { "e", "buoy" },
  { "f", "rocks" },
  { "g", "red daymark" },
  { "h", "green daymark" },
  { "i", "bell" },
  { "j", "danger" },
  { "k", "diver down" },
  { "l", "fish" },
  { "m", "house" },
  { "n", "mark" },
  { "o", "car" },
  { "p", "tent" },
  { "q", "boat" },
  { "r", "food" },
  { "s", "fuel" },
  { "t", "tree" },
  { NULL, NULL }
};

static icon_mapping_t map330_icon_table[] = {
  { "a", "crossed square" },
  { "b", "box" },
  { "c", "house" },
  { "d", "aerial" },
  { "e", "airport" },
  { "f", "amusement park" },
  { "g", "ATM" },
  { "g", "Bank" },
  { "h", "auto repair" },
  { "i", "boating" },
  { "j", "camping" },
  { "k", "exit ramp" },
  { "l", "first aid" },
  { "m", "nav aid" },
  { "n", "buoy" },
  { "o", "fuel" },
  { "p", "garden" },
  { "q", "golf" },
  { "r", "hotel" },
  { "s", "hunting/fishing" },
  { "t", "large city" },
  { "u", "lighthouse" },
  { "v", "major city" },
  { "w", "marina" },
  { "x", "medium city" },
  { "y", "museum" },
  { "z", "obstruction" },
  { "aa", "park" },
  { "ab", "resort" },
  { "ac", "restaurant" },
  { "ad", "rock" },
  { "ae", "scuba" },
  { "af", "RV service" },
  { "ag", "shooting" },
  { "ah", "sight seeing" },
  { "ai", "small city" },
  { "aj", "sounding" },
  { "ak", "sports arena" },
  { "al", "tourist info" },
  { "am", "truck service" },
  { "an", "winery" },
  { "ao", "wreck" },
  { "ap", "zoo" },
  { "ah", "Virtual cache"}, 	/* Binos: because you "see" them. */
  { "ak", "Micro-Cache" },	/* Looks like a film canister. */
  { "an", "Multi-Cache"}, 	/* Winery: grapes 'coz they "bunch" */
  { "s",  "Unknown Cache"}, 	/* 'Suprise' cache: use a target. */
  { "ac",  "Event Cache"}, 	/* Event caches.  May be food. */
  { NULL, NULL }
};

pid_to_model_t pid_to_model[] = {
  { mm_gps315320, 19, "ColorTrak" },
  { mm_gps315320, 24, "GPS 315/320" },
  { mm_map410, 25, "Map 410" },
  { mm_map330, 30, "Map 330" },
  { mm_gps310, 31, "GPS 310" },
  { mm_meridian, 33, "Meridian" },
  { mm_meridian, 35, "ProMark 2" },
  { mm_sportrak, 36, "SporTrak Map/Pro" },
  { mm_sportrak, 37, "SporTrak" },
  { mm_meridian, 38, "FX324 Plotter" },
  { mm_meridian, 39, "Meridian Color" },
  { mm_meridian, 40, "FX324C Plotter" },
  { mm_sportrak, 41, "Sportrak Color" },
  { mm_sportrak, 42, "Sportrak Marine" },
  { mm_meridian, 43, "Meridian Marine" },
  { mm_sportrak, 44, "Sportrak Topo" },
  { mm_sportrak, 45, "Mystic" },
  { mm_meridian, 46, "MobileMapper" },
  { mm_meridian, 110, "Explorist 100" },
  { mm_meridian, 111, "Explorist 200" },
  { mm_unknown, 0, NULL }
};

static icon_mapping_t* icon_mapping = map330_icon_table;

/*
 *   For each receiver type, return a "cleansed" version of the string
 *   that's valid for a waypoint name or comment.   The string should be
 *   freed when you're done with it.
 */
static char*
m315_cleanse(char* istring)
{
  char* rstring = (char*) xmalloc(strlen(istring)+1);
  char* i,*o;
  static char m315_valid_chars[] =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789";
  for (o=rstring,i=istring; *i; i++) {
    if (strchr(m315_valid_chars, toupper(*i))) {
      *o++ = toupper(*i);
    }
  }
  *o = 0;
  return rstring;
}

/*
 * Do same for 330, Meridian, and SportTrak.
 */
char*
m330_cleanse(char* istring)
{
  static char m330_valid_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ "
                                   "abcdefghijklmnopqrstuvwxyz"
                                   "0123456789+-.'/!@#<%^&>()=:\\";
  char* rstring = (char*) xmalloc(strlen(istring)+1);
  char* o, *i;

  for (o=rstring,i=istring; *i; i++) {
    if (strchr(m330_valid_chars, *i)) {
      *o++ = *i;
    }
  }
  *o = 0;
  return rstring;
}

/*
 * Given a protocol message, compute the checksum as needed by
 * the Magellan protocol.
 */
unsigned int
mag_checksum(const char* const buf)
{
  int csum = 0;
  const char* p;

  for (p = buf; *p; p++) {
    csum  ^= *p;
  }

  return csum;
}
static unsigned int
mag_pchecksum(const char* const buf, int len)
{
  int csum = 0;
  const char* p = buf;
  for (; len ; len--) {
    csum ^= *p++;
  }
  return csum;
}

static void
mag_writemsg(const char* const buf)
{
  unsigned int osum = mag_checksum(buf);
  int retry_cnt = 5;
  int i;
  char obuf[1000];

  if (debug_serial) {
    warning("WRITE: $%s*%02X\r\n",buf, osum);
  }

retry:

  i = sprintf(obuf, "$%s*%02X\r\n",buf, osum);
  termwrite(obuf, i);
  if (magrxstate == mrs_handon || magrxstate == mrs_awaiting_ack) {
    magrxstate = mrs_awaiting_ack;
    mag_readmsg(trkdata);
    if (last_rx_csum != osum) {
      if (debug_serial) {
        warning("COMM ERROR: Expected %02x, got %02x",
                osum, last_rx_csum);
      }
      if (retry_cnt--) {
        goto retry;
      } else {
        mag_handoff();
        fatal(MYNAME
              ": Too many communication errors.\n");
      }
    }
  }
}

static void
mag_writeack(int osum)
{
  char obuf[200];
  char nbuf[200];
  int i;
  unsigned int nsum;

  if (is_file) {
    return;
  }

  i = sprintf(nbuf, "PMGNCSM,%02X", osum);
  nsum = mag_checksum(nbuf);
  i = sprintf(obuf, "$%s*%02X\r\n",nbuf, nsum);

  if (debug_serial) {
    warning("ACK WRITE: %s",obuf);
  }
  /*
   * Don't call mag_writemsg here so we don't get into ack feedback
   * loops.
   */
  termwrite(obuf, i);
}

static void
mag_handon(void)
{
  if (!is_file) {
    mag_writemsg("PMGNCMD,HANDON");
  }
  magrxstate = mrs_handon;

}

static void
mag_handoff(void)
{
  if (!is_file) {
    mag_writemsg("PMGNCMD,HANDOFF");
  }
  magrxstate = mrs_handoff;
}

void
mag_verparse(char* ibuf)
{
  int prodid = mm_unknown;
  char version[1024];
  pid_to_model_t* pp = pid_to_model;

  got_version = 1;
  sscanf(ibuf,"$PMGNVER,%d,%[^,]", &prodid, version);

  for (pp = pid_to_model; pp->model != mm_unknown; pp++) {
    if (pp->pid == prodid) {
      break;
    }
  }

  if (prodid == 37) {
    broken_sportrak = 1;
  }

  switch (pp->model) {
  case mm_gps315320:
  case mm_map410:
    icon_mapping = gps315_icon_table;
    setshort_length(mkshort_handle, 6);
    setshort_mustupper(mkshort_handle, 1);
    mag_cleanse = m315_cleanse;
    break;
  case mm_map330:
  case mm_meridian:
  case mm_sportrak:
    icon_mapping = map330_icon_table;
    setshort_length(mkshort_handle, wpt_len);
    setshort_mustupper(mkshort_handle, 0);
    mag_cleanse = m330_cleanse;
    break;
  default:
    fatal(MYNAME ": Unknown receiver type %d, model version '%s'.\n", prodid, version);
  }
}

#define IS_TKN(x) (strncmp(ibuf,x, sizeof(x)-1) == 0)

static void
mag_readmsg(gpsdata_type objective)
{
  char ibuf[512];	/* oliskoli: corrupted data (I've seen descr with a lot
				     of escaped FFFFFFFF) may need more size  */
  int isz;
  unsigned int isum;
  char* isump;
  char* gr;
  int retrycnt = 20;

retry:
  gr = termread(ibuf, sizeof(ibuf));

  if (!gr) {
    if (!got_version) {
      /*
       * The 315 can take up to six seconds to respond to
       * a VERSION command.   Since this is on startup,
       * we'll be fairly persistent in retrying.
       */
      if (retrycnt--) {
        goto retry;
      } else {
        fatal(MYNAME ": No data received from GPS.\n");
      }
    } else {
      if (is_file)  {
        found_done = 1;
      }
      return;
    }
  }

  /* If column zero isn't a dollar sign, it's not for us */
  if (ibuf[0] != '$') {
    fatal(MYNAME ": line doesn't start with '$'.\n");
  }


  isz = strlen(ibuf);

  if (isz < 5) {
    if (debug_serial) {
      warning("SHORT READ %d\n", isz);
    }
    return;
  }
  mag_error = 0;
  while (!isprint(ibuf[isz])) {
    isz--;
  }
  isump = &ibuf[isz-1];
  isum  = strtoul(isump, NULL,16);
  if (isum != mag_pchecksum(&ibuf[1], isz-3)) {
    if (debug_serial) {
      warning("RXERR %02x/%02x: '%s'\n", isum, mag_pchecksum(&ibuf[1],isz-5), ibuf);
    }
    /* Special case receive errors early on. */
    if (!got_version) {
      fatal(MYNAME ": bad communication.  Check bit rate.\n");
    }
  }
  if (debug_serial) {
    warning("READ: %s\n", ibuf);
  }
  if (IS_TKN("$PMGNCSM,")) {
    last_rx_csum = strtoul(&ibuf[9], NULL, 16);
    magrxstate = mrs_handon;
    return;
  }
  if (strncmp(ibuf, "$PMGNWPL,", 7) == 0) {
    waypoint* wpt = mag_wptparse(ibuf);
    waypoint_read_count++;
    if (global_opts.verbose_status) {
      waypt_status_disp(waypoint_read_count,
                        waypoint_read_count);
    }

    if (extension_hint) {
      if (extension_hint == WPTDATAMASK) {
        waypt_add(wpt);
      } else if (extension_hint == RTEDATAMASK) {
        ENQUEUE_TAIL(&rte_wpt_tmp, &wpt->Q);
      }
    } else {
      switch (objective) {
      case wptdata:
        waypt_add(wpt);
        break;
      case rtedata:
        ENQUEUE_TAIL(&rte_wpt_tmp, &wpt->Q);
        break;
      default:
        break;
      }
    }
  }
  if (strncmp(ibuf, "$PMGNTRK,", 7) == 0) {
    waypoint* wpt = mag_trkparse(ibuf);
    /*
     * Allow lazy allocation of track head.
     */
    if (trk_head == NULL) {
      /* These tracks don't have names, so derive one
       * from input filename.
       */
      char* e;
      const char* s = get_filename(curfname);

      trk_head = route_head_alloc();

      /* Whack trailing extension if present. */
      trk_head->rte_name = xstrdup(s);
      e = strrchr(trk_head->rte_name, '.');
      if (e) {
        *e = '\0';
      }

      track_add_head(trk_head);
    }

    track_add_wpt(trk_head, wpt);
  }
  if (strncmp(ibuf, "$PMGNRTE,", 7) == 0) {
    mag_rteparse(ibuf);
  }
  if (IS_TKN("$PMGNVER,")) {
    mag_verparse(ibuf);
  }
  mag_error = 0;
  if (!ignore_unable && IS_TKN("$PMGNCMD,UNABLE")) {
    warning("Unable to send\n");
    found_done = 1;
    mag_error = 1;
    ignore_unable = 0;
    return;
  }
  if (IS_TKN("$PMGNCMD,END") || (is_file && (gbfeof(magfile_h)))) {
    found_done = 1;
    return;
  }

  if (magrxstate != mrs_handoff) {
    mag_writeack(isum);
  }
}

static void* serial_handle = NULL;

static int
terminit(const char* portname, int create_ok)
{
  if (gbser_is_serial(portname)) {
    if (serial_handle = gbser_init(portname), NULL != serial_handle) {
      int rc;
      if (rc = gbser_set_port(serial_handle, bitrate, 8, 0, 1), gbser_OK != rc) {
        fatal(MYNAME ": Can't configure port\n");
      }
    }
    is_file = 0;
    if (serial_handle == NULL) {
      fatal(MYNAME ": Could not open serial port %s\n", portname);
    }
    return 1;
  } else {
    /* Does this check for an error? */
    magfile_h = gbfopen(portname, create_ok ? "w+b" : "rb", MYNAME);
    is_file = 1;
    icon_mapping = map330_icon_table;
    mag_cleanse = m330_cleanse;
    got_version = 1;
    return 0;
  }
}

static char* termread(char* ibuf, int size)
{
  if (is_file) {
    return gbfgets(ibuf, size, magfile_h);
  } else {
    int rc;
    rc = gbser_read_line(serial_handle, ibuf, size, 2000, 0x0a, 0x0d);
    if (rc != gbser_OK) {
      fatal(MYNAME ": Read error\n");
    }
    return ibuf;
  }
}

/* Though not documented in the protocol spec, if the unit itself
 * wants to create a field containing a comma, it will encode it
 * as <escape>2C.  We extrapolate that any 2 digit hex encoding may
 * be valid.  We don't do this in termread() since we need to do it
 * after the scanf.  This means we have to do it field-by-field
 * basis.
 *
 * The buffer is modified in place and shortened by copying the remaining
 * string including the terminator.
 */
static
void
mag_dequote(char* ibuf)
{
  char* esc = NULL;

  while ((esc = strchr(ibuf, 0x1b))) {
    int nremains = strlen(esc);
    if (nremains >= 3) {
      static const char hex[17] = "0123456789ABCDEF";
      char* c1 = strchr(hex, esc[1]);
      char* c2 = strchr(hex, esc[2]);
      if (c1 && c2) {
        int escv = (c1 - hex) * 16 + (c2 - hex);
        if (escv == 255) {	/* corrupted data */
          char* tmp = esc + 1;
          while (*tmp == 'F') {
            tmp++;
          }
          memmove(esc, tmp, strlen(tmp) + 1);
        } else {
          *esc++ = (isprint(escv)) ? escv : '$';
          /* buffers overlap */
          memmove(esc, esc+2, nremains - 2);
        }
      }
    } else {
      *esc = '\0';	/* trim corrupted data,
					   otherwise we get an endless loop */
    }
  }
}

static void
termwrite(char* obuf, int size)
{
  if (is_file) {
    size_t nw;
    if (nw = gbfwrite(obuf, 1, size, magfile_h), nw < (size_t) size) {
      fatal(MYNAME ": Write error");
    }
  } else {
    int rc;
    if (rc = gbser_write(serial_handle, obuf, size), rc < 0) {
      fatal(MYNAME ": Write error");
    }
  }
}

static void termdeinit()
{
  if (is_file) {
    gbfclose(magfile_h);
    magfile_h = NULL;
  } else {
    gbser_deinit(serial_handle);
    serial_handle = NULL;
  }
}

/*
 *  Arg tables are doubled up so that -? can output appropriate help
 */
static
arglist_t mag_sargs[] = {
  {
    "deficon", &deficon, "Default icon name", NULL, ARGTYPE_STRING,
    ARG_NOMINMAX
  },
  {
    "maxcmts", &cmts, "Max number of comments to write (maxcmts=200)",
    "200", ARGTYPE_INT, ARG_NOMINMAX
  },
  {
    "baud", &bs, "Numeric value of bitrate (baud=4800)", "4800",
    ARGTYPE_INT, ARG_NOMINMAX
  },
  {
    "noack", &noack, "Suppress use of handshaking in name of speed",
    NULL, ARGTYPE_BOOL, ARG_NOMINMAX
  },
  {
    "nukewpt", &nukewpt, "Delete all waypoints", NULL, ARGTYPE_BOOL,
    ARG_NOMINMAX
  },
  ARG_TERMINATOR
};

static
arglist_t mag_fargs[] = {
  {
    "deficon", &deficon, "Default icon name", NULL, ARGTYPE_STRING,
    ARG_NOMINMAX
  },
  {
    "maxcmts", &cmts, "Max number of comments to write (maxcmts=200)",
    NULL, ARGTYPE_INT, ARG_NOMINMAX
  },
  ARG_TERMINATOR
};

/*
 * The part of the serial init that's common to read and write.
 */
static void
mag_serial_init_common(const char* portname)
{
  time_t now, later;

  if (is_file) {
    return;
  }

  mag_handoff();
  if (!noack && !suppress_ack) {
    mag_handon();
  }

  now = current_time();
  /*
   * The 315 can take up to 4.25 seconds to respond to initialization
   * commands.   Time out on the side of caution.
   */
  later = now + 6;
  got_version = 0;
  mag_writemsg("PMGNCMD,VERSION");

  while (!got_version) {
    mag_readmsg(trkdata);
    if (current_time() > later) {
      fatal(MYNAME ": No acknowledgment from GPS on %s\n",
            portname);
    }
  }

  if ((icon_mapping != gps315_icon_table)) {
    /*
     * The 315 can't handle this command, so we set a global
     * to ignore the NAK on it.
     */
    ignore_unable = 1;
    mag_writemsg("PMGNCMD,NMEAOFF");
    ignore_unable = 0;
  }

  if (nukewpt) {
    /* The unit will send us an "end" message upon completion */
    mag_writemsg("PMGNCMD,DELETE,WAYPOINT");
    mag_readmsg(trkdata);
    if (!found_done) {
      fatal(MYNAME ": Unexpected response to waypoint delete command.\n");
    }
    found_done = 0;
  }

}
static void
mag_rd_init_common(const char* portname)
{
  char* ext;
  waypoint_read_count = 0;
  // For Explorist GC, intercept the device access and redirect to GPX.
  // We actually do the rd_init() inside read as we may have multiple
  // files that we have to read.
  if (0 == strcmp(portname, "usb:")) {
    const char** dlist = os_get_magellan_mountpoints();
    explorist_info = explorist_ini_get(dlist);
    if (explorist_info) {
      char* vec_opts = NULL;
      gpx_vec = find_vec("gpx", &vec_opts);
    }
    return;
  }

  if (bs) {
    bitrate=atoi(bs);
  }

  if (!mkshort_handle) {
    mkshort_handle = mkshort_new_handle();
  }

  terminit(portname, 0);
  mag_serial_init_common(portname);

  QUEUE_INIT(&rte_wpt_tmp);

  /* find the location of the tail of the path name,
   * make a copy of it, then lop off the file extension
   */

  curfname = get_filename(portname);

  /*
   * I'd rather not derive behaviour from filenames but since
   * we can't otherwise tell if we should put a WPT on the route
   * queue or the WPT queue in the presence of (-w -r -t) we
   * divine a hint from the filename extension when we can.
   */
  ext = strrchr(curfname, '.');
  if (ext) {
    ext++;
    if (0 == case_ignore_strcmp(ext, "upt")) {
      extension_hint = WPTDATAMASK;
    } else if (0 == case_ignore_strcmp(ext, "log")) {
      extension_hint = TRKDATAMASK;
    } else if (0 == case_ignore_strcmp(ext, "rte")) {
      extension_hint = RTEDATAMASK;
    }
  }

  return;
}

static void
mag_rd_init(const char* portname)
{
  explorist = 0;
  suppress_ack = 1;
  mag_rd_init_common(portname);
}

static void
magX_rd_init(const char* portname)
{
  explorist = 1;
  mag_rd_init_common(portname);
}

static void
mag_wr_init_common(const char* portname)
{
  suppress_ack = 0;
  if (bs) {
    bitrate=atoi(bs);
  }

  if (waypt_count() > 500) {
    fatal(MYNAME ": Meridian/Explorist does not support more than 500 waypoints in one file. Only\n200 waypoints may have comments.\nDecrease the number of waypoints sent.\n");
  }

  if (cmts) {
    wptcmtcnt_max = atoi(cmts);
  } else {
    wptcmtcnt_max = MAXCMTCT ;
  }

  if (!mkshort_handle) {
    mkshort_handle = mkshort_new_handle();
  }

  terminit(portname, 1);
  mag_serial_init_common(portname);

  QUEUE_INIT(&rte_wpt_tmp);
}

/*
 * Entry point for extended (explorist) points.
 */
static void
magX_wr_init(const char* portname)
{
  wpt_len = 20;
  explorist = 1;
  mag_wr_init_common(portname);
  setshort_length(mkshort_handle, wpt_len);
  setshort_whitespace_ok(mkshort_handle, 1);
}

static void
mag_wr_init(const char* portname)
{
  explorist = 0;
  wpt_len = 8;
  mag_wr_init_common(portname);
  /*
   * Whitespace is actually legal, but since waypoint name length is
   * only 8 bytes, we'll conserve them.
   */

  setshort_whitespace_ok(mkshort_handle, 0);
}

static void
mag_deinit(void)
{
  if (explorist_info) {
    explorist_ini_done(explorist_info);
    return;
  }
  mag_handoff();
  termdeinit();
  if (mkshort_handle) {
    mkshort_del_handle(&mkshort_handle);
  }

  waypt_flush(&rte_wpt_tmp);

  trk_head = NULL;
}

/*
 * I'm tired of arguing with scanf about optional fields .  Detokenize
 * an incoming string that may contain empty fields.
 *
 * Probably should be cleaned up and moved to common code, but
 * making it deal with an arbitrary number of fields of arbitrary
 * size is icky.  We don't have to solve the general case here...
 */

static char ifield[20][100];
static
void parse_istring(char* istring)
{
  int f = 0;
  int n,x;
  while (istring[0]) {
    char* fp = ifield[f];
    x = sscanf(istring, "%[^,]%n", fp, &n);
    f++;
    if (x) {
      istring += n;
      /* IF more in this string, skip delim */
      if (istring[0]) {
        istring++;
      }
    } else {
      istring ++;
    }
  }
}

/*
 * Given an incoming track messages of the form:
 * $PMGNTRK,3605.259,N,08644.389,W,00151,M,201444.61,A,,020302*66
 * create and return a populated waypoint.
 */
waypoint*
mag_trkparse(char* trkmsg)
{
  double latdeg, lngdeg;
  int alt;
  char altunits;
  char lngdir, latdir;
  int dmy;
  int hms;
  int fracsecs;
  struct tm tm;
  waypoint* waypt;

  waypt  = waypt_new();

  memset(&tm, 0, sizeof(tm));

  /*
   * As some of the fields are optional, sscanf works badly
   * for us.
   */
  parse_istring(trkmsg);
  latdeg = atof(ifield[1]);
  latdir = ifield[2][0];
  lngdeg = atof(ifield[3]);
  lngdir = ifield[4][0];
  alt = atof(ifield[5]);
  altunits = ifield[6][0];
  sscanf(ifield[7], "%d.%d", &hms, &fracsecs);
  /* Field 8 is constant */
  /* Field nine is optional track name */
  dmy = atoi(ifield[10]);

  tm.tm_sec = hms % 100;
  hms = hms / 100;
  tm.tm_min = hms % 100;
  hms = hms / 100;
  tm.tm_hour = hms % 100;

  tm.tm_year = 100 + dmy % 100;
  dmy = dmy / 100;
  tm.tm_mon =  dmy % 100 - 1;
  dmy = dmy / 100;
  tm.tm_mday = dmy % 100;

  waypt->creation_time = mkgmtime(&tm);
  waypt->microseconds = CENTI_TO_MICRO(fracsecs);

  if (latdir == 'S') {
    latdeg = -latdeg;
  }
  waypt->latitude = ddmm2degrees(latdeg);

  if (lngdir == 'W') {
    lngdeg = -lngdeg;
  }
  waypt->longitude = ddmm2degrees(lngdeg);

  waypt->altitude = alt;

  return waypt;

}

/*
 * Given an incoming route messages of the form:
 * $PMGNRTE,4,1,c,1,DAD,a,Anna,a*61
 * generate a route.
 */
void
mag_rteparse(char* rtemsg)
{
  char descr[100];
  int n;
  int frags,frag,rtenum;
  char xbuf[100],next_stop[100],abuf[100];
  char* currtemsg;
  static mag_rte_head* mag_rte_head;
  mag_rte_elem* rte_elem;
  char* p;
  char* rte_name = NULL;

  descr[0] = 0;
#if 0
  sscanf(rtemsg,"$PMGNRTE,%d,%d,%c,%d%n",
         &frags,&frag,xbuf,&rtenum,&n);
#else
  sscanf(rtemsg,"$PMGNRTE,%d,%d,%c,%d%n",
         &frags,&frag,xbuf,&rtenum,&n);

  /* Explorist has a route name here */
  if (explorist) {
    char* ca, *ce;

    ca = rtemsg + n;
    is_fatal(*ca++ != ',', MYNAME ": Incorrectly formatted route line '%s'", rtemsg);

    ce = strchr(ca, ',');
    is_fatal(ce == NULL, MYNAME ": Incorrectly formatted route line '%s'", rtemsg);

    if (ca == ce) {
      xasprintf(&rte_name, "Route%d", rtenum);
    } else {
      rte_name = xstrndup(ca, ce - ca);
    }

    n += ((ce - ca) + 1);
  }

#endif

  /*
   * This is the first component of a route.  Allocate a new
   * queue head.
   */
  if (frag == 1) {
    mag_rte_head = (struct mag_rte_head_*) xcalloc(sizeof(*mag_rte_head),1);
    QUEUE_INIT(&mag_rte_head->Q);
    mag_rte_head->nelems = frags;
  }

  currtemsg = rtemsg + n;

  /*
   * The individual line may contain several route elements.
   * loop and pick those up.
   */
  while (sscanf(currtemsg,",%[^,],%[^,]%n",next_stop, abuf,&n)) {
    if ((next_stop[0] == 0) || (next_stop[0] == '*')) {
      break;
    }

    /* trim CRC from waypoint icon string */
    if ((p = strchr(abuf, '*')) != NULL) {
      *p = '\0';
    }

    rte_elem = (mag_rte_elem*) xcalloc(sizeof(*rte_elem),1);
    QUEUE_INIT(&rte_elem->Q);

    rte_elem->wpt_name = xstrdup(next_stop);
    rte_elem->wpt_icon = xstrdup(abuf);

    ENQUEUE_TAIL(&mag_rte_head->Q, &rte_elem->Q);

    /* Sportrak (the non-mapping unit) creates malformed
     * RTE sentence with no icon info after the routepoint
     * name.  So if we saw an "icon" treat that as new
     * routepoint.
     */
    if (broken_sportrak && abuf[0]) {
      rte_elem = (mag_rte_elem*) xcalloc(sizeof(*rte_elem),1);
      QUEUE_INIT(&rte_elem->Q);
      rte_elem->wpt_name = xstrdup(abuf);

      ENQUEUE_TAIL(&mag_rte_head->Q, &rte_elem->Q);
    }

    next_stop[0] = 0;
    currtemsg += n;
  }

  /*
   * If this was the last fragment of the route, add it to the
   * gpsbabel internal structs now.
   */
  if (frag == mag_rte_head->nelems) {
    queue* elem, *tmp;
    route_head* rte_head;

    rte_head = route_head_alloc();
    route_add_head(rte_head);
    rte_head->rte_num = rtenum;
    rte_head->rte_name = xstrdup(rte_name);

    /*
     * It is quite feasible that we have 200 waypoints,
     * 3 of which are used in the route.  We'll need to find
     * those in the queue for SD routes...
     */

    QUEUE_FOR_EACH(&mag_rte_head->Q, elem, tmp) {
      mag_rte_elem* re = (mag_rte_elem*) elem;
      waypoint* waypt;
      queue* welem, *wtmp;

      /*
       * Copy route points from temp wpt queue.
       */
      QUEUE_FOR_EACH(&rte_wpt_tmp, welem, wtmp) {
        waypt = (waypoint*)welem;
        if (strcmp(waypt->shortname, re->wpt_name) == 0) {
          waypoint* wpt = waypt_dupe(waypt);
          route_add_wpt(rte_head, wpt);
          break;
        }
      }

      dequeue(&re->Q);
      xfree(re->wpt_name);
      xfree(re->wpt_icon);
      xfree(re);
    }
    xfree(mag_rte_head);
  }
  if (rte_name) {
    xfree(rte_name);
  }
}

const char*
mag_find_descr_from_token(const char* token)
{
  icon_mapping_t* i = icon_mapping;

  if (icon_mapping == NULL) {
    return "unknown";
  }

  for (i = icon_mapping; i->token; i++) {
    if (token[0] == 0) {
      break;
    }
    if (case_ignore_strcmp(token, i->token) == 0) {
      return i->icon;
    }
  }
  return icon_mapping[0].icon;
}

const char*
mag_find_token_from_descr(const char* icon)
{
  icon_mapping_t* i = icon_mapping;

  if (i == NULL || icon == NULL) {
    return "a";
  }

  for (i = icon_mapping; i->token; i++) {
    if (case_ignore_strcmp(icon, i->icon) == 0) {
      return i->token;
    }
  }
  return icon_mapping[0].token;
}

/*
 * Given an incoming waypoint messages of the form:
 * $PMGNWPL,3549.499,N,08650.827,W,0000257,M,HOME,HOME,c*4D
 * create and return a populated waypoint.
 */
static waypoint*
mag_wptparse(char* trkmsg)
{
  double latdeg, lngdeg;
  char latdir;
  char lngdir;
  int alt;
  char altunits;
  char shortname[100];
  char descr[256];
  char icon_token[100];
  waypoint* waypt;
  char* icons;
  char* icone;
  char* blah;
  int i = 0;

  descr[0] = 0;
  icon_token[0] = 0;

  waypt  = waypt_new();

  sscanf(trkmsg,"$PMGNWPL,%lf,%c,%lf,%c,%d,%c,%[^,],%[^,]",
         &latdeg,&latdir,
         &lngdeg,&lngdir,
         &alt,&altunits,shortname,descr);
  icone = strrchr(trkmsg, '*');
  icons = strrchr(trkmsg, ',')+1;

  mag_dequote(descr);

  for (blah = icons ; blah < icone; blah++) {
    icon_token[i++] = *blah;
  }
  icon_token[i++] = '\0';

  if (latdir == 'S') {
    latdeg = -latdeg;
  }
  waypt->latitude = ddmm2degrees(latdeg);

  if (lngdir == 'W') {
    lngdeg = -lngdeg;
  }
  waypt->longitude = ddmm2degrees(lngdeg);

  waypt->altitude = alt;
  waypt->shortname = xstrdup(shortname);
  waypt->description = xstrdup(descr);
  waypt->icon_descr = mag_find_descr_from_token(icon_token);

  return waypt;
}

static void
mag_read(void)
{
  if (gpx_vec) {
    char** f = os_gpx_files(explorist_info->track_path);
    while (f && *f) {
      gpx_vec->rd_init(*f);
      gpx_vec->read();
      f++;
    }

    f = os_gpx_files(explorist_info->waypoint_path);
    while (f && *f) {
      gpx_vec->rd_init(*f);
      gpx_vec->read();
      f++;
    }
#if 0
    f = os_gpx_files(explorist_info->geo_path);
    while (f && *f) {
      gpx_vec->rd_init(*f);
      gpx_vec->read();
      f++;
    }
#endif
    return;
  }

  found_done = 0;
  if (global_opts.masked_objective & TRKDATAMASK) {
    magrxstate = mrs_handoff;
    if (!is_file) {
      mag_writemsg("PMGNCMD,TRACK,2");
    }

    while (!found_done) {
      mag_readmsg(trkdata);
    }
  }

  found_done = 0;
  if (global_opts.masked_objective & WPTDATAMASK) {
    magrxstate = mrs_handoff;
    if (!is_file) {
      mag_writemsg("PMGNCMD,WAYPOINT");
    }

    while (!found_done) {
      mag_readmsg(wptdata);
    }
  }

  found_done = 0;
  if (global_opts.masked_objective & RTEDATAMASK) {
    magrxstate = mrs_handoff;
    if (!is_file) {
      /*
       * serial routes require waypoint & routes
       * messages commands.
       */
      mag_writemsg("PMGNCMD,WAYPOINT");

      while (!found_done) {
        mag_readmsg(rtedata);
      }

      mag_writemsg("PMGNCMD,ROUTE");

      found_done = 0;
      while (!found_done) {
        mag_readmsg(rtedata);
      }
    } else {
      /*
       * SD routes are a stream of PMGNWPL and
       * PMGNRTE messages, in that order.
       */
      while (!found_done) {
        mag_readmsg(rtedata);
      }
    }
  }
}

static
void
mag_waypt_pr(const waypoint* waypointp)
{
  double lon, lat;
  double ilon, ilat;
  int lon_deg, lat_deg;
  char obuf[200];
  char ofmtdesc[200];
  const char* icon_token=NULL;
  char* owpt;
  char* odesc;
  char* isrc = NULL;

  ilat = waypointp->latitude;
  ilon = waypointp->longitude;

  lon = fabs(ilon);
  lat = fabs(ilat);

  lon_deg = lon;
  lat_deg = lat;

  lon = (lon - lon_deg) * 60.0;
  lat = (lat - lat_deg) * 60.0;

  lon = (lon_deg * 100.0 + lon);
  lat = (lat_deg * 100.0 + lat);

  if (deficon)  {
    icon_token = mag_find_token_from_descr(deficon);
  } else {
    icon_token = mag_find_token_from_descr(waypointp->icon_descr);
  }

  if (get_cache_icon(waypointp)) {
    icon_token = mag_find_token_from_descr(get_cache_icon(waypointp));
  }

  isrc = waypointp->notes ? waypointp->notes : waypointp->description;
  owpt = global_opts.synthesize_shortnames ?
         mkshort_from_wpt(mkshort_handle, waypointp) : waypointp->shortname;
  odesc = isrc ? isrc : (char*)"";
  owpt = mag_cleanse(owpt);

  if (global_opts.smart_icons &&
      waypointp->gc_data->diff && waypointp->gc_data->terr) {
    sprintf(ofmtdesc, "%d/%d %s", waypointp->gc_data->diff,
            waypointp->gc_data->terr, odesc);
    odesc = mag_cleanse(ofmtdesc);
  } else {
    odesc = mag_cleanse(odesc);
  }

  /*
   * For the benefit of DirectRoute (which uses waypoint comments
   * to deliver turn-by-turn popups for street routing) allow a
   * cap on the comments delivered so we leave space for it to route.
   */
  if (odesc && /* !is_file && */ (wptcmtcnt++ >= wptcmtcnt_max)) {
    odesc[0] = 0;
  }

  sprintf(obuf, "PMGNWPL,%4.3f,%c,%09.3f,%c,%07.0f,M,%-.*s,%-.46s,%s",
          lat, ilat < 0 ? 'S' : 'N',
          lon, ilon < 0 ? 'W' : 'E',
          waypointp->altitude == unknown_alt ?
          0 : waypointp->altitude,
          wpt_len,
          owpt,
          odesc,
          icon_token);
  mag_writemsg(obuf);
  xfree(owpt);
  xfree(odesc);

  if (!is_file) {
    if (mag_error) {
      warning("Protocol error Writing '%s'\n", obuf);
    }
  }
}

static
void mag_track_nop(const route_head* rte)
{
  return;
}

static
void mag_track_disp(const waypoint* waypointp)
{
  double ilon, ilat;
  double lon, lat;
  int lon_deg, lat_deg;
  char obuf[200];
  int hms=0;
  int fracsec=0;
  int date=0;
  struct tm* tm = NULL;

  ilat = waypointp->latitude;
  ilon = waypointp->longitude;
  tm = NULL;
  if (waypointp->creation_time) {
    tm = gmtime(&waypointp->creation_time);
    if (tm) {
      hms = tm->tm_hour * 10000 + tm->tm_min  * 100 +
            tm->tm_sec;
      date = tm->tm_mday * 10000 + tm->tm_mon * 100 +
             tm->tm_year;
      fracsec = MICRO_TO_CENTI(waypointp->microseconds);
    }
  }
  if (!tm) {
    date = 0;
    fracsec = 0;
  }

  lon = fabs(ilon);
  lat = fabs(ilat);

  lon_deg = lon;
  lat_deg = lat;

  lon = (lon - lon_deg) * 60.0;
  lat = (lat - lat_deg) * 60.0;

  lon = (lon_deg * 100.0 + lon);
  lat = (lat_deg * 100.0 + lat);

  sprintf(obuf,"PMGNTRK,%4.3f,%c,%09.3f,%c,%05.0f,%c,%06d.%02d,A,,%06d",
          lat, ilat < 0 ? 'S' : 'N',
          lon, ilon < 0 ? 'W' : 'E',
          waypointp->altitude == unknown_alt ?
          0 : waypointp->altitude,
          'M',hms,fracsec,date);
  mag_writemsg(obuf);
}

static
void mag_track_pr()
{
  track_disp_all(mag_track_nop, mag_track_nop, mag_track_disp);
}

/*
The spec says to stack points:
	$PMGNRTE,2,1,c,1,FOO,POINT1,b,POINT2,c,POINT3,d*6C<CR><LF>

Meridian SD card and serial (at least) writes in pairs:
	$PMGNRTE,4,1,c,1,HOME,c,I49X73,a*15
	...
	$PMGNRTE,4,4,c,1,RON273,a,MYCF93,a*7B

The spec also says that some units don't like single-legged pairs,
and to replace the 2nd name with "<<>>", but I haven't seen one of those.
*/

static void
mag_route_trl(const route_head* rte)
{
  queue* elem, *tmp;
  waypoint* waypointp;
  char obuff[256];
  char buff1[64], buff2[64];
  char* pbuff, *owpt;
  const char* icon_token;
  int i, numlines, thisline;

  /* count waypoints for this route */
  i = rte->rte_waypt_ct;

  /* number of output PMGNRTE messages at 2 points per line */
  numlines = (i / 2) + (i % 2);

  /* increment the route counter. */
  route_out_count++;

  thisline = i = 0;
  QUEUE_FOR_EACH(&rte->waypoint_list, elem, tmp) {
    waypointp = (waypoint*) elem;
    i++;

    if (deficon) {
      icon_token = mag_find_token_from_descr(deficon);
    } else {
      icon_token = mag_find_token_from_descr(waypointp->icon_descr);
    }

    if (i == 1) {
      pbuff = buff1;
    } else {
      pbuff = buff2;
    }

    owpt = waypointp->shortname;
    if (strlen(owpt) > sizeof(buff1) - 3) {
      owpt[sizeof(buff1) - 3] = 0;
    }
    owpt = mag_cleanse(owpt);

    sprintf(pbuff, "%s,%s", owpt, icon_token);

    xfree(owpt);

    if ((tmp == &rte->waypoint_list) || ((i % 2) == 0)) {
      char expbuf[1024];
      thisline++;
      expbuf[0] = 0;
      if (explorist) {
        snprintf(expbuf, sizeof(expbuf), "%s,",
                 rte->rte_name ? rte->rte_name : "");
      }
      sprintf(obuff, "PMGNRTE,%d,%d,c,%d,%s%s,%s",
              numlines, thisline,
              rte->rte_num ? rte->rte_num : route_out_count,
              expbuf,
              buff1, buff2);

      mag_writemsg(obuff);
      buff1[0] = '\0';
      buff2[0] = '\0';
      i = 0;
    }
  }
}

static void
mag_route_hdr(const route_head* rh)
{
}

static void
mag_route_pr()
{
  route_out_count = 0;
  route_disp_all(mag_route_hdr, mag_route_trl, mag_waypt_pr);

}

static void
mag_write(void)
{

  wptcmtcnt = 0;

  switch (global_opts.objective) {
  case trkdata:
    mag_track_pr();
    break;
  case wptdata:
    waypt_disp_all(mag_waypt_pr);
    break;
  case rtedata:
    mag_route_pr();
    break;
  default:
    fatal(MYNAME ": Unknown objective.\n");
  }
}

const char** os_get_magellan_mountpoints()
{
#if __APPLE__
  const char** dlist = (const char**) xcalloc(2, sizeof *dlist);
  dlist[0] = xstrdup("/Volumes/Magellan");
  dlist[1] = NULL;
  return dlist;
#else
  fatal("Not implemented");
#endif
}

// My kingdom for container classes and portable tree-walking...
// Returns a pointer to a static vector that's valid until the next call.
static char**
os_gpx_files(const char* dirname)
{
#if HAVE_GLOB
  static glob_t g;
  char* path;
  xasprintf(&path, "%s/*.gpx", dirname);
  glob(path, 0, NULL, &g);
  xfree(path);
  return g.gl_pathv;
#else
  fatal("Not implemented");
#endif
}

/*
 *  This is repeated just so it shows up as separate menu options
 *  for the benefit of GUI wrappers.
 */
ff_vecs_t mag_svecs = {
  ff_type_serial,
  FF_CAP_RW_ALL,
  mag_rd_init,
  mag_wr_init,
  mag_deinit,
  mag_deinit,
  mag_read,
  mag_write,
  NULL,
  mag_sargs,
  CET_CHARSET_ASCII, 0	/* CET-REVIEW */
};

ff_vecs_t mag_fvecs = {
  ff_type_file,
  FF_CAP_RW_ALL,
  mag_rd_init,
  mag_wr_init,
  mag_deinit,
  mag_deinit,
  mag_read,
  mag_write,
  NULL,
  mag_fargs,
  CET_CHARSET_ASCII, 0	/* CET-REVIEW */
};

/*
 * Extended (Explorist) entry tables.
 */
ff_vecs_t magX_fvecs = {
  ff_type_file,
  FF_CAP_RW_ALL,
  magX_rd_init,
  magX_wr_init,
  mag_deinit,
  mag_deinit,
  mag_read,
  mag_write,
  NULL,
  mag_fargs,
  CET_CHARSET_ASCII, 0	/* CET-REVIEW */
};
