/** \file nmeaTest.cc

   Unit Tests for NMEA and subclasses. 

   @see NMEA.cc
  \author k. headley
   copyright MBARI 2008 

*/
/****************************************************************************/
/* Copyright (c) 2008 MBARI                                                 */
/* MBARI Proprietary Information. All rights reserved.                      */
/****************************************************************************/
/* Summary  :                                                               */
/* Filename : nmeaTest.cc                                                   */
/* Author   : K Headley                                                     */
/* Project  : Benthic Imaging AUV                                           */
/* Created  : 04/01/2008                                                    */
/****************************************************************************/
/* Modification History:                                                    */
/*  $Id: nmeaTest.cc,v 1.5 2009/02/11 02:01:18 headley Exp $   */
/*  $Name: FastSim $ */
/****************************************************************************/

#include "NMEA.h"
#include "Utils.h"
#include "Bicam.h"
#include "BicamConsts.h"

/* external functions */
/* local functions */
int testGpggaSetFields();
int testGprmcSetFields();
int testChecksum();
int testGpggaToString();
int testGprmcToString();
int testGpggaTime(boolean verbose, struct timeval tval, NMEA::TimestampResolution res);
int testGpggaTime(boolean verbose, struct timeval tval, NMEA::TimestampResolution res);

/** Test GPGGA class setFields method
    @returns OK if successful, ERROR otherwise
 */
