/* ********************************************************************
 * *                                                                  *
 * *  Pipe to convert a NEXTRAD Archive2 file to an IRIS RAW product  *
 * *                                                                  *
 * ********************************************************************
 * File: utils/archive2/Archive2ToIris.C
 *
 *                        COPYRIGHT (c) 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 <locale.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "sigtypes.h"
#include "dsp.h"
#include "headers.h"
#include "iris_task.h"
#include "ingest.h"
#include "nexrad.h"
#include "product.h"
#include "sigversion.h"

#include "dsp_lib.h"
#include "misc_lib.h"
#include "user_lib.h"

#include "archive2_h.h"

#define  TRACE_ON (0)
#define  TRACE2_ON (0)

/* This holds information to customize the convertor */
struct command_line
{
  char  sInFile[FILENAME_SIZE];
  char  sInPath[PATHNAME_SIZE];
  char  sOutPath[PATHNAME_SIZE];
  SINT4 iDevice;
};
struct convert_task_list
{
  char  sNrRadarId[12];      /* Radar ID, null terminated*/
  FLT4  fFixedAngle;         /* Fixed angle */
  char  sIrisTaskName[16];   /* IRIS task name, null terminated */
  FLT4  fZCalib;             /* Calibration reflectivity */
};
struct convert_site_list
{
  char sNrRadarId[12];       /* Radar ID, null terminated */
  char sIrisSitename[16];    /* Ingest sitename to use, space padded */
  UINT1 lRecordUtc;          /* Recorded time is UTC, else local */
  SINT2 iLocalWest;          /* Local time offset */
  char sLocalTZName[TZNAME_SIZE]; /* Name of local time */
};
#define MAX_CONVERT_SITES (20)
#define MAX_CONVERT_TASKS (40)
#define MAX_CONVERT_DATATYPES (20)
#define MAX_CONVERT_SWEEPS (40)
struct convert_config
{
  struct convert_site_list Sites[MAX_CONVERT_SITES];
  struct convert_task_list Tasks[MAX_CONVERT_TASKS];
  SINT2 iSweepMap[MAX_CONVERT_SWEEPS];  /* Sweep mapping table to apply skips */
  SINT4 iSiteCount;            /* Number of sites in the list */
  SINT4 iTaskCount;            /* Number of tasks in the list */
  SINT4 iDataCount;            /* Number of data types in the list */
  
  SINT4 iCurrentDevice;        /* Current device number, -1 means any */
  FLT4  fElevationAngleChange; /* Max change in elevation angles */
  FLT4  fWaveLength;           /* Override wavelength if not zero */

  SINT2 iSweepOffset;          /* Number added to all sweep numbers */
  UINT1 lLogToFile;
  UINT1 lLogToTerm;
  UINT1 lVerbose;              /* Log more details */
  UINT1 lVerbose2;             /* Log individual ray details */
  UINT1 lAllowMixedBinSpacing; /* Allow mixed bin spacing */
  UINT1 lAppendToLog;          /* Append to previous log file */
  FILE *iLogFile;              /* Open file descriptor to log file */
  FILE *iInFile;               /* Open file descriptor to input file */
  char  sIngSitename[16];      /* Ingest sitename to use, space padded */
};


/* This structure contains information learned about the input data */
struct input_sweep_info
{
  FLT4  fAverageResolution;    /* Average resolution in degrees */
  FLT4  fAverageFixedAngle;    /* Average fixed angle in degrees */
  struct ymds_time SweepTime;  /* Time of earliest ray in a sweep */
};
struct input_info
{
  struct input_sweep_info Sweep[MAX_SWEEPS];
  struct nr_volume_header      VHeader;
  struct nr_radar_data_header  FirstDHeader;
  struct nr_rda_adaptation     Adaptation;
  FLT4  fMasterStartRangeRef;   /* Master start range in km */
  FLT4  fMasterStartRangeDop;   /* Master start range in km */
  SINT4 iMasterBinSpacing;      /* Master bin spacing in meters */
  SINT4 iMasterPRT;          /* Master PRT in microseconds */
  struct ymds_time VolumeTime;
  struct convert_site_list *pSite;  /* Pointer to site mapping entry for our file */
  struct convert_task_list *pTask;  /* Pointer to task mapping entry for our file */
  struct dsp_data_mask DataMask;    /* Mask of data found in the whole scan */
  SINT4 iReplicateFactor[MAX_CONVERT_DATATYPES];
  FLT4  fMinimumSweepRes;   /* Min number from all the sweeps */
  SINT2 iSweepCount;        /* Number of sweeps found so far, clipped at MAX_SWEEPS */
  SINT4 iMaximumBinCount;   /* Maximum bin count of all the rays */
  SINT4 iRaySkipCount;
  FLT4  fNyquistVelocity;   /* Nyquist velocity taken from the first velocity field header */
  UINT1 lVolumeTimeValid;
  UINT1 lNyquistVelocityValid;
  UINT1 lAdaptationValid;      /* Adaptation data was found */
};
struct sweep_scan8
{
  FLT8  fRayStepSum;
  FLT8  fRayFixedSum;
  SINT4 iRayStepCount;
  SINT4 iRayFixedCount;
  struct ymds_time SweepTime;
  char  ipad_end[4];
};

struct ingest_file_state
{
  struct ingest_data_header *pIDH;
  UINT4                     *pTable;
  UINT1                     *pData;
  SINT4 iSize;
  SINT4 iChannel;
  SINT4 iDataUsed;
  UINT1 iMapTable[256];
  UINT1 lMapTableFilled;
  char  ipad_end[3];
};

static struct convert_config CSetup_c;
static char sMessage_c[200];

/* Local function prototypes
 */
static void ConvertArchive2
(const char *sOutPath_a);

static UINT1 ScanForInfo
( struct input_info *pInfo_a );

struct convert_task_list *BestStartingAngle
( struct convert_task_list *pTask1_a,   /* Use this one if equal, can be NULL */
  struct convert_task_list *pTask2_a,
  FLT4 fAverageFixedAngle_a );          /* Average fixed angle in degrees */

static void ProcessVolumeHeader
( struct input_info *pInfo_a );

static UINT1 CheckDataHeader
( const struct nr_radar_data_header *pCurrentDHeader_a,
  struct input_info *pInfo_a );

static UINT1 CompareDataHeaders
( const struct nr_radar_data_header *pCurrentDHeader_a,
  const struct nr_radar_data_header *pLastDHeader_a,
  struct input_info *pInfo_a );

static UINT1 CompareDataHeadersMiddle
( const struct nr_radar_data_header *pCurrentDHeader_a,
  const struct nr_radar_data_header *pLastDHeader_a,
  struct input_info *pInfo_a,
  struct sweep_scan8 *pScan_a );

static void MaskFromDHeader
(struct dsp_data_mask *pDataMask_a,
 const struct nr_radar_data_header *pDHeader_a );

static UINT1 BuildIngestHeader
(struct ingest_header *pIHeader_a,
 const struct input_info *pInfo_a );

static void BuildIngestConfig
(struct ingest_configuration *pIngCon_a,
 const struct input_info *pInfo_a );

static UINT1 BuildTaskConfig
(struct task_configuration *pTaskCon_a,
 const struct input_info *pInfo_a );

static UINT1 BuildGparm
(struct gparm *pGparm_a,
 const struct input_info *pInfo_a );

static UINT1 StoreSweepInfo
( struct input_info *pInfo_a, 
  const struct nr_radar_data_header *pLastDHeader_a,  /* Header from last ray in sweep */
  struct sweep_scan8 *pScan_a );

static void ScanAndWrite
( const char *sPathStart_a,
  const struct ingest_header *pIHeader_a,
  const struct input_info *pInfo_a );

/* Process one ray of radar data. */
static UINT1 ProcessRadarData
( const char *sPathStart_a,        /* Start of pathnames for ingest files */
  const struct ingest_header *pIHeader_a,
  const struct input_info *pInfo_a,
  struct ingest_file_state Files_a[NUM_DEFINED_DATA],
  const struct nr_radar_packet *pRadarData_a, 
  const struct data_convert *pConvert_a,
  SINT4 iIrisSweep_a, 
  SINT4 iLastSweep_a );

static void ConvertRay
( const struct input_info *pInfo_a,
  struct ingest_file_state *pFile_a,
  const struct nr_radar_packet *pRadarData_a, 
  const struct data_convert *pConvert_a,
  struct ray_header *pRayHeader_a,
  SINT4 iDataType_a,
  SINT4 ir_ptr_a );

static void OpenNewFiles
( struct ingest_file_state Files_a[NUM_DEFINED_DATA],
  const struct ingest_header *pIHeader_a,  /* Ingest header */
  SINT2  iSweepNumber_a,         /* New IRIS sweep number, origin 1 */
  const struct ymds_time *SweepTimeLocal_a,
  const char *sPathStart_a );    /* Start of pathnames */

static void CloseOldFiles
(struct ingest_file_state Files_a[NUM_DEFINED_DATA],
 const struct dsp_data_mask *pDataMask_a );

static void RemoveIngestFiles
( const struct ingest_header *pIHeader_a,  /* Ingest header */
  const char *sPathStart_a );    /* Start of pathnames */

static void FillDataMap
( UINT1 iDataMap_a[256],
  const struct data_convert *pConvert_a,
  UINT1 iDataType_a,
  const struct nr_radar_data_header *pDHeader_a );

static void ConvertRayHeader
( struct ray_header *pRayHeader_a,
  const struct nr_radar_data_header *pDHeader_a,
  const struct ymds_time *pSweepTime_a,
  UINT2 iBinCount_a );

static void ConvertRayData
( UINT1 *pIirisData_a,
  const UINT1 *pInData_a,
  const UINT1 iDataMap_a[256],
  UINT2 iBinCount_a,
  UINT4 iRepFactor_a ); // Bin replication factor

static SINT2 iSweepMapping
( SINT2 iNrSweep_a );

static void ProcessCommandLine
(int iArgc, 
 char *sArgv[],
 struct command_line *pCommand_a );

static void Usage( void );

static void LoadSetup
( struct convert_config *pCSetup_a,
  const struct command_line *pCommand_a );

static UINT2 ProcessConfigLine
(char *sline_a,
 struct convert_config *pCSetup_a,
 SINT4 iDevice_a );    /* Device number origin 0 of this pipe */

static void Lprintf
( const char *sString_a );

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

#define MAX_LINELENGTH (120)
/* ===============================================================
 * Main program starts here.
 */
