/* ******************************************************************
 * *                                                                *
 * *  Function to convert a UF file to an IRIS RAW product          *
 * *                                                                *
 * ******************************************************************
 * File: utils/pipes_in/UfToIris.C
 *
 *          COPYRIGHT (c) 2000, 2001, 2002, 2003, 2004, 2005 BY
 *          SIGMET INCORPORATED, WESTFORD MASSACHUSETTS, U.S.A.
 * 
 * THIS SOFTWARE IS FURNISHED UNDER A LICENSE AND MAY BE USED AND  COPIED
 * ONLY  IN  ACCORDANCE WITH  THE  TERMS  OF  SUCH  LICENSE  AND WITH THE
 * INCLUSION OF THE ABOVE COPYRIGHT NOTICE.  THIS SOFTWARE  OR  ANY OTHER
 * COPIES  THEREOF MAY NOT BE PROVIDED OR OTHERWISE MADE AVAILABLE TO ANY
 * OTHER PERSON.  NO TITLE TO AND OWNERSHIP OF  THE  SOFTWARE  IS  HEREBY
 * TRANSFERED.
 */

/* This program is a pipe which takes an input UF (Universal Format) file
 * and converts it into an IRIS RAW product containing that data.
 *
 * Note if you are modifying this program, and wish to insert a diagnostic
 * printf, be sure to write it to the error output.
 */
#include <arpa/nameser.h>
#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 "sigversion.h"
#include "product.h"
#include "uf.h"

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

#define  TRACE_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  sUfSitename[12];     /* UF sitename to use, space padded to 8 chars, possibly*/
  SINT2 iSweepMode;          /* UF style sweep mode */
  FLT4  fFixedAngle;         /* Fixed angle */
  char  sIrisTaskName[16];   /* IRIS task name, null terminated */
  FLT4  fZCalib;             /* Calibration reflectivity */
};
struct convert_site_list
{
  char sUfSitename[12];      /* UF sitename to use, space padded to 8 chars, possibly */
  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];
  struct uf_data_map   DataTypes[MAX_CONVERT_DATATYPES+1];  /* Must be null terminated*/
  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 */
  char  sRequiredUfData[4];    /* Data required in each ray, zero means N/A */
  
  SINT4 iCurrentDevice;        /* Current device number, -1 means any */
  FLT4  fFixedAngleChange;     /* Max change in fixed angles */
  FLT4  fWaveLength;           /* Override wavelength if not zero */

  SINT2 iSweepOffset;          /* Number added to all UF 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 UF input file */
  char  sIngSitename[16];      /* Ingest sitename to use, space padded */
};
struct uf_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 uf_info
{
  struct uf_sweep_info Sweep[MAX_SWEEPS];
  struct uf_mandatory_header2 FirstMHeader;
  struct uf_field_header2     FirstFHeader;
  FLT4  fMasterStartRange;   /* 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 iSweepMode;         /* UF sweep mode for the whole scan */
  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;
};
struct sweep_scan8
{
  FLT8  fRayStepSum;
  FLT8  fRayFixedSum;
  SINT4 iRayStepCount;
  SINT4 iRayFixedCount;
  struct ymds_time SweepTime;
  char  ipad_end[4];
};

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

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