int testGpggaSetFields(){
  int errors=0;
  time_t now;
  time(&now);
  GPGGA gpgga;
  /* test invalid (under range) */
  gpgga.setTIME(now);
  if(gpgga.setLATD(-91)!=ERROR){
    cout<<"ERROR: LATD (under range)"<<endl;
    errors++;
  }
  if(gpgga.setLATM(-0.9999)!=ERROR){
    cout<<"ERROR: LATM (under range)"<<endl;
    errors++;
  }
  if(gpgga.setLATH('A')!=ERROR){
    cout<<"ERROR: LATH (under range)"<<endl;
    errors++;
  }
  if(gpgga.setLOND(-181)!=ERROR){
    cout<<"ERROR: LOND (under range)"<<endl;
    errors++;
  }
  if(gpgga.setLONM(-0.0001)!=ERROR){
    cout<<"ERROR: LONM (under range)"<<endl;
    errors++;
  }
  if(gpgga.setLONH('A')!=ERROR){
    cout<<"ERROR: LONH (under range)"<<endl;
    errors++;
  }
  if(gpgga.setQUAL(-1)!=ERROR){
    cout<<"ERROR: QUAL (under range)"<<endl;
    errors++;
  }
  if(gpgga.setSIU(-1)!=ERROR){
    cout<<"ERROR: SIU (under range)"<<endl;
    errors++;
  }
  if(gpgga.setHDOP(0.4999)!=ERROR){
    cout<<"ERROR: HDOP (under range)"<<endl;
    errors++;
  }
  if(gpgga.setALT(-10000.0)!=ERROR){
    cout<<"ERROR: ALT (under range)"<<endl;
    errors++;
  }
  if(gpgga.setALTU('A')!=ERROR){
    cout<<"ERROR: ALTU (under range)"<<endl;
    errors++;
  }
  if(gpgga.setGEOH(-1000.0)!=ERROR){
    cout<<"ERROR: GEOH (under range)"<<endl;
    errors++;
  }
  if(gpgga.setGEOHU('A')!=ERROR){
    cout<<"ERROR: GEOHU (under range)"<<endl;
    errors++;
  }
  if(gpgga.setDUPT(-1)!=ERROR){
    cout<<"ERROR: DUPT (under range)"<<endl;
    errors++;
  }
  if(gpgga.setDRID(-1)!=ERROR){
    cout<<"ERROR: DRID (under range)"<<endl;
    errors++;
  }

  /* test invalid fields (over range)*/
  if(gpgga.setLATD(91)!=ERROR){
    cout<<"ERROR: LATD (over range)"<<endl;
    errors++;
  }
  if(gpgga.setLATM(60.0000)!=ERROR){
    cout<<"ERROR: LATM (over range)"<<endl;
    errors++;
  }
  if(gpgga.setLATH('Z')!=ERROR){
    cout<<"ERROR: LATH (over range)"<<endl;
    errors++;
  }
  if(gpgga.setLOND(181)!=ERROR){
    cout<<"ERROR: LOND (over range)"<<endl;
    errors++;
  }
  if(gpgga.setLONM(60.0000)!=ERROR){
    cout<<"ERROR: LONM (over range)"<<endl;
    errors++;
  }
  if(gpgga.setLONH('Z')!=ERROR){
    cout<<"ERROR: LONH (over range)"<<endl;
    errors++;
  }
  if(gpgga.setQUAL(9)!=ERROR){
    cout<<"ERROR: QUAL (over range)"<<endl;
    errors++;
  }
  if(gpgga.setSIU(13)!=ERROR){
    cout<<"ERROR: SIU (over range)"<<endl;
    errors++;
  }
  if(gpgga.setHDOP(100.0)!=ERROR){
    cout<<"ERROR: HDOP (over range)"<<endl;
    errors++;
  }
  if(gpgga.setALT(100000.0)!=ERROR){
    cout<<"ERROR: ALT (over range)"<<endl;
    errors++;
  }
  if(gpgga.setALTU('Z')!=ERROR){
    cout<<"ERROR: ALTU (over range)"<<endl;
    errors++;
  }
  if(gpgga.setGEOH(10000.0)!=ERROR){
    cout<<"ERROR: GEOH (over range)"<<endl;
    errors++;
  }
  if(gpgga.setGEOHU('Z')!=ERROR){
    cout<<"ERROR: GEOHU (over range)"<<endl;
    errors++;
  }
  if(gpgga.setDUPT(1001)!=ERROR){
    cout<<"ERROR: DUPT (over range)"<<endl;
    errors++;
  }
  if(gpgga.setDRID(1024)!=ERROR){
    cout<<"ERROR: DRID (over range)"<<endl;
    errors++;
  }

  /* test valid fields (min range)*/
   if(gpgga.setLATD(-90)==ERROR){
    cout<<"ERROR: LATD (min range)"<<endl;
    errors++;
  }
  if(gpgga.setLATM(0.0000)==ERROR){
    cout<<"ERROR: LATM (min range)"<<endl;
    errors++;
  }
  if(gpgga.setLATH('S')==ERROR){
    cout<<"ERROR: LATH (min range)"<<endl;
    errors++;
  }
  if(gpgga.setLOND(-180)==ERROR){
    cout<<"ERROR: LOND (min range)"<<endl;
    errors++;
  }
  if(gpgga.setLONM(0.0000)==ERROR){
    cout<<"ERROR: LONM (min range)"<<endl;
    errors++;
  }
  if(gpgga.setLONH('E')==ERROR){
    cout<<"ERROR: LONH (min range)"<<endl;
    errors++;
  }
  if(gpgga.setQUAL(0)==ERROR){
    cout<<"ERROR: QUAL (min range)"<<endl;
    errors++;
  }
  if(gpgga.setSIU(0)==ERROR){
    cout<<"ERROR: SIU (min range)"<<endl;
    errors++;
  }
  if(gpgga.setHDOP(0.5)==ERROR){
    cout<<"ERROR: HDOP (min range)"<<endl;
    errors++;
  }
  if(gpgga.setALT(-9999.9)==ERROR){
    cout<<"ERROR: ALT (min range)"<<endl;
    errors++;
  }
  if(gpgga.setALTU('M')==ERROR){
    cout<<"ERROR: ALTU (min range)"<<endl;
    errors++;
  }
  if(gpgga.setGEOH(-999.9)==ERROR){
    cout<<"ERROR: GEOH (min range)"<<endl;
    errors++;
  }
  if(gpgga.setGEOHU('M')==ERROR){
    cout<<"ERROR: GEOHU (min range)"<<endl;
    errors++;
  }
  if(gpgga.setDUPT(0)==ERROR){
    cout<<"ERROR: DUPT (min range)"<<endl;
    errors++;
  }
  if(gpgga.setDRID(0)==ERROR){
    cout<<"ERROR: DRID (min range)"<<endl;
    errors++;
  }

  /* test valid fields (max range)*/
    if(gpgga.setLATD(90)==ERROR){
    cout<<"ERROR: LATD (max range)"<<endl;
    errors++;
  }
  if(gpgga.setLATM(59.9999)==ERROR){
    cout<<"ERROR: LATM (max range)"<<endl;
    errors++;
  }
  if(gpgga.setLATH('N')==ERROR){
    cout<<"ERROR: LATH (max range)"<<endl;
    errors++;
  }
  if(gpgga.setLOND(180)==ERROR){
    cout<<"ERROR: LOND (max range)"<<endl;
    errors++;
  }
  if(gpgga.setLONM(59.9999)==ERROR){
    cout<<"ERROR: LONM (max range)"<<endl;
    errors++;
  }
  if(gpgga.setLONH('W')==ERROR){
    cout<<"ERROR: LONH (max range)"<<endl;
    errors++;
  }
  if(gpgga.setQUAL(8)==ERROR){
    cout<<"ERROR: QUAL (max range)"<<endl;
    errors++;
  }
  if(gpgga.setSIU(12)==ERROR){
    cout<<"ERROR: SIU (max range)"<<endl;
    errors++;
  }
  if(gpgga.setHDOP(99.9)==ERROR){
    cout<<"ERROR: HDOP (max range)"<<endl;
    errors++;
  }
  if(gpgga.setALT(99999.9)==ERROR){
    cout<<"ERROR: ALT (max range)"<<endl;
    errors++;
  }
  if(gpgga.setALTU('F')==ERROR){
    cout<<"ERROR: ALTU (max range)"<<endl;
    errors++;
  }
  if(gpgga.setGEOH(9999.9)==ERROR){
    cout<<"ERROR: GEOH (max range)"<<endl;
    errors++;
  }
  if(gpgga.setGEOHU('F')==ERROR){
    cout<<"ERROR: GEOHU (max range)"<<endl;
    errors++;
  }
  if(gpgga.setDUPT(1000)==ERROR){
    cout<<"ERROR: DUPT (max range)"<<endl;
    errors++;
  }
  if(gpgga.setDRID(1023)==ERROR){
    cout<<"ERROR: DRID (max range)"<<endl;
    errors++;
  }

  return OK;
}

