/* **********************************************************************
 * *                                                                    *
 * *               Antenna Export Socket Server Daemon                  *
 * *                                                                    *
 * **********************************************************************
 * File: utils/antenna/AntExport.C
 *
 *                     COPYRIGHT (c) 2003, 2004 BY
 *          SIGMET INCORPORATED, 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.
 */

/* All traffic going from the antenna library and to the AntExport program
 * will consist of packets formated as follows:
 *  size command|data
 * Where size is an 8-character ascii number indicating the size of the 
 * rest of the packet in bytes.  The command will normally be:
 * AntSet  - Packet is sent to AntSet()
 * Setup   - Please return Setup structure in a reply
 * PollOn  - Turn on sending poll packets.
 * PollOff - Turn off sending poll packets.
 *
 * Note that data from AntExport to the antenna library is different, and
 * not always ASCII.
 */

#include <errno.h>
#include <locale.h>
#include <netinet/in.h>

#ifndef LINUX
#include <signal.h>
#endif  /* #ifndef LINUX */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#define INCLUDE_SYSSOCKET_PROTOTYPES (1)
#include <sys/wait.h>
#include <unistd.h>
#include <sys/poll.h>

#include "sigtypes.h"
#include "event_flags.h"
#include "setup.h"
#include "signetwork.h"

#include "antenna_lib.h"
#include "antenna_int.h"
#include "config_lib.h"
#include "user_lib.h"

struct command_line {		/* Original command line args */
  UINT2 iPortNumber; 		/*   Port number to use */
  UINT1 lLogToFile ;		/*   Logging options */
  UINT1 lLogToTerm ;
  UINT1 lVerboseIn ;		/*   Log more details */
  UINT1 lVerboseOut;		/*   Log more details */
  UINT1 lAppendToLog ;		/*   Append to previous log file */
  UINT1 lDaemon;                /*   Run in daemon mode */
  FILE *iLogFile ;		/*   Open file descriptor to log file */
} ;

static pid_t ChildProcessList_c[ANTENNA_EXPORT_NUM];

/* ------------------------------
 * Local function prototypes
 */
static void ParentHandler
( pid_t iChildPid_a,
  int iConnectionNumber_a );

static int ConnectionTester
( const struct sockaddr_in *pCliAddr_a );

static void ProcessCommandLine
( int iArgc_a, char *sArgv_a[],
  struct command_line *pCmd_a ) ;

static void Usage( void ) ;

static void ConnectionHandler
( int iSocket_a,
  const struct sockaddr_in *pCliAddr_a,
  int iconnectionNumber_a );

static MESSAGE ProcessSetupPacket
(int iSocket_a );

static MESSAGE SignalHandler
( MESSAGE iStatus_a, UINT4 iCount_a, const UINT4 iSigArgs_a[] ) ;

static void SIGCHLD_handler( int isignal_a ) ;

void Lprintf
( const struct command_line *pCmd_a, const char *sString_a ) ;

/* ------------------------------
 * Shared variables
 */
static struct command_line Command_c ;

/* ********************
 * *                  *
 * *  Top Level MAIN  *
 * *                  *
 * ********************
 */
/* ------------------------------
 * Main program runs forever, servicing socket requests that come in
 * from the outside world.
 */
