/*
 *   THIS FILE IS UNDER RCS - DO NOT MODIFY UNLESS YOU HAVE
 *   CHECKED IT OUT USING THE COMMAND CHECKOUT.
 *
 *    $Id: naqsserTG.c,v 1.1 2003/02/14 19:45:21 dietz Exp $
 *
 *    Revision history:
 *
 *     Revision 1.1  2003/02/10 22:35:07  whitmore
 *     Initial revision (based on naqs2ew)
 *
 */

/*
 *   naqsserTG.c:  Program to receive transparent serial packets from a NaqsServer
 *                 via socket and log them to a disk file and send to RING.
 *                 Based on naqs2ew by dietz.
 *                 Serial packets expected here are ASCII output from the
 *                 real-time Sutron tide gage units at NOS gages.  One sample
 *                 is expected every 15 seconds.  This data is logged to disk
 *                 and RING for future display in the TIDE display programs or
 *                 export or procssing.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <fcntl.h>
#include <math.h>
#include <time.h>
#include <kom.h>
#include <earthworm.h>
#include <transport.h>
#include <trace_buf.h>
#include "naqsser.h"

/* Functions used in this source file
 ************************************/
int  naqsser_shipdata( NMX_SERIAL_DATA * );
void PadZeroes( int, char * );
char *itoaX( int, char * );

/* Globals declared in naqschassis.c
 ***********************************/
extern char          RingName[];  /* name of transport ring for output          */
extern char          ProgName[];  /* name of program - used in logging          */
extern int           Debug;       /* Debug logging flag (optionally configured) */
extern unsigned char MyInstId;    /* local installation id                      */
extern unsigned char MyModId;     /* Module Id for this program                 */
extern SHM_INFO      Region;      /* shared memory region to use for output     */

/* Globals specific to this naqsclient
 *************************************/
static int       MaxSamplePerMsg = 0; /* max # samples to expect in serial data */
static char      DataPath[64] = "\0"; /* Path to send data files to             */
static int       MaxReqSER       = 0; /* working limit on max # scn's in ReqSCN */ 
static int       nReqSER = 0;         /* # of scn's found in config file        */
static SER_INFO *ReqSER  = NULL;      /* Serial channels requested in config    */
static NMX_SERIAL_DATA  NaqsSer;      /* struct containing one Naqs serial data packet */
static unsigned char    TypeSerialTG; /* Serial Tide gage message type          */

static NMX_CHANNEL_INFO *ChanInf = NULL;  /* channel info structures from NAQS  */
static int                nChan = 0;      /* actual # channels in ChanInf array */

/*******************************************************************************
 * naqsclient_com() read config file commands specific to this client          *
 *   Return 1 if the command was processed, 0 if it wasn't                     *
 *******************************************************************************/
