/* ************************************************************************
 * *                                                                      *
 * *  Daemon to get UALF Lightning Data, process it and send it to IRIS   *
 * *                                                                      *
 * ************************************************************************
 * File: utils/nordrad2/UalfRelayIn.C
 *
 *                     COPYRIGHT (c) 2004, 2006 BY
 *              VAISALA INC., WESTFORD MASSACHUSETTS, U.S.A.
 * 
 * THIS SOFTWARE IS FURNISHED UNDER A LICENSE AND MAY BE USED AND  COPIED
 * ONLY  IN  ACCORDANCE WITH  THE  TERMS  OF  SUCH  LICENSE  AND WITH THE
 * INCLUSION OF THE ABOVE COPYRIGHT NOTICE.  THIS SOFTWARE  OR  ANY OTHER
 * COPIES  THEREOF MAY NOT BE PROVIDED OR OTHERWISE MADE AVAILABLE TO ANY
 * OTHER PERSON.  NO TITLE TO AND OWNERSHIP OF  THE  SOFTWARE  IS  HEREBY
 * TRANSFERED.
 */


/* 
 * Note if you are modifying this program, and wish to insert a diagnostic
 * printf, be sure to write it to the error output.
 */
#include <errno.h>
#include <locale.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <unistd.h>

#include "sigtypes.h"
#include "setup.h"
#include "headers.h"
#include "product.h"
#include "signetwork.h"

#include "user_lib.h"
#include "UalfConvert.h"
#include "UalfTokenize.h"


struct RelayState
{
  UINT4 iConnectStat;
  int   iSocket;
  UINT1 lLinkBroken;
};

static int iNumLines = 1;
static UINT4 iTimeOffset;
static char sBuffer[1000][200];
static struct RelayState irisState, ualfState;
static UalfConvert  ualfConvert;

/* Local function prototypes
 */

static void CheckObviousFaults(UalfConvert ualfConvert_a);
static MESSAGE SignalHandler (MESSAGE iStatus_a, UINT4 iCount_a, const UINT4 iSigArgs_a[] );
static UINT1 ConnectToUalf( struct RelayState *pState_a, char *sualf_host_a, UINT4 iualf_port_a);
static void DisconnectFromUalf(struct RelayState *pState_a);
static UINT1 ConnectToIris(struct RelayState *pState_a, char *siris_host_a, UINT4 iiris_port_a);
static void DisconnectFromIris(struct RelayState *pState_a);
static void RunRelay ( struct RelayState *pState_a, UalfConvert ualfConvert_a);
static UINT1 HandleDataAvailable ( struct RelayState *pState_a, UalfConvert ualfConvert_a );
static MESSAGE NotifyIris ( int iSocket_a, const char *sfile_a );
static void GenericSigioHandler( int signal_a );
static void IrisSigioHandler( int signal_a );
static void UalfSigioHandler( int signal_a );

/* ===============================================================
 * Main program starts here.
 */