int main(int iArgc, char *sArgv[])
{
  char *sptr;
  struct command_line Command;
  setlocale( LC_ALL, "" ) ;

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

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

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

  /* Starting here we can use the Lprintf function */
  sig_establish( SignalHandler, "Archive2ToIris" );

  if (CSetup_c.lVerbose)
    {
      struct full_time_spec Time;
      GetTimeFull( &Time, TIME_FLG_LOCAL );
      
      sprintf( sMessage_c, "Verbose logging on, TZ=%s\n", Time.sRecordTZName );
      Lprintf( sMessage_c );
    }

  /* Now check for missing data in the command line */
  if (Command.sInFile[0]==0)
    {
      Lprintf( "ERROR: missing -if command line option\n" );
      exit(EXIT_FAILURE);
    }
  if (Command.sInPath[0]==0)
    {
      Lprintf( "ERROR: missing -ip command line option\n" );
      exit(EXIT_FAILURE);
    }
  if (Command.sOutPath[0]==0)
    {
      Lprintf( "ERROR: missing -op command line option\n" );
      exit(EXIT_FAILURE);
    }

  /* Open the input file */
  CSetup_c.iInFile = fopen( Command.sInPath, "r" );
  if (NULL == CSetup_c.iInFile)
    {
      char sError[PATHNAME_SIZE+50];
      sprintf( sError, "Can not open input file: '%s'\n", Command.sInPath );
      Lprintf( sError );
      exit(EXIT_FAILURE);
    }

  if (CSetup_c.lVerbose)
    {
      char sString[FILENAME_SIZE+50];
      sprintf( sString, "Processing file: %s\n", Command.sInFile );
      Lprintf( sString );
    }

  ConvertArchive2(Command.sOutPath);

  fclose(CSetup_c.iInFile);
   
  exit( EXIT_SUCCESS );
}
/* =============================================================
 * Convert the data to IRIS.  Exit if there is an error.
 */
static void ConvertArchive2
(const char *sOutPath_a)
{
  MESSAGE istatus;
  UINT1 lOk, lError;
  struct input_info Info;
  struct ingest_header IHeader;
  UINT1 lSwpMask[MAX_SWEEPS];
  char sStartPath[PATHNAME_SIZE], sname[FILENAME_SIZE], *sptr, sSiteCode3[4];
  struct product_hdr   PHeader;

  /* Read through the data twice.  The first time to accumulate
   * information about the number of tilts, scan resolution, etc.
   */
  if (TRACE_ON) Lprintf( "ConvertArchive2 called.\n" );

  lOk = ScanForInfo( &Info );
  if (!lOk) exit(EXIT_FAILURE);

  lOk = BuildIngestHeader(&IHeader, &Info );
  if (!lOk) exit(EXIT_FAILURE);

  memset( &PHeader, 0, PRODUCT_HDR_SIZE );
  PcfFromScratch( &PHeader.pcf, &IHeader.icf.VolumeYmds, Info.pTask->sIrisTaskName );
  PHeader.pcf.iptype = PROD_RAW; 
  /* The rest of the product header is filled in below in RawFromIngest */

  if (TRACE_ON) Lprintf( "Rewinding.\n" );
  rewind(CSetup_c.iInFile);

  sptr = getenv( "IRIS_TEMP" );
  if (NULL == sptr) 
    strcpy( sStartPath, "/tmp/" );
  else
    strncpy( sStartPath, sptr, DIRECTORY_SIZE-1 );
  sStartPath[DIRECTORY_SIZE-1]=0;

  strcat( sStartPath, "Ar2ToIris" );

  strncpy( sSiteCode3, Info.pSite->sIrisSitename, 3 );
  sSiteCode3[3] = 0;
  lError = lstart_name_int( sname, sSiteCode3, &IHeader.icf.VolumeYmds ) ;
  if( lError )
    {
      Lprintf( "Error in time in input file.\n" );
      exit(EXIT_FAILURE);
    }
  strcat( sStartPath, sname );

  ScanAndWrite( sStartPath, &IHeader, &Info );

  /* Make up a mask of the sweeps to be processed by this run.  This consists
   * either of all sweeps for a complete raw product, or the one matching the
   * ingest time for a sweep-by-sweep raw product.
   */
  memset( lSwpMask, TRUE, MAX_SWEEPS );

  istatus = RawFromIngest( sOutPath_a, sStartPath, &PHeader, &IHeader, lSwpMask,
			   "Ar2ToIris        "/* Product gen site */,
			   0 /*Reference height */ );
  if (istatus!=SS_NORMAL) sig_signal( istatus );

  RemoveIngestFiles( &IHeader, sStartPath );

  return;
}
/* =====================================================================
 * Scan the file for information.  Return TRUE if everything was OK.
 * Otherwise print message in the log and return FALSE.
 */
static UINT1 ScanForInfo
( struct input_info *pInfo_a )
{
  SINT4 iCount, iRayCount, iSite;
  UINT1 lOk;
  char sSitename9[9];

  union  nr_packet             Packet1, Packet2;  /* Double buffer the packets */
  union  nr_packet            *pCurrentPacket,  *pLastPacket;
  struct nr_radar_data_header *pCurrentDHeader, *pLastDHeader;

  if (TRACE_ON) Lprintf( "ScanForInfo called.\n" );
  memset( pInfo_a, 0, sizeof(*pInfo_a) );

  iCount = ReadNrVolumeHeader( &pInfo_a->VHeader, CSetup_c.iInFile );
  if (iCount<=0)
    {
      Lprintf( "ERROR: Could not read volume header.\n" );
      return FALSE;
    }
  ProcessVolumeHeader( pInfo_a );

  pCurrentPacket  = &Packet1;
  pLastPacket     = NULL;
  pCurrentDHeader = NULL;
  pLastDHeader    = NULL;
  /* Loop reading in records, and processing the results */
  iRayCount = 0;
  iCount = ReadNrPacket( pCurrentPacket, CSetup_c.iInFile );
  while( iCount )
    {
      /* Only process digital radar data messages */
      switch( pCurrentPacket->MessageHeader.iMsgType)
	{
	case NR_MSG_TYPE_DIGITAL_RADAR:
	  {
	    pCurrentDHeader = &pCurrentPacket->RadarData.DataHeader;
	    if (pLastPacket)
	      pLastDHeader = &pLastPacket->RadarData.DataHeader;
	    else
	      pLastDHeader = NULL;
	      
	    iRayCount++;
	    
	    /* Unpack the data header */
	    DataHeaderUnpack(pCurrentDHeader );
	    
	    /* Entirely ignore the ray if the sweep is in the skip list */
	    if (0 == iSweepMapping(pCurrentDHeader->iSweepNumber)) goto SKIP_RAY;
	    
	    /* Entirely ignore the ray if desired data is missing */
	    if (!CheckDataHeader(pCurrentDHeader, pInfo_a)) goto SKIP_RAY;
	    
	    /* Compare and process info from the last 2 data headers */
	    lOk = CompareDataHeaders( pCurrentDHeader, pLastDHeader, pInfo_a );
	    if (!lOk) return FALSE;
	    
	    // DataHeaderPrint(&Packet.DataHeader, &Options );
	    //	  PrintData(&Packet, &Options );
	    
	    /* Rotate pointers */
	    pLastPacket  = pCurrentPacket;
	    if (pCurrentPacket==&Packet1)
	      pCurrentPacket = &Packet2;
	    else
	      pCurrentPacket = &Packet1;
	  }
	  break;

	case NR_MSG_TYPE_RDA_ADAPTATION:
	  AdaptationAccumulate( &pCurrentPacket->Generic, &pInfo_a->Adaptation );
	  if (pCurrentPacket->MessageHeader.iSegmentCount ==
	      pCurrentPacket->MessageHeader.iSegmentNumber )
	    {
	      AdaptationUnpack( &pInfo_a->Adaptation );
	      pInfo_a->lAdaptationValid = TRUE;
	    }
	  break;

	default:
	  break;
	}
      
      
    SKIP_RAY:
      /* Check here for excessive skipped rays */
      if (pInfo_a->iRaySkipCount>100)
	{
	  Lprintf( "ERROR: Ray skip count exceeded 100.\n" );
	  return FALSE;
	}

      /* Skip to the next ray */
      iRayCount++;
      iCount = ReadNrPacket( pCurrentPacket, CSetup_c.iInFile );
    }

  if ((iCount !=0) || (ferror(CSetup_c.iInFile)))
    {
      Lprintf( "Read error.\n" );
      return FALSE;
    }

  /* Process to get last sweeps information */
  lOk = CompareDataHeaders( NULL, pLastDHeader, pInfo_a );
  if (!lOk) return FALSE;


  if (!pInfo_a->lAdaptationValid)
    {
      Lprintf( "WARNING: Adaptation data missing from the file.\n" );
    }

  /* Search to see if we can find the sitename in the site mapping table.  Note
   * that wildcards are allowed.
   */

  strncpy( sSitename9, pInfo_a->VHeader.sRadarId, 4 );
  sSitename9[4] = 0;
  for (iSite=0; iSite<CSetup_c.iSiteCount; iSite++)
    {
      if (sig_fnmatch( CSetup_c.Sites[iSite].sNrRadarId, sSitename9 ))
	{
	  pInfo_a->pSite = &CSetup_c.Sites[iSite];
	  goto GOT_SITELIST;
	}
    }
  sprintf( sMessage_c, "ERROR, NR radar ID '%s' not found in config site list\n", 
	   sSitename9 );
  Lprintf( sMessage_c );
  return FALSE;
 GOT_SITELIST:

// Search to see if we can find the file in the task mapping table.  Note
  // that wildcards are allowed for sitename.
//  

  pInfo_a->pTask = NULL;
  for (iSite=0; iSite<CSetup_c.iTaskCount; iSite++)
    {
      struct convert_task_list *pOneTask=&CSetup_c.Tasks[iSite];
      if (sig_fnmatch( pOneTask->sNrRadarId, sSitename9 ))
	{
	  
	  // Find the best match for the starting angle
	  pInfo_a->pTask = BestStartingAngle( pInfo_a->pTask, pOneTask, 
					      pInfo_a->Sweep[0].fAverageFixedAngle );
	}
    }
  if (!pInfo_a->pTask)
    {
      sprintf( sMessage_c, "ERROR, NR radar ID '%s', fixed angle %5.2f not found in config task list\n", 
	       sSitename9, pInfo_a->Sweep[0].fAverageFixedAngle );
      Lprintf( sMessage_c );
      return FALSE;
    }

  return TRUE;
}
/* =========================================================
 * Compare two proposed task list entries to see which is the
 * closest to the desired angle.
 */
struct convert_task_list *BestStartingAngle
( struct convert_task_list *pTask1_a,   /* Use this one if equal, can be NULL */
  struct convert_task_list *pTask2_a,
  FLT4 fAverageFixedAngle_a )           /* Average fixed angle in degrees */
{
  FLT4 fError1, fError2;
  if (!pTask1_a) return pTask2_a;

  fError1 = pTask1_a->fFixedAngle - fAverageFixedAngle_a;
  if (fError1 > 180.0)
    fError1 -= 360.0;
  else if (fError1 < -180.0)
      fError1 += 360.0;
  fError1 = fabs(fError1);

  fError2 = pTask2_a->fFixedAngle - fAverageFixedAngle_a;
  if (fError2 > 180.0)
    fError2 -= 360.0;
  else if (fError2 < -180.0)
    fError2 += 360.0;
  fError2 = fabs(fError2);

  if (fError2 < fError1) return pTask2_a;

  return pTask1_a;

}