/** Test GPRMC class setFields method
    @returns OK if successful, ERROR otherwise
 */
int testGprmcSetFields(){
  int errors=0;
  time_t now;
  time(&now);
  GPRMC gprmc;
  /* test invalid (under range) */
  gprmc.setTIME(now);
  gprmc.setDATE(now);

  if(gprmc.setLATD(-91)!=ERROR){
    cout<<"ERROR: LATD (under range)"<<endl;
    errors++;
  }
  if(gprmc.setLATM(-0.9999)!=ERROR){
    cout<<"ERROR: LATM (under range)"<<endl;
    errors++;
  }
  if(gprmc.setLATH('A')!=ERROR){
    cout<<"ERROR: LATH (under range)"<<endl;
    errors++;
  }
  if(gprmc.setLOND(-181)!=ERROR){
    cout<<"ERROR: LOND (under range)"<<endl;
    errors++;
  }
  if(gprmc.setLONM(-0.0001)!=ERROR){
    cout<<"ERROR: LONM (under range)"<<endl;
    errors++;
  }
  if(gprmc.setLONH('A')!=ERROR){
    cout<<"ERROR: LONH (under range)"<<endl;
    errors++;
  }
  if(gprmc.setSTAT(-1)!=ERROR){
    cout<<"ERROR: STAT (under range)"<<endl;
    errors++;
  }
  if(gprmc.setSOG(-0.1)!=ERROR){
    cout<<"ERROR: SOG (under range)"<<endl;
    errors++;
  }
  if(gprmc.setCOG(-0.1)!=ERROR){
    cout<<"ERROR: COG (under range)"<<endl;
    errors++;
  }
  if(gprmc.setMVM(-1000.0)!=ERROR){
    cout<<"ERROR: MVM (under range)"<<endl;
    errors++;
  }
  if(gprmc.setMVD('A')!=ERROR){
    cout<<"ERROR: MVD (under range)"<<endl;
    errors++;
  }

  /* test invalid fields (over range)*/
  if(gprmc.setLATD(91)!=ERROR){
    cout<<"ERROR: LATD (over range)"<<endl;
    errors++;
  }
  if(gprmc.setLATM(60.0000)!=ERROR){
    cout<<"ERROR: LATM (over range)"<<endl;
    errors++;
  }
  if(gprmc.setLATH('Z')!=ERROR){
    cout<<"ERROR: LATH (over range)"<<endl;
    errors++;
  }
  if(gprmc.setLOND(181)!=ERROR){
    cout<<"ERROR: LOND (over range)"<<endl;
    errors++;
  }
  if(gprmc.setLONM(60.0000)!=ERROR){
    cout<<"ERROR: LONM (over range)"<<endl;
    errors++;
  }
  if(gprmc.setLONH('Z')!=ERROR){
    cout<<"ERROR: LONH (over range)"<<endl;
    errors++;
  }
  if(gprmc.setSOG(1000.0)!=ERROR){
    cout<<"ERROR: SOG (over range)"<<endl;
    errors++;
  }
  if(gprmc.setCOG(360.0)!=ERROR){
    cout<<"ERROR: COG (over range)"<<endl;
    errors++;
  }
  if(gprmc.setSTAT('Z')!=ERROR){
    cout<<"ERROR: STAT (over range)"<<endl;
    errors++;
  }
  if(gprmc.setMVM(10000.0)!=ERROR){
    cout<<"ERROR: MVM (over range)"<<endl;
    errors++;
  }
  if(gprmc.setMVD('Z')!=ERROR){
    cout<<"ERROR: MVD (over range)"<<endl;
    errors++;
  }

  /* test valid fields (min range)*/
   if(gprmc.setLATD(-90)==ERROR){
    cout<<"ERROR: LATD (min range)"<<endl;
    errors++;
  }
  if(gprmc.setLATM(0.0000)==ERROR){
    cout<<"ERROR: LATM (min range)"<<endl;
    errors++;
  }
  if(gprmc.setLATH('S')==ERROR){
    cout<<"ERROR: LATH (min range)"<<endl;
    errors++;
  }
  if(gprmc.setLOND(-180)==ERROR){
    cout<<"ERROR: LOND (min range)"<<endl;
    errors++;
  }
  if(gprmc.setLONM(0.0000)==ERROR){
    cout<<"ERROR: LONM (min range)"<<endl;
    errors++;
  }
  if(gprmc.setLONH('E')==ERROR){
    cout<<"ERROR: LONH (min range)"<<endl;
    errors++;
  }
  if(gprmc.setSOG(0.0)==ERROR){
    cout<<"ERROR: SOG (min range)"<<endl;
    errors++;
  }
  if(gprmc.setCOG(000.0)==ERROR){
    cout<<"ERROR: COG (min range)"<<endl;
    errors++;
  }
  if(gprmc.setSTAT('A')==ERROR){
    cout<<"ERROR: STAT (min range)"<<endl;
    errors++;
  }
  if(gprmc.setMVM(000.0)==ERROR){
    cout<<"ERROR: MVM (min range)"<<endl;
    errors++;
  }
  if(gprmc.setMVD('E')==ERROR){
    cout<<"ERROR: MVD (min range)"<<endl;
    errors++;
  }

  /* test valid fields (max range)*/
    if(gprmc.setLATD(90)==ERROR){
    cout<<"ERROR: LATD (max range)"<<endl;
    errors++;
  }
  if(gprmc.setLATM(59.9999)==ERROR){
    cout<<"ERROR: LATM (max range)"<<endl;
    errors++;
  }
  if(gprmc.setLATH('N')==ERROR){
    cout<<"ERROR: LATH (max range)"<<endl;
    errors++;
  }
  if(gprmc.setLOND(180)==ERROR){
    cout<<"ERROR: LOND (max range)"<<endl;
    errors++;
  }
  if(gprmc.setLONM(59.9999)==ERROR){
    cout<<"ERROR: LONM (max range)"<<endl;
    errors++;
  }
  if(gprmc.setLONH('W')==ERROR){
    cout<<"ERROR: LONH (max range)"<<endl;
    errors++;
  }
  if(gprmc.setSOG(999.9)==ERROR){
    cout<<"ERROR: SOG (max range)"<<endl;
    errors++;
  }
  if(gprmc.setCOG(359.9)==ERROR){
    cout<<"ERROR: COG (max range)"<<endl;
    errors++;
  }
  if(gprmc.setSTAT('V')==ERROR){
    cout<<"ERROR: STAT (max range)"<<endl;
    errors++;
  }
  if(gprmc.setMVM(180.0)==ERROR){
    cout<<"ERROR: MVM (max range)"<<endl;
    errors++;
  }
  if(gprmc.setMVD('W')==ERROR){
    cout<<"ERROR: MVD (max range)"<<endl;
    errors++;
  }

  return OK;
}

