/*
 * ******************************************************************
 * *	                                                            *
 * *     IRIS Simulated TDWR Product Generator and Inserter         *
 * *                                                                *
 * ******************************************************************
 * File: utils/examples/tdwr_sim.C
 *
 *         COPYRIGHT (c) 1999, 2000, 2001, 2002, 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.
 */

#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <locale.h>
#include <math.h>
#include <netdb.h>
#include <netinet/tcp.h>
#ifndef LINUX
#include <signal.h>
#endif  // #ifndef LINUX
#include <stdio.h>
#include <stdlib.h>
#ifndef LINUX
#include <string.h>
#endif  // #ifndef LINUX
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/wait.h>

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

#include "user_lib.h"
#include "config_lib.h"
#include "misc_lib.h"

#define MAX_PACKET_SIZE (90)

/* Function prototypes used just in this file */
static void ProcessCommandLine
( int iargc_a,
  char** sargv_a,
  struct command_line *pCommand_a );

static int init_tcpip
( const struct command_line *pCommand_a );

static void RunLoop
( const struct command_line *pCommand_a );

static void MakeInitialProduct
(struct tdwr_results pBody_a[TDWR_RUNWAY_MAX],
 const struct corridor_list *pCorList_a );

static void MakeSimulatedHeader
(struct product_hdr *pHeader_a,
 const struct iris_setup *pISetup_a,
 const struct license_setup *pLSetup_a,
 const struct command_line *pCommand_a );

static void PromptForUserChanges
(struct product_hdr *pHeader_a,
 struct tdwr_results pData_a[] );

static void WriteProductFile
( const char sFile_a[40],         /* File name is returned here. */
  struct product_hdr *pHeader_a,  /* Product header here, final size to be filled */
  const struct protect_setup *pPSetup_a,
  const struct tdwr_results pResults_a[TDWR_RUNWAY_MAX],
  const struct command_line *pCommand_a );

static void sigio_handler
( int signal_a );

/* Shared variables */
static int socket_c;

struct command_line
{
  FLT8 fLat;     /* Location */
  FLT8 fLon;
  FLT8 fHeight;  /* Height of radar dish in meters */
  char sHost[MAXHOSTNAMELEN];
  char sDirectory[DIRECTORY_SIZE];
  unsigned int iPort;
};

/* =====================================================
 * This is an example program showing how to send files to IRIS.
 * This program prompt for a file (or path) name, then sends it to IRIS.
 * It uses RCP to copy the file.
 *
 */
int main( int iargc_a, char** sargv_a )
{
  int ierror;
  struct command_line Command;

  /* ------------------------------------------------------------------- */

  setlocale( LC_ALL, "" );

  ProcessCommandLine( iargc_a, sargv_a, &Command );

  ierror = init_tcpip( &Command );
  if (ierror != 0) exit(EXIT_FAILURE);

  RunLoop( &Command );

  exit(EXIT_SUCCESS) ;
}

/* ===========================================================
 */
static void ProcessCommandLine
( int iargc_a,
  char** sargv_a,
  struct command_line *pCommand_a )
{
  int ierror, iarg;
  char *sptr;

  memset( pCommand_a, 0, sizeof(*pCommand_a) );
  pCommand_a->fHeight = 10.0;
  pCommand_a->fLon    = 114.21;  /* HK location */
  pCommand_a->fLat    =  22.36;
  pCommand_a->iPort  = PORT_NETWORK_RECEIVER;
  ierror = gethostname( pCommand_a->sHost, MAXHOSTNAMELEN );
  if (ierror < 0)
    {
      perror( "gethostname" );
      exit(EXIT_FAILURE);
    }
  sptr = getenv( "IRIS_PRODUCT" );
  if (!sptr)
    {
      sig_signal_va( USER_NO_ENV, 1, "IRIS_PRODUCT" );
      exit(EXIT_FAILURE);
    }
  strcpy( pCommand_a->sDirectory, sptr );

