/* ************************************************************************
 * *                                                                      *
 * *  Function to convert an IRIS Raw product to a UKMO format file       *
 * *                                                                      *
 * ************************************************************************
 * File: utils/pipes_out/IrisToUKMO.C
 *
 *             COPYRIGHT (c) 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 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.
 */
#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 "product.h"

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

struct UKMO_Time
{
  UINT2 Year;
  UINT2 Month;
  UINT2 Day;
  UINT2 Hour;
  UINT2 Minute;
  UINT2 Second;
};

struct UKMO_LL
{
  UINT2 Degree;
  UINT2 Minute;
  UINT2 Second;
};

struct aDop
{
  UINT2 Reflectivity : 12;
  UINT2 SQI_Thresh : 1;
  UINT2 CSR_Thresh : 1;
  UINT2 LOG_Thresh : 1;
  UINT2 DataAvail: 1;
  UINT1 Velocity;
  UINT1 Width;
};

struct ray_hdr
{
  UINT2 RayIndex;
  UINT2 Azimuth;
  UINT2 Elevation;
  UINT2 TotalBytes; 
};

struct sweep_hdr
{
  UINT2 ScanIndex;
  UINT2 NumRays;
  UINT2 NumBins;
  UINT2 StartRange;
  UINT2 ScanStart;
  UINT2 ScanStop;
  UINT2 AzStart;
  UINT2 AzStop;
  UINT2 ElStart;
  UINT2 ElStop;
  UINT2 Pad1[2];
};

struct Identification
{
  UINT1 MagicNumber[4];
  UINT2 ByteOrdering[2];
  UINT2 Version;
  UINT2 Mode;
  struct UKMO_Time CreateTime; 
  struct UKMO_Time StartTime; 
  struct UKMO_Time StopTime; 
};

struct SiteInfo 
{
  UINT2 WMO_Country;
  UINT2 WMO_Site;
  struct UKMO_LL Longitude; 
  struct UKMO_LL Latitude; 
  UINT2 SiteNumber;
  UINT2 GridEast;
  UINT2 GridNorth;
  UINT2 RadarHeight;
};

struct HardwareConfig
{
  UINT2 HardwareType;
  UINT2 SoftwareType;
  UINT2 NumberChannels;
  UINT2 Pad1;
  UINT2 Channel1Polarization;
  UINT2 Channel2Polarization;
  UINT2 Channel1Constant;
  UINT2 Channel2Constant;
  UINT2 Channel1WaveLength;
  UINT2 Channel2WaveLength;
  UINT2 Channel1BeamWidth;
  UINT2 Channel2BeamWidth;
  UINT2 Pad2[6];

};

struct ProcessorConfig
{
  UINT2 ScanType;
  UINT2 NumberOfScans;
  UINT2 NumberOfRays;
  UINT2 NumberOfBins;
  UINT2 Range;
  UINT2 PulseLength;
  UINT2 AntennaRate;
  UINT2 Samples;
  UINT2 PRF1;
  UINT2 PRF2;
  UINT2 UnambigRange;
  UINT2 UnambigVel;
  UINT2 Pad1[6];
};

struct DataProcessing
{
  UINT2 Information : 1;
  UINT2 LOG_Thresh : 1;
  UINT2 CSR_Thresh : 1;
  UINT2 SGI_Thresh : 1;
  UINT2 SpeckleFilter : 1;
  UINT2 RangeNormalization : 1;
  UINT2 GaseousAttenuation : 1;
  UINT2 AttenuationCorrection : 1;
  UINT2 Pad1 : 8;
  UINT2 FilterType;
  UINT2 FilterIndex;
  UINT2 CSR_Value;
  UINT2 LOG_Value;
  UINT2 SQI_Value;
  UINT2 Pad2[6];
};

struct DataStorage
{
  UINT2 DerivedDataType;
  UINT2 SourceDataType;
  UINT2 BytesPerElement;
  UINT2 ValuesPerElement;
  UINT2 Compression;
  UINT2 Pad1[7];
};

struct Diagnostics
{
  UINT2 Flags;
  UINT2 Pad1[31];
};

struct vol_hdr
{
  struct Identification Id;
  struct SiteInfo Site;
  struct HardwareConfig Hw;
  struct ProcessorConfig Proc;
  struct DataProcessing DataProc;
  struct DataStorage DataStore;
  struct Diagnostics Diag;
};

