/* *******************************************************************************
 * *                                                                             *
 * *  Function to convert an IRIS Raw product to a NEXRAD Archive 2 format file  *
 * *                                                                             *
 * *******************************************************************************
 * File: utils/archive2/IrisToArchive.C
 *
 *          COPYRIGHT (c) 2001, 2002, 2003, 2004, 2005, 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.
 */

/* This program is a pipe which takes an IRIS product in the standard input
 * and writes a modified product out the standard output.  This will not
 * work correctly on IRIS data which is using summer time.  It must be a
 * full scan RAW product as input, and it must be a PPI or PPI Sector scan.
 * Also SIGMET recommends turning off product generation from partial
 * ingest scans to avoid format problems on conversion.
 *
 * Note if you are modifying this program, and wish to insert a diagnostic
 * printf, be sure to write it to the error output.
 */

#ifndef LINUX
#include <arpa/nameser.h>  /* Defines BYTE_ORDER on HP and SGI */
#endif  /* #ifndef LINUX */
#include <locale.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>

#include "sigtypes.h"
#include "dsp.h"
#include "headers.h"
#include "iris_task.h"
#include "ingest.h"
#include "product.h"
#include "user_lib.h"
#include "dsp_lib.h"
#include "misc_lib.h"
#include "nexrad.h"

/* Information prepared for each data moment.
 */
struct moment_info
{
  UINT1 lValid;              /* Data will be converted */
  UINT1 lVelocityHighRes;    /* Velocity data is in 1/2 m/s */
  UINT2 iBinNumber;          /* Number of range bins converted */
  UINT2 iPointer;            /* Offset in packet's data to moment's data, origin 0 */
  SINT2 iInputStep;          /* Step in IRIS input bins, 1 means use all bins */
  FLT4  fBinRes;             /* Range bin resolution in meters */
  FLT4  fNyquistVel;         /* Nyquist velocity in m/s */
  struct ingest_data_header IDHeader;
  UINT1 iDataMap[65536];     /* Mapping table to convert */
};

/* This holds information to customize the convertor */
struct convert_config
{
  UINT1 lLogToFile;
  UINT1 lLogToTerm;
  UINT1 lVerbose;              /* Log more details */
  UINT1 lAppendToLog;          /* Append to previous log file */
  UINT1 lIgnoreRayTimes;       /* Ignore ray times, use sweep time instead */
  FILE *iLogFile;              /* Open file descriptor to log file */
  FLT4  fMinReflectivity;      /* Minimum refl. All lower values are thresholded */
  FLT4  fDesiredDopRes;        /* Desired Doppler resolution in meters */
  FLT4  fDesiredRefRes;        /* Desired Reflectivity resolution in meters */
};

/* This holds information preserved between runs */
struct convert_state
{
  int  iYear;      /* Data time of last file converted */
  int  iMonth;
  int  iDay;
  int  iExtension; /* Filename extension */
};

static struct convert_config CConfig_c;
static char sLog_c[200];

/* Local function prototypes
 */
static void ProcessCommandLine
(int iArgc, 
 char *sArgv[] );

static void Usage( void );

static MESSAGE ProcessProductHeader
(const struct product_hdr *pheader_a);

static MESSAGE ConvertRawToArchive2
(const struct raw_product *pRaw_a,
 const struct convert_state *pState_a );   /* State gets updated to current info */

static UINT1 ConvertOneRay
( struct nr_radar_packet *pOutNr_a,
  SINT4 iSweep_a,                     /* Sweep number with volume, origin 0 */
  SINT4 iIrisMoments_a,               /* Number of IRIS moments */
  const struct product_hdr *pPHeader_a,
  const struct ingest_header *pIHeader_a,
  const UINT1 iMomentMapTable_a[MAX_MOMENTS],
  const struct moment_info MInfo_a[3],
  UINT1 lFirstRay_a );                /* This is the first ray of a sweep */

static void get_raw_bytes
( SINT2 *buf_a, SINT4 icnt_a ) ;

static void FillMessageHeader
( struct nr_message_header *pMHeader_a,
  const struct ingest_header *pIHeader_a,     /* Ingest header */
  const struct ingest_data_header *pDHeader_a,/* Ingest data header */
  const struct ray_header *pRHeader_a         /* Ray header */
  );

static void NrTimeFromYmds
( UINT2 *pDate_a,
  UINT4 *pTime_a,
  const struct ymds_time *pYmds_a );

static void FillRadarDataHeader
( struct nr_radar_data_header *pRDHeader_a,
  const struct nr_message_header *pMHeader_a, /* Previously filled in message header */
  SINT4 iSweep_a,                             /* Sweep number with volume, origin 0 */
  SINT4 iRayNumber_a,                         /* Ray number origin 0 */
  const struct ingest_header *pIHeader_a,     /* Ingest header */
  const struct moment_info MInfo_a[3],        /* Moment info for all three moments */
  const struct ray_header *pRHeader_a         /* Ray header */
  );

static void FillEndSweepStatus
( struct nr_radar_data_header *pRDHeader_a,   /* Radar Data Header to be filled */
  SINT4 iSweep_a,                             /* Sweep number with volume, origin 0 */
  const struct ingest_header *pIHeader_a );   /* Ingest header */

static void FillDataMap
( struct moment_info *pMInfo_a,
  const struct data_convert *pConvert_a,
  UINT1 iSlot_a,
  UINT4 iDType_a );

static UINT1 FillDataOne
(FLT4 fValue_a,
 UINT1 iSlot_a,
 UINT1 lVelocityHighRes_a );

static void FillFieldData
( UINT1 *iBuffer_a,
  const struct data_ray *pRay_a, 
  const struct moment_info *pMInfo_a );


static void Pack2
( UINT2 *pData2_a );

static void Pack4
( UINT4 *pData2_a );

#define SLOT_REFLECTIVITY (0)
#define SLOT_VELOCITY     (1)
#define SLOT_WIDTH        (2)
#define SLOT_INVALID      (255)
static UINT1 iNrValidFromData
( UINT1 idata_a );

static void LoadSetup
( struct convert_config *pCSetup_a );

static UINT2 ProcessConfigLine
(char *sline_a,
 struct convert_config *pCSetup_a );

static void LoadState
( struct convert_state *pState_a );

static UINT2 ProcessStateLine
(char *sline_a,
 struct convert_state *pConfig_a );

static void SaveState
( const struct convert_state *pState_a );

static void Lprintf
( const char *sString_a );

/* ===============================================================
 * Main program starts here.
 */