/* ========================================================
 * Store sweep info at the end of a sweep.  Return TRUE if
 * there is no error to stop the conversion.
 */
static UINT1 StoreSweepInfo
( struct input_info *pInfo_a, 
  const struct nr_radar_data_header *pLastDHeader_a,  /* Header from last ray in sweep */
  struct sweep_scan8 *pScan_a )
{
  struct input_sweep_info *pSweep;

  if (TRACE_ON) Lprintf( "StoreSweepInfo called.\n" );

  pSweep = &pInfo_a->Sweep[iSweepMapping(pLastDHeader_a->iSweepNumber)-1];

  /* Some simple error checking */
  if (iSweepMapping(pLastDHeader_a->iSweepNumber)>MAX_SWEEPS)
    {
      sprintf( sMessage_c, "WARNING: sweep %d ignored.\n", pLastDHeader_a->iSweepNumber );
      Lprintf( sMessage_c );
      return TRUE;
    }
  if (pScan_a->iRayStepCount < 1)
    {
      sprintf( sMessage_c, "ERROR, sweep %d does not have at least 2 rays.\n",
	       pLastDHeader_a->iSweepNumber );
      Lprintf( sMessage_c );
      return FALSE;
    }
  pInfo_a->iSweepCount = iSweepMapping(pLastDHeader_a->iSweepNumber);

  /* Compute average resolution */
  pSweep->fAverageResolution = pScan_a->fRayStepSum / (FLT8)pScan_a->iRayStepCount;
  pSweep->fAverageFixedAngle = pScan_a->fRayFixedSum / (FLT8)pScan_a->iRayFixedCount;

  if (pInfo_a->iSweepCount==1)
    pInfo_a->fMinimumSweepRes = pSweep->fAverageResolution;
  else if (pSweep->fAverageResolution < pInfo_a->fMinimumSweepRes )
    pInfo_a->fMinimumSweepRes = pSweep->fAverageResolution;    

  pSweep->SweepTime = pScan_a->SweepTime;
  
  return TRUE;
}

/* ===============================================
 * Process the optional header to get the sweep start time, if possible.
 */
static void ProcessVolumeHeader
( struct input_info *pInfo_a )
{

  char sRadarId[8];
  
  YmdsFromNrTime( &pInfo_a->VolumeTime, pInfo_a->VHeader.iFileDate, 
		  pInfo_a->VHeader.iFileTime );

  /* Old format does not include radar ID */

  if (0==strncmp(pInfo_a->VHeader.sFileName,"ARCHIVE2.", 9))
    strcpy( sRadarId, "XXXX" );
  else
    strncpy( sRadarId, pInfo_a->VHeader.sRadarId, 4 );
  sRadarId[4]=0;

  if (CSetup_c.lVerbose)
    {
      char sBuffer[TIMENAME_SIZE];
      sprintf( sMessage_c, "Data from %s, at: %s\n", sRadarId,
	       shhmmssddmonyyyy_r(&pInfo_a->VolumeTime, sBuffer) );
      Lprintf( sMessage_c );
    }
  pInfo_a->lVolumeTimeValid = TRUE;

  if ( (0!=strncmp(pInfo_a->VHeader.sFileName,"ARCHIVE2.", 9)) &&
       (0!=strncmp(pInfo_a->VHeader.sFileName,"AR2V0001.", 9)) )
    {
      sprintf( sMessage_c, "Filename '%9.9s' in volume header not recognized\n", 
	       pInfo_a->VHeader.sFileName );
      Lprintf( sMessage_c );
    }
 
  return;

}

/* ===========================================================
 * Check the data header to see if we want to skip this ray.
 * Returns TRUE if the ray is OK.
 */
static UINT1 CheckDataHeader
( const struct nr_radar_data_header *pCurrentDHeader_a,
  struct input_info *pInfo_a )                              /* Optional */
{
  //  SINT4 iDataIndex;
  /* No test if not specified */

  /* Feature not implemented yet */

  return TRUE;

}
/* ===========================================================
 * Process and compare information from the last two data headers.
 * Returns TRUE if there was no error.  Either header could be null.
 */
static UINT1 CompareDataHeaders
( const struct nr_radar_data_header *pCurrentDHeader_a,
  const struct nr_radar_data_header *pLastDHeader_a,
  struct input_info *pInfo_a )
{
  static struct sweep_scan8 Scan;

  /* ===================================================
   * SECTION 1: Do this for the first ray only.
   * If the last header pointer is null, then this is the first ray
   * of the volume scan.  Initialize the mask.
   */
  if (NULL == pLastDHeader_a)
    {
      /* We must have at least 1 ray! */
      if (!pCurrentDHeader_a)
	{
	  Lprintf( "ERROR, no rays passed the test criteria.\n" );
	  return FALSE;
	}

      MaskFromDHeader( &pInfo_a->DataMask, pCurrentDHeader_a );
      if (lDspMaskEmpty( &pInfo_a->DataMask))
	{
	  Lprintf( "ERROR: First ray contains no valid data types.\n" );
	  return FALSE;
	}
  
      /* Store away the first data header */
      pInfo_a->FirstDHeader = *pCurrentDHeader_a;
      pInfo_a->fMasterStartRangeRef  = ((FLT4)pCurrentDHeader_a->iBinStartRef)/1000.0;
      pInfo_a->fMasterStartRangeDop  = ((FLT4)pCurrentDHeader_a->iBinStartDop)/1000.0;
      pInfo_a->iMasterBinSpacing     = pCurrentDHeader_a->iBinStepRef;
      pInfo_a->iMaximumBinCount      = pCurrentDHeader_a->iBinNumberRef;

      /* Make sure the volume starts with sweep number 1 */
      if (iSweepMapping(pCurrentDHeader_a->iSweepNumber)!=1)
	{
	  sprintf( sMessage_c, "ERROR, the first ray must map to sweep 1 not %d\n",
		   iSweepMapping(pCurrentDHeader_a->iSweepNumber) );
	  Lprintf( sMessage_c );
	  return FALSE;
	}

      memset( &Scan, 0, sizeof(Scan) );
      /* Set the sweep start time to the time of the first ray */
      YmdsFromNrTime( &Scan.SweepTime, pCurrentDHeader_a->iDataDate, 
		      pCurrentDHeader_a->iDataTime );

      /* Init average fixed angle */
      Scan.fRayFixedSum = fElDegFromBin2(pCurrentDHeader_a->iElevation);
      Scan.iRayFixedCount = 1;

      /* No reason to do any comparisons between headers */
      return TRUE;
    }


  /* ==================================================
   * SECTION 2: Do this for the last ray only.
   * If the current header point is NULL, then the last ray was the last
   * of the volume scan.
   */

  if (NULL == pCurrentDHeader_a)
    {
      return StoreSweepInfo( pInfo_a, pLastDHeader_a, &Scan );
    }


  /* =================================================== */
  /* SECTION 3, Do this for all rays in the middle.
   */
  return CompareDataHeadersMiddle( pCurrentDHeader_a, pLastDHeader_a, pInfo_a, &Scan );

}

/* ===========================================================
 * Process and compare information from the last two data headers.
 * Returns TRUE if there was no error.  Both headers are valid.
 */
static UINT1 CompareDataHeadersMiddle
( const struct nr_radar_data_header *pCurrentDHeader_a,
  const struct nr_radar_data_header *pLastDHeader_a,
  struct input_info *pInfo_a,
  struct sweep_scan8 *pScan_a )
{
  struct dsp_data_mask DataMask;
  UINT1 lEqual;
  FLT4 fBinSpacingFactorRef, fBinSpacingFactorDop, fCurrentStartRange;
  SINT4 iCurrentBinCount;


  MaskFromDHeader( &DataMask, pCurrentDHeader_a );
  
  /* Special processing for Nyquist velocity.
   */
  if ( lDspMaskTest( &DataMask, DB_VEL ) )
    {
      FLT4 fNyquistVelocity;
      fNyquistVelocity = ((FLT4)pCurrentDHeader_a->iNyquistVel)/100.0;
      
      /* Protect against values close to zero */
      if (fNyquistVelocity < 0.01)
	{
	  sprintf( sMessage_c, "ERROR: Nyquist velocity %5.4f m/s to close to zero in sweep %d.\n", 
		   fNyquistVelocity, pCurrentDHeader_a->iSweepNumber);
	  Lprintf( sMessage_c );
	  
	  return FALSE;
	}
      
      if (pInfo_a->lNyquistVelocityValid)
	{
	  FLT4 fRatio;
	  /* Protect against drifting by more than 1% */
	  fRatio = fabs( (pInfo_a->fNyquistVelocity/fNyquistVelocity)- 1.0);
	  if (fRatio > 0.01)
	    {
	      sprintf( sMessage_c, "ERROR: Nyquist velocity changed from %5.2f to %5.2f m/s in sweep %d.\n", 
		       pInfo_a->fNyquistVelocity, fNyquistVelocity, 
		       pCurrentDHeader_a->iSweepNumber );
	      Lprintf( sMessage_c );
	      return FALSE;
	    }
	}
      else
	{
	  pInfo_a->fNyquistVelocity = fNyquistVelocity;
	  pInfo_a->lNyquistVelocityValid = TRUE;
	}
    }
  
  /* AND in the mask. */
  
  lEqual = lDspMaskEqual( &pInfo_a->DataMask, &DataMask );
  if (!lEqual)
    {
      char sMessage[100];
      DspMaskAnd( &pInfo_a->DataMask, &DataMask );
      
      if (lDspMaskEmpty( &pInfo_a->DataMask))
	{
	  sprintf( sMessage, "ERROR: No consistent data types at sweep %d, ray %d.\n",
		   pCurrentDHeader_a->iSweepNumber, pCurrentDHeader_a->iRayNumber );
	  Lprintf( sMessage );
	  return FALSE;
	}
      else
	{
	  /* Print a warning message the first time the data types change */
	  static UINT1 lWarningPrinted=FALSE;
	  if (!lWarningPrinted)
	    {
	      sprintf( sMessage, "WARNING: Data types change at sweep %d, ray %d.\n",
		       pCurrentDHeader_a->iSweepNumber, pCurrentDHeader_a->iRayNumber );
	      Lprintf( sMessage );
	      lWarningPrinted=TRUE;
	    }
	}
    }
    