int main(int iArgc, char *sArgv[])
{
  UINT1 lUalfOk, lIrisOk;
  char  stxt[100];

  setlocale( LC_ALL, "" ) ;

  /* Clear the initial state */
  memset( &irisState, 0, sizeof(irisState) );
  memset( &ualfState, 0, sizeof(irisState) );
  irisState.iSocket = -1;
  ualfState.iSocket = -1;
  irisState.iConnectStat = SIG_CONNECT_OK;
  ualfState.iConnectStat = SIG_CONNECT_OK;

  /* Process the setup file,if any.  We're using:
  **  /usr/sigmet/config/UalfToIris.conf */
  ualfConvert.loadSetup();

  /* Must daemonize before the log file is opened */
  if (ualfConvert.isDaemon())
     sig_daemon_init();

  ualfConvert.setupLogging();

  sprintf(stxt,"Lat Min %7.4f Max %7.4f\n", 
   ualfConvert.getLatMin(), ualfConvert.getLatMax());
  ualfConvert.Lprintf(stxt);

  sprintf(stxt,"Lon Min %7.4f Max %7.4f\n", 
   ualfConvert.getLonMin(), ualfConvert.getLonMax());
  ualfConvert.Lprintf(stxt);


   
  /* Starting here we can use the Lprintf function */
  sig_establish( SignalHandler, "UalfRelayIn" );
 
  /* Check for obvious faults which will prevent us from running */
  CheckObviousFaults(ualfConvert );

  //Get the Initial Time Offset. 
  //Time Offset will be computed once we start receiving UALF data
  iTimeOffset = ualfConvert.getInitialTimeOffset();
  sprintf(stxt,"Initial Time Offset: %d \n",  iTimeOffset);
  ualfConvert.Lprintf(stxt);

  lUalfOk = FALSE;
  lIrisOk = FALSE;
  while (TRUE)
  {
    ///////////////////////////////////
    //Try to connect to UALF data port
    ///////////////////////////////////
    if (!lUalfOk)
    {
      lUalfOk = ConnectToUalf( &ualfState, ualfConvert.getUalfHost(),
        ualfConvert.getUalfPort());
      sprintf(stxt,"Ualf Host %s Port %d Socket %d\n",
       ualfConvert.getUalfHost(),ualfConvert.getUalfPort(),ualfState.iSocket); 
      ualfConvert.Lprintf(stxt);
    }

    ///////////////////////////////////
    //Try to connect to UALF data port
    ///////////////////////////////////
    if (lUalfOk)
    {
      ualfConvert.Lprintf( "Connected to UALF.\n");

      /* Code to connect to IRIS */
      lIrisOk = ConnectToIris( &irisState, ualfConvert.getIrisHost(),
        ualfConvert.getIrisPort());
      sprintf(stxt, "IRIS Host %s Port %d Socket %d\n",
        ualfConvert.getIrisHost(),ualfConvert.getIrisPort(),irisState.iSocket); 
      ualfConvert.Lprintf(stxt);
      if (lIrisOk)
      {
        iNumLines = 1;
        ualfConvert.Lprintf( "Connected to IRIS.\n");
        /* The RunRelay function will only return if there is a failure
         * in either the UALF or IRIS connections.
         */
        RunRelay( &irisState, ualfConvert); 
        /* Always disconnect from UALF and IRIS on a failure */
        DisconnectFromIris(&irisState);
        lIrisOk = FALSE;
        DisconnectFromUalf(&ualfState);
        lUalfOk = FALSE;
        ualfConvert.Lprintf( "Disconnected from IRIS.\n");
        ualfConvert.Lprintf( "Disconnected from UALF.\n");
      } 
      else
      {
        //If can't connect to IRIS then disconnect
        //from UALF data port.
        DisconnectFromUalf(&ualfState);
        lUalfOk = FALSE;
        ualfConvert.Lprintf("Can't Connect to IRIS. Disconnecting from UALF.\n");
      }
    }

    /* If we are not able to connect completely, then wait here for 60 seconds 
     * before retrying. This is to prevent an infinite burst of error messages.
     */
    sig_microSleep(60000000);
  }
  exit( EXIT_SUCCESS );
}


/* ==================================================
 * Do preliminary checking.
 */
static void CheckObviousFaults( UalfConvert ualfConvert_a)
{
  int iError;
  struct stat DirStat;
  MESSAGE iStatus;
  char sMessage[100];
  char *sdir;

  sdir = ualfConvert_a.getIrisDirectory();
  /* Directory must be defined */
  if ( 0 == sdir[0] )
    {
      sprintf( sMessage, "No IRIS directory in config file.\n" );
      ualfConvert_a.Lprintf( sMessage );
      exit( EXIT_FAILURE );
    }

  /* Stat will get an error if either the file does not exist, or it is
   * not a directory. (because of the trailing /) */
  iError = stat( sdir, &DirStat );
  if (iError)
    {
      iStatus = errno_to_message( errno );
      sig_signal_va( iStatus, 1, sdir);
      exit( EXIT_FAILURE );
    }

/*DOUG
  if (!(DirStat.st_uid == geteuid()))
    {
      sprintf( sMessage, "Not owner of directory %s\n", pSetup_a->sIrisDirectory );
      Lprintf( sMessage );
      exit( EXIT_FAILURE );
    }
*/

}

/* ======================================================================
 * Function to print signaled errors to the loging file or terminal.
 */
static MESSAGE SignalHandler
(MESSAGE iStatus_a,
 UINT4 iCount_a,
 const UINT4 iSigArgs_a[] )
{
  char sMessage[170];

  strncpy( sMessage, message_to_text( iStatus_a, iCount_a, iSigArgs_a ), 165 );
  sMessage[165]=0;
  strcat( sMessage, "\n" );

//  Lprintf( sMessage );
  return(SS_NORMAL);
}