int main(int iArgc, char *sArgv[])
{
  struct product_hdr ProdHeader;
  struct raw_product *pInRaw;
  MESSAGE istatus;
  SINT4 iCount, iWantCount ;
  struct convert_state State;
  char sProdTime[TIMENAME_SIZE];

  setlocale( LC_ALL, "" ) ;

  /* Process the command line arguments */
  ProcessCommandLine( iArgc, sArgv );

  /* Process the setup file, if any */
  LoadSetup( &CConfig_c );

  /* Open the logging file, if any */
  if (CConfig_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, "IrisToArchive2.log" );
      if (CConfig_c.lAppendToLog)
	CConfig_c.iLogFile = fopen( sLogPath, "a" );
      else
	CConfig_c.iLogFile = fopen( sLogPath, "w" );
      if (NULL == CConfig_c.iLogFile)
	{
	  fprintf( stderr, "Can not open log file: '%s'\n", sLogPath );
	  exit(EXIT_FAILURE);
	}
    }

  /* Starting here we can use the Lprintf function */

  /* Process the state file, if any */
  LoadState( &State );
    
  /* Read in the header */
  iCount =  fread( &ProdHeader, 1, sizeof(ProdHeader), stdin );
  if (iCount != sizeof(ProdHeader)) 
    {
      sprintf( sLog_c, "Bad read, got%d, wanted%d\n", iCount, sizeof(ProdHeader));
      Lprintf( sLog_c );
      exit(EXIT_FAILURE);
    }

  istatus = ProcessProductHeader( &ProdHeader );
  if (istatus != SS_NORMAL) exit(EXIT_FAILURE);

  /* Malloc input and output buffers */
  pInRaw  = (struct raw_product*)malloc( ProdHeader.hdr.ibytes );
  pInRaw->Record[0].PHeader = ProdHeader;

  iWantCount = ProdHeader.hdr.ibytes-sizeof(ProdHeader);
  iCount =  fread( (&pInRaw->Record[0].PHeader)+1, 1, iWantCount, stdin );
  if (iCount != iWantCount)
    {
      sprintf( sLog_c, "Bad read2, got%d, wanted%d\n", iCount, iWantCount);
      Lprintf( sLog_c );
      exit(EXIT_FAILURE);
    }
  
  /* Update state */
  if ((ProdHeader.pcf.ing_time.iyear==State.iYear)&&
      (ProdHeader.pcf.ing_time.imon ==State.iMonth)&&
      (ProdHeader.pcf.ing_time.iday==State.iDay))
    {
      State.iExtension++;
      if (State.iExtension>999) State.iExtension=0;
    }
  else
    {
      State.iYear     = ProdHeader.pcf.ing_time.iyear;
      State.iMonth    = ProdHeader.pcf.ing_time.imon;
      State.iDay      = ProdHeader.pcf.ing_time.iday;
      State.iExtension= 0;
    }
  istatus = ConvertRawToArchive2( pInRaw, &State );
  if (istatus != SS_NORMAL) exit(EXIT_FAILURE);

  shhmmssddmonyyyy_r( &ProdHeader.pcf.ing_time, sProdTime );
  sprintf( sLog_c, "Converted RAW product: %-8.8s, Date: %s\n", 
	   ProdHeader.pcf.spname, sProdTime );
  Lprintf( sLog_c );
  
  /* Update the state file */
  SaveState( &State );

  exit( EXIT_SUCCESS );
}
/* ======================================================================
 * Function to process the command line arguments.
 */
static void ProcessCommandLine
(int iArgc_a, 
 char *sArgv_a[] )
{

  int iArg;

  /* Parse command line options
   */
  for( iArg=1 ; iArg<iArgc_a ; iArg++ )
    {
      const char *sArg = sArgv_a[iArg];
      /* fprintf( stderr, "Processing %d: '%s'\n", iArg, sArg ); */

      if (0 == strncmp( sArg, "-h", 2 ))
	{
	  Usage();
	}
      
    }
  return;
}
/* ==================================================
 */
static void Usage( void )
{
  fprintf( stderr, "Options: -help\n" );
  fprintf( stderr, "         (all other arguments ignored)\n" );
  exit(EXIT_FAILURE) ;
}
/* =======================================================================
 * Check for some consistency.  Only full volume raw products are supported.
 */
static MESSAGE ProcessProductHeader
(const struct product_hdr *pheader_a )
{
  /* Check that the product type is RAW */
  if (pheader_a->pcf.iptype != PROD_RAW )
    {
      sprintf( sLog_c, "Sorry, Product type must be RAW.\n" );
      Lprintf( sLog_c );
      return USER_ERROR_SIG;
    }

  /* Check to make sure this is a full volume scan raw product */
  if (pheader_a->pcf.psi.raw.iflags & RAW_FLG_SWEEP)
    {
      Lprintf( "Sorry, sweep-by-sweep RAW products are not supported.\n" );
      return USER_ERROR_SIG;
    }

  /* Check to make sure there is at least one convertable data type */
  {
    SINT4 ii, iIrisMoments, iNrMoments;
    struct dsp_data_mask DataMask;
    GetRawPsiMask( &DataMask, &pheader_a->pcf.psi.raw );
    iIrisMoments=0; iNrMoments=0;
    for( ii=0 ; ii < NUM_DEFINED_DATA ; ii++ )
      {
	if( lDspMaskTest( &DataMask, ii ) && (iIrisMoments<MAX_MOMENTS) )
	  {
	    UINT1 lValid;
	    lValid = (SLOT_INVALID != iNrValidFromData(ii));
	    if ( lValid ) iNrMoments++;
	    iIrisMoments++ ;
	  }
      }
    if (iNrMoments==0)
      {
	Lprintf( "Sorry, recorded data not convertable to Archive2\n" );
	return USER_ERROR_SIG;
      }
  }

  return SS_NORMAL;
}


/* Co-Routine to extract bytes, and static variables used by the
 * routine.
 */

static SINT4        irec_c ;	/* Record number */
static SINT4        ioff_c ;	/* Offset within record */
static const raw_product *pRaw_c ;	/* Pointer to product */

#define PRODPTR (((UINT1 *)(pRaw_c->Record+irec_c)) + ioff_c)

/* ==============================================================
 * Converts an IRIS RAW product to NEXRAD Archive2 format.  Entered 
 * with a pointer to the beginning of the full product.
 */