  for (iarg=1; iarg<iargc_a; iarg++)
    {
      if ( 0 == strncmp( sargv_a[iarg], "-h", 2 ) )
	{
	  printf( "tdwr_sim command line options:\n" );
	  printf( "  -h         : help\n" );
	  printf( "  -l lon lat : specify radar location\n" );
	  printf( "  -n hhhh    : specify host name to send to (defaults to localhost)\n" );
	  printf( "  -p pppp    : specify port number (default %d)\n", PORT_NETWORK_RECEIVER );
	  exit(EXIT_SUCCESS);
	}
      else if ( 0 == strcmp( sargv_a[iarg], "-l" ) )
	{
	  iarg++;
	  if ((iarg+1)<iargc_a)
	    {
	      /* Process the longitude and latitude */
	      pCommand_a->fLon = strtod( sargv_a[iarg], &sptr );
	      if (sptr == sargv_a[iarg])
		{
		  printf("Bad longitude\n");
		  exit(EXIT_FAILURE);
		}
	      iarg++;
	      pCommand_a->fLat = strtod( sargv_a[iarg], &sptr );
	      if (sptr == sargv_a[iarg])
		{
		  printf("Bad latitude\n");
		  exit(EXIT_FAILURE);
		}
	    }
	  else
	    {
	      printf( "Missing lon/lat after '-l'\n" );
	      exit(EXIT_FAILURE);
	    }
	}
      else if ( 0 == strcmp( sargv_a[iarg], "-n" ) )
	{
	  iarg++;
	  if (iarg<iargc_a)
	    {
	      /* Process the host name */
	      strncpy( pCommand_a->sHost, sargv_a[iarg], MAXHOSTNAMELEN-1 );
	      pCommand_a->sHost[MAXHOSTNAMELEN-1] = 0;
	    }
	  else
	    {
	      printf( "Missing host name after '-n'\n" );
	      exit(EXIT_FAILURE);
	    }
	}
      else if ( 0 == strcmp( sargv_a[iarg], "-p" ) )
	{
	  iarg++;
	  if (iarg<iargc_a)
	    {
	      /* Process the port number */
	      pCommand_a->iPort = strtol( sargv_a[iarg], &sptr, 10 );
	      if ((pCommand_a->iPort<MIN_USER_PORT) || (pCommand_a->iPort>65535))
		{
		  printf( "Port number %d invalid.\n", pCommand_a->iPort );
		  exit(EXIT_FAILURE);
		}
	    }
	  else
	    {
	      printf( "Missing port number after '-p'\n" );
	      exit(EXIT_FAILURE);
	    }
	}
      else
	{
	  printf( "Unrecognized command line argument '%s'\n", sargv_a[iarg] );
	  exit(EXIT_FAILURE);
	}
    }
  return;
}

/* ===========================================================
 * Initialize the socket connection.  Returns -1 if there was an error, 0 if OK.
 */
static int init_tcpip
( const struct command_line *pCommand_a )
{
  int ierror, iflag, iaddrsize;
  struct sockaddr_in servaddr_in;
  struct sockaddr *pservaddr;
  struct hostent *hostptr;
  struct in_addr *paddress;

  /* Convert the server host name to an internet address:
   */
  hostptr = gethostbyname(pCommand_a->sHost);
  if (hostptr != NULL)
    {
      memset( &servaddr_in, 0, sizeof(servaddr_in));
      servaddr_in.sin_family = AF_INET;
      paddress = (struct in_addr *)hostptr->h_addr_list[0];
      servaddr_in.sin_addr = *paddress;
      servaddr_in.sin_port = htons(pCommand_a->iPort);
      
      pservaddr = (struct sockaddr *)(void*)&servaddr_in;
      iaddrsize = sizeof( servaddr_in );
    }
  else
    {
      perror( "gethostbyname" );
      return -1;
    }

  socket_c = socket(AF_INET,SOCK_STREAM,0);
  if (socket_c == -1)
    {
      perror( "socket" );
      return -1;
    }
  /* We now have a valid opened socket, it must be closed if we exit.
   * Set the TCP_NODELAY option.
   */
  iflag = TRUE;
  ierror = setsockopt( socket_c, IPPROTO_TCP, TCP_NODELAY, (char *)&iflag,
		      sizeof(iflag) );
  if (ierror < 0) perror( "setsockopt" );

  ierror = connect(socket_c, pservaddr, iaddrsize);
  if (ierror >= 0)
    {
      struct sigaction action;
      /* Set up the socket to be nonblocking asynchronous mode */
      /* Set up a signal handler for the SIGIO signal */
      action.sa_handler = sigio_handler;
      sigemptyset( &action.sa_mask );
      action.sa_flags = SA_RESTART;
      ierror = sigaction( SIGIO, &action, NULL );
      if (ierror == -1) perror( "sigaction" );
      
      /* Set up so that i/o causes the SIGIO signal */
#ifdef LINUX
      {
	pid_t iPid = getpid();
	ierror = fcntl( socket_c, F_SETOWN, iPid );
	if (ierror == -1) perror( "fcntl1" );
      }
      ierror = fcntl( socket_c, F_SETFL, FASYNC );
      if (ierror == -1) perror( "fcntl2" );
#else
      iflag = getpid();
      ierror = ioctl( socket_c, SIOCSPGRP, &iflag );
      if (ierror == -1) perror( "ioctl1" );

      iflag = 1;
      ierror = ioctl( socket_c, FIOASYNC, &iflag );
      if (ierror == -1) perror( "ioctl2" );
#endif
      /* Set up so that i/o is nonblocking */
      iflag = 1;
      ierror = ioctl( socket_c, FIONBIO, &iflag );
      if (ierror == -1) perror( "ioctl" );
      
      return 0;
    }
  else
    {
      perror( "connect" );
    }
  /* Close the socket, if there was an error before returning */

  ierror = close( socket_c );
  if (ierror == -1) perror( "close" );

  return -1;

}
/* ===========================================
 * Run a loop sending products to the specified machine 
 */
