/*---------------------------------------------------------------------------*\
   gpstr2shp.c

   A translator of GPStrans files into Shapefile format files

   This program was developed for use with
   gpsman --- GPS Manager: a manager for GPS receiver data

   Copyright (c) 2002-2013 Miguel Filgueiras (migfilg@t-online.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 3 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.

------
    This program reads from the standard input; its single argument is
    the (path to the) base-name for the Shapefile files (.shp, .shx, .dbf)

    This program uses
    - shapelib, Copyright (c) 1999 by Frank Warmerdam <warmerda@home.com>

    A similar program, that translates "Generate" format, is
    - gen2shp.c, Copyright (c) 1999 by Jan-Oliver Wagner <jan@intevation.de>

    The GPStrans format is defined by
    - GPStrans, Copyright (c) 1995 by Carsten Tschach
    <tschach@zedat.fu-berlin.de>

\*---------------------------------------------------------------------------*/

/* File: gentr2shp.c *\
\* Last modified: 6 October 2013 */


/* Format of GPStrans files:

   - header: DDD (decimal degrees) is the only position format supported

   Format: DDD  UTC Offset:  -6.00 hrs  Datum[061]: NAD27 CONUS
           |||              ||||||            |||
   0         1         2         3         4         5         6
   0123456789012345678901234567890123456789012345678901234567890123456789

   - a single data type per file
   - possible data types: waypoints, routes, tracks
   - waypoint files: one or more lines
     W\t%s\t%s\t%s\t%lf\t%lf
   with name, comment (40 chars), date (MM/DD/YYYY HH:MM:SS), lat, long
   - route files: one or more of
     R\t%d\t%s
   with route number, comment, followed by
     lines for route waypoints (as in waypoint files)
   - track files: tracks are separated by a blank line, each track has one
   or more lines
     T\t%s\t%lf\t%lf
   for time-stamp (MM/DD/YYYY HH:MM:SS), lat, long

   Generated files:
   - point or polyline shapes
   - attributes in data-base:
     - for waypoints: name, commt (comment) and date as strings
     - for routes: number and commt (comment) as strings
     - for tracks: date (time-stamp of first track point) as string
*/

#include <libshp/shapefil.h>
#include <string.h>

/* #define DEBUG 1 */

/* lengths of strings */
#define WPNAMEWD 50
#define WPCOMMTWD 128
#define WPDATEWD 25
#define RTIDWD 50
#define RTCOMMTWD 128
#define TRDATEWD 25

typedef struct wpstrt {
  char wpname[WPNAMEWD], wpcommt[WPCOMMTWD], wpdate[WPDATEWD];
  double wplat, wplong;
  struct wpstrt *wpnext;
} WPDATA, *WPLIST;

WPDATA WP;
int WPCount = 0, WPNameField, WPCommtField, WPDateField;

typedef struct {
  char rtid[RTIDWD], rtcommt[RTCOMMTWD];
  WPLIST rtwps;
} RTDATA;

RTDATA RT;
int RTCount = 0, RTlgth, RTIdField, RTCommtField;

typedef struct tpstrt {
  char tpdate[TRDATEWD];
  double tplat, tplong;
  struct tpstrt *tpnext;
} TPDATA, *TPLIST;

TPLIST TR;
int TRCount = 0, TRlgth, TRDateField;

char Buffer[256];

SHPHandle SHPFile;
DBFHandle DBFFile;

#define WPTYPE SHPT_POINT
#define RTTYPE SHPT_ARC
#define TRTYPE SHPT_ARC

int getline(int skipnl)
{ char *p = Buffer;
  int empty = 1;

  while (! feof(stdin)) {
    if ((*p++=getchar()) == '\n') {
      --p;
      if (skipnl && empty)  continue;
      *p = 0;
      return 1;
    }
    empty = 0;
  }
  return 0;
}

int cpnotab(char **pp, char *dest)
/* cp string until tab, return 0 if not found */
{
  while ((*dest++=**pp) && **pp != '\t')  (*pp)++;
  if (**pp == 0)  return 1;
  *--dest = 0;  (*pp)++;
  return 0;
}

int badheader()
{
  if (! getline(0)) {
    fprintf(stderr,"no header found\n");
    return(1);
  }
  if (strncmp("Format: DDD",Buffer,11)) {
    fprintf(stderr,"bad format in header: %s\n",Buffer);
    return(1);
  }
  return 0;
}

int badWP(WPLIST pWP)
{ char *p = Buffer;

  if (*p++ != 'W' || *p++ != '\t' || cpnotab(&p,pWP->wpname) ||
      cpnotab(&p,pWP->wpcommt) || cpnotab(&p,pWP->wpdate))  return 1;
  return sscanf(p,"%lf\t%lf",&pWP->wplat,&pWP->wplong) != 2;
}