static MESSAGE ConvertRawToArchive2
(const struct raw_product *pRaw_a,
 const struct convert_state *pState_a )   /* State gets updated to current info */
{
#define RAYSIZE MAX_BINS
  SINT4 ii, iIrisMoments, irptr, isweep, iIn=0, iOut=1 ;
  UINT1 iMomentMapTable[MAX_MOMENTS];
  struct moment_info MInfo[3];
  struct data_convert Convert;
  const struct product_hdr *pPHeader;
  const struct ingest_header *pIHeader ;
  struct nr_radar_packet NrPackets[2];

  /* The first two "records" of the product consist of a product
   * header structure and an ingest header structure.  Each is
   * padded out to one record.
   */
  pRaw_c   = pRaw_a ;
  pPHeader = &pRaw_a->Record[0].PHeader;
  pIHeader = &pRaw_a->Record[1].IHeader;

  /* Make sure that this file really looks like a raw product.  Check
   * the ID's in the product and ingest headers.
   */
  if((pPHeader->hdr.id   != ST_PRODUCT_HDR) ||
     (pIHeader->hdr.id     != ST_INGEST_HDR ) ||
     (pIHeader->tcf.hdr.id != ST_TASK_CONF  ) )
    {
      Lprintf( "File headers contain invalid ID's\n" ) ;
      return USER_ERROR_SIG;
    }

  /* Make sure the scan has the correct geometry.
   */
  switch( pIHeader->tcf.scan.iscan )
    {
    case TASK_SCAN_PPI:
    case TASK_SCAN_CONT:
      break;

    default:
      Lprintf( "Task must be a PPI.\n" ) ;
      return USER_ERROR_SIG;
      // break;
    }

  /* Fill in moment mapping information for the moments which will be
   * converted.
   */
  PendFillDataConvert( &Convert, &pPHeader->end, CONVERT_FLAGS_DEFAULT );

  memset( &MInfo, 0, 3*sizeof(struct moment_info) );
  memset( iMomentMapTable, SLOT_INVALID, MAX_MOMENTS );
  iIrisMoments=0; 
  for( ii=0 ; ii<NUM_DEFINED_DATA ; ii++ )
    {
      if( lDspMaskTest( &pIHeader->tcf.dsp.DataMask, ii ) && (iIrisMoments<MAX_MOMENTS) )
	{
	  UINT1 iSlot;
	  iSlot = iNrValidFromData(ii);
	  /* Assume only 1 hit per slot */
	  if (iSlot!=SLOT_INVALID)
	    {
	      iMomentMapTable[iIrisMoments] = iSlot;
	      FillDataMap( &MInfo[iSlot], &Convert, iSlot, ii );
	    }
	  iIrisMoments++ ;
	}
    }

  /* Fill in final data pointer info based on available data */
  {
    FLT4  fFullBinRes, fInputStep;
    SINT4 iDataBase, iBinNumber;
    iDataBase = 0;
    fFullBinRes = ((FLT4)pIHeader->tcf.rng.ibin_out_step)/100.0;
    if ((fFullBinRes<1.0)||(fFullBinRes>10000.0))
      {
	sprintf( sLog_c, "Task has invalid range resolution of %5.2f meters.\n", fFullBinRes );
	Lprintf( sLog_c ) ;
	return USER_ERROR_SIG;
      }
    if (MInfo[SLOT_REFLECTIVITY].lValid)
      {
	struct moment_info *pMInfo = &(MInfo[SLOT_REFLECTIVITY]);
	
	pMInfo->iPointer   = iDataBase;

	fInputStep = ceil(CConfig_c.fDesiredRefRes/fFullBinRes);
	pMInfo->iInputStep = (SINT4)fInputStep;
	pMInfo->fBinRes    = fFullBinRes*fInputStep;

	iBinNumber = pIHeader->tcf.rng.ibin_out_num/pMInfo->iInputStep;
	pMInfo->iBinNumber = MIN(460, iBinNumber );
	iDataBase += pMInfo->iBinNumber;
      }
    if (MInfo[SLOT_VELOCITY].lValid)
      {
	struct moment_info *pMInfo = &(MInfo[SLOT_VELOCITY]);

	pMInfo->iPointer   = iDataBase;

	fInputStep = ceil(CConfig_c.fDesiredDopRes/fFullBinRes);
	pMInfo->iInputStep = (SINT4)fInputStep;
	pMInfo->fBinRes    = fFullBinRes*fInputStep;

	iBinNumber = pIHeader->tcf.rng.ibin_out_num/pMInfo->iInputStep;
	pMInfo->iBinNumber = MIN(920, iBinNumber );
	iDataBase += pMInfo->iBinNumber;
      }
    if (MInfo[SLOT_WIDTH].lValid)
      {
	struct moment_info *pMInfo = &(MInfo[SLOT_WIDTH]);

	pMInfo->iPointer   = iDataBase;

	fInputStep = ceil(CConfig_c.fDesiredDopRes/fFullBinRes);
	pMInfo->iInputStep = (SINT4)fInputStep;
	pMInfo->fBinRes    = fFullBinRes*fInputStep;

	iBinNumber = pIHeader->tcf.rng.ibin_out_num/pMInfo->iInputStep;
	pMInfo->iBinNumber = MIN(920, iBinNumber );
	iDataBase += pMInfo->iBinNumber;
      }
  }

  /* Write the volume scan title. */
  {
    struct timeval CurrentTimeval;
    struct nr_volume_header Title;
    UINT4 iSeconds;
    int iWriteCount;
    char sTemp[16];

    memset( &Title, 0, sizeof(Title) );

    gettimeofday( &CurrentTimeval, NULL );
    memcpy( Title.sFileName, "ARCHIVE2.", 9 );
    sprintf( sTemp, "%03d", pState_a->iExtension );
    memcpy( Title.sFileExt,  sTemp, 3 );
    Title.iFileDate = 1 + (CurrentTimeval.tv_sec/(24*60*60));
    Pack4( &Title.iFileDate );
    iSeconds = CurrentTimeval.tv_sec % (24*60*60);
    Title.iFileTime = (1000*iSeconds) + (CurrentTimeval.tv_usec/1000);
    Pack4( &Title.iFileTime );
    iWriteCount = fwrite( &Title, sizeof(Title), 1, stdout );
    if (iWriteCount!=1) Lprintf( "Write error\n" ) ;
  }

  /* Initialize record pointer to the first data record (third one in)
   * and loop for each sweep that was recorded.  We will scan through
   * the entire product (to check for errors), and will convert all of it.
   */
  irec_c = 2 ;			/* Record number */
  ioff_c = 0 ;			/* Offset within record */

  for( isweep=0 ; isweep<pIHeader->tcf.scan.isweeps; isweep++ )
    {
      SINT4 iIrisMoment;
      UINT1 lFirstRay, lInValid;
      SINT4 iWriteCount;
      
      /* Extract the INGEST data file headers for each of the parameters
       * that were recorded.  The headers appear sequentially in the
       * first record of each sweep.
       */
      for( iIrisMoment=0 ; iIrisMoment < iIrisMoments ; iIrisMoment++ )
	{
	  UINT1 iSlot;
	  struct ingest_data_header Unused;
	  iSlot = iMomentMapTable[iIrisMoment];
	  if (iSlot==SLOT_INVALID)
	    get_raw_bytes( (SINT2 *)&Unused, INGEST_DATA_HEADER_SIZE );
	  else
	    get_raw_bytes( (SINT2 *)&MInfo[iSlot].IDHeader, INGEST_DATA_HEADER_SIZE );
	}
      if (CConfig_c.lVerbose)
	{
	  char sTimeBuf[TIMENAME_SIZE];
	  sprintf( sLog_c, "Sweep began at: %s\n", 
		   shhmmssddmonyyyy_r( &MInfo[0].IDHeader.time, sTimeBuf ) ) ;
	  Lprintf( sLog_c );
	}
      
      /* Read the data from each of the angles, and for each of the
       *  parameters that were recorded.
       */
      lFirstRay = TRUE;
      for( irptr=0 ; irptr < pIHeader->icf.irtotl ; irptr++ )
	{

	  lInValid = ConvertOneRay( &NrPackets[iIn], isweep, iIrisMoments, pPHeader,
				  pIHeader, iMomentMapTable, MInfo, lFirstRay );
	  if (lInValid)
	    {
	      if (!lFirstRay)
		{
		  iWriteCount = fwrite( &NrPackets[iOut], sizeof(struct nr_radar_packet), 1, 
					stdout );
		  if (iWriteCount!=1) Lprintf( "Write error\n" ) ;
		}
	      lFirstRay = FALSE;
	      /* Cycle the ring buffer pointers */
	      iOut = iIn;
	      iIn = (iIn+1) % 2;
	    }

	}  /* end  for( irptr=0 ... */

      /* Unwind the pipeline by writing the last record out, if any. */
      if (!lFirstRay)
	{
	  FillEndSweepStatus( &(NrPackets[iOut].DataHeader), isweep, pIHeader );
	  iWriteCount = fwrite( &NrPackets[iOut], sizeof(struct nr_radar_packet), 1, stdout );
	  if (iWriteCount!=1) Lprintf( "Write error\n" ) ;
	}
      
      /* Done with this sweep.  Discard the remainder of this block, if
       * any.
       */
      if( ioff_c ) { ioff_c = 0 ; irec_c++ ; }

    }  /* end for( isweep = isweeplo... */
  return SS_NORMAL;
}