/* Variable sized structure, do not use sizeof */
struct radial
{
  struct ray_hdr rhdr;
  struct aDop aDop[1];
};
/* Variable sized structure, do not use sizeof, cannot directly index radials */
struct sweep
{
  struct sweep_hdr shdr;
  struct radial radial[1];
};
/* Variable sized structure, do not use sizeof, cannot directly index sweeps */
struct vol
{
  struct vol_hdr vhdr;
  struct sweep sweep[1];
};

/* This holds information to customize the convertor */
struct convert_config
{
  UINT1 lLogToFile;
  UINT1 lLogToTerm;
  UINT1 lVerbose;              /* Log more details */
  UINT1 lAppendToLog;          /* Append to previous log file */
  FILE *iLogFile;              /* Open file descriptor to log file */
  UINT2 WMO_Site;
  UINT2 SiteNumber;
  UINT2 GridNorth;
  UINT2 GridEast;
};

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 ConvertRawToUKMO
  (const struct product_hdr *phead_a);

static SINT4 SizeOfRadial
( SINT4 iNumBins_a);

static SINT4 SizeOfSweep
( SINT4 iNumRadials_a,
  SINT4 iRadialSize_a);

static SINT4 SizeOfVolume
( SINT4 iNumSweeps_a,
  SINT4 iSweepSize_a);

struct sweep *Sweep
( struct vol *pVol_a,
  SINT4 iSweepNumber_a );

struct radial *Radial
( struct sweep *pSweep_a,
  SINT4 iRadialNumber_a );

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

static void LoadSetup
( struct convert_config *pCSetup_a );

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

static void Lprintf
( const char *sString_a );

/* ===============================================================
 * Main program starts here.
 */