/* ================================================================
 * Connect the socket to UALF.  Return TRUE on success.
 */
static UINT1 ConnectToUalf
( struct RelayState *pState_a, char *sualf_host_a, UINT4 iualf_port_a)
{
  pState_a->lLinkBroken = FALSE;
  pState_a->iSocket = SocketConnectNoBlock(AF_INET, sualf_host_a, iualf_port_a, 
                                GenericSigioHandler, &pState_a->iConnectStat);
  return (pState_a->iSocket != -1);
}

/* ================================================================
 * Disconnect the socket from UALF.
 */
static void DisconnectFromUalf
(struct RelayState *pState_a)
{
  SocketDisconnect( pState_a->iSocket );
  pState_a->iSocket=  -1;
  return;
}

/* ================================================================
 * Connect the socket to IRIS.  Return TRUE on success.
 */
static UINT1 ConnectToIris
( struct RelayState *pState_a, char *siris_host_a, UINT4 iiris_port_a)
{
  pState_a->lLinkBroken = FALSE;
  pState_a->iSocket = 
    SocketConnectNoBlock( AF_INET, siris_host_a, iiris_port_a,
			  GenericSigioHandler, &pState_a->iConnectStat );
  return (pState_a->iSocket != -1);

}
/* ================================================================
 * Disconnect the socket from IRIS.
 */
static void DisconnectFromIris
(struct RelayState *pState_a)
{
  SocketDisconnect( pState_a->iSocket );
  pState_a->iSocket=  -1;
  return;
}

/* =====================================================
 * This runs the relay operation. 
 * This function will normally run forever, but it will return 
 * if either IRIS or UALF data feed goes down.
 */
static void RunRelay
( struct RelayState *pState_a,
  UalfConvert ualfConvert_a)
{
  char sMessage[100];

  struct ymds_time host_ymds_time; 

  // irisState.lLinkBroken and ualfState.lLinkBroken 
  // get set to TRUE if where is a error in receiving data in
  // UalfSigioHandler() or IrisSigioHandler()
  while ((!irisState.lLinkBroken) && (!ualfState.lLinkBroken))
  {
    sig_microSleep(1000000);

    //If the host time and ualf time don't match 
    //then add an offset
    GetTimeYmds(&host_ymds_time, TIME_FLG_UTC); 

    ymds_add_seconds(-iTimeOffset, &host_ymds_time);
//    printf("TIME OFFSET %d\n", iTimeOffset);
//    printf("AFT Min %d Sec %d\n", (host_ymds_time.isec%3600)/60, host_ymds_time.isec%60);

    //Create a header block in buffer idx 0 every x seconds
    if ((host_ymds_time.isec) % ualfConvert_a.getNewProdTimeInterval() == 0)
    {
      int iyear = host_ymds_time.iyear;
      int imon  = host_ymds_time.imon;
      int iday  = host_ymds_time.iday;
      int ihour = host_ymds_time.isec/3600;
      int imin  = (host_ymds_time.isec%3600)/60;
      int isec  = host_ymds_time.isec%60;
      sprintf(sBuffer[0],"9	%d	%d	%d	%d	%d	%d",
        iyear, imon, iday, ihour, imin, isec);
     
      if (ualfConvert_a.isVerbose())
      {
        sprintf(sMessage, "New Header <%s>\n", sBuffer[0]);
        ualfConvert_a.Lprintf(sMessage);
      }
      UINT1 lOk = HandleDataAvailable(pState_a, ualfConvert_a );
      if (!lOk) goto error;
    }
  }
  return;

 error:
  sprintf(sMessage, "RunRelay error: \n");
  ualfConvert_a.Lprintf( sMessage );
  return;

}

/* =====================================================
 * Handle the data available events.  Returns TRUE on success,
 * FALSE if there is an error.
 */