/* ==================================================
 * Convert One ray.  Returns nonzero if a ray was found.
 */
static UINT1 ConvertOneRay
( struct nr_radar_packet *pOutNr_a,
  SINT4 iSweep_a,                     /* Sweep number with volume, origin 0 */
  SINT4 iIrisMoments_a,               /* Number of IRIS moments */
  const struct product_hdr *pPHeader_a,
  const struct ingest_header *pIHeader_a,
  const UINT1 iMomentMapTable_a[MAX_MOMENTS],
  const struct moment_info MInfo_a[3],
  UINT1 lFirstRay_a )                 /* This is the first ray of a sweep */
{
  UINT2 lmissing;
  UINT1 lAllDataThere = TRUE;
  SINT4 iNrMomentsFound = 0;
  SINT4 iIrisMoment;
  static SINT4 iNexradRay=1;

  if (lFirstRay_a) iNexradRay=1;
  //  fprintf( stderr, "Ray pointer:%d\n", iRay_a ) ;
  for( iIrisMoment=0 ; iIrisMoment < iIrisMoments_a; iIrisMoment++ )
    {
      UINT1 iSlot;
      struct data_ray ray ;
      SINT4 inlen, ioutlen ;
      
      uncompress_cowords
	(get_raw_bytes,
	 pPHeader_a->hdr.ibytes - ((irec_c * TAPE_RECORD_LEN) + ioff_c),
	 &inlen, (SINT2 *)&ray, sizeof(ray)/2, &ioutlen ) ;
      
      lmissing = (ioutlen == 0) ;
      iSlot = iMomentMapTable_a[iIrisMoment];
      if (iSlot!=SLOT_INVALID)
	{
	  if (!lmissing)
	    {
	      /* Fill header on the first moment of each valid ray */
	      if (iNrMomentsFound==0)
		{
		  memset( pOutNr_a, 0, sizeof(*pOutNr_a) );
		  FillMessageHeader( &(pOutNr_a->MessageHeader),  
				     pIHeader_a, &MInfo_a[iSlot].IDHeader, &ray.hdr );
		  FillRadarDataHeader( &(pOutNr_a->DataHeader), &(pOutNr_a->MessageHeader),
				       iSweep_a, iNexradRay, pIHeader_a, MInfo_a, &ray.hdr );
		}
	      /* Fill in data */
	      FillFieldData( pOutNr_a->iData, &ray, &MInfo_a[iSlot] );
	      iNrMomentsFound++;
	    }
	  else
	    lAllDataThere = FALSE;
	}  /* end  if (iSlot!=SLOT_INVALID) */
    }  /* end  for( iIrisMoment=0; ... */

  if (lAllDataThere) iNexradRay++;
  /* If a new record was produced, then return true. */
  return lAllDataThere;
}
/* ==================================================
 * Co-Routine to read the next run of bytes from the raw product file,
 * skipping and checking the record headers as we go.
 */
static void get_raw_bytes( SINT2 *buf_a, SINT4 icnt_a )
{
  SINT4 icnt, iremain = icnt_a ; UINT1 *pbuf = (UINT1 *)buf_a ;

  while( iremain > 0 ) {
    /* If the offset is zero, then get the next record header and
     * compare the record numbers.  Exit if there is a mismatch.
     */
    if( ioff_c == 0 ) {
      struct raw_prod_bhdr bhdr ;
      memcpy( (void *)&bhdr, PRODPTR, RAW_PROD_BHDR_SIZE ) ;
      if( bhdr.irec != irec_c ) 
	{
	  sprintf( sLog_c, "Block header mismatch (%d) at block %d\n", bhdr.irec, irec_c ) ;
	  Lprintf( sLog_c );
	  exit(EXIT_FAILURE) ;
	}
      ioff_c += RAW_PROD_BHDR_SIZE ;
    }

    /* Extract as many bytes out of this record as we can.  Bump record
     * number and zero the offset if we read it all.
     */
    icnt = TAPE_RECORD_LEN - ioff_c ;
    if( icnt > iremain ) icnt = iremain ;

    memcpy( pbuf, PRODPTR, icnt ) ;
    iremain -= icnt ; ioff_c += icnt ; pbuf += icnt ;

    if( ioff_c == TAPE_RECORD_LEN ) { ioff_c = 0 ; irec_c++ ; }
  }
  return;
}