/** Test NMEA checksum method
    @returns OK if successful, ERROR otherwise
 */
int testChecksum(){
  char *cp;
  unsigned short int chksum;

  /* test valid string with known value */
  GPGGA nmea;

  cp="$GPRMC,171319,A,3650.0256,N,12154.1646,W,000.4,345.1,210607,014.8,E*67";
  try{
    chksum=nmea.checksum(cp);
  }catch(InvalidNMEAException i){
    cout<<"caught InvalidNMEAException: "<<i.msg()<<endl;
    return ERROR;
  }

  if(chksum!=0x67){
    cout<<"ERROR: Incorrect checksum"<<endl;
    return ERROR;
  }
  printdbg("testChecksum: checksum test returned %02X for %s\r\n",chksum,cp);

  /* test invalid string */
  cp="GPRMC,171319,A,3650.0256,N,12154.1646,W,000.4,345.1,210607,014.8,E*67";
  try{
    chksum=nmea.checksum(cp);
    cout << "Should have caught InvalidNMEAException but didn't" <<endl;
    return ERROR;
  }catch(InvalidNMEAException i){
    //cout<<"caught InvalidNMEAException, as it should have: "<<i.msg()<<endl;
  }
  return OK;
}

/** test GPGGA class toString method
    @returns OK if successful, ERROR otherwise
 */
