/******************************************************************************/
/** Copyright 2007 MBARI                                                      */
/******************************************************************************/
/******************************************************************************/
/** Filename : SAMPLER.C                                                      */
/** Author   : Luke Coletti                                                   */
/** Project  : ISUS_ARGO                                                      */
/** Version  : 1.00                                                           */
/** Created  : 04/09/07                                                       */
/** Compiler : Mix Power C ver 2.2.0                                          */
/** Ext_Libs : MarshallSoft PCL4C Comms Lib v 6.2                             */
/** Archived :                                                                */
/** Summary  : Program to initiate an ISUS Sample Cycle at a specified rate:  */
/**            0) Retrieve ISUS Configuration Data (C command)                */
/**            1) Wake ISUS (W command)                                       */
/**            2) Send CTD values to ISUS (CTD command)                       */
/**            3) Initiate Sample Collection (TS command)                     */
/**            4) Retrieve Sample Data (SL command)                           */
/**            5) Put ISUS to Sleep (SLP command)                             */
/**            6) Do it again...                                              */
/******************************************************************************/
/** Modification History: LJC - 3/9/16 Ported to Win32 from DOS16             */
/******************************************************************************/

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <conio.h>
#include <time.h>
#include <math.h>
#include <getopt.h>
#include <errno.h>
#include <sys\stat.h>
#include <sys\types.h>
#include <sys\timeb.h>

#include "userio.h"
#include "wsc.h"
#include "keycode.h"
#include "sayerror.h"

//---------------------------------------------------------
//
// Enable hardware flow control (USE_HARDWARE_FLOW_CONTROL)
// if and only if connected to a serial device with hardware
// flow control enabled..
//
// #define USE_HARDWARE_FLOW_CONTROL
//
//---------------------------------------------------------
//
// Enabling Overlapped I/O (USE_OVERLAPPED_IO) can improve
// application program performance. However, some virtual
// serial port drivers (such as some USB-RS232 converters)
// do not implement overlapped I/O.
//
// Overlapped I/O is disabled by default (WSC 5.4.1 & up).
//
// #define USE_OVERLAPPED_IO
//
//---------------------------------------------------------

#define PACKAGE    			"msctime"
#define VERSION    			"1.0"

#define FIRST_PORT 			COM1
#define LAST_PORT  			COM256

#define TRUE                1
#define FALSE               0
#define OK                  0
//#define ERROR               (-1)
#define TIMEOUT             (-2)
#define CTRL_C              0x03

#define	ISUS_MODE			1
#define DURA_MODE			2

#define CMD_ACK_TIMEOUT     2       //seconds

typedef unsigned char       uchar;
typedef unsigned short      ushort;
typedef unsigned int        unsint;
typedef unsigned long       ulong;

/*** Global Variables ***/
static char *BaudRate[10] 			= {"0","300","1200","2400","4800","9600","19200","38400","57600","115200"};

int  	MSC_port;

/* MSC Command prototypes */
int    	WakeUpMsc(int port, ushort mode);
int    	SetMscToIsusMode(int port);
int    	SetMscToDuraMode(int port);
int    	ConfirmMscCommandMode(int port);
int    	PutMscToSleep(int port);
time_t	GetMscTime(int port);
int		SetMscTime(int port);
int		SetMscRtcToPcTime(int port);

/* Serial I/O prototypes */
int    	SerGetcTmout(int port, ushort secs);
int    	SerGetsTmout(int port, char *buf, ushort buflen, ushort secs);
int    	SerPuts(int port, char *str);
long   	msSleep(long ms);
long   	msStopWatch(long ms);
void   	InitCommPort(int port, int baud);
void 	InitCommLib(void);
int    	BaudMatch(char *);

/* Misc prototypes */
void   	ExitProgram(int ExitCode);
int    	ErrorCheck(int Port, int Code);
int    	IsPrintable(char c);
BOOL   	WINAPI BreakHandler(DWORD dwCtrlType);