/* =====================================================
 * Fill in the message header
 */
static void FillMessageHeader
( struct nr_message_header *pMHeader_a,
  const struct ingest_header *pIHeader_a,     /* Ingest header */
  const struct ingest_data_header *pDHeader_a,/* Ingest data header */
  const struct ray_header *pRHeader_a         /* Ray header */
)
{
  static UINT4 iIDSequence=0;
  struct ymds_time RayTime;
  SINT4 iTimeOffset;

  pMHeader_a->iMsgSize = (sizeof(nr_radar_packet)-16)/2;
  Pack2( &pMHeader_a->iMsgSize );
  pMHeader_a->iRdaChannel = 0;
  pMHeader_a->iMsgType = NR_MSG_TYPE_DIGITAL_RADAR;
  pMHeader_a->iIDSequence = iIDSequence;
  Pack2( &pMHeader_a->iIDSequence );
  iIDSequence = (iIDSequence+1)%0x8000;

  RayTime = pDHeader_a->time;
  /* Convert time to UT */
  if (CConfig_c.lIgnoreRayTimes)
    iTimeOffset = 60*(SINT4)pIHeader_a->icf.iMinutesWest;
  else
    iTimeOffset = ((SINT4)pRHeader_a->itime) + 60*(SINT4)pIHeader_a->icf.iMinutesWest;
  ymds_add_seconds( iTimeOffset, &RayTime );
  
  NrTimeFromYmds( &pMHeader_a->iMsgDate, &pMHeader_a->iMsgTime, &RayTime );
  Pack2( &pMHeader_a->iMsgDate );
  Pack4( &pMHeader_a->iMsgTime );

  /* Segment count and number always 1 */
  pMHeader_a->iSegmentCount=1;
  Pack2( &pMHeader_a->iSegmentCount );
  pMHeader_a->iSegmentNumber=1;
  Pack2( &pMHeader_a->iSegmentNumber );
  return;

}
/* ==================================================================
 * Convert time formats.
 */
static void NrTimeFromYmds
( UINT2 *pDate_a,      /* Day number */
  UINT4 *pTime_a,      /* Milliseconds within the day */
  const struct ymds_time *pYmds_a )  /* Time in GMT */
{
  struct tm RayTm;
  time_t Time;
  UINT2 iDay, iHour, iMinute, iSecond;
  UINT4 iSeconds;
  
  /* Now convert the time to time_t format.  Note special hack to get
   * around the local time problem.
   */
  memset( &RayTm, 0, sizeof(RayTm) );
  RayTm.tm_year = pYmds_a->iyear-1900;
  RayTm.tm_mon  = pYmds_a->imon-1;
  RayTm.tm_mday = pYmds_a->iday;
  unpacktime(pYmds_a->isec, &iDay, &iHour, &iMinute, &iSecond );
  RayTm.tm_hour = iHour;
  RayTm.tm_min  = iMinute;
  RayTm.tm_sec  = iSecond;

  Time = mktime( &RayTm );

  /* Correct for the timezone offset included by mktime */
  Time -= timezone;

  *pDate_a = 1 + (Time/(24*60*60));
  iSeconds = Time % (24*60*60);
  *pTime_a = (1000*iSeconds) + MS_FROM_MILLS( pYmds_a->imills );
  return;
}
/* =====================================================
 * Fill in the message header
 */
static void FillRadarDataHeader
( struct nr_radar_data_header *pRDHeader_a,   /* Radar Data Header to be filled */
  const struct nr_message_header *pMHeader_a, /* Previously filled/packed message header */
  SINT4 iSweep_a,                             /* Sweep number with volume, origin 0 */
  SINT4 iRayNumber_a,                         /* Nexrad Ray number, origin 1 */
  const struct ingest_header *pIHeader_a,     /* Ingest header */
  const struct moment_info MInfo_a[3],        /* Moment info for all three moments */
  const struct ray_header *pRHeader_a         /* Ray header */
)
{
  SINT2 iAngleDiff;
  pRDHeader_a->iDataTime = pMHeader_a->iMsgTime;
  pRDHeader_a->iDataDate = pMHeader_a->iMsgDate;
  
  /* Ignore random phase range unfolding for now */
  pRDHeader_a->iUnambRange = NINT( 10.0 * fambiguous_range(pIHeader_a->tcf.dsp.iprf) );
  Pack2( &pRDHeader_a->iUnambRange );
  iAngleDiff = ((SINT2)(pRHeader_a->iaz_end - pRHeader_a->iaz_start))/2;
  pRDHeader_a->iAzimuth    = pRHeader_a->iaz_start+iAngleDiff;
  Pack2( &pRDHeader_a->iAzimuth );
  pRDHeader_a->iRayNumber  = iRayNumber_a;
  Pack2( &pRDHeader_a->iRayNumber );
  /* Only detect sweep starts here.  Ends detected when we write the last
   * ray out.
   */
  if (iRayNumber_a==1)
    {
      if (iSweep_a==0)
	pRDHeader_a->iRayStatus = NR_RAY_STATUS_START_VOLUME;
      else
	pRDHeader_a->iRayStatus = NR_RAY_STATUS_START_SWEEP;
    }
  else
    {
      pRDHeader_a->iRayStatus = NR_RAY_STATUS_MIDDLE;
    }
  Pack2( &pRDHeader_a->iRayStatus );
  iAngleDiff = ((SINT2)(pRHeader_a->iel_end - pRHeader_a->iel_start))/2;
  pRDHeader_a->iElevation = pRHeader_a->iel_start+iAngleDiff;
  Pack2( &pRDHeader_a->iElevation );

  pRDHeader_a->iSweepNumber  = iSweep_a+1;
  Pack2( &pRDHeader_a->iSweepNumber );
  pRDHeader_a->iBinStartRef  = (50+pIHeader_a->tcf.rng.ibin_first)/100;
  Pack2( (UINT2*)&pRDHeader_a->iBinStartRef );
  pRDHeader_a->iBinStartDop  = (50+pIHeader_a->tcf.rng.ibin_first)/100;
  Pack2( (UINT2*)&pRDHeader_a->iBinStartDop );

  
  if (MInfo_a[0].lValid)
    {
      pRDHeader_a->iBinStepRef   = NINT(MInfo_a[0].fBinRes);
      pRDHeader_a->iBinNumberRef = MInfo_a[0].iBinNumber;
      pRDHeader_a->iRefPointer   = sizeof(struct nr_radar_data_header)+MInfo_a[0].iPointer;
    }
  if (MInfo_a[1].lValid)
    {
      pRDHeader_a->iBinStepDop   = NINT(MInfo_a[1].fBinRes);
      pRDHeader_a->iBinNumberDop = MInfo_a[1].iBinNumber;
      pRDHeader_a->iVelPointer   = sizeof(struct nr_radar_data_header)+MInfo_a[1].iPointer;
    }
  if (MInfo_a[2].lValid)
    {
      pRDHeader_a->iBinStepDop   = NINT(MInfo_a[2].fBinRes);
      pRDHeader_a->iBinNumberDop = MInfo_a[2].iBinNumber;
      pRDHeader_a->iWidPointer   = sizeof(struct nr_radar_data_header)+MInfo_a[2].iPointer;
    }
  Pack2( &pRDHeader_a->iBinStepRef );
  Pack2( &pRDHeader_a->iBinStepDop );
  Pack2( &pRDHeader_a->iBinNumberRef );
  Pack2( &pRDHeader_a->iBinNumberDop );
  Pack2( &pRDHeader_a->iRefPointer );
  Pack2( &pRDHeader_a->iVelPointer );
  Pack2( &pRDHeader_a->iWidPointer );

  /* Sector number fixed at 1 */
  pRDHeader_a->iSectorNumber = 1;
  Pack2( &pRDHeader_a->iSectorNumber );

  if (MInfo_a[SLOT_VELOCITY-1].lVelocityHighRes)
    pRDHeader_a->iVelocityRes = 2;
  else
    pRDHeader_a->iVelocityRes = 4;
  Pack2( &pRDHeader_a->iVelocityRes );

  /* Fill in the volume coverage based on a crude guess */
  if ( pIHeader_a->tcf.scan.isweeps <= 7)
    pRDHeader_a->iVolumePattern = 32;
  else if ( pIHeader_a->tcf.scan.isweeps == 8)
    pRDHeader_a->iVolumePattern = 31;
  else if ( pIHeader_a->tcf.scan.isweeps <= 11)
    pRDHeader_a->iVolumePattern = 21;
  else 
    pRDHeader_a->iVolumePattern = 11;
  Pack2( &pRDHeader_a->iVolumePattern );

  pRDHeader_a->iNyquistVel  = NINT(100.0 * MInfo_a->fNyquistVel );
  Pack2( &pRDHeader_a->iNyquistVel );
  pRDHeader_a->iAttenuation = 
    -NINT(1000.0 * fGasAttenFromCode16(pIHeader_a->tcf.dsp.igas_atten) );
  Pack2( (UINT2*)&pRDHeader_a->iAttenuation );

  return;

}
/* =====================================================
 * Fill in the EOS or EOV status
 */