int main(int iArgc, char *sArgv[])
{  
  struct product_hdr ProdHeader, *pInRaw;
  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, "IrisToUKMO.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 */
    
  /* Read in the header */
  iCount =  fread( &ProdHeader, 1, sizeof(ProdHeader), stdin );
  if (iCount != sizeof(ProdHeader)) 
  {
    sprintf( sLog, "Bad read, got%d, wanted%d\n", iCount, sizeof(ProdHeader));
    Lprintf( sLog );
    exit(EXIT_FAILURE);
  }

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

  /* Malloc input buffer */
  pInRaw  = (struct product_hdr*)malloc( ProdHeader.hdr.ibytes );
  *pInRaw = ProdHeader;

  iWantCount = ProdHeader.hdr.ibytes-sizeof(ProdHeader);
  iCount =  fread( pInRaw+1, 1, iWantCount, stdin );
  if (iCount != iWantCount)
  {
    sprintf( sLog, "Bad read2, got%d, wanted%d\n", iCount, iWantCount);
    Lprintf( sLog );
    exit(EXIT_FAILURE);
  }
  
  istatus = ConvertRawToUKMO( pInRaw );
  if (istatus != SS_NORMAL) exit(EXIT_FAILURE);

  shhmmssddmonyyyy_r( &ProdHeader.pcf.ing_time, sProdTime );
  sprintf( sLog, "Converted RAW product: %-8.8s, Date: %s\n", ProdHeader.pcf.spname, 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 this is a full volume scan raw product */
  if (pheader_a->pcf.psi.raw.iflags & RAW_FLG_SWEEP)
  {
    Lprintf( "Sorry, sweep-by-sweep RAW product 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)

static SINT4 iRadialSize_c, iSweepSize_c, iVolumeSize_c;
/* ==============================================================
 * Print information about a RAW product.  Entered with a pointer
 * to the beginning (first 6144-byte record) of the full product.  */
static MESSAGE ConvertRawToUKMO (const struct product_hdr *phead_a)
{
#define RAYSIZE MAX_BINS
  UINT2 lmissing ;
  SINT4 ii, iIrisMoments, isweep, iWriteCount;
  struct ingest_data_header idhdr[MAX_MOMENTS];
  struct tm unpacked_time;
  const struct ingest_header *inghdr ;
  SINT4 num_bins, num_rays, num_sweeps;
  UINT2 iday, ihour, imin, isec;
  UINT2 iDegree, iMinute;
  FLT8  fval, fSecond;
  SINT4 iIrisMoment, RequiredData ;
  SINT4 iray, ibin, ray_start_index;
  UINT2 ray_start_time, ray_end_time; 
  BIN2  start_ray_az, end_ray_az, mid_az;
  struct ymds_time temp_time;
  struct vol *UKMO_Vol;  
  
  struct sweep_store
  {
    UINT1 iData[MAX_INGEST_RAYS][MAX_BINS];
  };
  struct sweep_store *DBT;
  time_t Time;

  /* 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 *) phead_a ;
  inghdr = (struct ingest_header *)(prod_c + TAPE_RECORD_LEN) ;

  num_bins = inghdr->tcf.rng.ibin_out_num;
  num_rays = inghdr->icf.irtotl;
  num_sweeps = inghdr->tcf.scan.isweeps;

  iRadialSize_c = SizeOfRadial(num_bins);
  iSweepSize_c  = SizeOfSweep(num_rays,iRadialSize_c);
  iVolumeSize_c = SizeOfVolume(num_sweeps,iSweepSize_c);

  UKMO_Vol = (struct vol *)malloc( iVolumeSize_c );
  DBT      = (struct sweep_store *)malloc( num_sweeps*sizeof(struct sweep_store) );

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

  if (inghdr->tcf.scan.iscan != SCAN_CONT)
    {
      Lprintf( "Only Full 360 PPI Scans supported.  Data not converted.\n" ) ;
      return USER_ERROR_SIG;
    } 
   
  iIrisMoments=0;
  RequiredData=0;
  for( ii=0 ; ii <NUM_DEFINED_DATA ; ii++ )
    {
      if( lDspMaskTest( &inghdr->tcf.dsp.DataMask, ii) && (iIrisMoments<MAX_MOMENTS) )
	{
	  iIrisMoments++ ;
	  if ( (ii == DB_DBT) || (ii == DB_DBZ) ) RequiredData++;
	}
    }
/*
  if (RequiredData != 2)
    {
      Lprintf( "Input Data must contain at least T and Z data types.  Data not converted.\n" ) ;
      return USER_ERROR_SIG;
    } 
*/
  /* Initialize record pointer to the first data record (third one in)
     * and loop for each sweep that was recorded.  We will scan through
     * the entire product (to check for errors), and will convert all of it.
     */
  irec_c = 2 ;			/* Record number */
  ioff_c = 0 ;			/* Offset within record */

  for( isweep = 0 ; isweep < num_sweeps; isweep++ ) /* for all sweeps in this volume */
    {
      struct sweep *pSweep = Sweep(UKMO_Vol,isweep);
      /* Fill in the UKMO structure saying all data missing.  
       * Later we will fill it in as we find it.
       */
      for (iray = 0; iray < num_rays; iray++)
	{
	  struct radial *pRay = Radial(pSweep,iray);
	  for (ibin = 0; ibin < num_bins; ibin++)
	    {
	      pRay->aDop[ibin].Reflectivity = 0xFFF;
	      pRay->aDop[ibin].Velocity = 0xFF;
	      pRay->aDop[ibin].Width = 0xFF;
	      pRay->aDop[ibin].DataAvail = 0;
	      pRay->aDop[ibin].CSR_Thresh = 0;
	      pRay->aDop[ibin].LOG_Thresh = 0;
	      pRay->aDop[ibin].SQI_Thresh = 0;
	      DBT[isweep].iData[iray][ibin] = 0xFF;
	    }
	} 

      /* 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 *)&idhdr[iIrisMoment], INGEST_DATA_HEADER_SIZE ) ;
	}
      if (CConfig_c.lVerbose)
	{
	  char sLog[200], sTimeBuf[TIMENAME_SIZE];
	  sprintf( sLog, "Sweep began at: %s\n", shhmmssddmonyyyy_r( &idhdr[0].time, sTimeBuf ) ) ;
	  Lprintf( sLog );
	}
      
      /* Read the data from each of the angles, and for each of the
       *  parameters that were recorded.
       */
      ray_start_time = 0xFFFF;
      ray_end_time = 0;
      for( iray = 0 ; iray < num_rays ; iray++ ) /* for all rays in this sweep */
	{
	  struct radial *pRay = Radial(pSweep,iray);
	  for( iIrisMoment=0 ; iIrisMoment < iIrisMoments ; iIrisMoment++ ) /* for all moments in file */
	    {
	      struct data_ray ray ;
	      SINT4 inlen, ioutlen ;
	     
	      /* in this ray, read all bins of the current data type - 
		 even its a data type we are not intersted in */ 
	      uncompress_cowords
	     	(get_raw_bytes,
		 phead_a->hdr.ibytes - ((irec_c * TAPE_RECORD_LEN) + ioff_c),
		 &inlen, (SINT2 *)&ray, sizeof(ray)/2, &ioutlen ) ;
	      
	      if (idhdr[iIrisMoment].idata == DB_DBZ)
		{
		  pRay->rhdr.TotalBytes = num_bins * sizeof(struct aDop);; 
		}

	      lmissing = (ioutlen == 0) ;
	      if (!lmissing) /* if this ray is filled in */
		{
		  /* keep track of which ray is first and which is last */
		  if (idhdr[iIrisMoment].idata == DB_DBZ)
		    {
		      pRay->rhdr.Azimuth   = mid_az =
			iBin2NarrowMidPt(ray.hdr.iaz_start, ray.hdr.iaz_end);

		      pRay->rhdr.Elevation =
			iBin2NarrowMidPt(ray.hdr.iel_start, ray.hdr.iel_end);

		      if (ray.hdr.itime < ray_start_time)
			{
			  ray_start_time = ray.hdr.itime; 
			  ray_start_index = iray;
			  start_ray_az = mid_az;
			}
		      if (ray.hdr.itime >= ray_end_time)
			{
			  ray_end_time = ray.hdr.itime; 
			  end_ray_az = mid_az;
			}
		    } 

		  for(ibin = 0; ibin < num_bins; ibin++)
		    {
		      UINT2 ivalue;
		      ivalue = (UINT2)ray.data.iData1[ibin]; 
		      switch (idhdr[iIrisMoment].idata)
			{
			case DB_DBT:
			  DBT[isweep].iData[iray][ibin] = ivalue;
			  break;
			case DB_DBZ:
			  if (ivalue == 0 || ivalue == 0xFF) 
			    {
			      pRay->aDop[ibin].Reflectivity = 0xFFF;
			    }
			  else  
			    { 
			      ivalue *= 5; 
			      pRay->aDop[ibin].Reflectivity = ivalue;
			    }
			  break;
			case DB_VEL:
			  if (ivalue == 0) 
			    {
			      pRay->aDop[ibin].Velocity = 0xFF;
			    }
			  else 
			    {
			      pRay->aDop[ibin].Velocity = ivalue - 1;
			    }
			case DB_WIDTH:
			  if (ivalue == 0) 
			    {
			      pRay->aDop[ibin].Width = 0xFF;
			    }
			  else 
			    {
			      pRay->aDop[ibin].Width = ivalue - 1;
			    }
			  break;
			} /* end case data type */
		    } /* for all bins in this ray for this data type */
		} /* if not missing */
	    } /* for all data types */
	}  /* for all radials in this sweep   */
        
      /* set the RayIndex such that the earlier time ray gets index 0 and so forth */
      for( iray=0 ; iray < num_rays ; iray++ ) /* for all rays in this sweep */
	{
	  struct radial *pRay = Radial(pSweep,iray);
	  pRay->rhdr.RayIndex = (iray + num_rays - ray_start_index) % num_rays;
	}
      
      pSweep->shdr.ScanIndex = isweep;
      pSweep->shdr.NumRays = num_rays;
      pSweep->shdr.NumBins = num_bins;
      pSweep->shdr.StartRange = inghdr->tcf.rng.ibin_first * 100;
      pSweep->shdr.ScanStart = (UINT2)(abs(iymds_subtract(&(inghdr->icf.VolumeYmds), &(idhdr[0].time))));
      pSweep->shdr.ScanStop = pSweep->shdr.ScanStart + ray_end_time;
      pSweep->shdr.AzStart = start_ray_az;
      pSweep->shdr.AzStop = end_ray_az;
      pSweep->shdr.ElStart = inghdr->tcf.scan.u.ppi.iellst[isweep];
      pSweep->shdr.ElStop = inghdr->tcf.scan.u.ppi.iellst[isweep];
      pSweep->shdr.Pad1[0] = 0;
      pSweep->shdr.Pad1[1] = 0;

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

  /* Now fill in bitflag array */
  for( isweep = 0 ; isweep < num_sweeps; isweep++ )
    {
      struct sweep *pSweep = Sweep(UKMO_Vol,isweep);
      for (iray = 0; iray < num_rays; iray++)
	{
	  struct radial *pRay = Radial(pSweep,iray);
	  for (ibin = 0; ibin < num_bins; ibin++)
	    {
	      /* if DBT shows data, then all data is valid (but may be thresholded) */
	      if (DBT[isweep].iData[iray][ibin] < 0xFF)
		{
		  pRay->aDop[ibin].DataAvail = 1;
		  /* if data is valid, but DBT is thresholded, then LOG Theshold is active */
		  if (DBT[isweep].iData[iray][ibin] == 0) 
		    {
		      pRay->aDop[ibin].LOG_Thresh = 1;
		    } 
		  else
		    { 
		      /* if DBT is not thesholded, but DBZ is, then CSR Threshold is active */
		      if (pRay->aDop[ibin].Reflectivity == 0xFFF) 
			{
			  pRay->aDop[ibin].CSR_Thresh = 1;
			}
		      else
			{
			  /* if DBZ is not thesholded, but VEL is, then SQI Theshold is active */
			  if (pRay->aDop[ibin].Velocity == 0xFF) 
			    {
			      pRay->aDop[ibin].SQI_Thresh = 1;
			    }
			}
		    }
		}
	    }
	} 
    }

  /* Now fill in volume header */
  memcpy(UKMO_Vol->vhdr.Id.MagicNumber, "RADF", 4);
  UKMO_Vol->vhdr.Id.ByteOrdering[0] = 32771;
  UKMO_Vol->vhdr.Id.ByteOrdering[1] = 49153;
  UKMO_Vol->vhdr.Id.Version = 0;
  UKMO_Vol->vhdr.Id.Mode = 0;
  Time = time(NULL);
  localtime_r( &Time, &unpacked_time );
  UKMO_Vol->vhdr.Id.CreateTime.Year = unpacked_time.tm_year + 1900; 
  UKMO_Vol->vhdr.Id.CreateTime.Month = unpacked_time.tm_mon + 1; 
  UKMO_Vol->vhdr.Id.CreateTime.Day = unpacked_time.tm_mday; 
  UKMO_Vol->vhdr.Id.CreateTime.Hour = unpacked_time.tm_hour; 
  UKMO_Vol->vhdr.Id.CreateTime.Minute = unpacked_time.tm_min; 
  UKMO_Vol->vhdr.Id.CreateTime.Second = unpacked_time.tm_sec; 
  UKMO_Vol->vhdr.Id.StartTime.Year = inghdr->icf.VolumeYmds.iyear; 
  UKMO_Vol->vhdr.Id.StartTime.Month = inghdr->icf.VolumeYmds.imon; 
  UKMO_Vol->vhdr.Id.StartTime.Day = inghdr->icf.VolumeYmds.iday;
  temp_time = inghdr->icf.VolumeYmds;
  unpacktime(temp_time.isec, &iday, &ihour, &imin, &isec);
  UKMO_Vol->vhdr.Id.StartTime.Hour = ihour;
  UKMO_Vol->vhdr.Id.StartTime.Minute = imin;
  UKMO_Vol->vhdr.Id.StartTime.Second = isec;
  temp_time = inghdr->icf.VolumeYmds;
  ymds_add_seconds(Sweep(UKMO_Vol,inghdr->tcf.scan.isweeps-1)->shdr.ScanStop, &temp_time);
  UKMO_Vol->vhdr.Id.StopTime.Year = temp_time.iyear; 
  UKMO_Vol->vhdr.Id.StopTime.Month = temp_time.imon; 
  UKMO_Vol->vhdr.Id.StopTime.Day = temp_time.iday; 
  unpacktime(temp_time.isec, &iday, &ihour, &imin, &isec);
  UKMO_Vol->vhdr.Id.StopTime.Hour = ihour; 
  UKMO_Vol->vhdr.Id.StopTime.Minute = imin; 
  UKMO_Vol->vhdr.Id.StopTime.Second = isec; 
  UKMO_Vol->vhdr.Site.WMO_Country = 3;
  UKMO_Vol->vhdr.Site.WMO_Site = CConfig_c.WMO_Site;
  fval = fDegFromBin4(inghdr->icf.ilat);
  if (fval < 0.0) fval = 180.0 - fval;
  iDegree = (UINT2)fval;
  iMinute = (UINT2)(60.0*(fval -(FLT8)iDegree));
  fSecond = 60.0*((60.0*(fval -(FLT8)iDegree))-(FLT8)iMinute);
  UKMO_Vol->vhdr.Site.Latitude.Degree = iDegree;
  UKMO_Vol->vhdr.Site.Latitude.Minute = iMinute;
  UKMO_Vol->vhdr.Site.Latitude.Second = (UINT2)(fSecond+0.5);
  fval = fDegFromBin4(inghdr->icf.ilon);
  if (fval < 0.0) fval = 360.0 - fval;
  iDegree = (UINT2)fval;
  iMinute = (UINT2)(60.0*(fval -(FLT8)iDegree));
  fSecond = 60.0*((60.0*(fval -(FLT8)iDegree))-(FLT8)iMinute);
  UKMO_Vol->vhdr.Site.Longitude.Degree = iDegree;
  UKMO_Vol->vhdr.Site.Longitude.Minute = iMinute;
  UKMO_Vol->vhdr.Site.Longitude.Second = (UINT2)(fSecond+0.5);
  UKMO_Vol->vhdr.Site.SiteNumber = CConfig_c.SiteNumber;
  UKMO_Vol->vhdr.Site.GridEast = CConfig_c.GridEast;
  UKMO_Vol->vhdr.Site.GridNorth = CConfig_c.GridNorth;
  UKMO_Vol->vhdr.Site.RadarHeight = inghdr->icf.ignd_hgt + inghdr->icf.irad_hgt;
  UKMO_Vol->vhdr.Hw.HardwareType = 1;
  UKMO_Vol->vhdr.Hw.SoftwareType = 0;
  UKMO_Vol->vhdr.Hw.NumberChannels = 0;
  UKMO_Vol->vhdr.Hw.Pad1 = 0;
  switch (inghdr->tcf.misc.ipolar)
    {
    case POL_HORIZ_FIX:
      UKMO_Vol->vhdr.Hw.Channel1Polarization = 3;
      break;
    case POL_VERT_FIX:
      UKMO_Vol->vhdr.Hw.Channel1Polarization = 1;
      break;
    default:
      UKMO_Vol->vhdr.Hw.Channel1Polarization = 0;
      break;
    }
  UKMO_Vol->vhdr.Hw.Channel2Polarization = 0;
  fval = (float)inghdr->tcf.cal.iz_calib / 16.0;
  fval *= 10.0;
  fval += 0.5;
  UKMO_Vol->vhdr.Hw.Channel1Constant = (UINT2)fval;
  UKMO_Vol->vhdr.Hw.Channel2Constant = 0;
  UKMO_Vol->vhdr.Hw.Channel1WaveLength = (inghdr->tcf.misc.ilambda + 5) / 10;
  UKMO_Vol->vhdr.Hw.Channel2WaveLength = 0;
  fval = fDegFromBin4(inghdr->tcf.misc.iHorzBeamWidth);
  fval *= 100.0;
  fval += 0.5;
  UKMO_Vol->vhdr.Hw.Channel1BeamWidth = (UINT2)fval;
  UKMO_Vol->vhdr.Hw.Channel2BeamWidth = 0;
  for (ii=0; ii<6; ii++) UKMO_Vol->vhdr.Hw.Pad2[ii] = 0;
  UKMO_Vol->vhdr.Proc.ScanType = 1;
  UKMO_Vol->vhdr.Proc.NumberOfScans = inghdr->tcf.scan.isweeps;
  UKMO_Vol->vhdr.Proc.NumberOfRays = num_rays;
  UKMO_Vol->vhdr.Proc.NumberOfBins = num_bins;
  UKMO_Vol->vhdr.Proc.Range = (inghdr->tcf.rng.ibin_out_step + 50) / 100;
  UKMO_Vol->vhdr.Proc.PulseLength = inghdr->tcf.dsp.ipw * 10;
  fval = fabs(fDegFromBin2(inghdr->tcf.scan.iscan_speed));
  fval *=60.0;
  fval +=0.5;
  UKMO_Vol->vhdr.Proc.AntennaRate = (UINT2)fval;
  UKMO_Vol->vhdr.Proc.Samples = inghdr->tcf.dsp.isamp;
  UKMO_Vol->vhdr.Proc.PRF1 = inghdr->tcf.dsp.iprf;
  ii = inghdr->tcf.dsp.itrig; 
  if (ii > 0 && ii < 4)
    {
      fval = ((float)UKMO_Vol->vhdr.Proc.PRF1) * ((float)(ii+1)) / ((float)(ii+2));
      fval += 0.5;
      UKMO_Vol->vhdr.Proc.PRF2 = (UINT2)fval;
    }
  else
    {
      UKMO_Vol->vhdr.Proc.PRF2 = 0;
    }
  if (inghdr->tcf.dsp.iprf > 0)
    {
      fval = (FSPEED_OF_LIGHT / 0.2) / ((float)inghdr->tcf.dsp.iprf);
      fval += 0.5;
    }
  UKMO_Vol->vhdr.Proc.UnambigRange = (UINT2)fval;
  fval = fNyquistVelocity(inghdr->tcf.dsp.iprf, inghdr->tcf.dsp.itrig, 
			  inghdr->tcf.misc.ilambda, inghdr->tcf.misc.ipolar);
  fval *= 100;
  fval += 0.5;
  UKMO_Vol->vhdr.Proc.UnambigVel = (UINT2)fval;
  for (ii=0; ii<6; ii++) UKMO_Vol->vhdr.Proc.Pad1[ii] = 0;
  UKMO_Vol->vhdr.DataProc.Information = 1;
  UKMO_Vol->vhdr.DataProc.LOG_Thresh = (0 != (inghdr->tcf.cal.icz_tcf & TCF_LOG));
  UKMO_Vol->vhdr.DataProc.CSR_Thresh = (0 != (inghdr->tcf.cal.icz_tcf & TCF_CSR));
  UKMO_Vol->vhdr.DataProc.SGI_Thresh = (0 != (inghdr->tcf.cal.ivl_tcf & TCF_SQI));
  UKMO_Vol->vhdr.DataProc.SpeckleFilter = (0 != (inghdr->tcf.cal.iflags & TCF_CAL_ZSPECKLE));
  UKMO_Vol->vhdr.DataProc.RangeNormalization = (0 != (inghdr->tcf.cal.iflags & TCF_CAL_RANGENORM));
  if (inghdr->tcf.dsp.igas_atten) UKMO_Vol->vhdr.DataProc.GaseousAttenuation = 1;
  else UKMO_Vol->vhdr.DataProc.GaseousAttenuation = 0;
  UKMO_Vol->vhdr.DataProc.AttenuationCorrection = 0;
  UKMO_Vol->vhdr.DataProc.Pad1 = 0;
  if (inghdr->tcf.dsp.idop_filter_first)
    {
      if (inghdr->tcf.dsp.imajor_mode == PMODE_PPP) UKMO_Vol->vhdr.DataProc.FilterType = 4;
      else UKMO_Vol->vhdr.DataProc.FilterType = 5;
    }
  else UKMO_Vol->vhdr.DataProc.FilterType = 0;
  UKMO_Vol->vhdr.DataProc.FilterIndex = inghdr->tcf.dsp.idop_filter_first;
  fval = fabs((float)inghdr->tcf.cal.iccr_thr / 16.0);
  fval = (fval * 10.0) + 0.5;
  UKMO_Vol->vhdr.DataProc.CSR_Value = (UINT2)fval;
  fval = fabs((float)inghdr->tcf.cal.izns_thr / 16.0);
  fval = (fval * 10.0) + 0.5;
  UKMO_Vol->vhdr.DataProc.LOG_Value = (UINT2)fval;
  fval = (float)inghdr->tcf.cal.isqi_thr / 256.0;
  fval = (fval * 1000.0) + 0.5;
  UKMO_Vol->vhdr.DataProc.SQI_Value = (UINT2)fval;
  for (ii=0; ii<6; ii++) UKMO_Vol->vhdr.DataProc.Pad2[ii] = 0;
  UKMO_Vol->vhdr.DataStore.DerivedDataType = 2131;
  UKMO_Vol->vhdr.DataStore.SourceDataType = 2131;
  UKMO_Vol->vhdr.DataStore.BytesPerElement = 4;
  UKMO_Vol->vhdr.DataStore.ValuesPerElement = 4;
  UKMO_Vol->vhdr.DataStore.Compression = 0;
  for (ii=0; ii<7; ii++) UKMO_Vol->vhdr.DataStore.Pad1[ii] = 0;
  UKMO_Vol->vhdr.Diag.Flags = 0;
  for (ii=0; ii<31; ii++) UKMO_Vol->vhdr.Diag.Pad1[ii] = 0;

  /* Write out the data */
  iWriteCount = fwrite( UKMO_Vol, iVolumeSize_c, 1, stdout );
  if (iWriteCount!=1) Lprintf( "Write error\n" ) ;
  return SS_NORMAL;
}
/* =================================================
 * Return the size of a radial in bytes.
 */
static SINT4 SizeOfRadial
( SINT4 iNumBins_a)
{
  SINT4 iSize;
  iSize = sizeof( struct ray_hdr );
  iSize += iNumBins_a*sizeof( struct aDop );
  return iSize;
}
/* =================================================
 * Return the size of a sweep in bytes.
 */
static SINT4 SizeOfSweep
( SINT4 iNumRays_a,
  SINT4 iRadialSize_a)
{
  SINT4 iSize;
  iSize = sizeof( struct sweep_hdr );
  iSize += iNumRays_a*iRadialSize_a;
  return iSize;
}
/* =================================================
 * Return the size of a volume in bytes.
 */
static SINT4 SizeOfVolume
( SINT4 iNumSweeps_a,
  SINT4 iSweepSize_a)
{
  SINT4 iSize;
  iSize = sizeof( struct vol_hdr );
  iSize += iNumSweeps_a*iSweepSize_a;
  return iSize;
}
/* ==================================================
 * Gets the sweep pointer from the volume pointer and sweep number.
 */
struct sweep *Sweep
( struct vol *pVol_a,
  SINT4 iSweepNumber_a )
{
  return (struct sweep*)(((UINT1*)&(pVol_a->sweep[0])) + (iSweepNumber_a*iSweepSize_c));
}
/* ==================================================
 * Gets the radial pointer from the sweep pointer and radial number.
 */
struct radial *Radial
( struct sweep *pSweep_a,
  SINT4 iRadialNumber_a )
{
  return (struct radial*)(((UINT1*)&(pSweep_a->radial[0])) + (iRadialNumber_a*iRadialSize_c));
}
/* ==================================================
 * 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;
}

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

  /* 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, "IrisToUKMO.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 IrisToUKMO.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];
  UINT4 icount, ioffset;
  
  ioffset = 0;
  icount  = 0;
  /* Process up to 9 tokens, stop when the line is exhausted. */
  while (icount<9) 
  {
    stokens[icount] = sigtok( sline_a, &ioffset );
    if (NULL == stokens[icount])
      break;
    else
      icount++;
  }
  
  /* Blank lines are allowed and skipped */
  if (icount == 0) return FALSE;
  
  /* Skip commented lines */
  if (stokens[0][0] == '#') return FALSE;
  
  if ( 0 == strcmp( "APPEND_TO_LOG", stokens[0] ) )
  {
    pConfig_a->lAppendToLog=TRUE;
    return FALSE;
  }

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

  if ( 0 == strcmp( "WMOSITE", stokens[0] ) )
  {
    if ( icount < 2 ) return TRUE;
    pConfig_a->WMO_Site = atoi(stokens[1]); 
    return FALSE;
  }

  if ( 0 == strcmp( "LOCALSITE", stokens[0] ) )
  {
    if ( icount < 2 ) return TRUE;
    pConfig_a->SiteNumber = atoi(stokens[1]); 
    return FALSE;
  }

  if ( 0 == strcmp( "GRIDNORTH", stokens[0] ) )
  {
    if ( icount < 2 ) return TRUE;
    pConfig_a->GridNorth = atoi(stokens[1]); 
    return FALSE;
  }

  if ( 0 == strcmp( "GRIDEAST", stokens[0] ) )
  {
    if ( icount < 2 ) return TRUE;
    pConfig_a->GridEast = atoi(stokens[1]); 
    return FALSE;
  }

  if ( 0 == strcmp( "VERBOSE", stokens[0] ) )
  {
    pConfig_a->lVerbose = TRUE;
    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 )
{
  if (CConfig_c.lLogToTerm) fputs( sString_a, stderr );
  if (CConfig_c.lLogToFile)
  {
    fputs( sString_a, CConfig_c.iLogFile );
    fflush( CConfig_c.iLogFile );
  }
}