int naqsclient_com( char *configfile )
{
   char *str;

/* Add an entry to the request-list
 **********************************/
   if(k_its("RequestChannel") ) {
      int badarg = 0;
      char *argname[] = {"","sta","comp","net",
                            "pinno","delay","sendbuffer","dtexp"};
      if( nReqSER >= MaxReqSER )
      {
         size_t size;
         MaxReqSER += 10;
         size     = MaxReqSER * sizeof( SER_INFO );
         ReqSER   = (SER_INFO *) realloc( ReqSER, size );
         if( ReqSER == NULL )
         {
            logit( "e",
                   "%s: Error allocating %d bytes"
                   " for requested channel list; exiting!\n", ProgName, size );
            exit( -1 );
         }
      }
      memset( &ReqSER[nReqSER], 0, sizeof(SER_INFO) );

      str=k_str();
      if( !str || strlen(str)>(size_t)STATION_LEN ) {
         badarg = 1; goto EndRequestChannel;
      }
      strcpy(ReqSER[nReqSER].sta,str);

      str=k_str();
      if( !str || strlen(str)>(size_t)CHAN_LEN) {
         badarg = 2; goto EndRequestChannel;
      }
      strcpy(ReqSER[nReqSER].chan,str);

      str=k_str();
      if( !str || strlen(str)>(size_t)NETWORK_LEN) {
         badarg = 3; goto EndRequestChannel;
      }
      strcpy(ReqSER[nReqSER].net,str);

      ReqSER[nReqSER].pinno = k_int();
      if( ReqSER[nReqSER].pinno <= 0 || ReqSER[nReqSER].pinno > 32767 ) {
         badarg = 4; goto EndRequestChannel;
      }

      ReqSER[nReqSER].delay = k_int();
      if( ReqSER[nReqSER].delay < -1 || ReqSER[nReqSER].delay > 300 ) {
         badarg = 5; goto EndRequestChannel;
      }

      ReqSER[nReqSER].sendbuffer = k_int();
      if( ReqSER[nReqSER].sendbuffer!=0 && ReqSER[nReqSER].sendbuffer!=1 ) {
         badarg = 6; goto EndRequestChannel;
      }

      ReqSER[nReqSER].dtexp = k_val();
      if( ReqSER[nReqSER].dtexp < 0. ) {
         badarg = 7; goto EndRequestChannel;
      }
	  ReqSER[nReqSER].first = 0;

   EndRequestChannel:
      if( badarg ) {
         logit( "e", "%s: Argument %d (%s) bad in <RequestChannel> "
                "command (too long, missing, or invalid value):\n"
                "   \"%s\"\n", ProgName, badarg, argname[badarg], k_com() );
         logit( "e", "%s: exiting!\n", ProgName );
         free( ReqSER );
         exit( -1 );
      }
      nReqSER++;
      return( 1 );

   } /* end of RequestChannel */

   if(k_its("MaxSamplePerMsg") ) {
      MaxSamplePerMsg = k_int();
      if( MaxSamplePerMsg <= 0 )
      {
         logit( "e", "%s: <MaxSamplePerMsg %d> must be positive; exiting!\n",
                ProgName, MaxSamplePerMsg );
         exit( -1 );
      }
      return( 1 );
   }

   if(k_its("DataPath") ) {
      str = k_str();
      strcpy( DataPath, str );
      return( 1 );
   }

   return( 0 );
}


/*******************************************************************************
 * naqsclient_init() initialize client stuff                                   *
 *******************************************************************************/
int naqsclient_init( void )
{
/* Check that all commands were read from config file
 ****************************************************/
   if( nReqSER == 0 )
   {
     logit("e", "%s: No <RequestChannel> commands in config file!\n",
            ProgName );
     return( -1 );
   }
   if( MaxSamplePerMsg == 0 )
   {
     logit("e", "%s: No <MaxSamplePerMsg> commands in config file!\n",
            ProgName );
     return( -1 );
   }
   if( !DataPath || strlen( DataPath ) <= 0 )
   {
      logit( "e", "%s: No <DataPath> commands in config file!\n",
             ProgName );
      return( -1 );
   }

/* Look up message types in earthworm.d tables
 *********************************************/
   if ( GetType( "TYPE_SERTGTWC", &TypeSerialTG ) != 0 ) {
      logit("e","%s: Invalid message type <TYPE_SERTGTWC>!\n",
             ProgName );
      return( -1 );
   }

/* Allocate memory 
 *****************/
   NaqsSer.maxdatalen = MaxSamplePerMsg; 
   if ( ( NaqsSer.data = (char *) malloc( NaqsSer.maxdatalen ) ) == NULL )
   {
     logit( "e", "%s: Cannot allocate %ld bytes for serial data!\n",
                  ProgName, NaqsSer.maxdatalen );
     return( -1 );
   }

   return( 0 );
} 


/*******************************************************************************
 * naqsclient_process() handle interesting packets from NaqsServer             *
 *  Returns 1 if packet was processed successfully                             *
 *          0 if packet was NOT processed                                      *
 *         -1 if there was trouble processing the packet                       *
 *******************************************************************************/