void saveWP(WPLIST wpp)  /* save a single WP pointed to by wpp */
{ SHPObject *pwpo;
  int entno; 

#ifdef DEBUG
  printf("W\t%s\t%s\t%s\t%lf\t%lf\n",wpp->wpname,wpp->wpcommt,wpp->wpdate,
	 wpp->wplat,wpp->wplong);
#endif

  pwpo = SHPCreateSimpleObject(WPTYPE,1,&wpp->wplong,&wpp->wplat,NULL);
  entno = SHPWriteObject(SHPFile,-1,pwpo);
  SHPDestroyObject(pwpo);

  if (DBFWriteStringAttribute(DBFFile,entno,WPNameField,wpp->wpname) == 0 ||
      DBFWriteStringAttribute(DBFFile,entno,WPCommtField,wpp->wpcommt) == 0 ||
      DBFWriteStringAttribute(DBFFile,entno,WPDateField,wpp->wpdate) == 0) {
    fprintf(stderr,"failed to write .dbf field, WP #%d\n",WPCount);
    exit(1);    
  }
  WPCount++;
}

void transWPs()
{
  if ((WPNameField=DBFAddField(DBFFile,"name",FTString,WPNAMEWD,0)) == -1 ||
      (WPCommtField=DBFAddField(DBFFile,"commt",FTString,WPCOMMTWD,0)) == -1 ||
      (WPDateField=DBFAddField(DBFFile,"date",FTString,WPDATEWD,0)) == -1) {
    fprintf(stderr,"failed to add .dbf field\n");
    exit(1);
  }
  do {
    if (badWP(&WP)) {
      fprintf(stderr,"bad WP: %s\n",Buffer);
      exit(1);
    }
    saveWP(&WP);
  } while (getline(1));
}

int badRT()
{ char *p = Buffer, *q = RT.rtcommt;

  if (*p++ != 'R' || *p++ != '\t' || cpnotab(&p,RT.rtid))  return 1;
  while ((*q++=*p++));
  return 0;
}

void saveRT()   /* and free list of WPs */
{ WPLIST wpp, wpl;
  SHPObject *prto;
  int entno, i;
  double *x, *y;

#ifdef DEBUG
  printf("R\t%s\t%s\n",RT.rtid,RT.rtcommt);
  wpp = RT.rtwps;
  while (wpp != NULL) {
    printf("W\t%s\t%s\t%s\t%lf\t%lf\n",wpp->wpname,wpp->wpcommt,wpp->wpdate,
	   wpp->wplat,wpp->wplong);
    wpp = wpp->wpnext;
  }
#endif

  if ((x=(double *) malloc(RTlgth*sizeof(double))) == NULL ||
      (y=(double *) malloc(RTlgth*sizeof(double))) == NULL) {
    fprintf(stderr,"out of memory, RT #%d\n",RTCount);
    exit(1);
  }
  wpp = RT.rtwps;
  for(i=0; wpp != NULL; i++) {
    x[i] = wpp->wplong;  y[i] = wpp->wplat;
    wpl = wpp;
    wpp = wpp->wpnext;
    free(wpl);
  }
  prto = SHPCreateObject(RTTYPE,RTCount,0,NULL,NULL,RTlgth,x,y,NULL,NULL);
  entno = SHPWriteObject(SHPFile,-1,prto);
  SHPDestroyObject(prto);
  free(x);  free(y);

  if (DBFWriteStringAttribute(DBFFile,entno,RTIdField,RT.rtid) == 0 ||
      DBFWriteStringAttribute(DBFFile,entno,RTCommtField,RT.rtcommt) == 0) {
    fprintf(stderr,"failed to write .dbf field, RT #%d\n",RTCount);
    exit(1);    
  }
  RTCount++;
}

void transRTs()
{ WPLIST curr, prev;
  int on;

  if ((RTIdField=DBFAddField(DBFFile,"id",FTString,RTIDWD,0)) == -1 ||
      (RTCommtField=DBFAddField(DBFFile,"commt",FTString,RTCOMMTWD,0)) == -1) {
    fprintf(stderr,"failed to add .dbf field\n");
    exit(1);
  }
  do {
    if (badRT()) {
      fprintf(stderr,"bad RT: %s\n",Buffer);
      exit(1);
    }
    if ((curr=RT.rtwps=(WPLIST) malloc(sizeof(WPDATA))) == NULL) {
      fprintf(stderr,"out of memory!");
      exit(1);
    }
    if (! getline(1) || badWP(curr)) {
      fprintf(stderr,"route without valid WPs");
      exit(1);
    }
    RTlgth = 0;
    do {
      RTlgth++;
      prev = curr;
      if ((curr=(WPLIST) malloc(sizeof(WPDATA))) == NULL) {
	fprintf(stderr,"out of memory!");
	exit(1);
      }
      prev->wpnext = curr;
    } while ((on=getline(1)) && ! badWP(curr));
    free(curr);
    prev->wpnext = NULL;
    saveRT();
  } while (on);
}