static void FillEndSweepStatus
( struct nr_radar_data_header *pRDHeader_a,   /* Radar Data Header to be filled */
  SINT4 iSweep_a,                             /* Sweep number with volume, origin 0 */
  const struct ingest_header *pIHeader_a )    /* Ingest header */
{
  if (iSweep_a==(pIHeader_a->tcf.scan.isweeps-1))
    pRDHeader_a->iRayStatus = NR_RAY_STATUS_END_VOLUME;
  else
    pRDHeader_a->iRayStatus = NR_RAY_STATUS_END_SWEEP;

  Pack2( &pRDHeader_a->iRayStatus );

  return;

}
/* =================================================================
 * Fill in the data map for the specified data type.  The ingest data
 * header has not yet been read.
 */
static void FillDataMap
( struct moment_info *pMInfo_a,
  const struct data_convert *pConvert_a,
  UINT1 iSlot_a,
  UINT4 iDType_a )
{
  FLT4 fValue;
  SINT4 iRes, iIndex;

  pMInfo_a->lValid = TRUE;

  iRes = idata_resolution( iDType_a );

  pMInfo_a->fNyquistVel = pConvert_a->fvelun_velocity;
  pMInfo_a->lVelocityHighRes = (pConvert_a->fvelun_velocity < 63.5);
  if (iRes==16)
    {
      for (iIndex=0; iIndex<65536; iIndex++)
	{
	  fValue = fdata_to_user( iIndex, iDType_a, pConvert_a );
	  pMInfo_a->iDataMap[iIndex] = FillDataOne( fValue, iSlot_a, 
						    pMInfo_a->lVelocityHighRes );
	}
      pMInfo_a->iDataMap[DV_NOSCAN2] = (UINT1)NR_NO_DATA;
      pMInfo_a->iDataMap[DV_THRESH2] = (UINT1)NR_NO_DATA;

    }
  else
    {
      for (iIndex=0; iIndex<256; iIndex++)
	{
	  fValue = fdata_to_user( iIndex, iDType_a, pConvert_a );
	  pMInfo_a->iDataMap[iIndex] = FillDataOne( fValue, iSlot_a, 
						    pMInfo_a->lVelocityHighRes );
	}
      /* pMInfo_a->iDataMap[DV_NOSCAN1] = (UINT1)NR_NO_DATA; No noscan case in raw data */
      pMInfo_a->iDataMap[DV_THRESH1] = (UINT1)NR_NO_DATA;
    }
  return;

}
/* ==========================================
 * Fill one slot of the data map.
 */
static UINT1 FillDataOne
(FLT4 fValue_a,
 UINT1 iSlot_a,
 UINT1 lVelocityHighRes_a )
{
  SINT4 iValue;
  
  switch (iSlot_a)
    {
    case SLOT_REFLECTIVITY:
    default:
      if (fValue_a<CConfig_c.fMinReflectivity)
	iValue = NR_NO_DATA;
      else
	{
	  iValue = NINT(2.0 * (fValue_a + 33.0));
	  if (iValue < 2) 
	    iValue = NR_NO_DATA;
	  else if (iValue>255)
	    iValue = 255;
	}
      break;
    case SLOT_VELOCITY:
      if (lVelocityHighRes_a)
	iValue = NINT(2.0 * (fValue_a + 64.5));
      else
	iValue = NINT(fValue_a + 129.0);
      if (iValue < 2) 
	iValue = 2;
      else if (iValue>255)
	iValue = 255;
      break;
    case SLOT_WIDTH:
      iValue = NINT(2.0 * (fValue_a + 64.5));
      if (iValue < 2) 
	iValue = 2;
      else if (iValue>255)
	iValue = 255;
      break;
    }
  return (UINT1)iValue;
}
/* =================================================================
 * Fill in the array of data
 */