//---------------------------------------------------------

//---------------------------------------------------------
int main(int argc, char *argv[])
{
char	timebuf[64];

int     MSC_baud;           /* baud rate determined via initial index into BaudRate[] */
int     opt;
int 	Port, Code, CountOK, CountERR; 		//used for port lister option -l
time_t 	msc_time, pc_time;
struct  tm *tp;


	while( (opt = getopt(argc, argv, "P:B:lv")) != -1 ){
		switch( opt ){

			case 'P':
			MSC_port = atoi(optarg) - 1;
			if( (MSC_port<COM1) || (MSC_port>COM20) ){
				printf("Error, MSC CommPort Range = [1,20]\n");
				exit(0);
			}	
			break;


			case 'B':		
			MSC_baud = BaudMatch(optarg);
			if( MSC_baud < 0 ){
				printf("Error, unrecognized MSC baud rate: %s\n",argv[2]);
				exit(0);
			}
			MSC_baud = atoi(BaudRate[MSC_baud]);		
			break;
			
			case 'l':
			InitCommLib();
			CountOK = CountERR = 0;
			printf("\nChecking COM%1d through COM%1d...\n", 1+FIRST_PORT,1+LAST_PORT);
			for( Port=FIRST_PORT; Port<=LAST_PORT; Port++ ){ // reset the port
				Code = SioReset(Port,1024,1024);
				if( Code<0 ){ // error
					if( Code==WSC_IO_ERROR ){// port exists but not enabled
						printf("COM%1d: Port exists but not enabled (event I/O error %d)\n", 1+Port, Code);
						CountERR++;
					}
				continue; // no port
				}
				printf("COM%1d: OK, port exists\n", 1+Port); // found port
				CountOK++;
				SioDone(Port);
			} // end-for
			printf("\n%d port(s) found\n", CountOK);
			if( CountERR>0 )
				printf("%d 'not ready' port(s) found (virtual port not ready?)\n", CountERR);
			ExitProgram(0);
			break;

			case 'v':
			printf("%s %s\n\n", PACKAGE, VERSION); 
			exit(0);
			break;

			default: // ? 
			fprintf(stderr, "Usage: %s -P MSC_Port -B MSC_Baud | -l | -v\n", argv[0]);
			exit(0);
			break;
		}
    }
	   
	if( (optind != 5) || (argc != 5) ){
		fprintf(stderr, "Usage: %s -P MSC_Port -B MSC_Baud | -l | -v\n", argv[0]);
		exit(0);
    }   
	   

//---------------------------------------------------------

//---------------------------------------------------------	
	printf("\n\n***Initializing Communications***\n");
	
	InitCommLib();
    InitCommPort(MSC_port, MSC_baud);
   	SetConsoleCtrlHandler(BreakHandler, TRUE);
	
//---------------------------------------------------------

//---------------------------------------------------------		
    printf("\n***Getting MSC and PC GMT Time***\n");

	msc_time = GetMscTime(MSC_port);
	tp = gmtime(&msc_time);
    strftime(timebuf, sizeof(timebuf)-1, "%a %d %b %Y %H:%M:%S", tp);	
	printf("MSC Date/Time = %s\n", timebuf);	

	time(&pc_time);
	tp = gmtime(&pc_time);
    strftime(timebuf, sizeof(timebuf)-1, "%a %d %b %Y %H:%M:%S", tp);	
	printf("PC  Date/Time = %s\n", timebuf);	
			
	if( QueryYesNo("\nSync MSC RTC to PC?", FALSE) )
	  SetMscRtcToPcTime(MSC_port);
  	else
		printf("\n");
	
	SioDone(MSC_port);

	return( OK );
	

} /* end main */

//---------------------------------------------------------

/* MSC Command prototypes */