static UINT1 HandleDataAvailable
( struct RelayState *pState_a,
  UalfConvert ualfConvert_a )
{
  int     total_bytes;
  FILE    *out_stream;
  char    sMessage[100];
  struct  warn_product warnProduct;
  char sPath[PATHNAME_SIZE], sFile[FILENAME_SIZE]; 
  static int iFileCount=0;
      
  /* Generate the local filename */
  iFileCount++;
  if (iFileCount>10000)
    iFileCount=0;
  sprintf( sFile, "Ualf%d", iFileCount );
  strcpy( sPath, ualfConvert_a.getIrisDirectory() );
  strcat( sPath, sFile );
  out_stream = fopen( sPath, "w" );

  memset( &warnProduct, 0, sizeof(struct warn_product));
  ualfConvert_a.initializeProdHeader(&warnProduct);

  /* now loop through the file reading and parsing lines from the standard input */
  UINT1 lfirstpass = TRUE;
  for (int ii= 0; ii < iNumLines; ii++)
  {
    ualfConvert_a.lProcessAsciiLine(sBuffer[ii], &warnProduct, lfirstpass);
    lfirstpass = FALSE;
  }

  total_bytes = PRODUCT_HDR_SIZE + sizeof(struct protect_setup) +
    warnProduct.Header.end.iresults_count*sizeof(struct warning_results);

  warnProduct.Header.hdr.ibytes = total_bytes;

  /* Write out the results to the standard output */
  fwrite( &warnProduct, sizeof(struct warn_product), 1, out_stream );
  fclose(out_stream);

  MESSAGE iStatus;
  iStatus = NotifyIris( pState_a->iSocket, sFile );
  if (iStatus!=SS_NORMAL)
  {
    sig_signal( iStatus );
    sprintf( sMessage, "Notify Error\n" );
    ualfConvert_a.Lprintf( sMessage );
  }
  iNumLines = 1;
  return 1;
}

/* =================================================================
 * Function to notify IRIS about the new file.
 */
static MESSAGE NotifyIris
( int iSocket_a,
  const char *sfile_a )
{
  SINT4 ilength;
  MESSAGE istatus;
  int ierror;
  char smessage[100];

  strcpy( smessage, "FILE " );
  strncat( smessage, sfile_a, 99-5 );
  smessage[99] = 0;
  ilength = strlen(smessage);
  ierror = send( iSocket_a, smessage, ilength+1, 0 );
  if (ierror == -1)
    istatus = errno_to_message_va( errno, 1, sfile_a ) ;
  else
    istatus = SS_NORMAL;
  return istatus;
}

/* ================================================================
 * Signal handler for SIGIO signal.  It just does a read, and processes
 * the results.
 */

static void UalfSigioHandler( int signal_a )
{
  char data[8000];
  int ierror, isize;
  static UINT2 lcompute_time_offset = FALSE;

  isize = recv( ualfState.iSocket, data, 7999, 0 );
  if (isize > 0)
  {
    char *stok;

    // We got some valid data here.  Add terminating NULL, then process 
    data[isize] = 0;

    //Copy to sBuffer all of the ualf data we are receiving in
    //RunRelay will processes data after 15 seconds and write
    //it to a file.  We then send a message to IRIS saying data is ready
    stok = strtok(data,"\n");
    while (stok != NULL)
    {
      UalfTokenize ut;
      UINT2 lvalid = ut.tokenize(stok);

      if (lvalid)
      {
        //Get the time offset between the computer time and the ualf data time
        if (!lcompute_time_offset)
        {
          struct ymds_time ualf_ymds_time;
          ualf_ymds_time = ut.getYmdsTime();

          struct ymds_time host_ymds_time;
          GetTimeYmds(&host_ymds_time, TIME_FLG_UTC); 
          iTimeOffset = iymds_subtract(&host_ymds_time, &ualf_ymds_time);
          char stxt[80];
          sprintf(stxt," Inital Time Offset %d Computed Time Offset %d\n", 
            ualfConvert.getInitialTimeOffset(), iTimeOffset);
          ualfConvert.Lprintf(stxt);
        }
        lcompute_time_offset = TRUE;

        //Check of UALF is valid 
        //Filter out all UALF data that has a Lat/Lot of 0.0
        if ((ut.getLat() == 0.0) && (ut.getLon() == 0.0))
          lvalid = false;
      }
      if (lvalid)
        strcpy(sBuffer[iNumLines++], stok);
      stok = strtok(NULL, "\n");
    }
  }
  else if (isize < 0)
  {
    /* We an error here, signal all errors except EWOULDBLOCK */
    ierror = errno;
    if (ierror != EWOULDBLOCK)
    {
      sig_signal( errno_to_message_va( ierror, 1, "out_tcpip_handler()") );
      ualfState.lLinkBroken = TRUE;
    }
  }
  else
  {
    /* Zero size indicates that the socket has been closed at the other end */
    ualfState.lLinkBroken = TRUE;
  }

//  fprintf(stderr, "UalfSigioHandler: Link Broken %d\n",ualfState.lLinkBroken);

  if (ualfState.lLinkBroken)
  {
    int ierror;
    struct sigaction action;
    /* On HP-UX 11.0, you get an infinite series of zero sized reads
     * when the other end closes.  To fix this, turn off signal handler
     * on error here.
     */
    /* Ignore option required on SGI.  Other platforms worked OK with NULL */
    action.sa_handler = SIG_IGN;
    sigemptyset( &action.sa_mask );
    action.sa_flags =  SA_RESTART;
    ierror = sigaction( SIGIO, &action, NULL );
    if (ierror == -1) sig_signal( errno_to_message(errno) );
  }
  return;

}