int testGpggaToString(boolean verbose){
  GPGGA gpgga;
  const char* checkStr="$GPGGA,233130.00,1234.5600,N,12345.6700,E,0,00,01.2,001.2,M,12.3,M,,*5a";
  const char* gpggaStr;
  unsigned int i;
  //23:31:30 UTC on February 13, 2009:the Unix time number 1234567890 seconds
  
  // Initialize a GPGGA object
  time_t now=1234567890;
  struct timeval tvnow;

  tvnow.tv_sec=now;
  tvnow.tv_usec=0;
  gpgga.setTIME(now);
  gpgga.setTIME((struct timeval)tvnow);
  gpgga.setLATD(12);
  gpgga.setLATM(34.56);
  gpgga.setLATH('N');
  gpgga.setLOND(123);
  gpgga.setLONM(45.67);
  gpgga.setLONH('E');
  gpgga.setQUAL('A');
  gpgga.setHDOP(1.2);
  gpgga.setALT(1.234);
  gpgga.setALTU('M');
  gpgga.setSIU(123);
  gpgga.setGEOH(12.3);
  gpgga.setGEOHU('M');
  gpgga.setDUPT(1);
  gpgga.setDRID(1);

  gpggaStr=gpgga.toString();

  if(verbose){
    // print GPGGA object
    cout<< endl<< gpggaStr << endl <<checkStr <<endl;
  }

  if(strcmp(gpggaStr,checkStr)==0){
    return OK;
  }

  if(verbose){
    cout<<dec<<"strlen gpgga:"<<strlen(gpggaStr)<<" strlen expected:"<<strlen(checkStr)<<" strcmp returned:"<<strcmp(gpggaStr,checkStr)<<endl;

    for(i=0;i<strlen(checkStr);i++){
      if(gpggaStr[i]!=checkStr[i]){
	printf("character %2d is mismatched: gpgga[%d]=0x%02x (%c) expected[%d]=0x%02x (%c)\n",i,i,gpggaStr[i],gpggaStr[i],i,checkStr[i],checkStr[i]);
      }
    }
  }

  return ERROR;
}