int main( int iArgc_a, char** sArgv_a )
{
  int ierror; UINT1 lFirst;
  struct sigaction action ;
  setlocale( LC_ALL, "" ) ;

  /* Process the command line arguments
   */
  ProcessCommandLine( iArgc_a, sArgv_a, &Command_c ) ;

  if (Command_c.lDaemon) sig_daemon_init();

  /* Open the logging file, if any.  Starting from here we can use the
   * Lprintf function.
   */
  if (Command_c.lLogToFile) {
    char *sptr ;
    char sLogPath[PATHNAME_SIZE] ;
    sptr = getenv( "IRIS_LOG" ) ;
    if (sptr)
      {
	strncpy( sLogPath, sptr, PATHNAME_SIZE-20 ) ;
	sLogPath[PATHNAME_SIZE-20] = 0 ;
      }
    else
      strcpy( sLogPath, "/tmp/" ) ;
    strcat( sLogPath, "AntExport.log" ) ;
    if (Command_c.lAppendToLog)
      Command_c.iLogFile = fopen( sLogPath, "a" ) ;
    else
      Command_c.iLogFile = fopen( sLogPath, "w" ) ;
    if (NULL == Command_c.iLogFile)
      {
	fprintf( stderr, "Can not open log file: '%s'\n", sLogPath ) ;
	exit(EXIT_FAILURE) ;
      }
    }
  /* Print out welcome messages */
  Lprintf( &Command_c, "Antenna Export Utility Started.\n") ;
  if (Command_c.lVerboseIn) Lprintf( &Command_c, "Verbose Input Logging On.\n") ;
  if (Command_c.lVerboseOut) Lprintf( &Command_c, "Verbose Output Logging On.\n") ;

  /* Setup the signal handler.  This handler uses Lprintf(), so wait
   * till here to set it up.
   */
  sig_establish( SignalHandler, "AntExport" ) ;

  action.sa_handler = SIGCHLD_handler ;
  sigemptyset( &action.sa_mask ) ;
  action.sa_flags = SA_RESTART ;
  ierror = sigaction( SIGCHLD, &action, NULL ) ;
  if (ierror == -1) sig_signal( errno_to_message_va( errno, 1, "sigaction()" ) ) ;

  /* This will return only on an error while attempting to register
   * the port.  In that case, wait 1 minute and try again.
   */
  lFirst = TRUE;
  while(TRUE)
    {
      if (!lFirst) Lprintf( &Command_c, "Trying to Listen.\n") ;
      SocketListenBlock( Command_c.iPortNumber, ConnectionTester, ConnectionHandler,
			 ParentHandler ) ;
      lFirst = FALSE;
      Lprintf( &Command_c, "Port busy, waiting.\n") ;
      sig_microSleep( 60*1000*1000 ) ;
    }
}

/* ===========================================
 * Handle operations for the parent each time a connection is
 * established.  Should return quickly.
 */
static void ParentHandler
( pid_t iChildPid_a,
  int iConnectionNumber_a )
{
  ChildProcessList_c[iConnectionNumber_a]=iChildPid_a;
  return;
}
/* ===========================================
 * Test to see if we want to accept another connection.  Returns
 * the connection number, negative if it is rejected.
 */
static int ConnectionTester
( const struct sockaddr_in *pCliAddr_a )
{
  int iIndex;
  for (iIndex=0; iIndex<ANTENNA_EXPORT_NUM; iIndex++)
    {
      if ( 0 == ChildProcessList_c[iIndex] ) return iIndex;
    }
  return -ANTENNA_EXPORT_NUM;
}
/* ------------------------------
 * Function to process the command line arguments.
 */
static void ProcessCommandLine
( int iArgc_a, char *sArgv_a[],
  struct command_line *pCmd_a )
{
  int iArg ; char *pEnd ;

  memset( pCmd_a, 0, sizeof(*pCmd_a) ) ;
  pCmd_a->iPortNumber = PORT_ANTLIB_SERVER ;
  
  /* Parse command line options
   */
  for( iArg=1 ; iArg<iArgc_a ; iArg++ )
    {
      const char *sArg = sArgv_a[iArg] ;
      
      if( 0 == strncmp( sArg, "-daemon", 7 ) ) 
	{
	  pCmd_a->lDaemon = TRUE ;
	}
      else if (0 == strncmp( sArg, "-h", 2 ))
	{
	  Usage() ;
	}
      else if ( 0 == strncmp( sArg, "-log:", 5 ))
	{
	  if ( 0 == strcmp( "both", &sArg[5] ) ) 
	    {pCmd_a->lLogToFile=TRUE ;  pCmd_a->lLogToTerm=TRUE ;}
	  else if ( 0 == strcmp( "file", &sArg[5] ) ) 
	    {pCmd_a->lLogToFile=TRUE ;  pCmd_a->lLogToTerm=FALSE ;}
	  else if ( 0 == strcmp( "term", &sArg[5] ) )
	    {pCmd_a->lLogToFile=FALSE ; pCmd_a->lLogToTerm=TRUE ;}
	  else if ( 0 == strcmp( "none", &sArg[5] ) )
	    {pCmd_a->lLogToFile=FALSE ; pCmd_a->lLogToTerm=FALSE ;}
	}
      else if (0 == strncmp( sArg, "-port:", 6 ))
	{
	  SINT4 iPortNumber = strtol( &sArg[6], &pEnd, 10 ) ;
	  if( (iPortNumber < MIN_USER_PORT) || (iPortNumber > 65535) ) {
	    printf( "Port number %d invalid.\n", iPortNumber ) ;
	    exit(EXIT_FAILURE) ;
	  }
	  pCmd_a->iPortNumber = iPortNumber ;
	}
      else if (0 == strncmp( sArg, "-vi", 3 ))
	{
	  pCmd_a->lVerboseIn = TRUE ;
	}
      else if (0 == strncmp( sArg, "-vo", 3 ))
	{
	  pCmd_a->lVerboseOut = TRUE ;
	}
    }