/////////////////////////////////////////////////////////////
//IRIS sending back data which gives current state of IRIS
/////////////////////////////////////////////////////////////
static void IrisSigioHandler( int signal_a )
{
  char data[100];
  int ierror, isize;

  isize = recv( irisState.iSocket, data, 99, 0 );

 
  if (isize > 0)
    {
      /* We got some valid data here.  Add terminating NULL, then process */
      data[isize] = 0;
      if (0 == strcmp( data, "NOP" ))
	{

	}
      else if (0 == strcmp( data, "EXIT" ))
	{
	  irisState.lLinkBroken = TRUE;
	}
      else
	{
	  sig_signal_va( USER_NETPACKET_COMMAND, 1, data );
	}
    }

  else if (isize < 0)
    {
      /* We an error here, signal all errors except EWOULDBLOCK */
      ierror = errno;
      if (ierror != EWOULDBLOCK)
	{
	  sig_signal( errno_to_message_va( ierror, 1, "out_tcpip_handler()") );
	  irisState.lLinkBroken = TRUE;
	}
    }

  else
    {
      /* Zero size indicates that the socket has been closed at the other end */
      irisState.lLinkBroken = TRUE;

    }

  fprintf(stderr, "IrisSigioHandler: Link Broken %d\n", irisState.lLinkBroken);

  if (irisState.lLinkBroken)
    {
      int ierror;
      struct sigaction action;
      /* On HP-UX 11.0, you get an infinite series of zero sized reads
       * when the other end closes.  To fix this, turn off signal handler
       * on error here.
       */
      /* Ignore option required on SGI.  Other platforms worked OK with NULL */
      action.sa_handler = SIG_IGN;
      sigemptyset( &action.sa_mask );
      action.sa_flags =  SA_RESTART;
      ierror = sigaction( SIGIO, &action, NULL );
      if (ierror == -1) sig_signal( errno_to_message(errno) );
    }
  return;
}


static void GenericSigioHandler( int signal_a )
{
  int istat;
  fd_set rset;
  struct timeval time_val;

//  fprintf(stderr,"GenericSigioHandler: UALF Socket %d IRIS Socket %d\n", ualfState.iSocket, irisState.iSocket);

  FD_ZERO(&rset);
  if (ualfState.iSocket != -1)
    FD_SET(ualfState.iSocket, &rset);
  if (irisState.iSocket != -1)
    FD_SET(irisState.iSocket, &rset);

  time_val.tv_sec = 0;
  time_val.tv_usec = 0;
  istat = select(32, &rset, NULL, NULL, &time_val);
  switch (istat)
  {
    case 0: /* timeout */
      break;

    case -1: /* error */
      char log_str[80];
      sprintf(log_str, "select() for IRIS notification server returned errno %d\n", errno);
//      Lprintf(log_str);
      break;

    default:
      if (ualfState.iSocket != -1)
      {
        if (FD_ISSET(ualfState.iSocket, &rset))
        {
          UalfSigioHandler( signal_a );
        }
      }

      if (irisState.iSocket != -1)
      {
        if (FD_ISSET(irisState.iSocket, &rset))
        {
          IrisSigioHandler( signal_a );
        }
      }
      break;
  } 
}
