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

/* This program is a pipe which takes an IRIS product in the standard input
 * and writes a modified product out the standard output.
 *
 * Note if you are modifying this program, and wish to insert a diagnostic
 * printf, be sure to write it to the error output.
 */
#ifndef LINUX
#include <arpa/nameser.h>  /* Defines BYTE_ORDER on HP and SGI */
#endif  /* #ifndef LINUX */
#include <locale.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.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 UF_MAX_BUFFER_LENGTH (40000)
/* Information prepared for each data moment.
 */
struct moment_info
{
  SINT4 idtype;
  UINT1 lUfValid;            /* Data will be converted to UF */
  UINT1 lReflectivity;
  struct ingest_data_header idhdr;
  SINT2 iDataMap[65536];     /* Mapping table to convert to UF */
};

#define MAX_CONVERT_DATATYPES (20)
/* This holds information to customize the convertor */
struct convert_config
{
  struct uf_data_map   DataTypes[MAX_CONVERT_DATATYPES+1];  /* Must be null terminated*/
  SINT4 iDataCount;
  UINT1 lLogToFile;
  UINT1 lLogToTerm;
  UINT1 lVerbose;              /* Log more details */
  UINT1 lAppendToLog;          /* Append to previous log file */
  UINT1 lAllowSingleSweep;     /* Allow single sweep RAW products */
  char  ipad5x3[3];
  FILE *iLogFile;              /* Open file descriptor to log file */
  FLT4  fHorizontalBeamwidth;  /* Antenna beamwidth in degrees */
  FLT4  fVerticalBeamwidth;
  FLT4  fMinReflectivity;      /* Minimum refl. All lower values are thresholded */
  char  sProjectName[8];       /* Project name, space padded */
};

struct convert_config CConfig_c;

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

static void Usage( void );

static MESSAGE ProcessProductHeader
(const struct product_hdr *pheader_a);

static MESSAGE Process2Records
(const union tape_record Records_a[2] );

static MESSAGE ConvertRawToUf
  (const struct raw_product *pRaw_a,
   SINT2 iUfBuffer_a[UF_MAX_BUFFER_LENGTH/2] );

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

static SINT2 iDataScaleUf
( UINT4 idata_a );

static void FillMandatoryHeader
( struct uf_mandatory_header2 *pMHeader_a,
  SINT4 iRecordNumber_a,                      /* Record number, origin 0 */
  SINT4 iRayNumber_a,                         /* Ray number origin 0 */
  const struct ingest_header *pIHeader_a,     /* Ingest header */
  const struct ingest_data_header *pDHeader_a,/* Ingest data header */
  const struct ray_header *pRHeader_a         /* Ray header */
  );

static void FillOptionalHeader
( struct uf_optional_header *pOpHeader_a,
  const struct ingest_header *pIHeader_a      /* Ingest header */
  );

static void FillFieldHeader
( struct uf_field_header2 *pDHeader_a,
  const struct ingest_header *pIHeader_a,     /* Ingest header */
  SINT4 iDType_a,                            /* Data type */
  const struct data_convert *pConvert_a,     /* Data convert structure */
  const struct ray_header   *pRHeader_a,     /* Ray header */
  SINT4 *pOffset_a                           /* Offset is updated */
  );

static void FillDataMap
( struct moment_info *pMInfo_a,
  const struct data_convert *pConvert_a,
const struct uf_data_map Maps_a[] );

static void FillFieldData
( SINT2 *iUfBuffer_a,
  const struct data_ray *pRay_a, 
  const struct moment_info *pMInfo_a,
  SINT4 *pOffset_a );

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

static void swap2_array
(SINT2 *buf, 
 SINT4 iCount_a );

static void LoadSetup
( struct convert_config *pCSetup_a );

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

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

static void Lprintf
( const char *sString_a );

/* ===============================================================
 * Main program starts here.
 */