/** Test GPRMC toString method
    @returns OK if successful, ERROR otherwise
 */
int testGprmcToString(boolean verbose){
  GPRMC gprmc;
  const char* checkStr="$GPRMC,233130.00,V,1234.5678,N,12345.6789,E,001.2,123.4,130209,012.3,E*48";
  //23:31:30 UTC on February 13, 2009:the Unix time number 1234567890 seconds
  const char *gprmcStr;
  unsigned int i;

  // Initialize a GPRMC object
  // Make sure to feed in set valid values
  // since we're not checking return values
  // of setXXXX() for errors...
  time_t now=1234567890;
  struct timeval tvnow;
  tvnow.tv_sec=now;
  tvnow.tv_usec=0;
  gprmc.setTIME((time_t)now);
  gprmc.setTIME((struct timeval)tvnow);
  gprmc.setSTAT('V');
  gprmc.setLATD(12);
  gprmc.setLATM(34.5678);
  gprmc.setLATH('N');
  gprmc.setLOND(123);
  gprmc.setLONM(45.6789);
  gprmc.setLONH('E');
  gprmc.setSOG(1.2);
  gprmc.setCOG(123.4);
  gprmc.setDATE(now);
  gprmc.setMVM(12.3);
  gprmc.setMVD('E');

  gprmcStr=gprmc.toString();

  if(verbose){
    // print GPRMC object
    cout<< endl<< gprmcStr<<endl<<checkStr<<endl;
  }

  if(strcmp(gprmcStr,checkStr)==0){
    return OK;
  }

  if(verbose){
    cout<<dec<<"strlen gprmc:"<<strlen(gprmcStr)<<" strlen expected:"<<strlen(checkStr)<<" strcmp returned:"<<strcmp(gprmcStr,checkStr)<<endl;
    for(i=0;i<strlen(checkStr);i++){
      if(gprmcStr[i]!=checkStr[i]){
	printf("gprmc[%d]=0x%02x (%c) does not match test string: 0x%02x (%c)\n",i,gprmcStr[i],gprmcStr[i],checkStr[i],checkStr[i]);
      }
    }
  }\
  return ERROR;
}

/** test GPGGA class toString method
    @returns OK if successful, ERROR otherwise
 */
int testGpggaTime(boolean verbose, struct timeval tval, NMEA::TimestampResolution res){
  GPGGA gpgga;
  const char* gpggaStr;
  time_t now;
  char buf[32];
  struct tm *pt;
  int hours;
  int min;
  double sec;
  
  // Initialize a GPGGA object
  now=tval.tv_sec;

  gpgga.setTIME(now);
  gpgga.setTIME(tval);
  gpgga.setLATD(-52);
  gpgga.setLATM(30.0);
  gpgga.setLATH('N');
  gpgga.setLOND(-123);//-123
  gpgga.setLONM(52);
  gpgga.setLONH('E');
  gpgga.setQUAL('A');
  gpgga.setHDOP(1.2);
  gpgga.setALT(1.234);
  gpgga.setALTU('M');
  gpgga.setSIU(123);
  gpgga.setGEOH(12.3);
  gpgga.setGEOHU('M');
  gpgga.setDUPT(1);
  gpgga.setDRID(1);

  gpgga.timestampResolution(res);

  gpggaStr=gpgga.toString();

  // print GPGGA object
  if(verbose){
    cout<< endl<<gpggaStr << endl;
  }
  pt=gmtime( &tval.tv_sec );
  hours= pt->tm_hour;
  min  = pt->tm_min;
  sec  = (double)pt->tm_sec+((double)tval.tv_usec/1000000.0);
  sprintf(buf,"%02d%02d%0*.*f,",hours,min,(res+2),res,sec);

  if(verbose){
    cout<< "test time:"<<buf<<endl;
  }

  if(strstr(gpggaStr,buf)>0){
    return OK;
  }
  return ERROR;
}