  /* Do some cross checking */
  if (pCmd_a->lDaemon && pCmd_a->lLogToTerm)
    {
      fprintf( stderr, "Warning: -daemon flag overriding -log:term\n" ) ;
      pCmd_a->lLogToTerm=FALSE;
    }

}

/* ------------------------------
 * Print command line usage
 */
static void Usage( void )
{
  fprintf( stderr, "AntExport options:\n" ) ;
  fprintf( stderr, "  -daemon        -- Run as daemon\n" ) ;
  fprintf( stderr, "  -help          -- Print this list\n" ) ;
  fprintf( stderr, "  -log:<option>  -- Log options: term, file, both, none\n" ) ;
  fprintf( stderr, "  -port:<number> -- Specify listen port (default %d)\n",
	   PORT_ANTLIB_SERVER ) ;
  fprintf( stderr, "  -vi            -- Verbose logging of inputs\n" ) ;
  fprintf( stderr, "  -vo            -- Verbose logging of outputs\n" ) ;
  fprintf( stderr, "   (all other arguments ignored)\n" ) ;
  exit(EXIT_FAILURE) ;
}

/* ********************************************
 * *                                          *
 * *  Child Process to Handle the Connection  *
 * *                                          *
 * ********************************************
 */
/* ------------------------------
 * Handle the communication from one other system.  This routine will
 * return when the communication is disconnected.
 */