  /* The bin spacing must remain constant */
  if (pInfo_a->iMasterBinSpacing != pCurrentDHeader_a->iBinStepRef)
    {
      if (CSetup_c.lAllowMixedBinSpacing)
	{
	  fBinSpacingFactorRef = ( ((FLT4)pCurrentDHeader_a->iBinStepRef)/
				   ((FLT4)pInfo_a->iMasterBinSpacing) );
	  /* If the bin spacing is finer, then lower the master res */
	  if (fBinSpacingFactorRef<1.0)
	    {
	      pInfo_a->iMasterBinSpacing  = pCurrentDHeader_a->iBinStepRef;
	    }
	}
      else
	{
	  sprintf( sMessage_c, "ERROR, Ref bin spacing changed from %d to %d meters in sweep %d.\n", 
		   pInfo_a->iMasterBinSpacing, pCurrentDHeader_a->iBinStepRef,
		   pCurrentDHeader_a->iSweepNumber );
	  Lprintf( sMessage_c );
	  return FALSE;
	}
    }
  else
    {
      fBinSpacingFactorRef = 1.0;
    }
  
  /* The Doppler bin spacing must remain constant */
  if (pInfo_a->iMasterBinSpacing != pCurrentDHeader_a->iBinStepDop)
    {
      if (CSetup_c.lAllowMixedBinSpacing)
	{
	  fBinSpacingFactorDop = ( ((FLT4)pCurrentDHeader_a->iBinStepDop)/
				   ((FLT4)pInfo_a->iMasterBinSpacing) );
	  /* If the bin spacing is finer, then lower the master res */
	  if (fBinSpacingFactorDop<1.0)
	    {
	      pInfo_a->iMasterBinSpacing = pCurrentDHeader_a->iBinStepDop;
	    }
	}
      else
	{
	  sprintf( sMessage_c, "ERROR, Dop bin spacing changed from %d to %d meters in sweep %d.\n", 
		   pInfo_a->iMasterBinSpacing, pCurrentDHeader_a->iBinStepDop,
		   pCurrentDHeader_a->iSweepNumber );
	  Lprintf( sMessage_c );
	  return FALSE;
	}
    }
  else
    {
      fBinSpacingFactorDop = 1.0;
    }
  
  /* Check the number of bins, not done on the first ray */
  iCurrentBinCount = NINT(fBinSpacingFactorRef) * pCurrentDHeader_a->iBinNumberRef;
  if (iCurrentBinCount > MAX_BINS)
    {
      sprintf( sMessage_c, "ERROR, Bin count %d exceeds max %d supported by IRIS in sweep %d.\n", 
	       iCurrentBinCount, MAX_BINS, pCurrentDHeader_a->iSweepNumber);
      Lprintf( sMessage_c );
      return FALSE;
    }
  
  /* Store the largest bin count.  This is used for the task configuration structure. */
  if (iCurrentBinCount > pInfo_a->iMaximumBinCount)
    pInfo_a->iMaximumBinCount = iCurrentBinCount;
  
  
  /* Check the number of bins, not done on the first ray */
  iCurrentBinCount = NINT(fBinSpacingFactorDop) * pCurrentDHeader_a->iBinNumberDop;
  if (iCurrentBinCount > MAX_BINS)
    {
      sprintf( sMessage_c, "ERROR, Bin count %d exceeds max %d supported by IRIS in sweep %d.\n", 
	       iCurrentBinCount, MAX_BINS, pCurrentDHeader_a->iSweepNumber);
      Lprintf( sMessage_c );
      return FALSE;
    }
  
  /* Store the largest bin count.  This is used for the task configuration structure. */
  if (iCurrentBinCount > pInfo_a->iMaximumBinCount)
    pInfo_a->iMaximumBinCount = iCurrentBinCount;
  
  
  /* Check that the start range remains the same */
  fCurrentStartRange  =  ((FLT4)pCurrentDHeader_a->iBinStartRef)/1000.0;
  /* The start range must remain constant, to within 5 meters */
  if (0.005<fabs(pInfo_a->fMasterStartRangeRef-fCurrentStartRange))
    {
      sprintf( sMessage_c, "ERROR: Ref start range changed from %5.3f to %5.3f km in sweep %d.\n", 
	       pInfo_a->fMasterStartRangeRef, fCurrentStartRange, pCurrentDHeader_a->iSweepNumber );
      Lprintf( sMessage_c );
      return FALSE;
    }
  
  fCurrentStartRange  =  ((FLT4)pCurrentDHeader_a->iBinStartDop)/1000.0;
  /* The start range must remain constant, to within 5 meters */
  if (0.005<fabs(pInfo_a->fMasterStartRangeDop-fCurrentStartRange))
    {
      sprintf( sMessage_c, "ERROR: Ref start range changed from %5.3f to %5.3f km in sweep %d.\n", 
	       pInfo_a->fMasterStartRangeDop, fCurrentStartRange, pCurrentDHeader_a->iSweepNumber );
      Lprintf( sMessage_c );
      return FALSE;
    }
    
  /* Check for a change in sweep number */
  if (iSweepMapping(pCurrentDHeader_a->iSweepNumber) != 
      iSweepMapping(pLastDHeader_a->iSweepNumber) )
    {
      UINT1 lOk;
      /* Two rays bridging the sweep change */
      /* Sweeps must be stored sequentially */
      if (1 != (iSweepMapping(pCurrentDHeader_a->iSweepNumber)-
		iSweepMapping(pLastDHeader_a->iSweepNumber)) )
	{
	  sprintf( sMessage_c, "ERROR, sweep skips from %d to %d\n",
		   pLastDHeader_a->iSweepNumber, pCurrentDHeader_a->iSweepNumber );
	  Lprintf( sMessage_c );
	  return FALSE;
	}
      lOk = StoreSweepInfo( pInfo_a, pLastDHeader_a, pScan_a );
      if (!lOk) return FALSE;

      /* Zero the sweep scan information */
      memset( pScan_a, 0, sizeof(*pScan_a) );
      /* Set the sweep start time to the time of the first ray */
      YmdsFromNrTime( &pScan_a->SweepTime, pCurrentDHeader_a->iDataDate, 
		      pCurrentDHeader_a->iDataTime );

      /* Init average fixed angle */
      pScan_a->fRayFixedSum = fElDegFromBin2(pCurrentDHeader_a->iElevation);
      pScan_a->iRayFixedCount = 1;

    }
  else
    {
      FLT8 fAngleSpan;
      struct ymds_time RayTime;
      SINT4 iRayDiff;
      /* Two rays within a sweep */
      /* The fixed angle must remain constant, or at least below the
       * threshold. */
      fAngleSpan = fDegFromBin2(pCurrentDHeader_a->iElevation - 
				pLastDHeader_a->iElevation);
      fAngleSpan = fabs(fAngleSpan);

      if (fAngleSpan > CSetup_c.fElevationAngleChange)
	{
	  sprintf( sMessage_c, "ERROR: Elevation angle changed from %5.2f to %5.2f in sweep %d.\n", 
		   fElDegFromBin2(pLastDHeader_a->iElevation),
		   fElDegFromBin2(pCurrentDHeader_a->iElevation),
		   pCurrentDHeader_a->iSweepNumber );
	  Lprintf( sMessage_c );
	  return FALSE;
	}
      /* Update the scan resolution information */
      fAngleSpan = fDegFromBin2(pCurrentDHeader_a->iAzimuth - 
				pLastDHeader_a->iAzimuth);
      fAngleSpan = fabs(fAngleSpan);
      /* Ignore sequential rays at the same angle */
      if (fAngleSpan > 0.0)
	{
	  pScan_a->fRayStepSum += fAngleSpan;
	  pScan_a->iRayStepCount++;
	}

      /* Update the average fixed angle */
      pScan_a->fRayFixedSum += fElDegFromBin2(pCurrentDHeader_a->iElevation);
      pScan_a->iRayFixedCount++;

      /* Update the sweep start time to the earliest ray time */
      YmdsFromNrTime( &RayTime, pCurrentDHeader_a->iDataDate, 
		      pCurrentDHeader_a->iDataTime );
      iRayDiff = iymds_subtract( &RayTime, &pScan_a->SweepTime );
      if (iRayDiff<0) pScan_a->SweepTime = RayTime;
    }

  return TRUE;

}

/* ====================================================
 * Based on the data header, return a mask of all the data
 * in the volume scan.
 */
static void MaskFromDHeader
(struct dsp_data_mask *pDataMask_a,
 const struct nr_radar_data_header *pDHeader_a )
{
  memset( pDataMask_a, 0, sizeof( *pDataMask_a ) );

  if (pDHeader_a->iRefPointer) DspMaskSet( pDataMask_a, DB_DBZ );
  if (pDHeader_a->iVelPointer) DspMaskSet( pDataMask_a, DB_VEL );
  if (pDHeader_a->iWidPointer) DspMaskSet( pDataMask_a, DB_WIDTH);

  return;

}

/* ======================================================================
 * Build the ingest header based on information from the first pass.
 * Returns nonzero if everything is OK to proceed.
 */
static UINT1 BuildIngestHeader
(struct ingest_header *pIHeader_a,
 const struct input_info *pInfo_a )
{
  if (TRACE_ON) Lprintf( "BuildIngestHeader called.\n" );

  memset( pIHeader_a, 0, sizeof(*pIHeader_a) );

  /* Fill in most of the Ingest Header Structure
   */
  pIHeader_a->hdr.id     = ST_INGEST_HDR;
  pIHeader_a->hdr.ivers  = SVER_INGEST_HDR_P;
  pIHeader_a->hdr.ibytes = INGEST_HEADER_SIZE;
  pIHeader_a->hdr.iflags = HD_STRUCTURE_COMPLETE;    /* Full RAW is always 'done' */
      
  /* Fill in the ingest_configuration
   */

  BuildIngestConfig( &pIHeader_a->icf, pInfo_a );
  
  BuildTaskConfig( &pIHeader_a->tcf, pInfo_a );

  BuildGparm( &pIHeader_a->GParm, pInfo_a );

  /*
  pIHeader_a->tcf.end.ing_time = pIHeader_a->icf.VolumeYmds;
  */

  return TRUE;
}

/* ================================================================
 * Fills in the ingest configuration structure.
 */