static void RunLoop
( const struct command_line *pCommand_a )
{
  char smessage[100], sFile[40];
  int ilength, ierror, iSendCount;
  struct product_hdr Header;
  struct tdwr_results PBody[TDWR_RUNWAY_MAX];
  struct iris_setup    ISetup;
  struct license_setup LSetup;
  struct corridor_list CorList;
  MESSAGE istatus;

  istatus = irisload(NULL, &ISetup );
  if (istatus!=SS_NORMAL) sig_signal(istatus);

  istatus = license_load_setup(NULL, &LSetup );
  if (istatus!=SS_NORMAL) sig_signal(istatus);

  GetCorridorList( &CorList, &ISetup.protect, ALL_AREAS_MASK );

  iSendCount = 0;
  MakeInitialProduct( PBody, &CorList );
  Header.end.iresults_count = 1;
  while (TRUE)
    {
      MakeSimulatedHeader( &Header, &ISetup, &LSetup, pCommand_a );
      PromptForUserChanges( &Header, PBody );
      sprintf( sFile, "tdwr_sim%4.4d", iSendCount++ );
      WriteProductFile( sFile, &Header, &ISetup.protect, PBody, pCommand_a );

      printf( "Sending TDWR product to IRIS.\n\n" );
      /* Notify the other end of the file */
      strcpy( smessage, "FILE " );
      strcat( smessage, sFile );
      ilength = strlen(smessage);
      ierror = send( socket_c, smessage, ilength+1, 0 );
      if (ierror == -1) perror( "send" );

    }

}
/* ==============================================================
 * Make the initial pass of the TDWR array.  Just fill in
 * the corridors.
 */
static void MakeInitialProduct
(struct tdwr_results pBody_a[TDWR_RUNWAY_MAX],
 const struct corridor_list *pCorList_a )
{
  SINT4 iCount;
  memset( pBody_a, 0, TDWR_RUNWAY_MAX*sizeof(struct tdwr_results) );
  for (iCount=0; iCount<TDWR_RUNWAY_MAX; iCount++)
    {
      struct tdwr_results *pResults;
      const struct one_corridor  *pList;
      SINT4 iListEntry;

      /* Corridor names will match the first from the list, but the 
       * list may be short!
       */
      iListEntry = MIN( iCount, pCorList_a->iCount-1 );
      pResults = &pBody_a[iCount];
      pList    = &pCorList_a->List[iListEntry];

      /* Initialize to all zero */
      memset( pResults, 0, sizeof(*pResults) );

      strncpy( pResults->sCorridor, pList->sCorridor, 16 );
      strncpy( pResults->sAlertType, "   ", 3 );
    }

}
/* ===============================================================
 * Make a simulated product header for the TDWR product.
 */