int badTP(TPLIST ptp)
{ char *p = Buffer;

  if (*p++ != 'T' || *p++ != '\t' || cpnotab(&p,ptp->tpdate))  return 1;
  return sscanf(p,"%lf\t%lf",&ptp->tplat,&ptp->tplong) != 2;
}

void saveTR()  /* and free TP list (i.e., the track) */
{ TPLIST tpp, tpl;
  SHPObject *ptro;
  int entno, i;
  double *x, *y;

#ifdef DEBUG
  tpp = TR;
  while (tpp != NULL) {
    printf("T\t%s\t%lf\t%lf\n",tpp->tpdate,tpp->tplat,tpp->tplong);
    tpp = tpp->tpnext;
  }
  putchar('\n');
#endif

  if ((x=(double *) malloc(TRlgth*sizeof(double))) == NULL ||
      (y=(double *) malloc(TRlgth*sizeof(double))) == NULL) {
    fprintf(stderr,"out of memory, TR #%d\n",TRCount);
    exit(1);
  }
  tpp = TR;
  for(i=0; tpp != NULL; i++) {
    x[i] = tpp->tplong;  y[i] = tpp->tplat;
    tpl = tpp;
    tpp = tpp->tpnext;
    free(tpl);
  }
  ptro = SHPCreateObject(TRTYPE,TRCount,0,NULL,NULL,TRlgth,x,y,NULL,NULL);
  entno = SHPWriteObject(SHPFile,-1,ptro);
  SHPDestroyObject(ptro);
  free(x);  free(y);

  if (DBFWriteStringAttribute(DBFFile,TRCount,TRDateField,TR->tpdate) == 0) {
    fprintf(stderr,"failed to write .dbf field, TR #%d\n",TRCount);
    exit(1);    
  }
  TRCount++;
}

void transTRs()
{ TPLIST curr, prev;

  if ((TRDateField=DBFAddField(DBFFile,"date",FTString,TRDATEWD,0)) == -1) {
    fprintf(stderr,"failed to add .dbf field\n");
    exit(1);
  }
  do {
    if ((TR=curr=(TPLIST) malloc(sizeof(TPDATA))) == NULL) {
      fprintf(stderr,"out of memory!");
      exit(1);
    }
    TRlgth = 0;
    do {
      if (badTP(curr)) {
	fprintf(stderr,"invalid TP in TR: %s\n",Buffer);
	exit(1);
      }
      TRlgth++;
      prev = curr;
      if ((curr=(TPLIST) malloc(sizeof(TPDATA))) == NULL) {
	fprintf(stderr,"out of memory!");
	exit(1);
      }
      prev->tpnext = curr;
    } while (getline(0) && Buffer[0]);
    free(curr);
    prev->tpnext = NULL;
    saveTR();
  } while (getline(1));
}

int main(int argc, char *argv[])
{
  if (argc != 2) {
    fprintf(stderr,"Usage: gpstr2shp BASENAME\n");
    exit(1);
  }
  if (badheader())  exit(1);
  if (! getline(1)) {
    fprintf(stderr,"no data found\n");
    exit(1);
  }
  if ((DBFFile=DBFCreate(argv[1])) == NULL) {
    fprintf(stderr,"cannot open %s for writing as .dbf\n",argv[1]);
    exit(1);
  }
  switch (Buffer[0]) {
  case 'W':
    if ((SHPFile=SHPCreate(argv[1],WPTYPE)) == NULL) {
      fprintf(stderr,"cannot open %s for writing\n",argv[1]);
      exit(1);
    }
    transWPs();
    break;
  case 'R':
    if ((SHPFile=SHPCreate(argv[1],RTTYPE)) == NULL) {
      fprintf(stderr,"cannot open %s for writing\n",argv[1]);
      exit(1);
    }
    transRTs();
    break;
  case 'T':
    if ((SHPFile=SHPCreate(argv[1],TRTYPE)) == NULL) {
      fprintf(stderr,"cannot open %s for writing\n",argv[1]);
      exit(1);
    }
    transTRs();
    break;
  default: fprintf(stderr,"bad line: %s\n",Buffer);
    exit(1);
  }
  SHPClose(SHPFile);  DBFClose(DBFFile);
  exit(0);
}