int WakeUpMsc(int port, ushort mode)
{
char rbuf[32];
short i;


    if( (mode != ISUS_MODE) && (mode != DURA_MODE) )
		return( ERROR );

    SioRxClear(port);

    SioBrkSig(port, 'A');     //send a 0.1 sec BREAK
    msSleep(0);
    msSleep(100);
    SioBrkSig(port, 'C');

    for(i = 10; i; i--){

      SioPutc(port, 'W');

      if( SerGetsTmout(port, rbuf, sizeof(rbuf), (ushort)CMD_ACK_TIMEOUT) > 0 ){
        if( strstr(rbuf, "ACK") != NULL ){
          SioRxClear(port);
		  if( mode == ISUS_MODE )
			SetMscToIsusMode(port);
		  else
			SetMscToDuraMode(port);
		  SioRxClear(port);
          return( TRUE );
          }
        else if( strchr(rbuf, '>') != NULL ){
          printf("\nMSC is in MENU MODE, sending a CTRL_C\n");
          SioPutc(port, (int)CTRL_C);
          SioRxClear(port);
          continue;
          }
        else{
          SioRxClear(port);
          continue;
          }
        }
      else
        SioRxClear(port);
      }

    return( TIMEOUT );

}

int SetMscToIsusMode(int port)
{
char rbuf[32];

    SioRxClear(port);

    SerPuts(port, "MODE,1");

    if( SerGetsTmout(port, rbuf, sizeof(rbuf), (ushort)CMD_ACK_TIMEOUT) > 0 ){
      if( strstr(rbuf, "ACK,MODE,1") != NULL ){
         SerPuts(port, "ACK\n");
         return( TRUE );
        }
      else
        return( ERROR );
      }
    else
      return( TIMEOUT );

}

int SetMscToDuraMode(int port)
{
char rbuf[32];

    SioRxClear(port);

    SerPuts(port, "MODE,2");

    if( SerGetsTmout(port, rbuf, sizeof(rbuf), (ushort)CMD_ACK_TIMEOUT) > 0 ){
      if( strstr(rbuf, "ACK,MODE,2") != NULL ){
         SerPuts(port, "ACK\n");
         return( TRUE );
        }
      else
        return( ERROR );
      }
    else
      return( TIMEOUT );

}

int ConfirmMscCommandMode(int port)
{
char rbuf[32];
short i;

    SioRxClear(port);

    for(i = 10; i; i--){

      SioPutc(port, 'W');

      if( SerGetsTmout(port, rbuf, sizeof(rbuf), (ushort)CMD_ACK_TIMEOUT) > 0 ){
        if( strstr(rbuf, "ACK") != NULL ){
          SioRxClear(port);
          return( TRUE );
          }
        else if( strchr(rbuf, '>') != NULL ){
          printf("\nMSC is in MENU MODE, sending a CTRL_C\n");
          SioPutc(port, (int)CTRL_C);
          SioRxClear(port);
          continue;
          }
        else{
          SioRxClear(port);
          continue;
          }
        }
      else
        SioRxClear(port);
      }

    return( TIMEOUT );

}


int PutMscToSleep(int port)
{
char rbuf[32];
short i;

    SioRxClear(port);

    for(i = 10; i; i--){

      SerPuts(port, "SLP");

      if( SerGetsTmout(port, rbuf, sizeof(rbuf), (ushort)CMD_ACK_TIMEOUT) > 0 ){
        if( strstr(rbuf, "ACK,SLP") != NULL ){
          SioRxClear(port);
          return( TRUE );
          }
        else if( strchr(rbuf, '>') != NULL ){
          printf("\nMSC is in MENU MODE, sending a CTRL_C\n");
          SioPutc(port, (int)CTRL_C);
          SioRxClear(port);
          continue;
          }
        else{
          SioRxClear(port);
          continue;
          }
        }
      else
        SioRxClear(port);
      }

    return( TIMEOUT );

}