/** Test GPRMC toString method
    @returns OK if successful, ERROR otherwise
 */
int testGprmcTime(boolean verbose, struct timeval tval, NMEA::TimestampResolution res){
  GPRMC gprmc;
  const char *gprmcStr;
  time_t now;
  char buf[32];
  struct tm *pt;
  int hours;
  int min;
  double sec;

  // Initialize a GPRMC object
  // Make sure to feed in set valid values
  // since we're not checking return values
  // of setXXXX() for errors...

  now=tval.tv_sec;

  gprmc.setTIME(now);
  gprmc.setTIME(tval);
  gprmc.setSTAT('V');
  gprmc.setLATD(-52);
  gprmc.setLATM(30.0);
  gprmc.setLATH('N');
  gprmc.setLOND(52);
  gprmc.setLONM(30.0);
  gprmc.setLONH('E');
  gprmc.setSOG(1.0);
  gprmc.setCOG(100.0);
  gprmc.setDATE(now);
  gprmc.setMVM(10.0);
  gprmc.setMVD('E');

  gprmc.timestampResolution(res);

  // print GPRMC object
  gprmcStr=gprmc.toString();
  if(verbose){
    cout<< endl<<gprmcStr<<endl;
  }

  pt=gmtime( &tval.tv_sec );
  hours= pt->tm_hour;
  min  = pt->tm_min;
  sec  = (double)pt->tm_sec+((double)tval.tv_usec/1000000.0);
  sprintf(buf,"%02d%02d%0*.*f,",hours,min,(res+2),res,sec);

  if(verbose){
    cout<< "test time:"<<buf<<endl;
  }

  if(strstr(gprmcStr,buf)>0){
    return OK;
  }
  return ERROR;
}

void printUsage(char *prog){
  cout<<endl<<"  Usage: "<<prog<<" [-v] [--help]"<<endl<<endl;
  cout<<"      -v : verbose output"<<endl;
  cout<<"  --help : print this help message"<<endl;
  cout<<endl;
}

/** NMEA unit tests entry point. */
int main (int argc, char *argv[]) {
  int test;
  boolean _verbose=FALSE;
  int i=0;
  struct timeval now;

  if(argc>1){
    for(i=1;i<argc;i++){
      if(strcmp(argv[i],"--help")==0){
	printUsage(argv[0]);
	return 0;
      }
      if(strcmp(argv[i],"-v")==0){
	_verbose=TRUE;
      }
    }
  }

  // test GPGGA setFields
  test=testGpggaSetFields();
  printf("testGpggaSetFields : %s\n",(test==OK?"OK":"ERR"));

  // test GPRMC setFields
  test=testGprmcSetFields();
  printf("testGprmcSetFields : %s\n",(test==OK?"OK":"ERR"));

  // test checksum
  test=testChecksum();
  printf("testChecksum       : %s\n",(test==OK?"OK":"ERR"));

  // test GPGGA toString
  test=testGpggaToString(_verbose);
  printf("testGpggaToString  : %s\n",(test==OK?"OK":"ERR"));

  // test GPRMC toString
  test=testGprmcToString(_verbose);
  printf("testGprmcToString  : %s\n",(test==OK?"OK":"ERR"));
  
  gettimeofday(&now,NULL);
  test=testGpggaTime(_verbose,now,NMEA::MILLISEC);
  printf("testGpggaTime : %s\n",(test==OK?"OK":"ERR"));

  test=testGprmcTime(_verbose,now,NMEA::DECISEC);
  printf("testGprmcTime : %s\n",(test==OK?"OK":"ERR"));

  return 0;
}