static void FillFieldData
( UINT1 *iBuffer_a,              /* Pointer to the data section of a nr_radar_packet */
  const struct data_ray *pRay_a, /* Pointer to an IRIS ray of a single moment */
  const struct moment_info *pMInfo_a )  /* Info about that moment */
{
  UINT1 *pBuffer;
  SINT4 iRes, iIndex;
  UINT2 iBinCount;
  iRes = idata_resolution( pMInfo_a->IDHeader.idata );
  iBinCount = MIN( pRay_a->hdr.ibincount, pMInfo_a->iBinNumber );
  pBuffer = iBuffer_a+pMInfo_a->iPointer;
  if (iRes==16)
    {
      const UINT2 *pData2;
      pData2 = pRay_a->data.iData2;
      for (iIndex=0; iIndex<iBinCount; iIndex++)
	{
	  *pBuffer = pMInfo_a->iDataMap[*pData2];
	  pBuffer++;
	  pData2+=pMInfo_a->iInputStep;
	}
    }
  else
    {
      const UINT1 *pData1;
      pData1 = pRay_a->data.iData1;
      for (iIndex=0; iIndex<iBinCount; iIndex++)
	{
	  *pBuffer = pMInfo_a->iDataMap[*pData1];
	  pBuffer++;
	  pData1+=pMInfo_a->iInputStep;
	}
    }
  return;
}

/* =========================
 * Byte swapping functions.  Swap into the big endian format of the
 * concurent computers. 
 */
static void Pack2( UINT2 *pData2_a )
{
  UINT1 *pData1;
  UINT2 Data2;

  pData1    = (UINT1 *)pData2_a;
  Data2     = pData1[0];
  Data2     = Data2<<8;
  Data2    += pData1[1];
  *pData2_a = Data2;
}

static void Pack4( UINT4 *pData4_a )
{
  UINT1 *pData1;
  UINT4 Data4;

  pData1    = (UINT1 *)pData4_a;
  Data4     = pData1[0];
  Data4     = Data4<<8;
  Data4    += pData1[1];
  Data4     = Data4<<8;
  Data4    += pData1[2];
  Data4     = Data4<<8;
  Data4    += pData1[3];
  *pData4_a = Data4;
}

/* ================================================================
 * Returns index indicating which valid data it is.
 */
static UINT1 iNrValidFromData( UINT1 idata_a )
{
  switch( idata_a )
    {
    case DB_DBZ:
    case DB_DBZ2:
      return SLOT_REFLECTIVITY;
      break;
    case DB_VEL:
    case DB_VEL2:
      return SLOT_VELOCITY;
      break;
    case DB_WIDTH:
    case DB_WIDTH2:
      return SLOT_WIDTH;
      break;

    default:
      return SLOT_INVALID;
    }
}

/* ============================================================
 * Function to load the convert setup from a configuration file.
 */
static void LoadSetup
( struct convert_config *pCSetup_a )
{
  char sPath[PATHNAME_SIZE];
  char sline[81], *sptr;
  FILE* stream;
  SINT4 iline;
  UINT2 lerror;

  /* Initialize the data to the default values */
  memset( pCSetup_a, 0, sizeof(*pCSetup_a) );
  pCSetup_a->fMinReflectivity = -1000.0;
  pCSetup_a->fDesiredDopRes   =   250.0;
  pCSetup_a->fDesiredRefRes   =  1000.0;

  /* Generate the file name and open the file */
  sptr = getenv( "IRIS_CONFIG" );
  if (NULL == sptr) 
    {
      sig_signal_va( USER_NO_ENV, 1, "IRIS_CONFIG" );
      exit(EXIT_FAILURE);
    }

  strncpy( sPath, sptr, 140 );
  strcat( sPath, "IrisToArchive2.conf" );
  stream = fopen( sPath, "r" );

  /* Don't worry about a missing config file */
  if (NULL == stream) return;

  /* now loop through the file reading and parsing lines */
  iline = 1;
  sptr = fgets( sline, 81, stream );
  while (NULL != sptr)
    {
      lerror = ProcessConfigLine( sptr, pCSetup_a );
      if (lerror) fprintf( stderr, "Syntax error on IrisToArchive2.conf file line %d\n", iline );
      iline +=1;
      sptr = fgets( sline, 81, stream );
    }

  fclose( stream );

  return;
}

/* ===============================================================
 * Process one line from the configuration file.  Returns nonzero if there
 * is a syntax error.
 *
 * The config file can contain the following commands.  All blank lines and
 * lines beginning with "#" are ignored.  Commands can be in any order.
 * Commands take effect in the order received.
 *
 * APPEND_TO_LOG
 * IGNORE_RAY_TIMES
 * LOG              <FILE TERM BOTH NONE>
 * MIN_REFLECTIVITY #
 * VERBOSE
 */
static UINT2 ProcessConfigLine
(char *sline_a,
 struct convert_config *pConfig_a )
{
  char *stokens[9], *sptr;
  UINT4 icount, ioffset;
  
  ioffset = 0;
  icount  = 0;
  /* Process up to 9 tokens, stop when the line is exhausted. */
  while (icount<9) 
    {
      stokens[icount] = sigtok( sline_a, &ioffset );
      if (NULL == stokens[icount])
	break;
      else
	icount++;
    }
  
  /* Blank lines are allowed and skipped */
  if (icount == 0) return FALSE;
  
  /* Skip commented lines */
  if (stokens[0][0] == '#') return FALSE;
  
  if ( 0 == strcmp( "APPEND_TO_LOG", stokens[0] ) )
    {
      pConfig_a->lAppendToLog=TRUE;
      return FALSE;
    }

  /* Set the desired doppler resolution in meters */
  if ( 0 == strcmp( "DESIRED_DOP_RES", stokens[0] ) )
    {
      FLT8 fValue;
      if ( icount < 2 ) return TRUE;
      
      fValue = strtod( stokens[1], &sptr );
      if (sptr == stokens[1]) return TRUE;

      if (fValue <     1.0 ) return TRUE;
      if (fValue > 10000.0 ) return TRUE;

      pConfig_a->fDesiredDopRes = fValue;
      return FALSE;
    }

  /* Set the desired reflectivity resolution in meters */
  if ( 0 == strcmp( "DESIRED_REF_RES", stokens[0] ) )
    {
      FLT8 fValue;
      if ( icount < 2 ) return TRUE;
      
      fValue = strtod( stokens[1], &sptr );
      if (sptr == stokens[1]) return TRUE;

      if (fValue <     1.0 ) return TRUE;
      if (fValue > 10000.0 ) return TRUE;

      pConfig_a->fDesiredRefRes = fValue;
      return FALSE;
    }