int naqsclient_process( long rctype, NMXHDR *pnaqshd, 
                        char **pbuf, int *pbuflen )
{
   char *sockbuf = *pbuf;  /* working pointer into socket buffer */
   int   rdrc;
   int   rc;

/* Process serial packets 
 ************************/
   if( rctype == NMXMSG_COMPRESSED_DATA  )  
   {
      if ( Debug ) logit( "et","%s: got a %d-byte data message\n",
                          ProgName, pnaqshd->msglen );

   Read_Waveform:
      /* read the message */
      sockbuf = *pbuf;
      rdrc = nmx_rd_serial_data( pnaqshd, sockbuf, &NaqsSer );
      if( rdrc < 0 )
      {
         logit("et","%s: trouble reading waveform message\n", ProgName );
         return( -1 );
      }

   /* data buffer wasn't big enough, realloc and try again */
      else if( rdrc > 0 )
      {
         char *ptmp;
         if( (ptmp=(char *)realloc( NaqsSer.data, rdrc)) == NULL )
         {
            logit("et","%s: error reallocing NaqsSer.data from %d to %d "
                    "bytes\n", ProgName, NaqsSer.maxdatalen, rdrc );
            return( -1 );
         }
         logit( "et","%s: realloc'd NaqsSer.data from %d to %d "
                "bytes\n", ProgName, NaqsSer.maxdatalen, rdrc );
         NaqsSer.data       = ptmp;
         NaqsSer.maxdatalen = rdrc;
         goto Read_Waveform;
      }

      if(Debug>=2)
      {
         int id;
         logit("et","chankey:%d  starttime:%.2lf  nbyte:%d\n",
                NaqsSer.chankey, NaqsSer.starttime, NaqsSer.nbyte );
         for( id=0; id<NaqsSer.nbyte; id++ ) {
            logit("e", "%c", NaqsSer.data[id] );
            if((id+1)%SAMPLE_LENGTH==0) logit("e", "\n");
         }
      }

   /* write it to the the disk file */
      rc = naqsser_shipdata( &NaqsSer );
	  
   } /* end if serial data */

/* got a list of all possible channels, continue startup protocol
 *  2) recv "channel list" message
 *  3) send "add channel" message(s)
 ****************************************************************/
   else if( rctype == NMXMSG_CHANNEL_LIST )
   {
      int msglen = 0;
      int nadd   = 0;
      int ir;

      nChan = 0;
      free( ChanInf );

   /* read the channel list message */
      if( nmx_rd_channel_list( pnaqshd, sockbuf, &ChanInf, &nChan ) < 0 )
      {
         logit("e", "%s: trouble reading channel list message\n", ProgName );
         return( -1 );
      }

   /* compare the Naqs channel list against the requested channels */

      rc = SelectSerChannels( ChanInf, nChan, ReqSER, nReqSER );

   /* build and send "add channel" message for each available channel */
          
      for( ir=0; ir<nReqSER; ir++ )
      {
         if( ReqSER[ir].flag == SER_NOTAVAILABLE ) continue;
         if( nmx_bld_add_serial( pbuf, pbuflen, &msglen,
                  &(ReqSER[ir].info.chankey), 1, ReqSER[ir].delay,
                  ReqSER[ir].sendbuffer ) != 0 )
         {
            logit("et","%s: trouble building add_serial message\n", ProgName);
            continue;
         }
         nmxsrv_sendto( sockbuf, msglen );
         if(Debug) logit("et","sent add_serial message %d\n", ++nadd );
      }
      
   } /* end if channel list msg */

/* Not a message type we're interested in
 ****************************************/
   else {
      return( 0 );
   }

   return( 1 );

} /* end of naqsclient_process */


/*******************************************************************************
 * naqsclient_shutdown()  frees malloc'd memory, etc                           *
 *******************************************************************************/
void naqsclient_shutdown( void )
{
/* Free all allocated memory
 ***************************/
   free( ReqSER );
   free( NaqsSer.data );
   free( ChanInf );
}