#define POLLWAKEUPMS  200	/* Polling wakeup interval */
static void ConnectionHandler
( int iSocket_a,
  const struct sockaddr_in *pCliAddr_a,
  int iConnectionNumber_a )
{
  MESSAGE iStatus ;
#define CON_HANDLER_STRING_SIZE (80)
  char sString[CON_HANDLER_STRING_SIZE];
  SINT4 iUpdateLen ; char *pUpdate = NULL ;
  UINT4 iClientAddr=ntohl(pCliAddr_a->sin_addr.s_addr) ;
  pid_t iPid=getpid();
  UINT4 iEventFlag = ANTENNA_EXPORT_EF0+iConnectionNumber_a;
  UINT4 iClientMask = (1<<iConnectionNumber_a);
  UINT1 iGetFlag, lPollOutOn;


  sig_establish( SignalHandler, "AntExportChild" ) ;

  sprintf( sString, "%5d: Connection request from:%d.%d.%d.%d\n", 
	   iPid, (iClientAddr>>24),  255&(iClientAddr>>16), 
	   255&(iClientAddr>>8), 255&(iClientAddr) ) ;
  Lprintf( &Command_c, sString ) ;

  /* Open the antenna driver, and send back an indication of whether
   * we were successful.
   */
  iStatus = AntPowerUpMain( ANT_IAM_EXPORT, 0 ) ;
  if( iStatus != SS_NORMAL ) {
    WriteSocketString( iSocket_a, "Nak", Command_c.lVerboseOut ) ;
    sig_signal( iStatus ) ; return ;
  }

  iStatus = WriteSocketString( iSocket_a, "Ack", Command_c.lVerboseOut );
  if( iStatus != SS_NORMAL ) {
    sig_signal( iStatus ) ; return ;
  }

  iGetFlag = ANTGET_ALL;
  lPollOutOn = FALSE;

  /* Poll the socket file descriptor for reading or writing, and keep
   * rechecking whether we have anything that needs to be written at
   * all.
   */  
  for(;;) {
    struct pollfd pollList[1] ; int iPollVal ;

    /* Check that the antenna library is still running */
    if (lAntLibShutDown()) break;

    /* Setup default polling request for read input available.
     */
    pollList[0].fd      = iSocket_a ;
    pollList[0].events  = POLLIN ;
    pollList[0].revents = 0 ;

    if (lPollOutOn)
      {
	/* If we do not have anything yet to send, then check the driver
	 * to see if any new updates are ready.  Add a poll output check
	 * if we have something ready to go.
	 */
	if( pUpdate == NULL ) {
	  iUpdateLen = iAntGetUpdatePackets( iClientMask, iGetFlag, &pUpdate ) ;
	  iGetFlag = ANTGET_EXPORT;
	}
	if( pUpdate ) pollList[0].events |= POLLOUT ;
      }

    /* Poll the socket to determine if we can read or write.
     */
    iPollVal = poll( pollList, 1, 0 ) ;

    if( iPollVal < 0 ) 
      {
	if( errno != EINTR ) {
	  sig_signal( errno_to_message_va( errno, 1, "HandleConnection()" ) ) ;
	  break ;
	}
      } 
    else if( iPollVal == 1 ) 
      {
	/* One returned events structure has been filled in for us.
	 * Check the bits to determine what we can do.
	 */
	if( pollList[0].revents & ( POLLHUP | POLLERR) ) {
	  /* Errors (or closes) exit the loop for good.
	   */
	  break ;
	}
	/* Output first, because we want low latency/jitter */
	else if( pollList[0].revents & POLLOUT ) {
	  /* Data can be written to the remote antenna driver.  Note
	   * that we only get here when we do, indeed, have something
	   * ready to write.
	   */
	  iStatus = WriteSocketData1( iSocket_a, pUpdate, iUpdateLen,
				      Command_c.lVerboseOut );
	  if (iStatus!=SS_NORMAL)
	    {
	      sig_signal( iStatus ) ;
	      return;
	    }
	  free( pUpdate ) ; pUpdate = NULL ;
	}
	else if( pollList[0].revents & POLLIN ) {
	  /* Data is available to be read from the remote antenna
	   * driver.
	   */
	  char *sStr; SINT4 iLength; UINT4 iFrom;

	  sStr = ReadSocketPacket( iSocket_a, &iLength, Command_c.lVerboseIn, &iFrom, 0,
				   NULL );
	  if (!sStr) return;  /* Return on closed socket */
	  /* Make sure string is null terminated */
	  sStr[ iLength-1 ] = '\0' ;

	  /* Check for the various possible commands */
	  if (0==strcmp( sStr, "Setup"))
	    {
	      iStatus = ProcessSetupPacket( iSocket_a );
	      if (iStatus!=SS_NORMAL) { sig_signal(iStatus); return; }
	    }
	  else if (0==strcmp( sStr, "PollOn"))
	    {
	      lPollOutOn = TRUE;
	      /* Get all old packets the first time we are called */
	      iGetFlag = ANTGET_ALL;
	    }
	  else if (0==strcmp( sStr, "PollOff"))
	    {
	      lPollOutOn = FALSE;
	    }
	  else if (sStr[0] == (char)ANT_SYNC_ASCMD)
	    {
	      SINT4 iAntSetPos, iAntSetLength;
	      /* The AntSet command.  Can have multiple commands in one 
	       * packet. */
	      iAntSetPos = 0;
	      while( iAntSetPos+2<iLength)
		{
		  if (sStr[iAntSetPos++]!=(char)ANT_SYNC_ASCMD) goto DONE; /* Check command */
		  iAntSetLength = sStr[iAntSetPos++];
		  if ((iAntSetPos+iAntSetLength) >= iLength) goto DONE; /* Check size */
		  sStr[iAntSetPos+iAntSetLength] = 0;           /* Must be null term */
		  AntSet( sStr+iAntSetPos ) ;
		  if (Command_c.lVerboseIn) 
		    {
		      sprintf( sString, "%5d: AntSet('%s')\n", iPid, sStr+iAntSetPos );
		      Lprintf( &Command_c, sString ) ;
		    }
		  iAntSetPos+=iAntSetLength+1;
		}
	    }
	  else
	    {
	      sprintf( sString, "%5d: Unrecognized packet 0x%02X.\n", iPid, 
		       (UINT4)(UINT1)sStr[0] ) ;
	      Lprintf( &Command_c, sString ) ;
	    }
	DONE:
	  free( sStr );
	}
      }  /* end  if( iPollVal < 0 ) ... */

    /* Wait for output event only if we are all caught up on I/O */
    if (iPollVal==0)
      {
	schedule_ef( iEventFlag, POLLWAKEUPMS );
	sig_waitef( iEventFlag );
	cancel_schedule_ef();
	sig_clref( iEventFlag );
      }

  }  /* end for(;;)... */
  return ;

}
/* ================================================================
 * Process the setup packet.  This will respond with an Ack with
 * the whole setup structure in ASCII.
 */