static UINT1 ScanForInfo
( struct uf_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 ReadMandatoryHeader
( FILE *pStream_a,
  SINT4 *pPosition_a,                       /* Position is updated correctly */
  struct uf_mandatory_header2 *pMHeader_a ); /* Strings are byte swapped correctly */

static UINT1 CompareMandatoryHeaders
( const struct uf_mandatory_header2 *pCurrentMHeader_a,
  const struct uf_mandatory_header2 *pLastMHeader,
  struct uf_info *pInfo_a );

static UINT1 StoreSweepInfo
( struct uf_info *pInfo_a, 
  const struct uf_mandatory_header2 *pLastMHeader_a,  /* Header from last ray in sweep */
  struct sweep_scan8 *pScan_a );

static void ReadOptionalHeader
( FILE *pStream_a,
  SINT4 *pPosition_a,
  struct uf_optional_header *pOpHeader_a );  /* Strings are byte swapped correctly */

static void ProcessOptionalHeader
(const struct uf_optional_header *pOpHeader_a,
 const struct uf_mandatory_header2 *pMHeader_a,
 struct uf_info *pInfo_a );

static void ReadDataHeader
(FILE  *pStream_a,         /* Stream to read from */
 SINT4 *pPosition_a,       /* Position within the record, must be updated */
 struct uf_data_header2 *pDataHeader_a );

static UINT1 CheckDataHeader
( const struct uf_data_header2 *pCurrentDHeader_a,
  const struct uf_mandatory_header2 *pCurrentMHeader,
  struct uf_info *pInfo_a );

static UINT1 CompareDataHeaders
( const struct uf_data_header2 *pCurrentDHeader_a,
  const struct uf_data_header2 *pLastDHeader_a,
  const struct uf_mandatory_header2 *pCurrentMHeader,
  struct uf_info *pInfo_a );

static void MaskFromDHeader
(struct dsp_data_mask *pDataMask_a,
 const struct uf_data_header2 *pDHeader_a,
 UINT1 lVerbose_a );             /* Print error messages */

static void ReadFieldHeader
(FILE  *pStream_a,         /* Stream to read from */
 SINT4 *pPosition_a,       /* Position within the record, must be updated */
 struct uf_field_header2 *pFieldHeader_a,  
 UINT1  iDataType_a );

static void ReadFieldData
(FILE  *pStream_a,              /* Stream to read from */
 SINT4 *pPosition_a,            /* Position within the record, must be updated */
 SINT2 iBinCount_a, 
 SINT2 iDataArray_a[MAX_BINS] ); /* Data returned here */

static UINT1 CompareFieldHeaders
( const struct uf_field_header2 *pCurrentFHeader_a,
  const struct uf_field_header2 *pLastFHeader_a,
  const struct uf_mandatory_header2 *pCurrentMHeader,
  struct uf_info *pInfo_a,
  UINT1 iDataType_a,       /* Data type of current Field Header */
  UINT1 lSameRay_a );

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

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

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

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

static void ScanAndWrite
( const char *sPathStart_a,        /* Start of pathnames for ingest files */
  const struct ingest_header *pIHeader_a,
  const struct uf_info *pInfo_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[65536],
  const struct data_convert *pConvert_a,
  UINT1 iDataType_a,
  SINT2 iScaleFactor_a,
  SINT2 UfNoData_a,
  const struct uf_data_map Maps_a[] );

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

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

static SINT2 iSweepMapping
( SINT2 iUfSweep_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[] );

static void swap2
(void *word);
static void swap4
(void *word);

static void swap2_array
(SINT2 *buf, 
 SINT4 iCount_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, "UfToIris.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, "UfToIris" );

  if (CSetup_c.lVerbose) Lprintf( "Verbose Logging On.\n");

  /* 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 );
    }

  ConvertUf(Command.sOutPath);

  fclose(CSetup_c.iInFile);
   
  exit( EXIT_SUCCESS );
}
/* =============================================================
 * Convert the UF data to IRIS.  Exit if there is an error.
 */
static void ConvertUf
(const char *sOutPath_a)
{
  MESSAGE istatus;
  UINT1 lOk, lError;
  struct uf_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 UF data twice.  The first time to accumulate
   * information about the number of tilts, scan resolution, etc.
   */
  if (TRACE_ON) Lprintf( "ConvertUf 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, "UfToIris" );

  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 UF 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,
			   "UfToIris        "/* Product gen site */,
			   0 /*Reference height */ );
  if (istatus!=SS_NORMAL) sig_signal( istatus );

  RemoveIngestFiles( &IHeader, sStartPath );

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

  struct uf_data_header2 DHeader1, DHeader2;
  struct uf_data_header2 *pCurrentDHeader, *pLastDHeader;

  struct uf_field_header2 FHeader1, FHeader2;
  struct uf_field_header2 *pCurrentFHeader, *pLastFHeader;

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

  /* Loop reading in mandatory headers, and processing the results */
  iRayCount=0;
  iCount = fread( &iRecordLength, 1, 4, CSetup_c.iInFile );
  pCurrentMHeader = &MHeader1;
  pLastMHeader = NULL;
  pCurrentDHeader = &DHeader1;
  pLastDHeader = NULL;
  pCurrentFHeader = &FHeader1;
  pLastFHeader = NULL;
  iPosition = 0;
  while( iCount==4 )
    {
      SINT4 iSeekSize, iMoment;
      if (LITTLE_END) swap4( &iRecordLength );

      /* Read mandatory header */
      ReadMandatoryHeader( CSetup_c.iInFile, &iPosition, pCurrentMHeader );

      /* Entirely ignore the ray if the sweep is in the skip list */
      if (0 == iSweepMapping(pCurrentMHeader->iSweepNumber)) goto SKIP_RAY;

      /* If this is the first ray, then Read and process optional header.
       * All we care about in this is the volume scan start time.
       */
      if ( (0==iRayCount) &&
	   (pCurrentMHeader->iOptionalHeaderPosition != 
	    pCurrentMHeader->iLocalUseHeaderPosition) )
	{
	  struct uf_optional_header OpHeader;
	  ReadOptionalHeader( CSetup_c.iInFile, &iPosition, &OpHeader );
	  ProcessOptionalHeader( &OpHeader, pCurrentMHeader, pInfo_a );
	}
      
      /* Read and process data header, maybe skip local use header first */
      iSeekSize = 2*(pCurrentMHeader->iDataHeaderPosition-1) - iPosition;
      if (iSeekSize !=0) iCount = fseek( CSetup_c.iInFile, iSeekSize, SEEK_CUR );
      iPosition += iSeekSize;
      ReadDataHeader( CSetup_c.iInFile, &iPosition, pCurrentDHeader );
      
      /* Entirely ignore the ray if desired data is missing */
      if (!CheckDataHeader(pCurrentDHeader, pCurrentMHeader, pInfo_a)) goto SKIP_RAY;

      /* Compare and process info from the last 2 mandatory headers */
      lOk = CompareMandatoryHeaders( pCurrentMHeader, pLastMHeader, pInfo_a );
      if (!lOk) return FALSE;
      
      /* Compare and process info from the last 2 data headers */
      lOk = CompareDataHeaders( pCurrentDHeader, pLastDHeader, pCurrentMHeader, pInfo_a );
      if (!lOk) return FALSE;
      
      /* Read and process data headers, maybe skip some local data each time */
      for (iMoment=0; iMoment<pCurrentDHeader->iFieldsThisRecord; iMoment++)
	{
	  UINT1 iDataType;
	  UINT1 lSameDataHeader=FALSE;
	  struct uf_dsi2 *pOneMoment = &pCurrentDHeader->Dsi[iMoment];
	  /* Only read field headers for data we can use */
	  iDataType = iDataFromUfName2(pOneMoment->sDataType, CSetup_c.DataTypes );
	  if (DB_NULL != iDataType)
	    {
	      iSeekSize = 2*(pOneMoment->iFieldHeaderPosition-1) - iPosition;
	      if (iSeekSize !=0) iCount = fseek( CSetup_c.iInFile, iSeekSize, SEEK_CUR );
	      iPosition += iSeekSize;
	      ReadFieldHeader( CSetup_c.iInFile, &iPosition, pCurrentFHeader, iDataType );
	      lOk = CompareFieldHeaders( pCurrentFHeader, pLastFHeader, pCurrentMHeader, 
					 pInfo_a, iDataType, lSameDataHeader );
	      lSameDataHeader = TRUE;
	      if (!lOk) return FALSE;
	    }
	}
      
      /* Rotate pointers */
      pLastMHeader = pCurrentMHeader;
      if (pCurrentMHeader==&MHeader1)
	pCurrentMHeader = &MHeader2;
      else
	pCurrentMHeader = &MHeader1;
      
      pLastDHeader = pCurrentDHeader;
      if (pCurrentDHeader==&DHeader1)
	pCurrentDHeader = &DHeader2;
      else
	pCurrentDHeader = &DHeader1;
      
      pLastFHeader = pCurrentFHeader;
      if (pCurrentFHeader==&FHeader1)
	pCurrentFHeader = &FHeader2;
      else
	pCurrentFHeader = &FHeader1;
      
    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 */
      iSeekSize = iRecordLength + 4 - iPosition;
      if (iSeekSize>0) iCount = fseek( CSetup_c.iInFile, iSeekSize, SEEK_CUR );
      iCount = fread( &iRecordLength, 1, 4, CSetup_c.iInFile );
      iPosition = 0;
      iRayCount++;
    }

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

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


  /* Search to see if we can find the sitename in the site mapping table.  Note
   * that wildcards are allowed.
   */
  memcpy( sSitename9, pInfo_a->FirstMHeader.sSiteName, 8 );
  sSitename9[8] = 0;
  for (iSite=0; iSite<CSetup_c.iSiteCount; iSite++)
    {
      if (sig_fnmatch( CSetup_c.Sites[iSite].sUfSitename, sSitename9 ))
	{
	  pInfo_a->pSite = &CSetup_c.Sites[iSite];
	  goto GOT_SITELIST;
	}
    }
  sprintf( sMessage_c, "ERROR, UF sitename '%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->sUfSitename, sSitename9 ))
	{
	  if (pOneTask->iSweepMode == pInfo_a->FirstMHeader.iSweepMode )
	    {

	      /* 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, UF sitename '%s', mode %d, fixed angle %5.2f not found in config task list\n", 
	       sSitename9, pInfo_a->FirstMHeader.iSweepMode,
	       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;

}
/* ===========================================================
 * Process and compare information from the last two mandatory headers.
 * Returns TRUE if there was no error.
 */
static UINT1 CompareMandatoryHeaders
( const struct uf_mandatory_header2 *pCurrentMHeader_a,
  const struct uf_mandatory_header2 *pLastMHeader_a,
  struct uf_info *pInfo_a )
{
  static struct sweep_scan8 Scan;
  /* If the last header pointer is null, then this is the first ray
   * of the volume scan.
   */
  if (TRACE_ON) Lprintf( "CompareMandatoryHeaders called.\n" );

  if (NULL == pLastMHeader_a)
    {
      /* We must have at least 1 ray! */
      if (!pCurrentMHeader_a)
	{
	  Lprintf( "ERROR, no rays passed the test criteria.\n" );
	  return FALSE;
	}

      pInfo_a->iSweepMode = pCurrentMHeader_a->iSweepMode;
      /* Check for supported sweep modes */
      switch( pInfo_a->iSweepMode)
	{
	case UF_SWEEP_PPI:
	case UF_SWEEP_RHI:
	  break;
	default:
	  sprintf( sMessage_c, "ERROR, sweep mode %d not supported\n", pInfo_a->iSweepMode );
	  Lprintf( sMessage_c );
	  return FALSE;
	  // break;
	}
      if (iSweepMapping(pCurrentMHeader_a->iSweepNumber)!=1)
	{
	  sprintf( sMessage_c, "ERROR, the first ray must map to sweep 1 not %d\n",
		   iSweepMapping(pCurrentMHeader_a->iSweepNumber) );
	  Lprintf( sMessage_c );
	  return FALSE;
	}
      memset( &Scan, 0, sizeof(Scan) );
      /* Set the sweep start time to the time of the first ray */
      Scan.SweepTime.iyear = pCurrentMHeader_a->iYear;
      Scan.SweepTime.imon  = pCurrentMHeader_a->iMonth;
      Scan.SweepTime.iday  = pCurrentMHeader_a->iDay;
      Scan.SweepTime.isec  = iPackTime( pCurrentMHeader_a->iHour,
					pCurrentMHeader_a->iMinute,
					pCurrentMHeader_a->iSecond );

      /* Init average fixed angle */
      Scan.fRayFixedSum = ((FLT8)pCurrentMHeader_a->iFixedAngle)/64.0;
      Scan.iRayFixedCount = 1;

      /* Store away the first header */
      pInfo_a->FirstMHeader = *pCurrentMHeader_a;

      return TRUE;
    }

  /* If the current header point is NULL, then the last ray was the last
   * of the volume scan.
   */

  if (NULL == pCurrentMHeader_a)
    {
      return StoreSweepInfo( pInfo_a, pLastMHeader_a, &Scan );
    }

  /* If we got here, then there are two rays to compare*/
  /* We require the same sweep mode for the whole volume scan */
  if (pInfo_a->iSweepMode != pCurrentMHeader_a->iSweepMode)
    {
      sprintf( sMessage_c, "ERROR, sweep mode change from %d to %d\n", 
	       pInfo_a->iSweepMode, pCurrentMHeader_a->iSweepMode );
      Lprintf( sMessage_c );
      return FALSE;
    }

  if (iSweepMapping(pCurrentMHeader_a->iSweepNumber) != 
      iSweepMapping(pLastMHeader_a->iSweepNumber) )
    {
      UINT1 lOk;
      /* Two rays bridging the sweep change */
      /* Sweeps must be stored sequentially */
      if (1 != (iSweepMapping(pCurrentMHeader_a->iSweepNumber)-
		iSweepMapping(pLastMHeader_a->iSweepNumber)) )
	{
	  sprintf( sMessage_c, "ERROR, sweep skips from %d to %d\n",
		   pLastMHeader_a->iSweepNumber, pCurrentMHeader_a->iSweepNumber );
	  Lprintf( sMessage_c );
	  return FALSE;
	}
      lOk = StoreSweepInfo( pInfo_a, pLastMHeader_a, &Scan );
      if (!lOk) return FALSE;

      /* Zero the sweep scan information */
      memset( &Scan, 0, sizeof(Scan) );
      /* Set the sweep start time to the time of the first ray */
      Scan.SweepTime.iyear = pCurrentMHeader_a->iYear;
      Scan.SweepTime.imon  = pCurrentMHeader_a->iMonth;
      Scan.SweepTime.iday  = pCurrentMHeader_a->iDay;
      Scan.SweepTime.isec  = iPackTime( pCurrentMHeader_a->iHour,
					pCurrentMHeader_a->iMinute,
					pCurrentMHeader_a->iSecond );

      /* Init average fixed angle */
      Scan.fRayFixedSum = ((FLT8)pCurrentMHeader_a->iFixedAngle)/64.0;
      Scan.iRayFixedCount = 1;
      return TRUE;
    }
  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 = ((FLT8)(pCurrentMHeader_a->iFixedAngle - 
			   pLastMHeader_a->iFixedAngle))/64.0;
      fAngleSpan = fmod( fAngleSpan, 360.0 );
      if (fAngleSpan >  180.0) fAngleSpan -= 360.0;
      if (fAngleSpan < -180.0) fAngleSpan += 360.0;
      fAngleSpan = fabs(fAngleSpan);

      if (fAngleSpan > CSetup_c.fFixedAngleChange)
	{
	  sprintf( sMessage_c, "ERROR: fixed angle changed from %5.2f to %5.2f in sweep %d.\n", 
		   ((FLT8)pLastMHeader_a->iFixedAngle)/64.0,
		   ((FLT8)pCurrentMHeader_a->iFixedAngle)/64.0,
		   pCurrentMHeader_a->iSweepNumber );
	  Lprintf( sMessage_c );
	  return FALSE;
	}
      /* Update the scan resolution information */
      switch( pInfo_a->iSweepMode)
	{
	default:
	case UF_SWEEP_PPI:
	  fAngleSpan = ((FLT8)(pCurrentMHeader_a->iAzimuth - 
			       pLastMHeader_a->iAzimuth))/64.0;
	  break;
	case UF_SWEEP_RHI:
	  fAngleSpan = ((FLT8)(pCurrentMHeader_a->iElevation - 
			       pLastMHeader_a->iElevation))/64.0;
	  break;
	}
      fAngleSpan = fmod( fAngleSpan, 360.0 );
      if (fAngleSpan >  180.0) fAngleSpan -= 360.0;
      if (fAngleSpan < -180.0) fAngleSpan += 360.0;
      fAngleSpan = fabs(fAngleSpan);
      /* Ignore sequential rays at the same angle */
      if (fAngleSpan > 0.0)
	{
	  Scan.fRayStepSum += fAngleSpan;
	  Scan.iRayStepCount++;
	}

      /* Update the average fixed angle */
      Scan.fRayFixedSum += ((FLT8)pCurrentMHeader_a->iFixedAngle)/64.0;
      Scan.iRayFixedCount++;

      /* Update the sweep start time to the earliest ray time */
      memset( &RayTime, 0, sizeof(RayTime) );
      RayTime.iyear = pCurrentMHeader_a->iYear;
      RayTime.imon  = pCurrentMHeader_a->iMonth;
      RayTime.iday  = pCurrentMHeader_a->iDay;
      RayTime.isec  = iPackTime( pCurrentMHeader_a->iHour,
				 pCurrentMHeader_a->iMinute,
				 pCurrentMHeader_a->iSecond );
      iRayDiff = iymds_subtract( &RayTime, &Scan.SweepTime );
      if (iRayDiff<0) Scan.SweepTime = RayTime;


    }

  return TRUE;

}
/* ========================================================
 * Store sweep info at the end of a sweep.  Return TRUE if
 * there is no error to stop the conversion.
 */
static UINT1 StoreSweepInfo
( struct uf_info *pInfo_a, 
  const struct uf_mandatory_header2 *pLastMHeader_a,  /* Header from last ray in sweep */
  struct sweep_scan8 *pScan_a )
{
  char sMessage[80];
  struct uf_sweep_info *pSweep;

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

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

  /* Some simple error checking */
  if (iSweepMapping(pLastMHeader_a->iSweepNumber)>MAX_SWEEPS)
    {
      sprintf( sMessage, "WARNING: sweep %d ignored.\n", pLastMHeader_a->iSweepNumber );
      Lprintf( sMessage );
      return TRUE;
    }
  if (pScan_a->iRayStepCount < 1)
    {
      sprintf( sMessage, "ERROR, sweep %d does not have at least 2 rays.\n",
	       pLastMHeader_a->iSweepNumber );
      Lprintf( sMessage );
      return FALSE;
    }
  pInfo_a->iSweepCount = iSweepMapping(pLastMHeader_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;
}
/* =====================================================
 * Read the mandatory header
 */
static void ReadMandatoryHeader
( FILE *pStream_a,
  SINT4 *pPosition_a,                       /* Position is updated correctly */
  struct uf_mandatory_header2 *pMHeader_a )  /* Strings are byte swapped correctly */
{
  int iCount;

  iCount = fread( pMHeader_a, 1, sizeof(*pMHeader_a), pStream_a );
  if (iCount != sizeof(*pMHeader_a)) perror("fread");

  *pPosition_a += sizeof(*pMHeader_a);

  if (LITTLE_END)
    {
      swap2_array( (SINT2*)pMHeader_a, sizeof(*pMHeader_a)/2 );
      swap2( pMHeader_a->sID );
      swap2_array( (SINT2 *)pMHeader_a->sRadarName, 8/2 );
      swap2_array( (SINT2 *)pMHeader_a->sSiteName, 8/2 );
      swap2( pMHeader_a->sTimeZone );
      swap2_array( (SINT2 *)pMHeader_a->sConvertName, 8/2 );
    }

    /* Year may have unneeded century ambiguity */
    if (pMHeader_a->iYear<50)
      pMHeader_a->iYear+=2000;
    else if (pMHeader_a->iYear<1900)
      pMHeader_a->iYear+=1900;

    /* Year may have unneeded century ambiguity */
    if (pMHeader_a->iConvertYear<50)
      pMHeader_a->iConvertYear+=2000;
    else if (pMHeader_a->iConvertYear<1900)
      pMHeader_a->iConvertYear+=1900;

  return;

}
/* =====================================================
 * Print the optional header.  Will modify the input to byte-swap
 * the strings, if needed.
 */
static void ReadOptionalHeader
( FILE *pStream_a,
  SINT4 *pPosition_a,
  struct uf_optional_header *pOpHeader_a )  /* Strings are byte swapped correctly */
{
  int iCount;

  iCount = fread( pOpHeader_a, 1, sizeof(*pOpHeader_a), pStream_a );
  if (iCount != sizeof(*pOpHeader_a)) perror("fread");
  *pPosition_a += sizeof(*pOpHeader_a);

  if (LITTLE_END)
    {
      swap2_array( (SINT2*)pOpHeader_a, sizeof(*pOpHeader_a)/2 );
      swap2_array( (SINT2 *)pOpHeader_a->sProjectName, 8/2 );
      swap2_array( (SINT2 *)pOpHeader_a->sFieldTapeName, 8/2 );
    }
  return;
  
}
/* ===============================================
 * Process the optional header to get the sweep start time, if possible.
 */
static void ProcessOptionalHeader
(const struct uf_optional_header *pOpHeader_a,
 const struct uf_mandatory_header2 *pMHeader_a,
 struct uf_info *pInfo_a )
{
  /* Check for a valid time */
  if ((pOpHeader_a->iVolumeScanHour<24) &&
      (pOpHeader_a->iVolumeScanMinute<60) &&
      (pOpHeader_a->iVolumeScanSecond<61) )
    {
      struct ymds_time RayTime;
      SINT4 iTimeDiff;
      memset( &RayTime, 0, sizeof(RayTime) );
      RayTime.iyear = pMHeader_a->iYear;
      RayTime.imon  = pMHeader_a->iMonth;
      RayTime.iday  = pMHeader_a->iDay;
      RayTime.isec  = iPackTime( pMHeader_a->iHour, pMHeader_a->iMinute,
				 pMHeader_a->iSecond );
      
      pInfo_a->VolumeTime = RayTime;
      pInfo_a->VolumeTime.isec = iPackTime( pOpHeader_a->iVolumeScanHour, 
					    pOpHeader_a->iVolumeScanMinute,
					    pOpHeader_a->iVolumeScanSecond );
      
      /* Since there is no date stored for the volume scan time,
       * we have a date ambiguity.  Set the date to get the nearest
       * to the first ray.
       */
      iTimeDiff = iymds_subtract( &RayTime, &pInfo_a->VolumeTime );
      if (iTimeDiff > 86400/2)  ymds_add_seconds(  86400, &pInfo_a->VolumeTime );
      if (iTimeDiff < -86400/2) ymds_add_seconds( -86400, &pInfo_a->VolumeTime );
      pInfo_a->lVolumeTimeValid = TRUE;
    }
}
/* =====================================================
 * Read and optionally print the data header.  Will modify the input to byte-swap
 * the strings, if needed.
 */
static void ReadDataHeader
(FILE  *pStream_a,         /* Stream to read from */
 SINT4 *pPosition_a,       /* Position within the record, must be updated */
 struct uf_data_header2 *pDataHeader_a )
{
  SINT4 iMoment, iCount;
  char sMessage[200];

  /* Zero the whole structure because used size is variable */
  memset( pDataHeader_a, 0, sizeof(*pDataHeader_a) );
  iCount = fread( pDataHeader_a, 1, 6, pStream_a );
  if (iCount != 6) perror( "fread" );
  *pPosition_a += 6;
  if (LITTLE_END) swap2_array( (SINT2*)pDataHeader_a, 3 );
  
  if (pDataHeader_a->iFieldsThisRecord > UF_MAX_MOMENTS)
    {
      sprintf( sMessage, "Warning: FieldsThisRecord value of %d exceeds max of %d\n",
	      pDataHeader_a->iFieldsThisRecord, UF_MAX_MOMENTS);
      Lprintf( sMessage );
      pDataHeader_a->iFieldsThisRecord = UF_MAX_MOMENTS;
    }

  /* Now loop through all the moments */
  for (iMoment=0; iMoment<pDataHeader_a->iFieldsThisRecord; iMoment++)
    {
      iCount = fread( &pDataHeader_a->Dsi[iMoment], 1, 4, pStream_a );
      if (iCount != 4) perror( "fread" );
      *pPosition_a += 4;
      if (LITTLE_END) swap2( &pDataHeader_a->Dsi[iMoment].iFieldHeaderPosition );
    }

  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 uf_data_header2 *pCurrentDHeader_a,
  const struct uf_mandatory_header2 *pCurrentMHeader_a,  /* Optional */
  struct uf_info *pInfo_a )                              /* Optional */
{
  SINT4 iDataIndex;
  /* No test if not specified */
  if (0 == CSetup_c.sRequiredUfData[0]) return TRUE;

  for (iDataIndex=0; iDataIndex<pCurrentDHeader_a->iFieldsThisRecord; iDataIndex++)
    {
      if (0 == strncmp( pCurrentDHeader_a->Dsi[iDataIndex].sDataType, 
			CSetup_c.sRequiredUfData, 2 )) return TRUE;
    }

  if (pCurrentMHeader_a && pInfo_a)
    {
      pInfo_a->iRaySkipCount++;
      sprintf( sMessage_c, "WARNING: UF data type %s missing from sweep %d, ray %d.\n",
	       CSetup_c.sRequiredUfData, pCurrentMHeader_a->iSweepNumber, 
	       pCurrentMHeader_a->iRayNumber );
      Lprintf( sMessage_c );
    }
  return FALSE;

}
/* ===========================================================
 * Process and compare information from the last two data headers.
 * Returns TRUE if there was no error.
 */
static UINT1 CompareDataHeaders
( const struct uf_data_header2 *pCurrentDHeader_a,
  const struct uf_data_header2 *pLastDHeader_a,
  const struct uf_mandatory_header2 *pCurrentMHeader,
  struct uf_info *pInfo_a )
{

  /* If the last header pointer is null, then this is the first ray
   * of the volume scan.  Initialize the mask.
   */
  if (NULL == pLastDHeader_a)
    {
      MaskFromDHeader( &pInfo_a->DataMask, pCurrentDHeader_a, TRUE );

      if (lDspMaskEmpty( &pInfo_a->DataMask))
	{
	  Lprintf( "ERROR: First ray contains no valid data types.\n" );
	  return FALSE;
	}

      return TRUE;
    }

  /* On later rays, AND in the mask. */
  {
    struct dsp_data_mask DataMask;
    UINT1 lEqual;

    MaskFromDHeader( &DataMask, pCurrentDHeader_a, FALSE );
    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",
		     pCurrentMHeader->iSweepNumber, pCurrentMHeader->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",
			 pCurrentMHeader->iSweepNumber, pCurrentMHeader->iRayNumber );
		Lprintf( sMessage );
		lWarningPrinted=TRUE;
	      }
	  }
      }
  }

  /* This routine does not need to be called with the current header point NULL,
   * at the end of the volume scan.
   */

  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 uf_data_header2 *pDHeader_a,
 UINT1 lVerbose_a )              /* Print error messages */
{
  SINT4 iDataIndex;
  memset( pDataMask_a, 0, sizeof( *pDataMask_a ) );
  for (iDataIndex=0; iDataIndex<pDHeader_a->iFieldsThisRecord; iDataIndex++)
    {
      UINT1 iData;
      iData = iDataFromUfName2( pDHeader_a->Dsi[iDataIndex].sDataType, CSetup_c.DataTypes );
      if (iData != DB_NULL)
	{
	  DspMaskSet( pDataMask_a, iData );
	}
      else
	{
	  if (lVerbose_a)
	    {
	      sprintf( sMessage_c, "WARNING: UF data type '%2.2s' not Converted.\n",
		       pDHeader_a->Dsi[iDataIndex].sDataType );
	      Lprintf( sMessage_c );
	    }
	}
    }
  return;

}
/* =====================================================
 * Read and the field header.  Returns field header correctly byte swapped.
 */
static void ReadFieldHeader
(FILE  *pStream_a,         /* Stream to read from */
 SINT4 *pPosition_a,       /* Position within the record, must be updated */
 struct uf_field_header2 *pFieldHeader_a,  
 UINT1  iDataType_a )
{
  SINT4 iReadSize, iCount;

  iReadSize = 2*19;
  if (DB_VEL == iDataType_a) iReadSize+=sizeof(struct uf_field_ve);

  /* Zero the whole structure because used size is variable */
  memset( pFieldHeader_a, 0, sizeof(*pFieldHeader_a) );
  iCount = fread( pFieldHeader_a, 1, iReadSize, pStream_a );
  if (iCount != iReadSize) perror( "fread" );
  *pPosition_a += iReadSize;
  if (LITTLE_END)
    {
      swap2_array( (SINT2*)pFieldHeader_a, iReadSize/2 );
      swap2( pFieldHeader_a->sThresholdData );
      swap2( pFieldHeader_a->sEditCode );
    }
  
  return;

}
/* =====================================================
 * Optionally Read print the field data.
 */
static void ReadFieldData
(FILE  *pStream_a,              /* Stream to read from */
 SINT4 *pPosition_a,            /* Position within the record, must be updated */
 SINT2 iBinCount_a, 
 SINT2 iDataArray_a[MAX_BINS] )  /* Data returned here */
{
  SINT4 iReadSize, iCount;


  /* Clip the maximum number of bins to display */

  iReadSize = 2*(SINT4)iBinCount_a;
  
  iCount = fread( iDataArray_a, 1, iReadSize, pStream_a );
  if (iCount != iReadSize) perror( "fread" );
  *pPosition_a += iReadSize;

  if (LITTLE_END)
    {
      swap2_array( iDataArray_a, iReadSize/2 );
    }
  
  return;

}

/* ===========================================================
 * Process and compare information from the last two field headers.
 * Returns TRUE if there was no error.
 */
static UINT1 CompareFieldHeaders
( const struct uf_field_header2 *pCurrentFHeader_a,
  const struct uf_field_header2 *pLastFHeader_a,
  const struct uf_mandatory_header2 *pCurrentMHeader_a,
  struct uf_info *pInfo_a,
  UINT1 iDataType_a,          /* Data type of current Field Header */
  UINT1 lSameDataHeader_a )   /* Two field headers are from the same ray */
{
  FLT4 fCurrentStartRange, fBinSpacingFactor;
  SINT4 iCurrentBinCount;

  /* Special processing for velocity field headers because they contain the
   * Nyquist velocity.
   */
  if (DB_VEL == iDataType_a)
    {
      FLT4 fNyquistVelocity;
      fNyquistVelocity = ((FLT4)pCurrentFHeader_a->Fsi.VE.iNyquistVelocity)/
	(FLT4)pCurrentFHeader_a->iScaleFactor;

      /* 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, pCurrentMHeader_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, 
		       pCurrentMHeader_a->iSweepNumber );
	      Lprintf( sMessage_c );
	      return FALSE;
	    }
	}
      else
	{
	  pInfo_a->fNyquistVelocity = fNyquistVelocity;
	  pInfo_a->lNyquistVelocityValid = TRUE;
	}
    }

  switch (iDataType_a)
    {
    case DB_VEL:
    case DB_WIDTH:
      /* The PRT must remain constant only for velocity and width data. 
       * I am assuming that for dual PRF data 
       * scans that the header will reflect the higher PRF, and that the nyquist
       * velocity will reflect the actual value.  I will reverse this out to
       * figure out the dual PRT scheme. */
      if (pInfo_a->iMasterPRT)
	{
	  if (pInfo_a->iMasterPRT != pCurrentFHeader_a->iPRT)
	    {
	      sprintf( sMessage_c, "ERROR, PRT change from %d to %d microseconds in sweep %d.\n", 
		       pInfo_a->iMasterPRT, pCurrentFHeader_a->iPRT,
		       pCurrentMHeader_a->iSweepNumber );
	      Lprintf( sMessage_c );
	      return FALSE;
	    }
	}
      else
	{
	  /* Store it the first time */
	  pInfo_a->iMasterPRT = pCurrentFHeader_a->iPRT;
	}
      break;
    }
  
  /* If the last header pointer is null, then this is the first ray
   * of the volume scan.
   */
  if (NULL == pLastFHeader_a)
    {
      /* Store away the first header */
      pInfo_a->FirstFHeader = *pCurrentFHeader_a;
      pInfo_a->fMasterStartRange  =  (FLT4)pCurrentFHeader_a->iStartRangeKm;
      pInfo_a->fMasterStartRange += ((FLT4)pCurrentFHeader_a->iStartRangeMeters)/1000.0;
      pInfo_a->iMasterBinSpacing  = pCurrentFHeader_a->iBinSpacing;
      pInfo_a->iMaximumBinCount   = pCurrentFHeader_a->iBinCount;

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

  /* If we got here, then there are two rays to compare*/

  /* The bin spacing must remain constant */
  if (pInfo_a->iMasterBinSpacing != pCurrentFHeader_a->iBinSpacing)
    {
      if (CSetup_c.lAllowMixedBinSpacing)
	{
	  fBinSpacingFactor = ( ((FLT4)pCurrentFHeader_a->iBinSpacing)/
				((FLT4)pInfo_a->iMasterBinSpacing) );
	  /* If the bin spacing is finer, then lower the master res */
	  if (fBinSpacingFactor<1.0)
	    {
	      pInfo_a->fMasterStartRange  =  (FLT4)pCurrentFHeader_a->iStartRangeKm;
	      pInfo_a->fMasterStartRange += ((FLT4)pCurrentFHeader_a->iStartRangeMeters)/1000.0;
	      pInfo_a->iMasterBinSpacing  = pCurrentFHeader_a->iBinSpacing;
	      fBinSpacingFactor = 1.0;
	    }
	}
      else
	{
	  sprintf( sMessage_c, "ERROR, Bin spacing changed from %d to %d meters in sweep %d.\n", 
		   pInfo_a->iMasterBinSpacing, pCurrentFHeader_a->iBinSpacing,
		   pCurrentMHeader_a->iSweepNumber );
	  Lprintf( sMessage_c );
	  return FALSE;
	}
    }
  else
    fBinSpacingFactor = 1.0;

  /* Check the number of bins, not done on the first ray */
  iCurrentBinCount = NINT(fBinSpacingFactor) * pCurrentFHeader_a->iBinCount;
  if (iCurrentBinCount > MAX_BINS)
    {
      sprintf( sMessage_c, "ERROR, Bin count %d exceeds max %d supported by IRIS in sweep %d.\n", 
	       iCurrentBinCount, MAX_BINS, pCurrentMHeader_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;


  fCurrentStartRange  =  (FLT4)pCurrentFHeader_a->iStartRangeKm;
  fCurrentStartRange += ((FLT4)pCurrentFHeader_a->iStartRangeMeters)/1000.0;
  /* The start range fudge offset is (N-1)/2 times the fine range res */
  fCurrentStartRange -= ((FLT4)pInfo_a->iMasterBinSpacing) * (fBinSpacingFactor-1.0)/2000.0;
  /* The start range must remain constant, to within 5 meters */
  if (0.005<fabs(pInfo_a->fMasterStartRange-fCurrentStartRange))
    {
      sprintf( sMessage_c, "ERROR: Start range changed from %5.3f to %5.3f km in sweep %d.\n", 
	       pInfo_a->fMasterStartRange, fCurrentStartRange, pCurrentMHeader_a->iSweepNumber );
      Lprintf( sMessage_c );
      return FALSE;
    }


  /* The number of bins must remain constant if in the same ray */
  if (lSameDataHeader_a)
    {
      if (pLastFHeader_a->iBinCount != iCurrentBinCount)
	{
	  sprintf( sMessage_c, "ERROR, Bin count changed from %d to %d in ray %d, sweep %d.\n", 
		   pLastFHeader_a->iBinCount, iCurrentBinCount,
		   pCurrentMHeader_a->iRayNumber, pCurrentMHeader_a->iSweepNumber);
	  Lprintf( sMessage_c );
	  return FALSE;
	}
    }

  /* The wavelength must remain constant */
  if ( (CSetup_c.fWaveLength==0.0) &&
       (pLastFHeader_a->iWaveLength != pCurrentFHeader_a->iWaveLength) )
    {
      sprintf( sMessage_c, "ERROR, Wave length changed from %4.2f to %4.2f cm in sweep %d.\n", 
	       ((FLT4)pLastFHeader_a->iWaveLength)/64.0,
	       ((FLT4)pCurrentFHeader_a->iWaveLength)/64.0,
	       pCurrentMHeader_a->iSweepNumber );
      Lprintf( sMessage_c );
      return FALSE;
    }

  return TRUE;

}

/* ======================================================================
 * 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 uf_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 uf_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 */
  
  /* Volume scan start date and time
   */
  if (pInfo_a->lVolumeTimeValid)
    pIngCon_a->VolumeYmds = pInfo_a->VolumeTime;
  else
    pIngCon_a->VolumeYmds = pInfo_a->Sweep[0].SweepTime;

  /* 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->FirstMHeader.iLatDegrees;
  fAngle += ((FLT8)pInfo_a->FirstMHeader.iLatMinutes)/60.0;
  fAngle += ((FLT8)pInfo_a->FirstMHeader.iLatSeconds)/(60.0*60.0*64.0);
  pIngCon_a->ilat      = iBin4FromFDeg( fAngle );

  fAngle  =  (FLT8)pInfo_a->FirstMHeader.iLonDegrees;
  fAngle += ((FLT8)pInfo_a->FirstMHeader.iLonMinutes)/60.0;
  fAngle += ((FLT8)pInfo_a->FirstMHeader.iLonSeconds)/(60.0*60.0*64.0);
  pIngCon_a->ilon      = iBin4FromFDeg( fAngle );

  pIngCon_a->ialtitude = 100 * (SINT4)pInfo_a->FirstMHeader.iAltitude;
  pIngCon_a->ignd_hgt  = 0;
  pIngCon_a->irad_hgt  = pInfo_a->FirstMHeader.iAltitude - pIngCon_a->ignd_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 uf_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;

  /* 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->fMasterStartRange);
  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);
  switch( pInfo_a->iSweepMode)
    {
    case UF_SWEEP_RHI:
      pTaskCon_a->scan.iscan = SCAN_RHI;
      for (iSweep=0; iSweep<pInfo_a->iSweepCount; iSweep++)
	{
	  pTaskCon_a->scan.u.rhi.istartel = 0;
	  pTaskCon_a->scan.u.rhi.iendel = iBin2FromFDeg(90.0);
	  pTaskCon_a->scan.u.rhi.iazlst[iSweep] = 
	    iBin2FromFDeg(pInfo_a->Sweep[iSweep].fAverageFixedAngle);
	}
      break;
    default:
      pTaskCon_a->scan.iscan = SCAN_CONT;
      for (iSweep=0; iSweep<pInfo_a->iSweepCount; iSweep++)
	{
	  pTaskCon_a->scan.u.ppi.iellst[iSweep] = 
	    iBin2FromFDeg(pInfo_a->Sweep[iSweep].fAverageFixedAngle);
	}
      break;
    }

  /* misc info */
  if (CSetup_c.fWaveLength==0.0)
    pTaskCon_a->misc.ilambda = (32 + 100*pInfo_a->FirstFHeader.iWaveLength)/64;
  else
    pTaskCon_a->misc.ilambda = NINT( 100.0*CSetup_c.fWaveLength );

  switch( pInfo_a->FirstFHeader.iPolarization )
    {
    case UF_POL_HORIZONTAL:
    default:
      pTaskCon_a->misc.ipolar = POL_HORIZ_FIX;
      break;
    case UF_POL_VERTICAL:
      pTaskCon_a->misc.ipolar = POL_VERT_FIX;
      break;
    }
  /* Height truncation is assumed off, set to the maximum range */
  pTaskCon_a->misc.itrunc = pTaskCon_a->rng.ibin_last;
  fAngle = ((FLT4)pInfo_a->FirstFHeader.iBeamWidthH)/64.0;
  pTaskCon_a->misc.iHorzBeamWidth = iBin4FromFDeg( fAngle );
  fAngle = ((FLT4)pInfo_a->FirstFHeader.iBeamWidthV)/64.0;
  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 uf_info *pInfo_a )
{
  /* Calibration info */
  pGparm_a->iz_calib = NINT( 16.0*pInfo_a->pTask->fZCalib );

  return TRUE;
}

struct ingest_file_state
{
  struct ingest_data_header *pIDH;
  UINT4                     *pTable;
  UINT1                     *pData;
  SINT4 iSize;
  SINT4 iChannel;
  SINT4 iDataUsed;
  UINT1 iMapTable[65536];
  UINT1 lMapTableFilled;
};
/* =====================================================================
 * 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 uf_info *pInfo_a )
{
  SINT4 iCount, iPosition, iRecordLength, iRayCount, iSweep;
  struct data_convert Convert;
  struct ingest_file_state Files[NUM_DEFINED_DATA];

  memset( Files, 0, NUM_DEFINED_DATA*sizeof(struct ingest_file_state));
  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 );
  /* Loop reading in mandatory headers, and processing the results */
  iRayCount=0;
  iCount = fread( &iRecordLength, 1, 4, CSetup_c.iInFile );
  iPosition = 0;
  iSweep = 0;
  while( iCount==4 )
    {
      SINT4 iSeekSize, iMoment, ir_ptr;
      SINT2 iIrisSweep;
      BIN4 ii4;
      struct uf_mandatory_header2 MHeader;
      struct uf_data_header2 DHeader;
      struct ray_header RayHeader;

      if (LITTLE_END) swap4( &iRecordLength );

      /* Read mandatory header */
      ReadMandatoryHeader( CSetup_c.iInFile, &iPosition, &MHeader );
      iIrisSweep = iSweepMapping(MHeader.iSweepNumber);
      if (!iIrisSweep) goto SKIP_TO_NEXT_RAY;

      if (iIrisSweep != iSweep)
	{
	  struct ymds_time SweepTimeLocal;
	  if (iSweep!=0) CloseOldFiles( Files, &pInfo_a->DataMask );
	  iSweep=iIrisSweep;
	  SweepTimeLocal = pInfo_a->Sweep[iSweep-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, pIHeader_a, iSweep, &SweepTimeLocal, sPathStart_a );
	}
      /* The bin count must be filled in later */
      ConvertRayHeader( &RayHeader, &MHeader, &pInfo_a->Sweep[iSweep-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\n", iSweep, fPDegFromBin2(ii4), 
		   ir_ptr );
	  Lprintf( sLine );
	}

      /* Read and process data header, maybe skip local use header first */
      iSeekSize = 2*(MHeader.iDataHeaderPosition-1) - iPosition;
      if (iSeekSize !=0) iCount = fseek( CSetup_c.iInFile, iSeekSize, SEEK_CUR );
      iPosition += iSeekSize;
      ReadDataHeader( CSetup_c.iInFile, &iPosition, &DHeader );

      /* Entirely ignore the ray if desired data is missing */
      if (!CheckDataHeader(&DHeader, NULL, NULL)) goto SKIP_TO_NEXT_RAY;

      /* Read and process field headers, maybe skip some data each time */
      for (iMoment=0; iMoment<DHeader.iFieldsThisRecord; iMoment++)
	{
	  struct uf_dsi2 *pOneMoment = &DHeader.Dsi[iMoment];
	  UINT1 iDataType;
	  /* Only read field headers for data we can use */
	  iDataType = iDataFromUfName2(pOneMoment->sDataType, CSetup_c.DataTypes );
	  if ((DB_NULL != iDataType)&&(lDspMaskTest( &pInfo_a->DataMask, iDataType) ))
	    {
	      struct uf_field_header2 FHeader;
	      SINT2 iDataArray[MAX_BINS];
	      struct data_ray Ray;
	      struct ingest_file_state *pFile;
	      SINT4 iRaySize;
	      UINT4 iRepCount;

	      pFile = &Files[iDataType];
	      /* Abort this ray now if the pointer is already filled */
	      if (pFile->pTable[ir_ptr]) goto SKIP_TO_NEXT_RAY;

	      iSeekSize = 2*(pOneMoment->iFieldHeaderPosition-1) - iPosition;
	      if (iSeekSize !=0) iCount = fseek( CSetup_c.iInFile, iSeekSize, SEEK_CUR );
	      iPosition += iSeekSize;
	      ReadFieldHeader( CSetup_c.iInFile, &iPosition, &FHeader, iDataType );
	      /* Get repeat count to make IRIS bins */
	      iRepCount=FHeader.iBinSpacing /pInfo_a->iMasterBinSpacing;
	      RayHeader.ibincount = iRepCount*FHeader.iBinCount;
	      ReadFieldData( CSetup_c.iInFile, &iPosition, FHeader.iBinCount, iDataArray );

	      /* Fill the mapping table, if this is the first time we are called */
	      if (!pFile->lMapTableFilled)
		{
		  FillDataMap( pFile->iMapTable, &Convert, iDataType,
			       FHeader.iScaleFactor, MHeader.iNoDataValue, 
			       CSetup_c.DataTypes );
		  pFile->lMapTableFilled = TRUE;
		}

	      Ray.hdr = RayHeader;
	      ConvertRayData( Ray.data.iData1, iDataArray, pFile->iMapTable, 
			      RayHeader.ibincount, iRepCount );
	      iRaySize = RayHeader.ibincount + sizeof(RayHeader);
	      /* Rays must always be even # bytes */
	      if (iRaySize&1) iRaySize++;
	      
	      pFile->pTable[ir_ptr] = pFile->iDataUsed+1;
	      memcpy( &pFile->pData[pFile->iDataUsed], &Ray, iRaySize );
	      pFile->iDataUsed += iRaySize;
	      pFile->pIDH->iwritn++;
	    }
	}

    SKIP_TO_NEXT_RAY:
      /* Skip to the next ray */
      iSeekSize = iRecordLength + 4 - iPosition;
      if (iSeekSize>0) iCount = fseek( CSetup_c.iInFile, iSeekSize, SEEK_CUR );
      iCount = fread( &iRecordLength, 1, 4, CSetup_c.iInFile );
      iPosition = 0;
      iRayCount++;
    }

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

  return;

}
/* ======================================================
 * 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;
  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 );
	  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 )
{
  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[65536],
  const struct data_convert *pConvert_a,
  UINT1 iDataType_a,
  SINT2 iScaleFactor_a,
  SINT2 UfNoData_a,
  const struct uf_data_map Maps_a[] )
{
  FLT4 fValue, fScale, fScale2, fOffset2;
  UINT4 iIndex;
  UINT2 iValue;

  fScale = (FLT4)iScaleFactor_a;

  fScale2 = 1.0;
  fOffset2 = 0.0;
  if (Maps_a)
    {
      int iIndex=0;
      while( Maps_a[iIndex].sUfDataType[0] )
	{
	  if (Maps_a[iIndex].iIrisDataType == iDataType_a)
	    {
	      fScale2  = Maps_a[iIndex].fScale;
	      fOffset2 = Maps_a[iIndex].fOffset;
	      goto MATCH_FOUND;
	    }
	  iIndex++;
	}
    }

 MATCH_FOUND:
  for (iIndex=0; iIndex<65536; iIndex++)
    {
      fValue = ((FLT4)(SINT2)(UINT2)iIndex)/fScale;
      fValue = (fValue*fScale2) + fOffset2;
      iValue = iuser_to_data( fValue, iDataType_a, pConvert_a );
      iDataMap_a[iIndex] = iValue;
    }
  iDataMap_a[(UINT2)UfNoData_a] = DV_THRESH1;
  return;
}

/* =====================================================================
 * Converts a ray header from UF to IRIS format.
 */
static void ConvertRayHeader
( struct ray_header *pRayHeader_a,
  const struct uf_mandatory_header2 *pMHeader_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 = iBin2FromFDeg(((FLT8)pMHeader_a->iAzimuth)/64.0);
  pRayHeader_a->iel_start = iBin2FromFDeg(((FLT8)pMHeader_a->iElevation)/64.0);
  pRayHeader_a->iaz_end   = pRayHeader_a->iaz_start;
  pRayHeader_a->iel_end   = pRayHeader_a->iel_start;

  memset( &RayTime, 0, sizeof(RayTime) );
  RayTime.iyear = pMHeader_a->iYear;
  RayTime.imon  = pMHeader_a->iMonth;
  RayTime.iday  = pMHeader_a->iDay;
  RayTime.isec  = iPackTime( pMHeader_a->iHour, pMHeader_a->iMinute, pMHeader_a->iSecond );

  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 UF to IRIS format.
 */
static void ConvertRayData
( UINT1 *pIrisData_a,
  const SINT2 *pUfData_a,
  const UINT1 iDataMap_a[65536],
  UINT2 iIrisBinCount_a,
  UINT4 iRepFactor_a )  /* Bin replication factor */
{
  UINT4 iIrisBin, iUfBin;
  iUfBin=0;
  for (iIrisBin=0; iIrisBin<iIrisBinCount_a; iIrisBin++)
    {
      pIrisData_a[iIrisBin] = iDataMap_a[(UINT2)pUfData_a[iUfBin]];
      if (0==((iIrisBin+1)%iRepFactor_a)) iUfBin++;
    }
  /* Odd rays always padded by a zero */
  if (1&iIrisBinCount_a) pIrisData_a[iIrisBinCount_a] = 0;
  
  return;
}
/* ====================================================
 * Convert a UF origin 1 sweep number to an IRIS origin 1
 * sweep number.  Skipped sweeps are zero.
 */
static SINT2 iSweepMapping
( SINT2 iUfSweep_a )
{
  SINT2 iSweepNumber;

  /* Add an offset to all the sweeps */
  iSweepNumber = iUfSweep_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, "UfToIris 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];
  char sline[81], *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;
    }
  
  /* 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, "UfToIris.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, pCommand_a->iDevice );
      if (lerror) fprintf( stderr, "Syntax error on config 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.
 *
 * 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 DATA command */
  /* Format is something like: DATA  <UFdatatype> <IRISdatatype> scale offset*/
  if ( 0 == strcmp( "DATA", stokens[0] ) )
    {
      struct uf_data_map *pOneData;
      char *sptr;
      SINT2 iIrisDataType;
      if (!lDeviceValid) return FALSE;

      if ( icount < 5 ) return TRUE;
      if ( pConfig_a->iDataCount >= MAX_CONVERT_DATATYPES) return TRUE;
      pOneData = &pConfig_a->DataTypes[pConfig_a->iDataCount];

      /* UF data names are exactly 2 characters long. */
      strncpy( pOneData->sUfDataType, stokens[1], 2 );
      pOneData->sUfDataType[2] = 0;

      iIrisDataType = iDataFromName6( stokens[2] );
      if (iIrisDataType == -1) return TRUE;

      pOneData->fScale = strtod( stokens[3], &sptr );
      if (sptr == stokens[3]) return TRUE;
      if (pOneData->fScale == 0.0) return TRUE;  /* Cannot be zero */

      pOneData->fOffset = strtod( stokens[4], &sptr );
      if (sptr == stokens[4]) return TRUE;

      pOneData->iIrisDataType = (UINT1)iIrisDataType;
      pConfig_a->iDataCount++;
      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;
    }

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

      if ( icount < 2 ) return TRUE;

      pConfig_a->fFixedAngleChange = strtod( stokens[1], &sptr );
      if (sptr == stokens[1]) return TRUE;
      if (pConfig_a->fFixedAngleChange < 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;
    }

  /* Data type to require: RAY_REQUIRES VR */
  if ( 0 == strcmp( "RAY_REQUIRES", stokens[0] ) )
    {
      if (!lDeviceValid) return FALSE;

      if ( icount < 2 ) return TRUE;

      strncpy( pConfig_a->sRequiredUfData, stokens[1], 2 );
      pConfig_a->sRequiredUfData[2]=0;
      return FALSE;
    }

  /* See if the line contains the SITE command */
  /* Format is something like: SITE  "<UFsitename>" "<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];
      /* Warning: UF sitenames may be either space padded or null terminated, depending
       * on the convertor software.  To accomodate this, you will need to specify
       * trailing spaces or not in the config file, be sure to enclose in double 
       * quotes.
       */
      strncpy( pOneSite->sUfSitename, stokens[1], 8 );
      pOneSite->sUfSitename[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  "<UFsitename>" PPI 0.5 PPI_VOL <fMinZ>*/
  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];
      /* Warning: UF sitenames may be either space padded or null terminated, depending
       * on the convertor software.  To accomodate this, you will need to specify
       * trailing spaces or not in the config file, be sure to enclose in double 
       * quotes.
       */
      strncpy( pOneTask->sUfSitename, stokens[1], 8 );
      pOneTask->sUfSitename[8] = 0;


      if (0 == strcmp( "PPI", stokens[2]))
	pOneTask->iSweepMode = UF_SWEEP_PPI;
      else if (0 == strcmp( "RHI", stokens[2]))
	pOneTask->iSweepMode = UF_SWEEP_RHI;
      else
	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 full_time_spec Time;
  char sTimeBuf[TIMENAME_SIZE];
  GetTimeFull( &Time, TIME_FLG_LOCAL );

  if (CSetup_c.lLogToTerm)
    {
      shhmmssddmonyyyy_r( &Time.Ymds, sTimeBuf );
      fprintf( stderr, "%s %s:", sTimeBuf, Time.sRecordTZName );
      fputs( sString_a, stderr );
    }

  if (CSetup_c.lLogToFile)
    {
      shhmmssddmonyyyy_r( &Time.Ymds, sTimeBuf );
      fprintf( CSetup_c.iLogFile, "%s %s:", sTimeBuf, Time.sRecordTZName );
      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);
}

/* =========================
 * Byte swapping functions.
 */
static void swap2(void *word)
{
  unsigned char *byte;
  unsigned char temp;
  byte    = (unsigned char *)word;
  temp    = byte[0];
  byte[0] = byte[1];
  byte[1] = temp;
}

static void swap4(void *word)
{
  unsigned char *byte;
  unsigned char temp;
  byte    = (unsigned char *)word;
  temp    = byte[0];
  byte[0] = byte[3];
  byte[3] = temp;
  temp    = byte[1];
  byte[1] = byte[2];
  byte[2] = temp;
}

static void swap2_array
(SINT2 *buf, 
 SINT4 iCount_a )
{
  short *end_addr;
  end_addr = buf + iCount_a;
  while (buf < end_addr) 
    {
      swap2(buf);
      buf++;
    }
  return;
}