static void BuildIngestConfig
(struct ingest_configuration *pIngCon_a,
 const struct input_info *pInfo_a )
{
  FLT8 fAngle;
  SINT4 iRaysInRev;

  if (TRACE_ON) Lprintf( "BuildIngestConfig called.\n" );

  iRaysInRev = 360; /* Default 1 degree resolution */
  if (pInfo_a->fMinimumSweepRes != 0.0)
    {
      iRaysInRev = NINT(360.0/pInfo_a->fMinimumSweepRes);
      if (iRaysInRev > MAX_INGEST_RAYS) 
	{
	  char sMessage[80];
	  sprintf( sMessage, "WARNING: resolution degraded from %3.2f to %3.2f degrees.\n",
		   pInfo_a->fMinimumSweepRes, 360.0/(FLT8)MAX_INGEST_RAYS );
	  Lprintf( sMessage );
	  iRaysInRev = MAX_INGEST_RAYS;
	}
    }
  
  pIngCon_a->inrev  = iRaysInRev;    /* # rays in one revolution */
  pIngCon_a->isndx  = 0;             /* First ray's angle expressed as index*/
  pIngCon_a->irtotl = iRaysInRev;    /* total # rays in sweep */
  

  /* By default, the Volume time is set to the time of the first sweep */
  pIngCon_a->VolumeYmds = pInfo_a->Sweep[0].SweepTime;

  /* If specified in the optional header, consider using that instead */
  if (pInfo_a->lVolumeTimeValid)
    {
      char sTime1[TIMENAME_SIZE], sTime2[TIMENAME_SIZE];
      SINT4 iTimeDiff;
      /* Check it against the time of the first sweep.  Log a warning
       * and override if it is not reasonable.
       */
      iTimeDiff = iymds_subtract( &pInfo_a->Sweep[0].SweepTime, &pInfo_a->VolumeTime );
      if ((iTimeDiff<0)||(iTimeDiff>60))
	{
	  sprintf( sMessage_c, "WARNING: Overriding volume time %s with time of first ray %s.\n",
		   shhmmssmmm_ddmonyyyy(&pInfo_a->VolumeTime,sTime1),
		   shhmmssmmm_ddmonyyyy(&pInfo_a->Sweep[0].SweepTime,sTime2) );
	  Lprintf( sMessage_c );
	}
      else
	{
	  pIngCon_a->VolumeYmds = pInfo_a->VolumeTime;
	}
    }

  /* Convert UTC to local recorded time */
  if (pInfo_a->pSite->lRecordUtc)
    {
      pIngCon_a->VolumeYmds.imills |= YMDS_FLG_UTC;
      pIngCon_a->iMinutesWest     =  0;
    }
  else
    {
      pIngCon_a->iMinutesWest = pInfo_a->pSite->iLocalWest;
      ymds_add_seconds( -60*(SINT4)pInfo_a->pSite->iLocalWest, &pIngCon_a->VolumeYmds );
    }
  pIngCon_a->iLocalWest = pInfo_a->pSite->iLocalWest;
    
  pIngCon_a->isweeps_done = 0 ;               /* # of sweeps done */
  pIngCon_a->idfnum    = 0;                   /* # associated data files, and */
  pIngCon_a->idfsize   = 0;                   /* total size of same. */
  pIngCon_a->ib_rayhed = RAY_HSIZE;           /* Ray header size */
  pIngCon_a->ib_task   = TASK_CONFIG_SIZE;    /* Task conf size */
  pIngCon_a->ib_gparm  = DSP_GPARM_SIZE;      /* GPARM size */
  
  pIngCon_a->ib_xhdr   = 0;                   /* We are not making extended headers */
  
  memmove( pIngCon_a->sSitename, pInfo_a->pSite->sIrisSitename, 16 );

  memmove( pIngCon_a->sHardwareName, pIngCon_a->sSitename, 16 );
  strncpy( pIngCon_a->siris_version, IRIS_VERSION, 8 );
  pIngCon_a->siris_version[7] = 0;

  fAngle  =  (FLT8)pInfo_a->Adaptation.iLatDeg;
  fAngle += ((FLT8)pInfo_a->Adaptation.iLatMin)/60.0;
  fAngle +=        pInfo_a->Adaptation.fLatSec /(60.0*60.0);
  if ('S'==pInfo_a->Adaptation.sLatDir[0] ) fAngle = -fAngle;
  pIngCon_a->ilat      = iBin4FromFDeg( fAngle );


  fAngle  =  (FLT8)pInfo_a->Adaptation.iLonDeg;
  fAngle += ((FLT8)pInfo_a->Adaptation.iLonMin)/60.0;
  fAngle +=        pInfo_a->Adaptation.fLonSec /(60.0*60.0);
  if ('W'==pInfo_a->Adaptation.sLonDir[0] ) fAngle = -fAngle;
  pIngCon_a->ilon      = iBin4FromFDeg( fAngle );

  pIngCon_a->ignd_hgt  = pInfo_a->Adaptation.ignd_hgt;
  pIngCon_a->irad_hgt  = pInfo_a->Adaptation.irad_hgt;
  pIngCon_a->ialtitude = 100 * (pIngCon_a->ignd_hgt+pIngCon_a->irad_hgt);
      
  pIngCon_a->inu_vel[0] = 0;
  pIngCon_a->inu_vel[1] = 0;
  pIngCon_a->inu_vel[2] = 0;
  pIngCon_a->inu_offset[0] = 0;
  pIngCon_a->inu_offset[1] = 0;
  pIngCon_a->inu_offset[2] = 0;
  memcpy( pIngCon_a->sLocalTZName, pInfo_a->pSite->sLocalTZName, TZNAME_SIZE );
  return;
}

/* ================================================================
 * Fills in the task configuration structure.
 */
static UINT1 BuildTaskConfig
(struct task_configuration *pTaskCon_a,
 const struct input_info *pInfo_a )
{
  SINT4 iSweep;
  FLT4 fAngle;
  if (TRACE_ON) Lprintf( "BuildTaskConfig called.\n" );

  /* Header */
  pTaskCon_a->hdr.id     = ST_TASK_CONF;
  pTaskCon_a->hdr.ivers  = SVER_TASK_CONF_P;
  pTaskCon_a->hdr.ibytes = TASK_CONFIG_SIZE;

  /* Scheduling info */
  /* None of this is important in the data */

  /* DSP info */
  if (lDspMaskEmpty( &pInfo_a->DataMask))
    {
      Lprintf( "ERROR: No valid data types in volume.\n" );
      return FALSE;
    }
  pTaskCon_a->dsp.imajor_mode = PMODE_PPP;     /* Just a guess */
  pTaskCon_a->dsp.DataMask = pInfo_a->DataMask; 
  pTaskCon_a->dsp.iprf = 1000;                 /* Default if no PRT supplied */
  /*
  if (pInfo_a->iMasterPRT)
    pTaskCon_a->dsp.iprf = NINT( 1000000.0 /(FLT4)pInfo_a->iMasterPRT);
  else if (pInfo_a->FirstFHeader.iPRT)
    pTaskCon_a->dsp.iprf = NINT( 1000000.0 /(FLT4)pInfo_a->FirstFHeader.iPRT);
  pTaskCon_a->dsp.ipw = NINT((100000.0*(FLT8)pInfo_a->FirstFHeader.iPulseWidth)/FSPEED_OF_LIGHT);
  */

  /* We will revisit this after we get the wavelength processed */
  pTaskCon_a->dsp.itrig = PRF_FIXED;

  /* pTaskCon_a->dsp.isamp = pInfo_a->FirstFHeader.iSampleSize; */
  pTaskCon_a->dsp.isamp = 64;

  /* Calibration info */
  pTaskCon_a->cal.iz_calib = NINT( 16.0*pInfo_a->pTask->fZCalib );

  /* range info */
  pTaskCon_a->rng.ibin_first    = NINT(100000.0 * pInfo_a->fMasterStartRangeDop);
  pTaskCon_a->rng.ibin_in_num   = pInfo_a->iMaximumBinCount;
  pTaskCon_a->rng.ibin_out_num  = pInfo_a->iMaximumBinCount;
  pTaskCon_a->rng.ibin_in_step  = 100*pInfo_a->iMasterBinSpacing;
  pTaskCon_a->rng.ibin_out_step = 100*pInfo_a->iMasterBinSpacing;
  pTaskCon_a->rng.ibin_last     = pTaskCon_a->rng.ibin_first +
    (pTaskCon_a->rng.ibin_out_num-1)*pTaskCon_a->rng.ibin_out_step;

  /* scan info */
  pTaskCon_a->scan.isweeps = pInfo_a->iSweepCount;
  pTaskCon_a->scan.ires1000 = NINT(1000.*pInfo_a->fMinimumSweepRes);
  pTaskCon_a->scan.iscan = TASK_SCAN_CONT;
  for (iSweep=0; iSweep<pInfo_a->iSweepCount; iSweep++)
    {
      pTaskCon_a->scan.u.ppi.iellst[iSweep] = 
	iBin2FromFDeg(pInfo_a->Sweep[iSweep].fAverageFixedAngle);
    }

  /* misc info */
  if (CSetup_c.fWaveLength==0.0)
    {
      FLT4 fWaveLength = 100000.0*FSPEED_OF_LIGHT/(FLT4)pInfo_a->Adaptation.itfreq_mhz;
      pTaskCon_a->misc.ilambda = NINT(fWaveLength);
    }
  else
    pTaskCon_a->misc.ilambda = NINT( 100.0*CSetup_c.fWaveLength );

  pTaskCon_a->misc.ipolar = POL_HORIZ_FIX;
  /* Height truncation is assumed off, set to the maximum range */
  pTaskCon_a->misc.itrunc = pTaskCon_a->rng.ibin_last;
  fAngle = pInfo_a->Adaptation.fbeam_width; 
  pTaskCon_a->misc.iHorzBeamWidth = iBin4FromFDeg( fAngle );
  pTaskCon_a->misc.iVertBeamWidth = iBin4FromFDeg( fAngle );

  /* end info */
  spacify_ncpy( pTaskCon_a->end.stname, pInfo_a->pTask->sIrisTaskName, 12 );

  /* Comments (none) */

  /* Try to figure out the trigger rate situation */
  if (pInfo_a->lNyquistVelocityValid)
    {
      FLT4 fNyquist, fNyquistError, fMinNyquistError;
      fNyquist = fNyquistVelocity( pTaskCon_a->dsp.iprf, PRF_FIXED,
				   pTaskCon_a->misc.ilambda, pTaskCon_a->misc.ipolar );
      fMinNyquistError = fabs( fNyquist - pInfo_a->fNyquistVelocity );

      fNyquist = fNyquistVelocity( pTaskCon_a->dsp.iprf, PRF_2_3,
				   pTaskCon_a->misc.ilambda, pTaskCon_a->misc.ipolar );
      fNyquistError = fabs( fNyquist - pInfo_a->fNyquistVelocity );
      if (fNyquistError < fMinNyquistError)
	{
	  fMinNyquistError = fNyquistError;
	  pTaskCon_a->dsp.itrig = PRF_2_3;
	}

      fNyquist = fNyquistVelocity( pTaskCon_a->dsp.iprf, PRF_3_4,
				   pTaskCon_a->misc.ilambda, pTaskCon_a->misc.ipolar );
      fNyquistError = fabs( fNyquist - pInfo_a->fNyquistVelocity );
      if (fNyquistError < fMinNyquistError)
	{
	  fMinNyquistError = fNyquistError;
	  pTaskCon_a->dsp.itrig = PRF_3_4;
	}

      fNyquist = fNyquistVelocity( pTaskCon_a->dsp.iprf, PRF_4_5,
				   pTaskCon_a->misc.ilambda, pTaskCon_a->misc.ipolar );
      fNyquistError = fabs( fNyquist - pInfo_a->fNyquistVelocity );
      if (fNyquistError < fMinNyquistError)
	{
	  fMinNyquistError = fNyquistError;
	  pTaskCon_a->dsp.itrig = PRF_4_5;
	}
   }

  return TRUE;
}
/* ================================================================
 * Fills in the gparm structure.
 */