/*****************************************************************************
 * naqsser_shipdata() takes a buffer of Naqs serial data, translates it into *
 *   TYPE_SERTGTWC message(s) and writes it to the transport region and      *
 *   to a disk file.                                                         * 
 *****************************************************************************/
int naqsser_shipdata( NMX_SERIAL_DATA *nbuf )
{
   char         FileName[64];             /* Path/file to output data to    */
   FILE        *hFile;                    /* Data File handle               */
   int          i, j;
   char         line[MAX_SERTGTWC_SIZE];  /* buffer for outgoing message    */
   int          lineLen;                  /* length of line in bytes        */
   static MSG_LOGO logo;
   double       dt=0.0;   
   int          numsamps;                 /* number of samples in this packet*/
   struct tm*   PacketTime;               /* Integer form time structure     */
   char         samp[MAX_SAMPS][SAMPLE_LENGTH+1]; /* serial data             */
   SER_INFO    *scn;                      /* pointer to channel information  */
   long         StartTime;                /* Data point time                 */
   char         Temp[10];                 /* Temporary character string      */

   logo.instid = MyInstId;
   logo.mod    = MyModId;
   logo.type   = TypeSerialTG;

/* Find this channel in the subscription list
 ********************************************/
   scn = FindSerChannel( ReqSER, nReqSER, nbuf->chankey );
   if( scn == NULL )
   {
      logit("et","%s: Received unknown chankey:%d "
                "(not in subscription list)\n", ProgName, nbuf->chankey );
      /* probably should report this to statmgr */
      return( NMX_SUCCESS );
   }
   if ( Debug ) logit ("e", "%s.%s.%s  new data starttime: %.3lf nsamp:%d\n",
                        scn->sta,scn->chan,scn->net,nbuf->starttime,
                        nbuf->nbyte );
					  
/* First data for this channel.  
 ******************************/
   if( scn->first == 0 ) 
   {
      scn->texp     = nbuf->starttime;
      scn->first    = 1;
   }
   
/* Check for time tears (overlaps/abnormal forward jumps). 
 *********************************************************/
   dt = nbuf->starttime - scn->texp;
   if( ABS(dt) > scn->dtexp ) 
   {
      if ( dt > 0.0 )                             /* time gap */
      {
         logit("e","%s: %s.%s.%s %.3lf %.3lf s gap detected\n",
                ProgName, scn->sta, scn->chan, scn->net, scn->texp, dt );
      }
      else                                        /* time overlap */ 
      {     
         logit("e","%s: %s.%s.%s %.3lf %.3lf s overlap detected\n",
               ProgName, scn->sta, scn->chan, scn->net, scn->texp, ABS(dt) );
      }
   }

/* Split msg in case there is more than one sample
 *************************************************/
   numsamps = 0;
   for ( i=0; i<nbuf->nbyte; i++ )
   {
      if ( nbuf->data[i] == '*' )           /* Start of new data */
      {
         for ( j=0; j<SAMPLE_LENGTH-1; j++ )/* Expect 9 chars. after * */
         {                                  /* Sutron format *+00002.34 */
            i += 1;
            samp[numsamps][j] = nbuf->data[i];
         }
         samp[numsamps][SAMPLE_LENGTH-1] = '\0';
         numsamps++;                            
         if ( numsamps >= MAX_SAMPS )
         {
            logit( "", "Too many samples in incoming serial data - increase MAX_SAMPS\n" );
            break;
         }
      }
   }

/* Write samples to disk and ring.
 *********************************/
   for ( i=0; i<numsamps; i++ )
   {
      /* Create TYPE_SERTGTWC message */
      sprintf( line, "%s %s %s %lf %s\0",
               scn->sta, scn->chan, scn->net,
               nbuf->starttime + (double) i*scn->dtexp, samp[i] );
      lineLen = strlen( line ) + 1;
      /* Send message to ring */	  
      if( tport_putmsg( &Region, &logo, lineLen, line ) != PUT_OK )
      {
         logit( "et", "%s: Error putting %d-byte SERTGTWC msg in %s\n",
                ProgName, lineLen, RingName );
      }
      if ( Debug ) logit( "", "samp %ld, msg = %s, len=%ld\n", i, line, lineLen );
      /* Create file name and open the data file */
      strcpy( FileName, DataPath );        
      strcat( FileName, "T" );
      strcat( FileName, scn->sta );
      StartTime = (long) (floor( nbuf->starttime + (double) i*scn->dtexp ));
      PacketTime = gmtime( (time_t*) &StartTime );
      itoaX( (int) PacketTime->tm_year+1900, Temp );
      strcat( FileName, Temp );
      strcat( FileName, "." );
      itoaX( (int) PacketTime->tm_yday+1, Temp );
      PadZeroes( 3, Temp );
      strcat( FileName, Temp );
      if ( (hFile = fopen( FileName, "a" )) != NULL )
      {             /* Convert time to Modified Julian Time */
         fprintf( hFile, "%s %lf\n", samp[i],
                  nbuf->starttime + (double) i*scn->dtexp + 3506630400. );
	     fclose( hFile );
      }
      else
         logit( "", "Could not open file for append - %s\n", FileName );
   }
   
/* Compute next expected sample time
   *********************************/
   if ( numsamps > 0 )
   {   
      scn->texp = nbuf->starttime + (double)numsamps*scn->dtexp;
      if ( Debug ) logit( "e","%s.%s.%s texp=%.3lf after data shipped\n",
                          scn->sta,scn->chan,scn->net,scn->texp );
   }
   
   return NMX_SUCCESS;
}

      /******************************************************************
       *                            itoaX()                             *
       *                                                                *
       *  Convert integer to character string.                          *
       *                                                                *
       *  From Stu at PTWC. (Added for compatibility with Solaris.)     *
       *                                                                *
       *  Arguments:                                                    *
       *   iNum             Integer to convert                          *
       *   pszNum           String pointer with int converted to char   *
       *                                                                *
       *  Return:                                                       *
       *   Pointer to character string (pszNum)                         *
       *                                                                *
       ******************************************************************/