  if ( 0 == strcmp( "IGNORE_RAY_TIMES", stokens[0] ) )
    {
      pConfig_a->lIgnoreRayTimes=TRUE;
      return FALSE;
    }

  /* See if the line contains the LOG file command */
  if ( 0 == strcmp( "LOG", stokens[0] ) )
    {
      if ( icount < 2 ) return TRUE;
      
      if      ( 0 == strcmp( "BOTH", stokens[1] ) ) 
	{pConfig_a->lLogToFile=TRUE;  pConfig_a->lLogToTerm=TRUE;}
      else if ( 0 == strcmp( "FILE", stokens[1] ) ) 
	{pConfig_a->lLogToFile=TRUE;  pConfig_a->lLogToTerm=FALSE;}
      else if ( 0 == strcmp( "TERM", stokens[1] ) )
	{pConfig_a->lLogToFile=FALSE; pConfig_a->lLogToTerm=TRUE;}
      else if ( 0 == strcmp( "NONE", stokens[1] ) )
	{pConfig_a->lLogToFile=FALSE; pConfig_a->lLogToTerm=FALSE;}
      else
	return TRUE;
      return FALSE;
    }

  /* Set the minimum refectivity */
  if ( 0 == strcmp( "MIN_REFLECTIVITY", stokens[0] ) )
    {
      FLT8 fValue;
      if ( icount < 2 ) return TRUE;
      
      fValue = strtod( stokens[1], &sptr );
      if (sptr == stokens[1]) return TRUE;

      if (fValue < -100.0 ) return TRUE;
      if (fValue >  100.0 ) return TRUE;

      pConfig_a->fMinReflectivity = fValue;
      return FALSE;
    }

  if ( 0 == strcmp( "VERBOSE", stokens[0] ) )
    {
      pConfig_a->lVerbose = TRUE;
      return FALSE;
    }
  
  /* Unrecognized lines will generate a syntax error */
  return TRUE;
}
/* ============================================================
 * Function to load the convert state
 */
static void LoadState
( struct convert_state *pState_a )
{
  char sPath[PATHNAME_SIZE];
  char sline[81], *sptr;
  FILE* stream;
  SINT4 iline;
  UINT2 lerror;

  /* Initialize the data to the default values */
  memset( pState_a, 0, sizeof(*pState_a) );

  /* Generate the file name and open the file */
  sptr = getenv( "IRIS_CONFIG" );
  if (NULL == sptr) 
    {
      sig_signal_va( USER_NO_ENV, 1, "IRIS_CONFIG" );
      exit(EXIT_FAILURE);
    }

  strncpy( sPath, sptr, PATHNAME_SIZE-20 );
  strcat( sPath, "IrisToArchive2.state" );
  stream = fopen( sPath, "r" );

  /* Don't worry about a missing config file */
  if (NULL == stream) return;

  /* now loop through the file reading and parsing lines */
  iline = 1;
  sptr = fgets( sline, 81, stream );
  while (NULL != sptr)
    {
      lerror = ProcessStateLine( sptr, pState_a );
      if (lerror)
	{ 
	  sprintf( sLog_c, "Syntax error in IrisToArchive2.state line %d\n", iline );
	  Lprintf( sLog_c );
	}
      iline +=1;
      sptr = fgets( sline, 81, stream );
    }

  fclose( stream );

  return;
}

/* ===============================================================
 * Process one line from the state file.  Returns nonzero if there
 * is a syntax error.
 *
 * DATE <year> <month> <day>
 * EXTENSION  <value>
 */
static UINT2 ProcessStateLine
(char *sline_a,
 struct convert_state *pState_a )
{
  char *stokens[5], *sptr;
  UINT4 icount, ioffset;
  
  ioffset = 0;
  icount  = 0;
  /* Process up to 5 tokens, stop when the line is exhausted. */
  while (icount<5) 
    {
      stokens[icount] = sigtok( sline_a, &ioffset );
      if (NULL == stokens[icount])
	break;
      else
	icount++;
    }
  
  /* Blank lines are allowed and skipped */
  if (icount == 0) return FALSE;
  
  /* Skip commented lines */
  if (stokens[0][0] == '#') return FALSE;
  

  /* The date of the last file sent */
  if ( 0 == strcmp( "DATE", stokens[0] ) )
    {
      int iValue;
      if ( icount < 4 ) return TRUE;
      
      iValue = strtol( stokens[1], &sptr, 10 );
      if (sptr == stokens[1]) return TRUE;
      pState_a->iYear = iValue;

      iValue = strtol( stokens[2], &sptr, 10 );
      if (sptr == stokens[2]) return TRUE;
      pState_a->iMonth = iValue;

      iValue = strtol( stokens[3], &sptr, 10 );
      if (sptr == stokens[3]) return TRUE;
      pState_a->iDay = iValue;

      return FALSE;
    }

  /* Set the desired reflectivity resolution in meters */
  if ( 0 == strcmp( "EXTENSION", stokens[0] ) )
    {
      int iValue;
      if ( icount < 2 ) return TRUE;
      
      iValue = strtol( stokens[1], &sptr, 10 );
      if (sptr == stokens[1]) return TRUE;

      if (iValue <= 0 ) return TRUE;

      pState_a->iExtension = iValue;
      return FALSE;
    }

  /* Unrecognized lines are ignored */
  return FALSE;
}

/* ============================================================
 * Function to save the convert state
 */
static void SaveState
( const struct convert_state *pState_a )
{
  char sPath[PATHNAME_SIZE], *sptr;
  FILE* stream;

  /* Generate the file name and open the file */
  sptr = getenv( "IRIS_CONFIG" );
  if (NULL == sptr) 
    {
      sig_signal_va( USER_NO_ENV, 1, "IRIS_CONFIG" );
      exit(EXIT_FAILURE);
    }

  strncpy( sPath, sptr, PATHNAME_SIZE-20 );
  strcat( sPath, "IrisToArchive2.state" );
  stream = fopen( sPath, "w" );

  /* Don't worry about a missing config file */
  if (NULL == stream) return;

  /* now loop through the file reading and parsing lines */
  fprintf( stream, "DATE %d %d %d\n", pState_a->iYear, pState_a->iMonth, pState_a->iDay );
  fprintf( stream, "EXTENSION %d\n", pState_a->iExtension );

  fclose( stream );

  return;
}


/* ===========================================
 * Print string to log and terminal, if desired.
 */
static void Lprintf
( const char *sString_a )
{
  if (CConfig_c.lLogToTerm) fputs( sString_a, stderr );
  if (CConfig_c.lLogToFile)
    {
      fputs( sString_a, CConfig_c.iLogFile );
      fflush( CConfig_c.iLogFile );
    }
}