int main(int iArgc, char *sArgv[])
{
  union  tape_record Record[2];
  struct raw_product *pInRaw;
  SINT2  *pOutUf;
  MESSAGE istatus;
  SINT4 iCount, iWantCount ;
  char sLog[200], sProdTime[TIMENAME_SIZE];

  setlocale( LC_ALL, "" ) ;

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

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

  /* Open the logging file, if any */
  if (CConfig_c.lLogToFile)
    {
      char *sptr;
      char sLogPath[PATHNAME_SIZE];
      sptr = getenv( "IRIS_LOG" );
      if (sptr)
	{
	  strncpy( sLogPath, sptr, PATHNAME_SIZE-20 );
	  sLogPath[PATHNAME_SIZE-20] = 0;
	}
      else
	strcpy( sLogPath, "/tmp/" );
      strcat( sLogPath, "IrisToUf.log" );
      if (CConfig_c.lAppendToLog)
	CConfig_c.iLogFile = fopen( sLogPath, "a" );
      else
	CConfig_c.iLogFile = fopen( sLogPath, "w" );
      if (NULL == CConfig_c.iLogFile)
	{
	  fprintf( stderr, "Can not open log file: '%s'\n", sLogPath );
	  exit(EXIT_FAILURE);
	}
    }

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

  if (CConfig_c.lVerbose)
    {
      struct full_time_spec Time;
      GetTimeFull( &Time, TIME_FLG_LOCAL );
      
      sprintf( sLog, "Verbose logging on, Version=%s%s%s, TZ=%s\n", IRIS_VERSION,
	       IRIS_MINOREV, IRIS_PATCHREV, Time.sRecordTZName );
      Lprintf( sLog );
    }
    
  /* Read in the first two tape records */
  iCount =  fread( Record, 1, 2*TAPE_RECORD_LEN, stdin );

  /* Allow header processing if size is large enough to hold a header.
   * This allows a helpful error message for the wrong data type.
   */
  if (iCount < PRODUCT_HDR_SIZE) 
    {
      sprintf( sLog, "Bad read, got%d, wanted%d\n", iCount, 2*TAPE_RECORD_LEN );
      Lprintf( sLog );
      exit(EXIT_FAILURE);
    }

  istatus = ProcessProductHeader( &Record[0].PHeader );
  if (istatus != SS_NORMAL) exit(EXIT_FAILURE);

  /* Now insist on getting the full first 2 records */
  if (iCount != 2*TAPE_RECORD_LEN) 
    {
      sprintf( sLog, "Bad read, got%d, wanted%d\n", iCount, 2*TAPE_RECORD_LEN );
      Lprintf( sLog );
      exit(EXIT_FAILURE);
    }

  istatus = Process2Records( Record );
  if (istatus != SS_NORMAL) exit(EXIT_FAILURE);

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

  iWantCount = Record[0].PHeader.hdr.ibytes-(2*TAPE_RECORD_LEN);
  iCount =  fread( &pInRaw->Record[2], 1, iWantCount, stdin );
  if (iCount != iWantCount)
    {
      sprintf( sLog, "Bad read2, got%d, wanted%d\n", iCount, iWantCount);
      Lprintf( sLog );
      exit(EXIT_FAILURE);
    }
  
  istatus = ConvertRawToUf( pInRaw, pOutUf );
  if (istatus != SS_NORMAL) exit(EXIT_FAILURE);

  shhmmssddmonyyyy_r( &Record[0].PHeader.pcf.ing_time, sProdTime );
  sprintf( sLog, "Converted RAW product: %s, Date: %s\n", 
	   nullify(Record[0].PHeader.pcf.spname,12), sProdTime );
  Lprintf( sLog );
  
  exit( EXIT_SUCCESS );
}
/* ======================================================================
 * Function to process the command line arguments.
 */
static void ProcessCommandLine
(int iArgc_a, 
 char *sArgv_a[] )
{

  int iArg;

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

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

  /* Check to make sure there is at least one convertable data type */
  {
    SINT4 ii, iIrisMoments, iUfMoments;
    struct dsp_data_mask DataMask;
    GetRawPsiMask( &DataMask, &pheader_a->pcf.psi.raw );
    iIrisMoments=0; iUfMoments=0;
    for( ii=0 ; ii < NUM_DEFINED_DATA ; ii++ )
      {
	if( lDspMaskTest( &DataMask, ii ) && (iIrisMoments<MAX_MOMENTS) )
	  {
	    char sUfName[3];
	    sUfName2FromData(sUfName,ii,CConfig_c.DataTypes);
	    if ( strcmp( "??", sUfName ) )iUfMoments++;
	    iIrisMoments++ ;
	  }
      }
    if (iUfMoments==0)
      {
	Lprintf( "Sorry, recorded data not convertable to UF\n" );
	return USER_ERROR_SIG;
      }
  }

  return SS_NORMAL;
}
/* =======================================================================
 * Check for some consistency based on the first two tape records.
 * Only full volume raw products are supported.
 */
static MESSAGE Process2Records
(const union tape_record Records_a[2] )
{
  char sLog[200];
  const struct product_hdr   *pPHeader = &Records_a[0].PHeader;
  const struct ingest_header *pIHeader = &Records_a[1].IHeader;

  /* Special alerts if this is a sweep-by-sweep product, and there are
   * more than one sweeps in the task.
   */
  if ((pPHeader->pcf.psi.raw.iflags & RAW_FLG_SWEEP)&&
      (pIHeader->tcf.scan.isweeps>1) )
    {
      if (CConfig_c.lAllowSingleSweep)
	{
	  sprintf( sLog, "Warning, input is a single sweep from a %d sweep task.\n",
		   pIHeader->tcf.scan.isweeps );
	  Lprintf( sLog );
	}
      else
	{
	  Lprintf( "Sorry, sweep-by-sweep RAW products are not supported.\n" );
	  return USER_ERROR_SIG;
	}
    }

  return SS_NORMAL;
}


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