time_t GetMscTime(int port)
{
char   rbuf[128];
char   *srchptr;
time_t msc_time;

    ConfirmMscCommandMode(port);

    SioRxClear(port);

    sprintf(rbuf, "RTC,?");     //get MSC time, should always be GMT
    SerPuts(port, rbuf);

    errno = 0;
    if( SerGetsTmout(port, rbuf, sizeof(rbuf), (ushort)CMD_ACK_TIMEOUT) > 0 ){
      srchptr = strchr(strchr(rbuf, '?'), ',');
      msc_time = strtol( ++srchptr, NULL, 10 );
      if( (msc_time <= 0) || (errno == ERANGE) )
        return( ERROR );
      else
        return( msc_time );
      }
    else
      return( TIMEOUT );


}

int SetMscTime(int port)
{
char   rbuf[128];
char   *srchptr;
time_t msc_time, pc_time;
double delta;

    ConfirmMscCommandMode(port);

    SioRxClear(port);

    time(&pc_time);
    sprintf(rbuf, "RTC,%lu", (ulong)pc_time);
    SerPuts(port, rbuf);

    errno = 0;
    if( SerGetsTmout(port, rbuf, sizeof(rbuf), (ushort)CMD_ACK_TIMEOUT) > 0 ){
      printf("\nSetMscTime() buf %s\n", rbuf);
      srchptr = strchr((strchr((strchr(rbuf, ',')+1), ',')+1), ',');
      printf("SetMscTime() ptr %s\n", srchptr+1);
      msc_time = strtol( ++srchptr, NULL, 10 );
      if( (msc_time <= 0) || (errno == ERANGE) )
        return( ERROR );

      delta = fabs( difftime(pc_time, msc_time) );

      if( delta <= 1.0 ){
        SioPuts(port, "ACK\n", 4);
        return( TRUE );
        }
      else{
        SioPuts(port, "NAK\n", 4);
        return( FALSE );
        }
      }
    else
      return( TIMEOUT );


}

int SetMscRtcToPcTime(int port)
{
int     ret;
ulong	input_var;
time_t  pc_time, msc_time;
double  delta;
struct  tm *tp;


    printf("\n***Getting Local Time Zone Offset***\n");

    if( getenv("TZ=") == NULL)
      printf("TZ environment var = NULL!\n");
    else
      printf("TZ environment var = %s\n", getenv("TZ="));

    tzset();      //update the timezone and daylight vars, see compiler's time.c

    printf("Local Time Zone Offset from GMT = %+02ld hrs, %ld secs\n", (timezone/3600), timezone );
    if( QueryYesNo("Modify", FALSE) ){
      while( TRUE ){
        input_var = (long)(timezone/3600);
        QueryNum("\nEnter local time zone offset in hours ( W is [+], E is [-] ) >", "%ld", "%ld", &input_var);
        if( (long)input_var != (timezone/3600) ){
          if( ((long)input_var >= -12) && ((long)input_var <= 12) ){
            timezone = ((long)input_var * 3600);
            printf("\nThe New Local Time Zone Offset = %+02ld hrs, %ld secs\n", (timezone/3600), timezone );
            break;
            }
          else{
            printf("\nInput Error, Time Zone Offset Range=[-12L, 12L], try again!\n");
            continue;
            }
          }
        } //end while
      } //end query if
    else
      printf("\n");

    printf("\n***Getting Local Daylight Savings Setting***\n");

    if(daylight == TRUE)
      printf("Daylight Observation = TRUE\n");
    else
      printf("Daylight Observation = FALSE\n");

    if( QueryYesNo("Modify", FALSE) ){
      while( TRUE ){
        input_var = (long)daylight;
        QueryNum("\nEnter Daylight Observation, [0]=False [1]=True >", "%lu", "%lu", &input_var);
        if( input_var != (long)daylight ){
          if( (input_var >= 0) && (input_var <= 1) ){
            daylight = (int)input_var;
            printf("\nThe New Daylight Adjusment = %d\n", daylight);
            break;
            }
          else{
            printf("\nInput Error, Daylight Adjustment Range=[0, 1], try again!\n");
            continue;
            }
          }
        } //end while
      } //end query if
    else
      printf("\n");

    printf("\n***Getting MSC Time***\n");
    if( (ret=WakeUpMsc(port, ISUS_MODE)) == TRUE ){
      if( (msc_time=GetMscTime(port)) > 0 ){
        time(&pc_time);           //corrects local time to GMT secs using timezone and daylight vars
        tp = gmtime(&pc_time);    //makes no conversion for TZ or DST, it simply takes the seconds from time() and converts it to date/time
        printf("PC  Time (GMT) = %lu = %s", pc_time, asctime(tp) );
        tp = gmtime(&msc_time);  //makes no conversion for TZ or DST, it simply takes the seconds from time() and converts it to date/time
        printf("MSC Time (GMT) = %lu = %s", msc_time, asctime(tp) );
        delta = fabs( difftime(pc_time, msc_time) );
        printf("Time Delta      = %.0lf secs\n", delta );
        if( QueryYesNo("\nTransfer PC Time to MSC", FALSE) ){
          if( (ret=SetMscTime(port)) == TRUE ){
            PutMscToSleep(port);
            printf("\nMSC Time has been set!\n");
            }
          else
            printf("\nERROR Setting MSC Time, ret = %d\n", ret);
          } //end querry if
        else{
          PutMscToSleep(port);
          printf("\n");
          }
        }
      else
        printf("\nERROR Getting MSC Time, ret = %d\n", ret);
    } //end wake if
    else{
      printf("\nERROR Waking MSC, ret = %d, exiting!\n", ret);
      }

	return( OK );

}