static void MakeSimulatedHeader
(struct product_hdr *pHeader_a,
 const struct iris_setup *pISetup_a,
 const struct license_setup *pLSetup_a,
 const struct command_line *pCommand_a )
{
  SINT4 iResultsCount;
  struct full_time_spec Time;
  /* -----------------------------------------------------------------------
   */
  /* Preserve the results count */
  iResultsCount = pHeader_a->end.iresults_count;

  GetTimeFull( &Time, pISetup_a->misc.lRecordGmt );

  HeaderFromScratch( pHeader_a, &Time, pLSetup_a->sSitename,
		     pLSetup_a->sHardwareName, "DEFAULT" );

  pHeader_a->pcf.iptype = PROD_TDWR;
  pHeader_a->pcf.ixsize = 480;
  pHeader_a->pcf.iysize = 480;
  pHeader_a->pcf.izsize = 1;

  pHeader_a->pcf.ixrad = 500*pHeader_a->pcf.ixsize;
  pHeader_a->pcf.iyrad = 500*pHeader_a->pcf.iysize;

  /* Task name gets "OK" or "FAULT" depending on check criteria
   */
  spacify_ncpy( pHeader_a->pcf.stname, "OK", 12 );

  /* Add in required PSI here.  Centerfield wind simulation is based on seconds.
   * Cycle simulation once per hour.
   */
  {
    FLT4 fSpeed, fDirection, fSimTime;
    SINT4 iSpeed, iDirection, iGustSpeed;
    char sTemp[40];
    fSimTime = ((FLT4)(Time.Ymds.isec % 3600))/3600.0;
    fDirection = 36.0 * fSimTime;
    fSpeed     = 5 + 50.0 * fSimTime;
    iDirection = 10 * NINT(fDirection);
    iSpeed = NINT(fSpeed);
    iGustSpeed = NINT(1.5*fSpeed);
    pHeader_a->pcf.psi.tdwr.iFlags = TDWR_FLG_LLWAS;
    strncpy( pHeader_a->pcf.psi.tdwr.sSourceID, "IIA ", 4 );
    sprintf( sTemp, "%3.3d", iDirection );
    strncpy( pHeader_a->pcf.psi.tdwr.sCFDirection, sTemp, 3 );
    sprintf( sTemp, "%2.2d", iSpeed );
    strncpy( pHeader_a->pcf.psi.tdwr.sCFSpeed, sTemp, 2 );
    sprintf( sTemp, "%2.2d", iGustSpeed );
    strncpy( pHeader_a->pcf.psi.tdwr.sCFGustSpeed, sTemp, 2 );
    /* Make max range 20 km */
    pHeader_a->pcf.psi.tdwr.iRMax = 20 * 100000;
  }

  /* Fill in projection scale info */
  pHeader_a->pcf.iprojection_type = MAPROJ_AZIMUTHAL;
  pHeader_a->pcf.ixscale =
    ((2*pHeader_a->pcf.psi.tdwr.iRMax)+pHeader_a->pcf.ixsize)/pHeader_a->pcf.ixsize;
  pHeader_a->pcf.iyscale = pHeader_a->pcf.ixscale;

  /* Radar location */

  pHeader_a->end.ilat_center = iBin4FromFDeg(pCommand_a->fLat);
  pHeader_a->end.ilon_center = iBin4FromFDeg(pCommand_a->fLon);
  pHeader_a->end.ilat_ref    = pHeader_a->end.ilat_center;
  pHeader_a->end.ilon_ref    = pHeader_a->end.ilon_center;
  pHeader_a->end.ignd_hgt    = NINT(pCommand_a->fHeight);
  pHeader_a->end.irad_hgt    = 0;
  
  /* Fill back in the results count */
  pHeader_a->end.iresults_count = iResultsCount;

}
/* ===============================================================
 * Prompt the user to override values from the simulator.
 */
static void PromptForUserChanges
(struct product_hdr *pHeader_a,
 struct tdwr_results pData_a[] )
{
  char *sfile, *sptr, sline[80];
  SINT4 iCorridor, iLength;
  struct tdwr_psi_struct *pPsi;

  printf( "Enter Corridor Count [%d]: ", pHeader_a->end.iresults_count );
  sfile = fgets( sline, 80, stdin );
  if (NULL != sfile)
    {
      /* Trim trailing carriage return */
      iLength = strlen( sfile )-1;
      if (iLength>0)
	{
	  SINT4 iCount;
	  sfile[iLength] = 0;
	  iCount = strtol( sfile, &sptr, 10 );
	  if ((iCount>=0)&&(iCount<=8)) pHeader_a->end.iresults_count = iCount;
	}
    }
  pPsi = &pHeader_a->pcf.psi.tdwr;