static UINT1 BuildGparm
(struct gparm *pGparm_a,
 const struct input_info *pInfo_a )
{
  /* Calibration info */
  pGparm_a->iz_calib = NINT( 16.0*pInfo_a->pTask->fZCalib );

  return TRUE;
}
/* =====================================================================
 * Scan through the data and write it to the IRIS product.
 */
static void ScanAndWrite
( const char *sPathStart_a,        /* Start of pathnames for ingest files */
  const struct ingest_header *pIHeader_a,
  const struct input_info *pInfo_a )
{
  SINT4 iCount, iRayCount, iIrisSweep, iLastSweep;
  UINT1 lOk;
  union  nr_packet    Packet; 
  struct nr_radar_data_header *pCurrentDHeader;
  struct data_convert Convert;
  struct ingest_file_state Files[NUM_DEFINED_DATA];

  if (TRACE_ON) Lprintf( "ScanAndWrite called.\n" );
  
  memset( Files, 0, NUM_DEFINED_DATA*sizeof(struct ingest_file_state));

  /* For hybrid scans this will need to be done separately for each */
  FillDataConvert( &Convert, pIHeader_a->tcf.dsp.iprf, pIHeader_a->tcf.dsp.itrig,
		   pIHeader_a->tcf.misc.ilambda, pIHeader_a->tcf.misc.ipolar,
		   CONVERT_FLAGS_DEFAULT );

  /* Skip the volume header */
  {
    struct nr_volume_header VHeader;
    iCount = ReadNrVolumeHeader( &VHeader, CSetup_c.iInFile );
  }

  /* Loop reading in records, and processing the results */
  iLastSweep = 0;
  iRayCount  = 0;
  iCount = ReadNrPacket( &Packet, CSetup_c.iInFile );
  while( iCount )
    {
      /* Only process digital radar data messages */
      switch( Packet.MessageHeader.iMsgType)
	{
	case NR_MSG_TYPE_DIGITAL_RADAR:
	  pCurrentDHeader = &Packet.RadarData.DataHeader;
	  
	  iRayCount++;
	  
	  /* Unpack the data header */
	  DataHeaderUnpack(pCurrentDHeader );
	  if (TRACE2_ON)
	    {
	      sprintf( sMessage_c, "Ray:%d, Sweep:%d\n", iRayCount, 
		       pCurrentDHeader->iSweepNumber );
	      Lprintf( sMessage_c );
	    }
	  
	  /* Entirely ignore the ray if the sweep is in the skip list */
	  iIrisSweep = iSweepMapping(pCurrentDHeader->iSweepNumber);
	  if (0 == iIrisSweep) goto SKIP_RAY;
	  
	  lOk = ProcessRadarData( sPathStart_a, pIHeader_a, pInfo_a, Files,
				  &Packet.RadarData, &Convert, iIrisSweep, iLastSweep );
	  iLastSweep = iIrisSweep;
	  if (!lOk) return;
	SKIP_RAY:
	  break;

	default:
	  break;
	}
      
      /* Read the next packet */
      iCount = ReadNrPacket( &Packet, CSetup_c.iInFile );
    }

  if ((iCount !=0) || (ferror(CSetup_c.iInFile)))
    {
      Lprintf( "Read error.\n" );
      return;
    }

  /* Process to get last sweeps information */
  CloseOldFiles( Files, &pInfo_a->DataMask );

  return;

}

/* =======================================================
 * Process one ray of radar data.  Returns OK flag.
 */
static UINT1 ProcessRadarData
( const char *sPathStart_a,        /* Start of pathnames for ingest files */
  const struct ingest_header *pIHeader_a,
  const struct input_info *pInfo_a,
  struct ingest_file_state Files_a[NUM_DEFINED_DATA],
  const struct nr_radar_packet *pRadarData_a, 
  const struct data_convert *pConvert_a,
  SINT4 iIrisSweep_a, 
  SINT4 iLastSweep_a )
{
  struct ray_header RayHeader;
  BIN4 ii4;
  SINT4 ir_ptr;

  if (TRACE2_ON) Lprintf( "ProcessRadarData called.\n" );

  /* Check for the sweep changing */
  if (iIrisSweep_a != iLastSweep_a)
    {
      struct ymds_time SweepTimeLocal;
      if (iLastSweep_a!=0) CloseOldFiles( Files_a, &pInfo_a->DataMask );
      SweepTimeLocal = pInfo_a->Sweep[iIrisSweep_a-1].SweepTime;
      /* Convert UTC to recorded local time */
      if (pInfo_a->pSite->lRecordUtc)
	{
	  SweepTimeLocal.imills |= YMDS_FLG_UTC;
	}
      else
	{
	  ymds_add_seconds( -60*(SINT4)pInfo_a->pSite->iLocalWest, &SweepTimeLocal );
	}
      OpenNewFiles( Files_a, pIHeader_a, iIrisSweep_a, &SweepTimeLocal, sPathStart_a );
    }

  /* Entirely ignore the ray if desired data is missing */
  if (!CheckDataHeader(&pRadarData_a->DataHeader, NULL)) return TRUE;

  /* The bin count must be filled in later */
  ConvertRayHeader( &RayHeader, &pRadarData_a->DataHeader, 
		    &pInfo_a->Sweep[iIrisSweep_a-1].SweepTime, 0 );
  ii4 = (BIN4)RayHeader.iaz_start;
  
  /* Compute the ray pointer */
  ir_ptr = ((( 1 + ((2*ii4*pIHeader_a->icf.inrev)>>16))/2) % pIHeader_a->icf.inrev ) -
    pIHeader_a->icf.isndx;
  
  if (CSetup_c.lVerbose2)
    {
      char sLine[80];
      sprintf( sLine, "%d: Az:%5.2f, ptr:%d, inrev:%d\n", iIrisSweep_a, fPDegFromBin2(ii4), 
	       ir_ptr, pIHeader_a->icf.inrev );
      Lprintf( sLine );
    }
  
  /* Read and process various data types */
  ConvertRay( pInfo_a, &Files_a[DB_DBZ], pRadarData_a, pConvert_a, &RayHeader, DB_DBZ, 
	      ir_ptr );
  ConvertRay( pInfo_a, &Files_a[DB_VEL], pRadarData_a, pConvert_a, &RayHeader, DB_VEL, 
	      ir_ptr );
  ConvertRay( pInfo_a, &Files_a[DB_WIDTH], pRadarData_a, pConvert_a, &RayHeader, DB_WIDTH, 
	      ir_ptr );

  return TRUE;
}

/* ======================================================
 * Convert a ray.
 */
static void ConvertRay
( const struct input_info *pInfo_a,
  struct ingest_file_state *pFile_a,
  const struct nr_radar_packet *pRadarData_a, 
  const struct data_convert *pConvert_a,
  struct ray_header *pRayHeader_a,
  SINT4 iDataType_a,
  SINT4 ir_ptr_a )
{
  struct data_ray Ray;
  SINT4 iRaySize;
  UINT4 iRepCount;
  SINT4 iInPosition;
      
  if (!lDspMaskTest( &pInfo_a->DataMask, iDataType_a )) return;
  
  /* Abort this ray now if the pointer is already filled */
  if (pFile_a->pTable[ir_ptr_a]) return;
  

  switch( iDataType_a)
    {
    case DB_VEL:
      iInPosition = 460;
      break;

    default:
    case DB_DBZ:
      iInPosition = 0;
      break;

    case DB_WIDTH:
      iInPosition = 460+920;
      break;
    }
  /* Get repeat count to make IRIS bins */
  iRepCount= pRadarData_a->DataHeader.iBinStepRef /pInfo_a->iMasterBinSpacing;
  pRayHeader_a->ibincount = iRepCount*pRadarData_a->DataHeader.iBinNumberRef;
  
  /* Fill the mapping table, if this is the first time we are called */
  if (!pFile_a->lMapTableFilled)
    {
      FillDataMap( pFile_a->iMapTable, pConvert_a, iDataType_a,
		   &pRadarData_a->DataHeader );
      pFile_a->lMapTableFilled = TRUE;
    }
  
  Ray.hdr = *pRayHeader_a;
  ConvertRayData( Ray.data.iData1, pRadarData_a->iData+iInPosition, pFile_a->iMapTable, 
		  pRayHeader_a->ibincount, iRepCount );
  iRaySize = pRayHeader_a->ibincount + sizeof(*pRayHeader_a);
  /* Rays must always be even # bytes */
  if (iRaySize&1) iRaySize++;
  
  pFile_a->pTable[ir_ptr_a] = pFile_a->iDataUsed+1;
  memcpy( &pFile_a->pData[pFile_a->iDataUsed], &Ray, iRaySize );
  pFile_a->iDataUsed += iRaySize;
  pFile_a->pIDH->iwritn++;
}

/* ======================================================
 * Open all the sweep mapped files.
 */