/* Serial I/O prototypes */

int SerGetcTmout(int port, ushort secs)
{

    msStopWatch(0);

    while( msStopWatch(1) < (long)(secs*1000) ){
      if ( SioRxQue(port) )
        return( SioGetc(port) );
      }

    return( ERROR );

}


int SerGetsTmout(int port, char *buf, ushort buflen, ushort secs)
{
register int   c;
register int   nchars;

    msStopWatch(0);

    nchars = 0;
    buf[0] = '\0';

    while( msStopWatch(1) < (long)(secs*1000) ){
      if ( SioRxQue(port) ){
        switch( c = SioGetc(port) ){
          case '\n':
          case '\r':
            if(nchars > 0)
              return( nchars );
            else
              break;

          default:
            buf[nchars++] = (char)c;
            buf[nchars] = '\0';
            if ( nchars >= (buflen-1) )
              return( nchars );
            break;
          } // end switch
        } // end if
      }  // end while

    if( nchars > 0 )
      return( nchars );
    else
      return( ERROR );

}


int SerPuts(int port, char *str)
{

    while( *str ){
      if (*str == '\n')
         SioPutc(port, '\r');
       SioPutc(port, *str++);
      }

    /* wait for Transmit Buffer to empty before returning */
    while( SioTxQue(port) != 0 ){
      continue;
      }

    return( TRUE );

}


long msStopWatch(long ms)
{
static struct timeb start, finish;
register long  deltasecs, deltamsecs;

    if( ms == 0 ){
      ftime(&start);
      return( 0 );
      }
    else{
      ftime(&finish);

      deltasecs = (long)(finish.time - start.time);

      deltamsecs = (long)((long)finish.millitm - (long)start.millitm);

      if( deltamsecs < 0 ){
        if( deltasecs != 0 )
          deltasecs--;
        deltamsecs += 1000L;
        }

      deltamsecs = deltamsecs + (deltasecs * 1000L);

      return( deltamsecs );
      }

}

long msSleep(long ms)
{
static struct timeb start, finish;
register long  deltasecs, deltamsecs;
int   i;

    if( ms == 0 ){
      ftime(&start);
      return( 0 );
      }
    else{
      i = 0;
      do{
        ftime(&finish);

        deltasecs = (long)(finish.time - start.time);

        deltamsecs = (long)((long)finish.millitm - (long)start.millitm);

        if( deltamsecs < 0 ){
          if( deltasecs != 0 )
            deltasecs--;
          deltamsecs += 1000L;
          }

        deltamsecs = deltamsecs + (deltasecs * 1000L);

        if( i == 0 ){
          if( deltamsecs > ms )
            return( -1 );       //we've already missed it!
          i = 1;
          }

        }while( deltamsecs < ms );                                                                                                                                                                                                                        \

      return( deltamsecs );
      }

}