static MESSAGE ProcessSetupPacket
(int iSocket_a )
{
  MESSAGE iStatus, iStatus2;
  char *sSetupFile = NULL;
  SINT4 iSize, iChannel;
  const struct ant_manual_setup *pSetup;
  char sFileName[FILENAME_SIZE];
  char sPath[PATHNAME_SIZE];
  UINT1 lTempFile = FALSE, lAckWasSent=FALSE;
  
  /* Get directory in which to place the temporary file */

  char *sDir = getenv( "IRIS_TEMP" ) ;
  if( NULL == sDir )
    {
      iStatus = iMessageMod_va( USER_NO_ENV, 1, "IRIS_TEMP" );
      goto END;
    }
  
  pSetup = AntGetSetupPointer( );
  
  sprintf( sFileName, "setup_antenna.AntExport%d", getpid() );

  /* Write setup info to a temporary file */
  iStatus = lim_saveFileDir( NULL, "IRIS_TEMP", sFileName, "ant_manual_setup", pSetup, 
			     setup_limits );
  if (iStatus!=SS_NORMAL) goto END;

  /* Starting here, we must be sure to delete the temporary file */
  lTempFile = TRUE;

  strcpy( sPath, sDir );
  strcat( sPath, sFileName );
  iStatus = imapopen( sPath, FALSE, (void**)&sSetupFile, &iSize, &iChannel );
  if (iStatus!=SS_NORMAL) goto END;
  /* Starting here, we must be sure to close the temporary file */

  iStatus = WriteSocketData2( iSocket_a, "Ack|", 4, sSetupFile, iSize,
			       Command_c.lVerboseOut );
  /* Starting here, we do not send a Nak on error, because the Ack was already sent.*/
  lAckWasSent = TRUE;

 END:
  /* Close the file */
  if (sSetupFile)
    {
      iStatus2 = imapclose( sSetupFile, iSize, iChannel );
      if (iStatus==SS_NORMAL) iStatus=iStatus2;
    }

  /* Delete the file */
  if (lTempFile)
    {
      iStatus2 = sig_delete_file( sPath );
      if (iStatus==SS_NORMAL) iStatus=iStatus2;
    }

  if ((iStatus!=SS_NORMAL) && (!lAckWasSent) )
    {
      char sReply[170], sMessage[160];
      sprintf( sReply, "Nak|%s", 
	       message_to_text_r( iStatus, 0, NULL, sMessage ) );
      WriteSocketString( iSocket_a, sReply, Command_c.lVerboseOut );
    }

  return iStatus;
}

/* ************************************
 * *                                  *
 * *  Exception Handlers and Logging  *
 * *                                  *
 * ************************************
 */
/* ------------------------------
 * Function to print signaled errors to the logging 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( &Command_c, sMessage ) ;
  return( SS_NORMAL ) ;
}

/* =============================================
 * Signal handler for the child death signal
 */
static void SIGCHLD_handler( int isignal_a )
{
  char sString[80] ; int status ; pid_t iPid ;

  /* Use the wait3 system call with the WNOHANG option.
   */
  while( (iPid=wait3( &status, WNOHANG, 0 )) > 0)
    {
      int iIndex;
      sprintf( sString, "%5d: Disconnect %d\n", iPid, status ) ;
      Lprintf( &Command_c, sString ) ;
      /* Find the matching slot in the child process list */
      for (iIndex=0; iIndex<ANTENNA_EXPORT_NUM; iIndex++)
	{
	  if (iPid==ChildProcessList_c[iIndex])
	    {
	      ChildProcessList_c[iIndex]=0;
	      goto FOUND_IT;
	    }
	}
      Lprintf( &Command_c, "Child missing\n" ) ;
    FOUND_IT:
      continue;
    }
}

/* ------------------------------
 * Print string to log and terminal, if desired.
 */
void Lprintf
( const struct command_line *pCmd_a,
  const char *sString_a )
{
  if (pCmd_a)
    {
      if (pCmd_a->lLogToTerm) fputs( sString_a, stderr ) ;
      if (pCmd_a->lLogToFile)
	{
	  fputs( sString_a, pCmd_a->iLogFile ) ;
	  fflush( pCmd_a->iLogFile ) ;
	}
    }
  else
    fputs( sString_a, stderr ) ;
}