static void OpenNewFiles
( struct ingest_file_state Files_a[NUM_DEFINED_DATA],
  const struct ingest_header *pIHeader_a,  /* Ingest header */
  SINT2  iSweepNumber_a,      /* New IRIS sweep number, origin 1 */
  const struct ymds_time *pSweepTimeLocal_a,
  const char *sPathStart_a )  /* Start of pathnames */
{
  UINT1 iDataType;
  SINT4 iFileSize, iRaySize;

  if (TRACE2_ON) Lprintf( "OpenNewFiles called.\n" );

  iRaySize   = sizeof(struct ray_header) + pIHeader_a->tcf.rng.ibin_out_num;
  if (iRaySize&1) iRaySize++;
  iFileSize  = INGEST_DATA_HEADER_SIZE;
  iFileSize += 4*pIHeader_a->icf.irtotl;
  iFileSize += ((SINT4)pIHeader_a->icf.irtotl) * iRaySize;
  for (iDataType=0; iDataType<NUM_DEFINED_DATA; iDataType++)
    {
      if (lDspMaskTest( &pIHeader_a->tcf.dsp.DataMask, iDataType ) )
	{
	  struct ingest_file_state *pFile;
	  MESSAGE istatus;
	  char sPath[PATHNAME_SIZE];

	  pFile = &Files_a[iDataType];
	  strcpy( sPath, sPathStart_a );
	  strcat( sPath, sIngestEnd( iDataType, iSweepNumber_a ) );
	  istatus = imapcreate( sPath, iFileSize, (void **)&pFile->pIDH, &pFile->iSize, 
			      &pFile->iChannel );
	  if (istatus!= SS_NORMAL) 
	    {sig_signal( istatus ); exit(EXIT_FAILURE); }
	  pFile->pTable = (UINT4 *)(pFile->pIDH+1);
	  pFile->pData  = (UINT1 *)(pFile->pTable+pIHeader_a->icf.irtotl);
	  pFile->iDataUsed = 0;
	  memset( pFile->pIDH, 0, sizeof(*pFile->pIDH) );
	  memset( pFile->pData, 0, 4*pIHeader_a->icf.irtotl );

	  pFile->pIDH->hdr.id     = ST_INGEST_DATA;
	  pFile->pIDH->hdr.ivers  = SVER_INGEST_DATA_P;
	  pFile->pIDH->hdr.ibytes = INGEST_DATA_HEADER_SIZE;
	  pFile->pIDH->hdr.iflags = HD_STRUCTURE_COMPLETE;   
	  pFile->pIDH->time       = *pSweepTimeLocal_a;
	  pFile->pIDH->isweep     = iSweepNumber_a;
	  pFile->pIDH->inrev      = pIHeader_a->icf.inrev;
	  pFile->pIDH->isndx      = pIHeader_a->icf.isndx;
	  pFile->pIDH->irtotl     = pIHeader_a->icf.irtotl;
	  pFile->pIDH->iwritn     = 0;
	  pFile->pIDH->iangle     = pIHeader_a->tcf.scan.u.ppi.iellst[iSweepNumber_a-1];
	  pFile->pIDH->ibits_bin  = 8;
	  pFile->pIDH->idata      = iDataType;
	}
    }
  return;

}
/* ======================================================
 * Close all the old mapped files.
 */
static void CloseOldFiles
(struct ingest_file_state Files_a[NUM_DEFINED_DATA],
 const struct dsp_data_mask *pDataMask_a )
{

  if (TRACE2_ON) Lprintf( "CloseOldFiles called.\n" );

  UINT1 iDataType;
  for (iDataType=0; iDataType<NUM_DEFINED_DATA; iDataType++)
    {
      if (lDspMaskTest( pDataMask_a, iDataType ) )
	{
	  struct ingest_file_state *pFile;
	  MESSAGE istatus;
	  pFile = &Files_a[iDataType];
	  pFile->pIDH->hdr.ibytes = INGEST_DATA_HEADER_SIZE + 
	    (4*(SINT4)pFile->pIDH->irtotl) + pFile->iDataUsed;
	  istatus = imapclose( pFile->pIDH, pFile->iSize, pFile->iChannel );
	  if (istatus!= SS_NORMAL) sig_signal( istatus );
	  pFile->pIDH = NULL;
	}
    }
  return;
}
/* ======================================================
 * Remove all the ingest sweep files.
 */
static void RemoveIngestFiles
( const struct ingest_header *pIHeader_a,  /* Ingest header */
  const char *sPathStart_a )  /* Start of pathnames */
{
  SINT2 iSweepNumber;
  UINT1 iDataType;

  for (iSweepNumber=1; iSweepNumber<=pIHeader_a->tcf.scan.isweeps; iSweepNumber++)
    {
      for (iDataType=0; iDataType<NUM_DEFINED_DATA; iDataType++)
	{
	  if (lDspMaskTest( &pIHeader_a->tcf.dsp.DataMask, iDataType ) )
	    {
	      char sPath[PATHNAME_SIZE];
	      int ierror;
	      
	      strcpy( sPath, sPathStart_a );
	      strcat( sPath, sIngestEnd( iDataType, iSweepNumber ) );
	      ierror = remove( sPath );
	      if (ierror!=0) perror("remove");
	      
	    }
	}
    }
  return;
}
/* ======================================================
 * Fill the mapping table for 1 data type.
 */
static void FillDataMap
( UINT1 iDataMap_a[256],
  const struct data_convert *pConvert_a,
  UINT1 iDataType_a,
  const struct nr_radar_data_header *pDHeader_a )
{
  FLT4 fValue, fGain, fOffset;
  UINT2 iNrValue;
  UINT1 iIrisValue;

  switch( iDataType_a)
    {
    case DB_VEL:
      if (pDHeader_a->iVelocityRes==4)
	{
	  fGain = 1.0;
	  fOffset = -129.0;
	}
      else
	{
	  fGain = 0.5;
	  fOffset = -64.5;
	}
      break;

    default:
    case DB_DBZ:
      fGain = 0.5;
      fOffset = -33.0;
      break;

    case DB_WIDTH:
      fGain = 0.5;
      fOffset = -64.5;
      break;
    }

  for (iNrValue=0; iNrValue<256; iNrValue++)
    {
      if( iNrValue == NR_NO_DATA )
	iIrisValue = DV_THRESH1;
      else if( iNrValue == NR_OVERLAID )
	iIrisValue = DV_THRESH1;
      else
	{
	  fValue = (fGain*(FLT4)iNrValue)+fOffset;
	  iIrisValue = iuser_to_data( fValue, iDataType_a, pConvert_a );
	}
      iDataMap_a[iNrValue] = iIrisValue;
    }
  return;
}

/* =====================================================================
 * Converts a ray header from NR to IRIS format.
 */
static void ConvertRayHeader
( struct ray_header *pRayHeader_a,
  const struct nr_radar_data_header *pDHeader_a,
  const struct ymds_time *pSweepTime_a,   /* Sweep start time in UTC */
  UINT2 iBinCount_a )
{
  struct ymds_time RayTime;  /* Ray time in UTC */
  SINT4 iTimeDiff;
  pRayHeader_a->iaz_start = pDHeader_a->iAzimuth;
  pRayHeader_a->iel_start = pDHeader_a->iElevation;
  pRayHeader_a->iaz_end   = pRayHeader_a->iaz_start;
  pRayHeader_a->iel_end   = pRayHeader_a->iel_start;

  YmdsFromNrTime( &RayTime, pDHeader_a->iDataDate, pDHeader_a->iDataTime );

  iTimeDiff = iymds_subtract( &RayTime, pSweepTime_a );
  pRayHeader_a->itime = (UINT2)(UINT4)iTimeDiff;
  pRayHeader_a->ibincount = iBinCount_a;
  return;
}

/* ====================================================
 * Convert the data in a ray from NR to IRIS format.
 */
static void ConvertRayData
( UINT1 *pIrisData_a,
  const UINT1 *pInData_a,
  const UINT1 iDataMap_a[256],
  UINT2 iIrisBinCount_a,
  UINT4 iRepFactor_a )  /* Bin replication factor */
{
  UINT4 iIrisBin, iInBin;

  if (TRACE2_ON)
    {
      sprintf( sMessage_c, "ConvertRayData called bins:%d, rep:%d, pIris:%d, pIn:%d.\n",
	       iIrisBinCount_a, iRepFactor_a, pIrisData_a, pInData_a );
      Lprintf( sMessage_c );
    }

  iInBin=0;
  for (iIrisBin=0; iIrisBin<iIrisBinCount_a; iIrisBin++)
    {
      pIrisData_a[iIrisBin] = iDataMap_a[pInData_a[iInBin]];
      if (0==((iIrisBin+1)%iRepFactor_a)) iInBin++;
    }
  /* Odd rays always padded by a zero */
  if (1&iIrisBinCount_a) pIrisData_a[iIrisBinCount_a] = 0;
  
  return;
}

/* ====================================================
 * Convert a NEXRAD origin 1 sweep number to an IRIS origin 1
 * sweep number.  Skipped sweeps are zero.
 */
static SINT2 iSweepMapping
( SINT2 iNrSweep_a )
{
  SINT2 iSweepNumber;

  /* Add an offset to all the sweeps */
  iSweepNumber = iNrSweep_a+CSetup_c.iSweepOffset;

  /* Now apply the mapping table */
  if (iSweepNumber<1) return 0;
  if (iSweepNumber>MAX_CONVERT_SWEEPS) return 0;

  return CSetup_c.iSweepMap[iSweepNumber-1];
}

/* ======================================================================
 * Function to process the command line arguments.
 */
static void ProcessCommandLine
(int iArgc_a, 
 char *sArgv_a[],
 struct command_line *pCommand_a )
{
  
  int iArg;
  memset( pCommand_a, 0, sizeof(*pCommand_a) );
  
  /* Parse command line options
   */
  for( iArg=1 ; iArg<iArgc_a ; iArg++ )
    {
      const char *sArg = sArgv_a[iArg];
      char *sptr;

      /* fprintf( stderr, "Processing %d: '%s'\n", iArg, sArg ); */
      if (0 == strncmp( sArg, "-h", 2 ))
	{
	  Usage();
	}
      else if (0 == strncmp( sArg, "-device:", 8 ))
	{
	  pCommand_a->iDevice = strtol( sArg+8, &sptr, 10 );
	}
      else if (0 == strncmp( sArg, "-if:", 4 ))
	{
	  strncpy( pCommand_a->sInFile, &sArg[4], FILENAME_SIZE-1 );
	}
      else if (0 == strncmp( sArg, "-ip:", 4 ))
	{
	  strncpy( pCommand_a->sInPath, &sArg[4], PATHNAME_SIZE-1 );
	}
      else if (0 == strncmp( sArg, "-op:", 4 ))
	{
	  strncpy( pCommand_a->sOutPath, &sArg[4], PATHNAME_SIZE-1 );
	}
      
    }
  return;
}

/* ==================================================
 */
static void Usage( void )
{
  fprintf( stderr, "Archive2ToIris options:\n" );
  fprintf( stderr, "  -device:#  -- Specify origin 0 device number\n" );
  fprintf( stderr, "  -help      -- Print this list\n" );
  fprintf( stderr, "  -if:<file> -- Specify input filename\n" );
  fprintf( stderr, "  -ip:<path> -- Specify input pathname\n" );
  fprintf( stderr, "  -op:<path> -- Specify output pathname\n" );
  fprintf( stderr, "   (all other arguments ignored)\n" );
  exit(EXIT_FAILURE) ;
}

/* ============================================================
 * Function to load the convert setup from a configuration file.
 */