int BaudMatch(char *P)
{
int  i;

    /* find baud rate in table */
    for(i=0;i<10;i++) if(strcmp(BaudRate[i],P)==0) return(i);

    return(-1);
}

void InitCommPort(int port, int baud)
{

	// reset (initialize) the CTD port
	ErrorCheck( port, SioReset(port, 1024, 1024) );
	ErrorCheck( port, SioBaud(port, baud) );
		
#ifdef USE_HARDWARE_FLOW_CONTROL
	// set flow control if and only if the other side is doing flow control
	//printf("Setting HW flow control\n");
	ErrorCheck( port, SioFlow(port,'H') );
	// set TX timeouts (used with hardware flow control and overlapped I/O)
	//printf("TX timeout = 1 mSec/char + 2 seconds\n");
	ErrorCheck( port, SioSetTimeouts(port,(DWORD)-1,(DWORD)0,(DWORD)0,(DWORD)1,(DWORD)2000) );
#else
	//printf("Port %d Flow control not set\n", port+1);
#endif

}

void InitCommLib(void)
{
int  Code;
int  Version;
int  Build;
char Temp[51];  

	Code = SioKeyCode(WSC_KEY_CODE);
	if( Code<0 ){
		printf("ERROR %d: Bad key code %d!", Code, WSC_KEY_CODE);
		ExitProgram(1);
	}
	if( Code!=999 ) 	// purchased version always returns 999
		printf("%d days left in evaluation period\n", Code);
		
	Version = SioInfo('V');
	Build   = SioInfo('B');
#ifdef _WIN64
	printf("Win64 WSC Version %d.%d.%d (Build %d)\n", (Version>>8),0x0f&(Version>>4),0x0f&Version, Build);
#else
	printf("Win32 WSC Version %d.%d.%d (Build %d)\n", (Version>>8),0x0f&(Version>>4),0x0f&Version, Build);
#endif

	Code = SioGetReg((char *)Temp, 50);
	if(Code>0)  // display registration string
		printf("%s\n", Temp);
			
#ifdef USE_OVERLAPPED_IO
	// overlapped I/O supported by Win98 and above
	SioSetInteger(-1, 'O', 1);
	//printf("Overlapped I/O is enabled\n");
#else
	// overlapped I/O is disabled by default (ver 5.4.1)
	// SioSetInteger(-1, 'O', 0);
	//printf("Overlapped I/O is disabled\n");
#endif

	// set defaults for all ports. Note 'Port' argument is -1
	//printf("Setting DTR & RTS on\n");
	ErrorCheck( -1, SioReset(-1,1,1)); // DTR & RTS set at port initialization (all ports)
	//printf("Setting no parity, 8 data bits, & one stop bit (8N1)\n");
	ErrorCheck( -1, SioParms(-1,WSC_NoParity,WSC_OneStopBit,WSC_WordLength8));
}


void ExitProgram(int ExitCode)
{printf("(exiting)...\n");
 Beep(1000, 100);
 Sleep(2500);
 Beep(1000, 100);
 exit(ExitCode);
}


// trap WSC error codes
int ErrorCheck(int Port, int Code)
{if(Code<0)
   {SayError(Code, NULL);
    if(Port>=0) SioDone(Port);
    ExitProgram(1);
   }
 return Code;
}


BOOL WINAPI BreakHandler(DWORD dwCtrlType)
{
    //printf("Hello from handler1!\n");
	SioPutc(MSC_port, (int)CTRL_C);
	SioRxClear(MSC_port);
    return TRUE;
}