static SINT4  irec_c ;		/* Record number */
static SINT4  ioff_c ;		/* Offset within record */
static UINT1 *prod_c ;		/* Pointer to product */

#define PRODPTR (UINT1 *)(prod_c + (irec_c * TAPE_RECORD_LEN) + ioff_c)

/* ==============================================================
 * Print information about a RAW product.  Entered with a pointer
 * to the beginning (first 6144-byte record) of the full product.
 */
static MESSAGE ConvertRawToUf
  (const struct raw_product *pRaw_a,
   SINT2 iUfBuffer_a[UF_MAX_BUFFER_LENGTH/2] )
{
#define RAYSIZE MAX_BINS
  UINT2 lmissing ; 
  SINT4 ii, iIrisMoments, iUfMoments, irptr, isweep, iSweepCount ;
  SINT4 iUfRecord;
  struct moment_info MInfo[MAX_MOMENTS];
  struct data_convert Convert;
  const struct product_hdr *pPHead;
  const struct ingest_header *inghdr ;

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

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

  /* Count up the number of parameters that were recorded.  This is
   * needed in order to skip through the sweeps properly.  Build filter
   * for what will be converted.
   */
  PendFillDataConvert( &Convert, &pPHead->end, CONVERT_FLAGS_DEFAULT );

  iIrisMoments=0; iUfMoments=0;
  for( ii=0 ; ii <NUM_DEFINED_DATA ; ii++ )
    {
      if( lDspMaskTest( &inghdr->tcf.dsp.DataMask, ii ) && (iIrisMoments<MAX_MOMENTS) )
	{
	  char sUfName[3];
	  MInfo[iIrisMoments].idtype = ii ;
	  sUfName2FromData(sUfName,ii,CConfig_c.DataTypes);
	  MInfo[iIrisMoments].lUfValid = strcmp( "??", sUfName );
	  if (MInfo[iIrisMoments].lUfValid)
	    {
	      FillDataMap( &MInfo[iIrisMoments], &Convert, CConfig_c.DataTypes );
	      if ((ii == DB_DBT)||(ii == DB_DBZ)||(ii == DB_DBT2)||(ii == DB_DBZ2))
		MInfo[iIrisMoments].lReflectivity = TRUE;
	      else
		MInfo[iIrisMoments].lReflectivity = FALSE;
	      iUfMoments++;
	    }
	  iIrisMoments++ ;
	}
    }

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

  if (pPHead->pcf.psi.raw.iflags & RAW_FLG_SWEEP)
    iSweepCount = 1;
  else
    iSweepCount = inghdr->tcf.scan.isweeps;
  for( isweep = 1 ; isweep <= iSweepCount; isweep++ )
    {
      SINT4 iIrisMoment ;
      
      /* Extract the INGEST data file headers for each of the parameters
       * that were recorded.  The headers appear sequentially in the
       * first record of each sweep.
       */
      for( iIrisMoment=0 ; iIrisMoment < iIrisMoments ; iIrisMoment++ )
	{
	  get_raw_bytes( (SINT2 *)&MInfo[iIrisMoment].idhdr, INGEST_DATA_HEADER_SIZE ) ;
	}
      if (CConfig_c.lVerbose)
	{
	  char sLog[200], sTimeBuf[TIMENAME_SIZE];
	  sprintf( sLog, "Sweep %d began at: %s\n", MInfo[0].idhdr.isweep,
		   shhmmssddmonyyyy_r( &MInfo[0].idhdr.time, sTimeBuf ) ) ;
	  Lprintf( sLog );
	}
      
      /* Read the data from each of the angles, and for each of the
       *  parameters that were recorded.
       */
      for( irptr=0 ; irptr < inghdr->icf.irtotl ; irptr++ )
	{
	  SINT4 iOffset = 0;
	  UINT1 lAllDataThere = TRUE;
	  SINT4 iUfMomentsFound = 0;
	  struct uf_mandatory_header2 *pMHeader = (struct uf_mandatory_header2 *)iUfBuffer_a;
	  for( iIrisMoment=0 ; iIrisMoment < iIrisMoments ; iIrisMoment++ )
	    {
	      
	      struct data_ray ray ;
	      SINT4 inlen, ioutlen ;
	      
	      uncompress_cowords
		(get_raw_bytes,
		 pPHead->hdr.ibytes - ((irec_c * TAPE_RECORD_LEN) + ioff_c),
		 &inlen, (SINT2 *)&ray, sizeof(ray)/2, &ioutlen ) ;
	      
	      lmissing = (ioutlen == 0) ;
	      if( MInfo[iIrisMoment].lUfValid )
		{
		  if (!lmissing)
		    {
		      struct uf_data_header2 *pDataHeader; 
		      char sUfName[3];
		      /* Fill header on the first valid ray */
		      if (iUfMomentsFound==0)
			{
			  FillMandatoryHeader( pMHeader, iUfRecord, irptr, inghdr,
					       &MInfo[iIrisMoment].idhdr, &ray.hdr );
			  iOffset+=45;
			  pMHeader->iOptionalHeaderPosition = 1+iOffset;
			  /* Optional header is only on the first record */
			  if (iUfRecord==0)
			    {
			      FillOptionalHeader( (struct uf_optional_header*)&iUfBuffer_a[iOffset], inghdr );
			      iOffset+=14;
			    }
			  /* We do not use the Local use header */
			  pMHeader->iLocalUseHeaderPosition = 1+iOffset; 
			  pMHeader->iDataHeaderPosition     = 1+iOffset; 

			  pDataHeader = (struct uf_data_header2*)
			    &iUfBuffer_a[pMHeader->iDataHeaderPosition-1];
			  /* Fill Data header, most already done */
			  pDataHeader->iFieldsThisRay    = iUfMoments;
			  pDataHeader->iRecordsThisRay   = 1;
			  pDataHeader->iFieldsThisRecord = iUfMoments;
			  iOffset+= 3+(2*iUfMoments);
			}
		      /* Fill in current slot in the DataHeader */
		      sUfName2FromData( sUfName, MInfo[iIrisMoment].idtype, CConfig_c.DataTypes );
 		      memcpy( pDataHeader->Dsi[iUfMomentsFound].sDataType, sUfName, 2 );
		      if (LITTLE_END) swap2(pDataHeader->Dsi[iUfMomentsFound].sDataType);
 		      pDataHeader->Dsi[iUfMomentsFound].iFieldHeaderPosition = iOffset+1;

		      FillFieldHeader( (struct uf_field_header2*)&iUfBuffer_a[iOffset],
				       inghdr, MInfo[iIrisMoment].idtype, &Convert, 
				       &ray.hdr, &iOffset );
		      FillFieldData( &iUfBuffer_a[iOffset], &ray, 
				     &MInfo[iIrisMoment], &iOffset );
		      iUfMomentsFound++;
		    }
		  else
		    lAllDataThere = FALSE;
		}
	    }
	  /* Write data out to the standard output only if all data was there.*/
	  if (lAllDataThere)
	    {
	      SINT4 iRecordLength, iRecordLengthSwap, iWriteCount;
	      /* Fill in one larger than the number of words used */
	      pMHeader->iRecordSize = iOffset+1;
	      iRecordLength = 2 * (iOffset+1);
	      iRecordLengthSwap = iRecordLength;
	      if (LITTLE_END) swap4( &iRecordLengthSwap );
	      iWriteCount = fwrite( &iRecordLengthSwap, 4, 1, stdout );
	      if (iWriteCount!=1) Lprintf( "Write error\n" ) ;
	      if (LITTLE_END) swap2_array( iUfBuffer_a, iRecordLength );
	      iWriteCount = fwrite( iUfBuffer_a, iRecordLength, 1, stdout );
	      if (iWriteCount!=1) Lprintf( "Write error\n" ) ;
	      iWriteCount = fwrite( &iRecordLengthSwap, 4, 1, stdout );
	      if (iWriteCount!=1) Lprintf( "Write error\n" ) ;
	      iUfRecord++;
	    }

	}  /* end  for( irptr=0 ... */
      
      /* Done with this sweep.  Discard the remainder of this block, if
       * any.
       */
      if( ioff_c ) { ioff_c = 0 ; irec_c++ ; }

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

/* ==================================================
 * Co-Routine to read the next run of bytes from the raw product file,
 * skipping and checking the record headers as we go.
 */
static void get_raw_bytes( SINT2 *buf_a, SINT4 icnt_a )
{
  SINT4 icnt, iremain = icnt_a ; UINT1 *pbuf = (UINT1 *)buf_a ;

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

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

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

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

/* ================================================================
 * Get the UF format's scale factor for the data type
 */
static SINT2 iDataScaleUf( UINT4 idata_a )
{
  SINT2 iScale;

  switch( idata_a )
    {
    default:
    case DB_DBT     :
    case DB_DBT2    :
    case DB_DBZ     :
    case DB_DBZ2    :
    case DB_DBZC    :
    case DB_DBZC2   :
    case DB_KDP     :
    case DB_KDP2    :
    case DB_LDRH    :
    case DB_LDRH2   :
    case DB_LDRV    :
    case DB_LDRV2   :
    case DB_PHIDP   :
    case DB_PHIDP2  :
    case DB_VEL     :
    case DB_VEL2    :
    case DB_VELC    :
    case DB_VELC2   :
    case DB_WIDTH   :
    case DB_WIDTH2  :
    case DB_ZDR     :
    case DB_ZDR2    :
      iScale = 100; break;

    case DB_RHOHV   :
    case DB_RHOHV2  :
      iScale = 1000; break;

    }
  return iScale;
}
/* =====================================================
 * Fill in the mandatory heaer
 */
static void FillMandatoryHeader
( struct uf_mandatory_header2 *pMHeader_a,
  SINT4 iRecordNumber_a,                      /* Record number, origin 0 */
  SINT4 iRayNumber_a,                         /* Ray number origin 0 */
  const struct ingest_header *pIHeader_a,     /* Ingest header */
  const struct ingest_data_header *pDHeader_a,/* Ingest data header */
  const struct ray_header *pRHeader_a         /* Ray header */
)
{
  memset( pMHeader_a, 0, sizeof(*pMHeader_a) );

  memcpy( pMHeader_a->sID, "UF", 2 );
  if (LITTLE_END) swap2( pMHeader_a->sID );
  /* Header positions are filled in later */
  pMHeader_a->iRecordNumber = iRecordNumber_a+1;
  pMHeader_a->iVolumeNumber = 1;
  pMHeader_a->iRayNumber = iRayNumber_a+1;
  pMHeader_a->iRecordInRay = 1;
  pMHeader_a->iSweepNumber = pDHeader_a->isweep;
  memcpy( pMHeader_a->sRadarName, pIHeader_a->icf.sSitename, 8 );
  if (LITTLE_END) swap2_array( (SINT2 *)pMHeader_a->sRadarName, 8/2 );
  memcpy( pMHeader_a->sSiteName, pIHeader_a->icf.sSitename, 8 );
  if (LITTLE_END) swap2_array( (SINT2 *)pMHeader_a->sSiteName, 8/2 );
  /* Convert signed degrees to D:M:S, all fields have the same sign */
  {
    SINT2 iDegree, iMinute;
    FLT8  fAngle, fSecond;
    fAngle = fDegFromBin4( pIHeader_a->icf.ilat );
    iDegree = (int)fAngle;
    iMinute = (int)(60.0*(fAngle -(FLT8)iDegree));
    fSecond = 60.0*((60.0*(fAngle -(FLT8)iDegree))-(FLT8)iMinute);
    pMHeader_a->iLatDegrees = iDegree;
    pMHeader_a->iLatMinutes = iMinute;
    pMHeader_a->iLatSeconds = (SINT2)(fSecond*64.0);
    
    fAngle = fDegFromBin4( pIHeader_a->icf.ilon );
    iDegree = (int)fAngle;
    iMinute = (int)(60.0*(fAngle -(FLT8)iDegree));
    fSecond = 60.0*((60.0*(fAngle -(FLT8)iDegree))-(FLT8)iMinute);
    pMHeader_a->iLonDegrees = iDegree;
    pMHeader_a->iLonMinutes = iMinute;
    pMHeader_a->iLonSeconds = (SINT2)(fSecond*64.0);
  }
  pMHeader_a->iAltitude = pIHeader_a->icf.ignd_hgt + pIHeader_a->icf.irad_hgt;
  {
    struct ymds_time RayTime;
    UINT2 iDays;
    SINT4 iTimeOffset;
    RayTime = pDHeader_a->time;
    /* Convert all times to UT */
    iTimeOffset = ((SINT4)pRHeader_a->itime) + 60*(SINT4)pIHeader_a->icf.iMinutesWest;
    ymds_add_seconds( iTimeOffset, &RayTime );
    pMHeader_a->iYear  = RayTime.iyear;
    pMHeader_a->iMonth = RayTime.imon;
    pMHeader_a->iDay   = RayTime.iday;
    unpacktime( RayTime.isec, &iDays, 
		(UINT2*)&pMHeader_a->iHour,
		(UINT2*)&pMHeader_a->iMinute,
		(UINT2*)&pMHeader_a->iSecond );
  }
  /* Needs more work here */
  memcpy( pMHeader_a->sTimeZone, "UT", 2 );
  if (LITTLE_END) swap2( pMHeader_a->sTimeZone );
  {
    SINT2 iAngleDiff;
    FLT4  fAngle;
    iAngleDiff = ((SINT2)(pRHeader_a->iaz_end - pRHeader_a->iaz_start))/2;
    fAngle = fDegFromBin2( pRHeader_a->iaz_start+iAngleDiff );
    pMHeader_a->iAzimuth= NINT(fAngle*64.0);
    iAngleDiff = ((SINT2)(pRHeader_a->iel_end - pRHeader_a->iel_start))/2;
    fAngle = fElDegFromBin2( pRHeader_a->iel_start+iAngleDiff );
    pMHeader_a->iElevation= NINT(fAngle*64.0);
    /* Needs more work here */
    switch( pIHeader_a->tcf.scan.iscan)
      {
      default:
      case TASK_SCAN_CONT: 
      case TASK_SCAN_PPI: 
	pMHeader_a->iSweepMode = UF_SWEEP_PPI; break;
      case TASK_SCAN_RHI: 
	pMHeader_a->iSweepMode = UF_SWEEP_RHI; break;
      case TASK_SCAN_FILE: 
      case TASK_SCAN_MANUAL: 
	pMHeader_a->iSweepMode = UF_SWEEP_MANUAL; break;
      }
    fAngle = fElDegFromBin2( pDHeader_a->iangle );
    pMHeader_a->iFixedAngle = NINT(fAngle*64.0);
    fAngle = fDegFromBin2( pIHeader_a->tcf.scan.iscan_speed );

    /* Units in 1/64 of a degree/second? */
    pMHeader_a->iSweepRate = NINT(fAngle*64.0);     
  }
  {
    time_t IntTime;
    struct tm Tm;
    IntTime = time(NULL);
    gmtime_r(&IntTime, &Tm);
    pMHeader_a->iConvertYear = Tm.tm_year+1900;       
    pMHeader_a->iConvertMonth= Tm.tm_mon+1;
    pMHeader_a->iConvertDay  = Tm.tm_mday;
  }
  {
    char sString[40];
    strcpy( sString, "IRIS" );
    strcat( sString, IRIS_VERSION );
    memcpy( pMHeader_a->sConvertName, sString, 8 );
    if (LITTLE_END) swap2_array( (SINT2 *)pMHeader_a->sConvertName, 8/2 );
  }
  pMHeader_a->iNoDataValue = (SINT2)UF_NO_DATA;

  return;

}
/* =====================================================
 * Fill in the optional header
 */
static void FillOptionalHeader
( struct uf_optional_header *pOpHeader_a,
  const struct ingest_header *pIHeader_a )     /* Ingest header */
{
  UINT2 iDay, iHour, iMinute, iSecond;
  SINT4 iSecondsWest;
  struct ymds_time VolumeTime;

  memset( pOpHeader_a, 0, sizeof(*pOpHeader_a) );
  
  memcpy( pOpHeader_a->sProjectName, CConfig_c.sProjectName, 8 );
  if (LITTLE_END) swap2_array( (SINT2*)pOpHeader_a->sProjectName, 8/2 );
  pOpHeader_a->iBaselineAzimuth = (SINT2)UF_NO_DATA;
  pOpHeader_a->iBaselineElevation = (SINT2)UF_NO_DATA;

  VolumeTime = pIHeader_a->icf.VolumeYmds;
  /* Convert all times to UT */
  iSecondsWest = 60*(SINT4)pIHeader_a->icf.iMinutesWest;
  ymds_add_seconds( iSecondsWest, &VolumeTime );
  unpacktime( VolumeTime.isec, &iDay, &iHour, &iMinute, &iSecond );
  pOpHeader_a->iVolumeScanHour = iHour;
  pOpHeader_a->iVolumeScanMinute = iMinute;
  pOpHeader_a->iVolumeScanSecond = iSecond;
  memcpy( pOpHeader_a->sFieldTapeName, "Default ", 8 );
  if (LITTLE_END) swap2_array( (SINT2*)pOpHeader_a->sFieldTapeName, 8/2 );
  pOpHeader_a->iFlag = 1;

  return;

}
/* =====================================================
 * Fill in the data header.
 */
static void FillFieldHeader
( struct uf_field_header2 *pFHeader_a,
  const struct ingest_header *pIHeader_a,    /* Ingest header */
  SINT4 iDType_a,                            /* Data type */
  const struct data_convert *pConvert_a,     /* Data convert structure */
  const struct ray_header   *pRHeader_a,     /* Ray header */
  SINT4 *pOffset_a                           /* Offset is updated */
)
{
  SINT4 iSize;
  FLT8 fAngle;
  memset( pFHeader_a, 0, 2*19 );

  /* Fill in size and optional fields */
  switch( iDType_a )
    {
    default:
      iSize = 19;
      break;
    case DB_VEL:
    case DB_VEL2:
      iSize = 21;
      memset( &pFHeader_a->Fsi.VE, 0, 2*2 );
      pFHeader_a->Fsi.VE.iNyquistVelocity = NINT(100.0 * pConvert_a->fvelun_velocity );
      pFHeader_a->Fsi.VE.ipad1 = 1;
      break;
    }

  *pOffset_a += iSize;
  pFHeader_a->iDataPosition = 1 + *pOffset_a;
  pFHeader_a->iScaleFactor  = iDataScaleUf(iDType_a);
  pFHeader_a->iStartRangeKm = pIHeader_a->tcf.rng.ibin_first/100000;
  pFHeader_a->iStartRangeMeters = 
    (pIHeader_a->tcf.rng.ibin_first-(100000*pFHeader_a->iStartRangeKm))/100;
  pFHeader_a->iBinSpacing   = (50+pIHeader_a->tcf.rng.ibin_out_step)/100;
  pFHeader_a->iBinCount     = pRHeader_a->ibincount;
  pFHeader_a->iPulseWidth   = NINT((FSPEED_OF_LIGHT*(FLT8)pIHeader_a->tcf.dsp.ipw)/100000.0);

  /* Beamwidths were not in the older IRIS formats (added in 7.18) */
  fAngle = fPDegFromBin4( pIHeader_a->tcf.misc.iHorzBeamWidth );
  if (fAngle==0.0) fAngle = CConfig_c.fHorizontalBeamwidth;
  pFHeader_a->iBeamWidthH   = NINT(64.0 * fAngle);
  fAngle = fPDegFromBin4( pIHeader_a->tcf.misc.iVertBeamWidth );
  if (fAngle==0.0) fAngle = CConfig_c.fVerticalBeamwidth;
  pFHeader_a->iBeamWidthV   = NINT(64.0 * fAngle);

  /* Use pulsewidth for bandwidth */
  pFHeader_a->iBandWidth = pFHeader_a->iPulseWidth;
  switch( pIHeader_a->tcf.misc.ipolar)
    {
    case POL_HORIZ_FIX:
    default:
      pFHeader_a->iPolarization = UF_POL_HORIZONTAL;
      break;
    case POL_VERT_FIX:
      pFHeader_a->iPolarization = UF_POL_VERTICAL;
      break;
    }
  pFHeader_a->iWaveLength   = (50+64*pIHeader_a->tcf.misc.ilambda)/100;
  pFHeader_a->iSampleSize   = pIHeader_a->tcf.dsp.isamp;
  memcpy( pFHeader_a->sThresholdData, "  ", 2 );
  pFHeader_a->iThresholdValue = (SINT2)UF_NO_DATA;
  pFHeader_a->iScale        = (SINT2)UF_NO_DATA;
  memcpy( pFHeader_a->sEditCode, "  ", 2 );
  if (pIHeader_a->tcf.dsp.iprf)
    pFHeader_a->iPRT = (1000000+(pIHeader_a->tcf.dsp.iprf/2))/pIHeader_a->tcf.dsp.iprf;
  pFHeader_a->iBitsPerBin = 16;

  return;

}
/* =================================================================
 * Fill in the data map for the specified data type.
 */
static void FillDataMap
( struct moment_info *pMInfo_a,
  const struct data_convert *pConvert_a,
  const struct uf_data_map Maps_a[] )
{
  FLT4 fValue, fScale, fScale2, fOffset2;
  SINT4 iRes, iIndex;

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

 MATCH_FOUND:
  iRes = idata_resolution( pMInfo_a->idtype );
  fScale = (FLT4)iDataScaleUf( pMInfo_a->idtype );
  fScale = fScale/fScale2;
  if (iRes==16)
    {
      for (iIndex=0; iIndex<65536; iIndex++)
	{
	  fValue = fdata_to_user( iIndex, pMInfo_a->idtype, pConvert_a );
	  if ( pMInfo_a->lReflectivity && (fValue<CConfig_c.fMinReflectivity))
	    pMInfo_a->iDataMap[iIndex] = (SINT2)UF_NO_DATA;
	  else
	    pMInfo_a->iDataMap[iIndex] = (SINT2)NINT(fScale*(fValue-fOffset2));
	}
      pMInfo_a->iDataMap[DV_NOSCAN2] = (SINT2)UF_NO_DATA;
      pMInfo_a->iDataMap[DV_THRESH2] = (SINT2)UF_NO_DATA;

    }
  else
    {
      for (iIndex=0; iIndex<256; iIndex++)
	{
	  fValue = fdata_to_user( iIndex, pMInfo_a->idtype, pConvert_a );
	  if ( pMInfo_a->lReflectivity && (fValue<CConfig_c.fMinReflectivity))
	    pMInfo_a->iDataMap[iIndex] = (SINT2)UF_NO_DATA;
	  else
	    pMInfo_a->iDataMap[iIndex] = (SINT2)NINT(fScale*(fValue-fOffset2));
	}
      pMInfo_a->iDataMap[DV_NOSCAN1] = (SINT2)UF_NO_DATA;
      pMInfo_a->iDataMap[DV_THRESH1] = (SINT2)UF_NO_DATA;
    }
  return;

}
/* =================================================================
 * Fill in the array of data
 */
static void FillFieldData
( SINT2 *iUfBuffer_a,
  const struct data_ray *pRay_a, 
  const struct moment_info *pMInfo_a,
  SINT4 *pOffset_a )
{
  SINT4 iRes, iIndex;
  iRes = idata_resolution( pMInfo_a->idtype );
  if (iRes==16)
    {
      const UINT2 *pData2;
      pData2 = pRay_a->data.iData2;
      for (iIndex=0; iIndex<pRay_a->hdr.ibincount; iIndex++)
	{
	  iUfBuffer_a[iIndex] = pMInfo_a->iDataMap[pData2[iIndex]];
	}
    }
  else
    {
      const UINT1 *pData1;
      pData1 = pRay_a->data.iData1;
      for (iIndex=0; iIndex<pRay_a->hdr.ibincount; iIndex++)
	{
	  iUfBuffer_a[iIndex] = pMInfo_a->iDataMap[pData1[iIndex]];
	}
    }
  *pOffset_a += pRay_a->hdr.ibincount;
  return;
}

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

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

  /* Initialize the data to the default values */
  memset( pCSetup_a, 0, sizeof(*pCSetup_a) );
  pCSetup_a->fHorizontalBeamwidth = 1.0;
  pCSetup_a->fVerticalBeamwidth = 1.0;
  pCSetup_a->fMinReflectivity = -100.0;
  strncpy( pCSetup_a->sProjectName, "Default ", 8 );

  /* 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, "IrisToUf.conf" );
  stream = fopen( sPath, "r" );

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

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

  fclose( stream );

  return;
}

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

  if ( 0 == strcmp( "APPEND_TO_LOG", stokens[0] ) )
    {
      pConfig_a->lAppendToLog=TRUE;
      return FALSE;
    }
  /* See if the line contains a DATA command */
  /* Format is something like: DATA  <UFdatatype> <IRISdatatype> */
  if ( 0 == strcmp( "DATA", stokens[0] ) )
    {
      struct uf_data_map *pOneData;
      SINT2 iIrisDataType;
      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 the HORIZONTAL_BEAMWIDTH command */
  if ( 0 == strcmp( "HORIZONTAL_BEAMWIDTH", stokens[0] ) )
    {
      FLT8 fBeamWidth;
      if ( icount < 2 ) return TRUE;
      
      fBeamWidth = strtod( stokens[1], &sptr );
      if (sptr == stokens[1]) return TRUE;

      if (fBeamWidth <   0.01 ) return TRUE;
      if (fBeamWidth >  90.00 ) return TRUE;

      pConfig_a->fHorizontalBeamwidth = fBeamWidth;
      return FALSE;
    }

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

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

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

      pConfig_a->fMinReflectivity = fValue;
      return FALSE;
    }

  /* Set the project name */
  if ( 0 == strcmp( "PROJECT_NAME", stokens[0] ) )
    {
      if ( icount < 2 ) return TRUE;
      
      spacify_ncpy( pConfig_a->sProjectName, stokens[1], 8 );
      return FALSE;
    }
  
  if ( 0 == strcmp( "VERBOSE", stokens[0] ) )
    {
      pConfig_a->lVerbose = TRUE;
      return FALSE;
    }
  
  /* Set the vertical beamwidth */
  if ( 0 == strcmp( "VERTICAL_BEAMWIDTH", stokens[0] ) )
    {
      FLT8 fBeamWidth;
      if ( icount < 2 ) return TRUE;
      
      fBeamWidth = strtod( stokens[1], &sptr );
      if (sptr == stokens[1]) return TRUE;

      if (fBeamWidth <   0.01 ) return TRUE;
      if (fBeamWidth >  90.00 ) return TRUE;

      pConfig_a->fVerticalBeamwidth = fBeamWidth;
      return FALSE;
    }
  
  /* Unrecognized lines will generate a syntax error */
  return TRUE;
}
/* ======================================================================
 * Function to print signaled errors to the logging file or terminal.
 */
static MESSAGE SignalHandler
(MESSAGE iStatus_a,
 UINT4 iCount_a,
 const UINT4 iSigArgs_a[] )
{
  char sMessage[170];

  strncpy( sMessage, message_to_text( iStatus_a, iCount_a, iSigArgs_a ), 165 );
  sMessage[165]=0;
  strcat( sMessage, "\n" );

  Lprintf( sMessage );
  return(SS_NORMAL);
}

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