static void LoadSetup
( struct convert_config *pCSetup_a,
  const struct command_line *pCommand_a )
{
  char sPath[PATHNAME_SIZE];
#define LINE_SIZE (101)
  char sline[LINE_SIZE], *sptr;
  FILE* stream;
  SINT4 iline;
  UINT2 lerror;
  SINT2 iIndex;

  /* Initialize the data to the default values */
  memset( pCSetup_a, 0, sizeof(*pCSetup_a) );
  spacify_ncpy( pCSetup_a->sIngSitename, "UNKNOWN", 16 );
  pCSetup_a->iCurrentDevice = -1;  /* all devices */
  for (iIndex=0; iIndex<MAX_CONVERT_SWEEPS; iIndex++ )
    {
      pCSetup_a->iSweepMap[iIndex]=iIndex+1;
    }
  pCSetup_a->lLogToFile=TRUE;  /* Logging on if there is no file */ 
  pCSetup_a->lLogToTerm=TRUE;
  
  /* 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, DIRECTORY_SIZE );
  strcat( sPath, "Archive2ToIris.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, LINE_SIZE, stream );
  while (NULL != sptr)
    {
      if (strlen(sline)==(LINE_SIZE-1))
	fprintf( stderr, "Config file line overflow, line %d\n", iline );
      lerror = ProcessConfigLine( sptr, pCSetup_a, pCommand_a->iDevice );
      if (lerror) fprintf( stderr, "Config file Syntax error, line %d\n", iline );
      iline +=1;
      sptr = fgets( sline, LINE_SIZE, stream );
    }

  fclose( stream );

  return;
}

/* ===============================================================
 * Process one line from the configuration file.  Returns nonzero if there
 * is a syntax error.
 *
 * All blank lines and lines beginning with "#" are ignored. 
 * You cannot log to Lprintf() inside this function.
 *
 */
static UINT2 ProcessConfigLine
(char *sline_a,
 struct convert_config *pConfig_a,
 SINT4 iDevice_a )    /* Device number origin 0 of this pipe */
{
  char *stokens[MAX_CONVERT_SWEEPS+1];
  UINT4 icount, ioffset;
  UINT1 lDeviceValid;
  
  ioffset = 0;
  icount  = 0;
  /* Process up to 9 tokens, stop when the line is exhausted. */
  while (icount<(MAX_CONVERT_SWEEPS+1)) 
    {
      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;

  /* Set flag to indicate that the current device is valid for us */
  lDeviceValid = ((pConfig_a->iCurrentDevice<0) ||
		  (pConfig_a->iCurrentDevice==iDevice_a));

  if ( 0 == strcmp( "ALLOW_MIXED_BIN_SPACING", stokens[0] ) )
    {
      if (!lDeviceValid) return FALSE;
      pConfig_a->lAllowMixedBinSpacing=TRUE;
      return FALSE;
    }

  if ( 0 == strcmp( "APPEND_TO_LOG", stokens[0] ) )
    {
      if (!lDeviceValid) return FALSE;
      pConfig_a->lAppendToLog=TRUE;
      return FALSE;
    }

  /* See if the line contains a DEVICE_NUMBER command */
  if ( 0 == strcmp( "DEVICE_NUMBER", stokens[0] ) )
    {
      char *sptr;

      if ( icount < 2 ) return TRUE;

      /* Convert from origin 1 to origin 0 */
      pConfig_a->iCurrentDevice=strtol( stokens[1], &sptr, 10 )-1;
      if (sptr == stokens[1]) return TRUE;

      return FALSE;
    }

  /* ELEVATION_ANGLE_CHANGE < angle) */
  if ( 0 == strcmp( "ELEVATION_ANGLE_CHANGE", stokens[0] ) )
    {
      char *sptr;
      if (!lDeviceValid) return FALSE;

      if ( icount < 2 ) return TRUE;

      pConfig_a->fElevationAngleChange = strtod( stokens[1], &sptr );
      if (sptr == stokens[1]) return TRUE;
      if (pConfig_a->fElevationAngleChange < 0.0) return TRUE;  /* Cannot be negative */

      return FALSE;
    }
  
  /* See if the line contains the LOG file command */
  if ( 0 == strcmp( "LOG", stokens[0] ) )
    {
      if (!lDeviceValid) return FALSE;
      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;
    }

  /* See if the line contains the SITE command */
  /* Format is something like: SITE  "<NRsitename>" "<IRISsitename>" 240 */
  if ( 0 == strcmp( "SITE", stokens[0] ) )
    {
      if (!lDeviceValid) return FALSE;
      struct convert_site_list *pOneSite;
      char *sptr;
      if ( icount < 4 ) return TRUE;
      if ( pConfig_a->iSiteCount >= MAX_CONVERT_SITES) return TRUE;
      pOneSite = &pConfig_a->Sites[pConfig_a->iSiteCount];

      strncpy( pOneSite->sNrRadarId, stokens[1], 8 );
      pOneSite->sNrRadarId[8] = 0;
      spacify_ncpy( pOneSite->sIrisSitename, stokens[2], 16 );

      pOneSite->iLocalWest = strtol( stokens[3], &sptr, 10 );
      if (sptr == stokens[3]) return TRUE;

      /* Allow 16 hours either way */
      if (pOneSite->iLocalWest < -16*60 ) return TRUE;
      if (pOneSite->iLocalWest >  16*60 ) return TRUE;

      /* Optional local TZ name */
      if (icount>4)
	{
	  strncpy( pOneSite->sLocalTZName, stokens[4], TZNAME_SIZE-1 );
	}

      /* Optional TZ recording flag */
      if (icount>5)
	{
	  if      ( 0 == strcmp( "LOCAL", stokens[5] ) ) 
	    {}
	  else if ( 0 == strcmp( "UTC", stokens[5] ) ) 
	    {pOneSite->lRecordUtc=TRUE;}
	  else
	    return TRUE;
	}
      pConfig_a->iSiteCount++;
      return FALSE;
    }

  /* List of sweep numbers to skip: SWEEP_SKIP #1 #2 #3 ... */
  if ( 0 == strcmp( "SWEEP_SKIP", stokens[0] ) )
    {
      char *sptr;
      int iIndex, iSkipCount;
      SINT2 iMapValue;
      UINT1 lSweepMask[MAX_CONVERT_SWEEPS];
      if (!lDeviceValid) return FALSE;

      /* Fill in the sweep map with the default 1-to-1 mapping, 
       * later we will remove skipped sweeps.
       */
      memset( lSweepMask, TRUE, MAX_CONVERT_SWEEPS );

      if ( icount < 2 ) return TRUE;
      if ( icount > (MAX_CONVERT_SWEEPS+1) ) return TRUE;
      iSkipCount = icount-1;
      for (iIndex=0; iIndex<iSkipCount; iIndex++)
	{
	  int iSweepSkip = strtol( stokens[iIndex+1], &sptr, 10 );
	  if (sptr == stokens[iIndex+1]) return TRUE;
	  if (iSweepSkip < 1) return TRUE;  /* Origin one */
	  if (iSweepSkip > MAX_CONVERT_SWEEPS) return TRUE; 
	  lSweepMask[iSweepSkip-1]=FALSE;
	}

      /* Convert the mask into a mapping table */
      iMapValue=1;
      for (iIndex=0; iIndex<MAX_CONVERT_SWEEPS; iIndex++ )
	{
	  if (lSweepMask[iIndex])
	    pConfig_a->iSweepMap[iIndex]=iMapValue++;
	  else
	    pConfig_a->iSweepMap[iIndex]=0;
	}
      return FALSE;
    }

  /* Signed number to add to all sweep numbers */
  if ( 0 == strcmp( "SWEEP_OFFSET", stokens[0] ) )
    {
      char *sptr;
      if (!lDeviceValid) return FALSE;

      if ( icount < 2 ) return TRUE;
      pConfig_a->iSweepOffset = strtol( stokens[1], &sptr, 10 );
      if (sptr == stokens[1]) return TRUE;
      /* Any value is allowed */
      return FALSE;
    }

  /* See if the line contains a TASK command */
  /* Format is something like: TASK  "<A2sitename>" PPI 0.5 PPI_VOL <fMinZ>*/
  /* Only PPI mode is supported */
  if ( 0 == strcmp( "TASK", stokens[0] ) )
    {
      struct convert_task_list *pOneTask;
      char *sptr;
      FLT8 fStartAngle;
      if (!lDeviceValid) return FALSE;
      if ( icount < 5 ) return TRUE;
      if ( pConfig_a->iTaskCount >= MAX_CONVERT_TASKS) return TRUE;
      pOneTask = &pConfig_a->Tasks[pConfig_a->iTaskCount];

      strncpy( pOneTask->sNrRadarId, stokens[1], 8 );
      pOneTask->sNrRadarId[8] = 0;


      if (0 != strcmp( "PPI", stokens[2])) return TRUE;

      fStartAngle = strtod( stokens[3], &sptr );
      if (sptr == stokens[3]) return TRUE;
      pOneTask->fFixedAngle = fStartAngle;

      strncpy( pOneTask->sIrisTaskName, stokens[4], 12 );
      pOneTask->sIrisTaskName[12] = 0;

      /* Sixth field (fZCalib) is optional */
      if ( icount > 5 ) 
	{
	  FLT8 fZCalib;
	  fZCalib = strtod( stokens[5], &sptr );
	  if (sptr == stokens[5]) return TRUE;
	  if ((fZCalib > 50.0) || (fZCalib<-100)) return TRUE;
	  pOneTask->fZCalib = fZCalib;
	}
      else
	pOneTask->fZCalib = -30.0;

      pConfig_a->iTaskCount++;
      return FALSE;
    }
  
  if ( 0 == strcmp( "VERBOSE", stokens[0] ) )
    {
      if (!lDeviceValid) return FALSE;
      pConfig_a->lVerbose = TRUE;
      return FALSE;
    }

  if ( 0 == strcmp( "VERBOSE2", stokens[0] ) )
    {
      if (!lDeviceValid) return FALSE;
      pConfig_a->lVerbose2 = TRUE;
      return FALSE;
    }

  /* WAVE_LENGTH_OVERRIDE # */
  if ( 0 == strcmp( "WAVE_LENGTH_OVERRIDE", stokens[0] ) )
    {
      char *sptr;
      if (!lDeviceValid) return FALSE;

      if ( icount < 2 ) return TRUE;

      pConfig_a->fWaveLength = strtod( stokens[1], &sptr );
      if (sptr == stokens[1]) return TRUE;
      if (pConfig_a->fWaveLength <= 0.0) return TRUE;  /* Must be positive */

      return FALSE;
    }
    
  /* Unrecognized lines will generate a syntax error */
  return TRUE;
}

/* ===========================================
 * Print string to log and terminal, if desired.
 */
static void Lprintf
( const char *sString_a )
{
  struct ymds_time Ymds;
  char sTimeBuf[TIMENAME_SIZE];
  GetTimeYmds( &Ymds, TIME_FLG_LOCAL );

  if (CSetup_c.lLogToTerm)
    {
      /* Do not include time stamp on the terminal */
      fprintf( stderr, "%s", sString_a );
    }

  if (CSetup_c.lLogToFile)
    {
      shhmmssmmm_ddmonyyyy( &Ymds, sTimeBuf );
      fprintf( CSetup_c.iLogFile, "%s:", sTimeBuf );
      fputs( sString_a, CSetup_c.iLogFile );
      fflush( CSetup_c.iLogFile );
    }
}
/* ======================================================================
 * 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);
}