  for (iCorridor=0; iCorridor<pHeader_a->end.iresults_count; iCorridor++)
    {
      struct tdwr_results *pResults;
      pResults = &pData_a[iCorridor];
      printf( "Enter Alert type for Corridor %d (%4.4s)[%3.3s]: ",
	      iCorridor, pResults->sCorridor, pResults->sAlertType );
      sfile = fgets( sline, 80, stdin );
      if (NULL != sfile)
	{
	  /* Trim trailing carriage return */
	  iLength = strlen( sfile )-1;
	  if (iLength>0)
	    {
	      int iIndex;
	      /* Copy out the first three characters of the typed string,
	       * replacing all non-printable characters with space.  We can
	       * overrun sfile, because sline is longer than 3.
	       */
	      char sString[10];
	      memcpy( sString, sfile, 3 );
	      for (iIndex=0; iIndex<3; iIndex++)
		{
		  if ( !isprint(sString[iIndex]) ) sString[iIndex] = ' ';
		}
	      strncpy( pResults->sAlertType, sString, 3 );
	      /* Blank the alert speed if no alert */
	      if (0 == strncmp( pResults->sAlertType, "   ", 3 ) )
		{
		  pResults->iLossGain = 0;
		  strncpy( pResults->sArena,    "   " , 3 );
		}
	      else
		{
		  FLT4 fKnots, fMperS;
		  if (0 == strncmp( pResults->sAlertType, "MBA", 3))
		    fKnots = -45;
		  else
		    fKnots = 20;
		  /* Convert from knots to m/s */
		  fMperS = fKnots * MPS_PER_KNOT;
		  pResults->iLossGain = NINT( 1000.0 * fMperS );
		  strncpy( pResults->sArena,    "3MF" , 3 );
		}
	    }
	}
      strncpy( pResults->sThresholdDirection, pPsi->sCFDirection, 3 );
      strncpy( pResults->sThresholdSpeed,     pPsi->sCFSpeed, 2 );
    }
}

/* ======================================================
 * Writes the TDWR product file to disk.
 */
static void WriteProductFile
( const char sFile_a[40],         /* File name to use. */
  struct product_hdr *pHeader_a,  /* Product header here, final size to be filled */
  const struct protect_setup *pPSetup_a,
  const struct tdwr_results pResults_a[TDWR_RUNWAY_MAX],
  const struct command_line *pCommand_a )
{
  MESSAGE istatus;
  struct tdwr_product *pProduct;
  SINT4 isize, ichannel, iAlertCount, iResult;
  char spathname[PATHNAME_SIZE] ;

  pHeader_a->hdr.ibytes = PRODUCT_HDR_SIZE + PROTECT_SETUP_SIZE +
    pHeader_a->end.iresults_count*sizeof(struct tdwr_results);

  strcpy( spathname, pCommand_a->sDirectory ) ;
  strcat( spathname, sFile_a ) ;

  istatus = imapcreate( spathname, pHeader_a->hdr.ibytes, (void **)&pProduct, 
			&isize, &ichannel ) ;
  if (istatus != SS_NORMAL ) 
    {
      sig_signal( istatus );
      return;
    }

  /* Move in the protected_area definitions */
  memmove( &pProduct->Protect, pPSetup_a, PROTECT_SETUP_SIZE );

  /* Move the results in first, this allows counting the warnings */
  iAlertCount = 0;
  for(iResult=0; iResult<pHeader_a->end.iresults_count; iResult++)
    {
      const struct tdwr_results *pResult;
      pResult = &pResults_a[iResult];

      memmove( &pProduct->Results[iResult], pResult, sizeof( struct tdwr_results ) );
      if (pResult->sAlertType[0] != ' ') iAlertCount++;
    }
  
  /* Copy fault information into the header */
  switch( iAlertCount )
    {
    default:
      {
	char sString[20];
	sprintf( sString, "%d_ALERTS", iAlertCount );
	spacify_ncpy( pHeader_a->pcf.stname, sString, 12 );
      }
      break;
    case 1:
      spacify_ncpy( pHeader_a->pcf.stname, "1_ALERT", 12 );
      break;
    case 0:
      spacify_ncpy( pHeader_a->pcf.stname, "OK", 12 );
      break;
    }
  memmove( &pProduct->Header, pHeader_a, sizeof( struct product_hdr ) );


  imapclose( pProduct, isize, ichannel );
}
/* ================================================================
 * Signal handler for SIGIO signal.  It just does a read, and processes
 * the result.
 */
static void sigio_handler( int signal_a )
{
  char data[100];
  int isize;

  isize = recv( socket_c, 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" ))
	{
	  printf( "Got 'EXIT' message.\n" );
	  exit(EXIT_SUCCESS);
	}
      else
	{
	  printf( "recv read unknown message: '%s'\n", data );
	}


    }

  else if (isize < 0)
    {
      /* We an error here, signal all errors except EWOULDBLOCK */
      if (errno != EWOULDBLOCK)
	{
	  perror( "recv" );
	  exit(EXIT_FAILURE);
	}
    }

  else
    {
      /* Zero size indicates that the socket has been closed at the other end */
      printf( "Link broken\n" );
      exit(EXIT_SUCCESS);
    }
  return;

}