char *itoaX( int iNum, char *pszNum )
{
   int   i;
   int   iDigit;       /* Values to convert */
   int   iPower;       /* Number of characters necessary in converted string */
 
   if ( pszNum == (char *) NULL )
   {
      logit( "", "No memory allocated for pszNum in itoaX\n");
      return NULL;
   }
 
   iPower = (int) log10( (double) iNum );  
 
   for ( i=0; i<=iPower; i++)
   {
      iDigit = iNum%10;
      pszNum[iPower-i]= iDigit + 48;
      iNum=iNum/10;
   }
 
   pszNum[iPower+1] = '\0';
   return pszNum;
}

      /******************************************************************
       *                           PadZeroes()                          *
       *                                                                *
       *  This function adds leading zeroes to a numeric character      *
       *  string. (10 characters maximum in string).                    *
       *                                                                *
       *  Arguments:                                                    *
       *   iNumDigits       # digits desired in output                  *
       *   pszString        String to be padded with zeroes             *
       *                                                                *
       ******************************************************************/
	   
void PadZeroes( int iNumDigits, char *pszString )
{
   int     i, iStringLen;
   char    szTemp1[10], szTemp2[10];

   strcpy( szTemp1, pszString );
   strcpy( szTemp2, szTemp1 );
   iStringLen = strlen( szTemp1 );
   if ( iNumDigits-iStringLen > 0 )     /* Number of zeroes to add */
   {
      for ( i=0; i<iNumDigits-iStringLen; i++ ) szTemp1[i] = '0';
      for ( i=iNumDigits-iStringLen; i<iNumDigits; i++ ) 
         szTemp1[i] = szTemp2[i-(iNumDigits-iStringLen)];
      szTemp1[iNumDigits] = '\0';       /* Add NULL character to end */
      strcpy( pszString, szTemp1 );
   }
}

