/* ********************************************************
 * *                                                      *
 * *  Function to convert an IRIS product to BUFR format  *
 * *                                                      *
 * ********************************************************
 * File: utils/bufr/IrisToBufr.C
 *
 *            COPYRIGHT (c) 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.
 */

#include <errno.h>
#include <locale.h>
#include <stdio.h>
#define INCLUDE_STDIO_PROTOTYPES 1
#include <stdlib.h>
#include <string.h>

extern "C" {
#include "desc.h"
#include "bufr.h"
#include "bitio.h"
#include "rlenc.h"
}

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

#include "himath_lib.h"
#include "misc_lib.h"
#include "user_lib.h"

#include "sigbufr.h"

#define MAXDESCS  (80)

#define DEBUG1_ON 0
#define DEBUG2_ON 0
/* Information from the product useful in the conversion */
struct convert_info
{
  int  bCartesianProduct;  /* Flag to indicate cartesian product */
  int  iBufrProductType;
  int  iBufrProjection;
  struct ymds_time IngTime;
  union level_control Level;
  char sGtsProductType;
  char sGtsProductSubType;
  char spad_end[2];
  const struct i2b_site_table8 *pSite;
};

/* Information from the command line */
struct private_scale_def
{
  UINT4 iflags;
  SINT2 icolcnt;
  UINT2 ilevel_seams[16];
};

struct i2b_site_table8
{
  int   iBlockNumber;
  int   iStationNumber;
  char  sSitename16[20];   /* IRIS Sitename, null terminated */
  SINT4 iSiteNumber;     /* IRIS Sitenumber, range 0-30 */
  char  sGtsBulletinId[4];     /* 2-char GTS product ID ii string */
};
/* Information from the config file to customize the convertor */
struct convert_config8
{
  struct i2b_site_table8 Sites[ MAX_CONVERT_SITES ];
  struct sig_logging_spec Logging;
  UINT1 lEnableGts;            /* Add GTS header/footer to BUFR output */
  UINT1 lEnableMessageLength;  /* Add WMO message length header to BUFR output */
  UINT1 ipad_1x2[2];
  FLT4  fMinReflectivity;      /* Minimum refl. All lower values are thresholded */
  char  sGtsSource[8];         /* 4-char GTS source string */
  SINT4 iSiteCount;
  SINT4 ibufr_edition;         /* BUFR edition number (3 or 4) */
  SINT4 icenter_id;            /* Originating Center id */
  SINT4 isubcenter_id;         /* Originating Subcenter id */
  SINT4 imaster_table;         /* Master table version number */
  SINT4 ilocal_table;          /* Local table version number */
  char  sBufrTablDir[ PATHNAME_SIZE ]; /* Directory containing BUFR tables */
};

struct convert_config8 CConfig_g;

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

static void Usage( void );

static MESSAGE CheckProductHeader
(const struct product_hdr *pheader_a,
 struct convert_info *pConvert_a );

static const struct i2b_site_table8 * FindSiteByName
( const char *sSitename_a,             /* IRIS sitename, NULL terminated */
  const struct convert_config8 *pCConfig_a ); /* Configuration */

static const struct i2b_site_table8 * FindSiteByNumber
( SINT4 iSiteNumber_a,             /* IRIS site number */
  const struct convert_config8 *pCConfig_a ); /* Configuration */

static void ConvertToBufr
(const union generic_product *pProd_a,
 struct convert_info *pConvert_a,
 const struct private_scale_def *pScaleDef_a );

static void ConvertProductHeader
( varfl **pVals_a,
  size_t *pValCount_a,
  struct dd Descr_a[MAXDESCS],
  size_t *pDescrCount_a, 
  const struct product_hdr *pPHeader_a,
  struct convert_info *pConvert_a,    /* Time gets filled in here */
  const struct private_scale_def *pScaleDef_a );

/* Fills in product header info specific to cartesian products only. */
static void ConvertCartesianHeader
( varfl **pVals_a,
  size_t *pValCount_a,
  struct dd Descr_a[MAXDESCS],
  size_t *pDescrCount_a, 
  const struct product_hdr *pPHeader_a,
  struct convert_info *pConvert_a, 
  const struct private_scale_def *pScaleDef_a );

static FLT4 fCheckLevel
( FLT4 fProposedLevel_a,
  FLT4 fMin_a,
  FLT4 fMax_a,
  int  iLevel_a );

static void ConvertProductData
( varfl **pVals_a,
  size_t *pValCount_a,
  struct dd Descr_a[MAXDESCS],
  size_t *pDescrCount_a, 
  const union generic_product *pProd_a,
  const struct convert_info *pConvert_a );

static void ConvertVvpData
( varfl **pVals_a,
  size_t *pValCount_a,
  struct dd Descr_a[MAXDESCS],
  size_t *pDescrCount_a, 
  const struct vvp_product *pVvp_a,
  const struct convert_info *pConvert_a );

static void FillDescr
( struct dd *pDescr_a,   /* Descriptor to fill in */
  int iF_a,
  int iX_a,
  int iY_a );

static int setup_section0125
( char *sec[6],
  size_t secl[6],
  sect_1_t *s1,
  UINT1 bufr_edition_a	);

static void set_sect_1 (sect_1_t * s1_a, const struct ymds_time *pTime_a,
                        uint16_t center_a,     uint16_t sub_center_a,
                        uint8_t  category_a,   uint8_t  sub_category_a,
                        uint8_t  master_tab_a, uint8_t  local_tab_a);


static void ComputeColormap
(UINT2 idtype_a,                    /* Data type */
 const struct product_end *pend_a,
 union level_control *plevel_a,     /* Filled in so we can levelize */
 struct scale_text newscale_a[16],  /* Label text is here */
 const struct private_scale_def *pPrivateScale_a );

static void WriteHeaders
( const struct convert_info *pConvert_a,
  const size_t secl_a[6] );  /* Array of section lengths to follow */

static void WriteGtsTrailer(void);

static void LoadSetup
( struct convert_config8 *pCConfig_a,
  const char* sFile_a,                  /* Filename of config file */
  int iDevice_a );                      /* Device number */

static UINT2 ProcessConfigLine
(char *sline_a,
 struct convert_config8 *pConfig_a );

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

/* ===============================================================
 * Main program starts here.
 */
int main(int iArgc, char *sArgv[])
{
  struct product_hdr ProdHeader;
  struct private_scale_def ScaleDef;
  union generic_product *pInProd;
  MESSAGE istatus;
  SINT4 iCount, iWantCount ;
  char sLog[200], sProdTime[TIMENAME_SIZE];
  struct convert_info Convert;

  setlocale( LC_ALL, "" ) ;

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

  /* Process the setup file, if any */
  LoadSetup( &CConfig_g, "IrisToBufr.conf", 0 );

  /* Open the logging file, if any */
  if (CConfig_g.Logging.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, "IrisToBufr.log" );
      if (CConfig_g.Logging.lAppendToLog)
	CConfig_g.Logging.iLogFile = fopen( sLogPath, "a" );
      else
	CConfig_g.Logging.iLogFile = fopen( sLogPath, "w" );
      if (NULL == CConfig_g.Logging.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, "IrisToBufr" );
    
  if (CConfig_g.Logging.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( &CConfig_g.Logging, sLog );
    }
  
  /* Check for no sites in the config file */
  if (CConfig_g.iSiteCount<1)
    {
      Lprintf( &CConfig_g.Logging, "ERROR: No SITE line in the IrisToBufr.conf file\n" );
      exit(EXIT_FAILURE);
    }

  /* 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( &CConfig_g.Logging, sLog );
      exit(EXIT_FAILURE);
    }

  istatus = CheckProductHeader( &ProdHeader, &Convert );
  if (istatus != SS_NORMAL) exit(EXIT_FAILURE);

  /* Malloc input buffer */
  pInProd  = (union generic_product*)malloc( ProdHeader.hdr.ibytes );
  pInProd->Header = ProdHeader;

  iWantCount = ProdHeader.hdr.ibytes-sizeof(ProdHeader);
  iCount =  fread( (&pInProd->Header)+1, 1, iWantCount, stdin );
  if (iCount != iWantCount)
    {
      sprintf( sLog, "Bad read2, got%d, wanted%d\n", iCount, iWantCount);
      Lprintf( &CConfig_g.Logging, sLog );
      exit(EXIT_FAILURE);
    }
  
  /* Force saturation at the top, threshold if below the bottom.
   */
  ScaleDef.iflags |= COLOR_SCALE_TOP_SAT;
  ConvertToBufr( pInProd, &Convert, &ScaleDef );

  shhmmssddmonyyyy_r(&ProdHeader.pcf.ing_time, sProdTime );
  sprintf( sLog, "Converted %s product: %s, Date: %s\n", 
	   sproduct_name5(ProdHeader.pcf.iptype),
	   nullify(ProdHeader.pcf.spname,12), sProdTime );
  Lprintf( &CConfig_g.Logging, sLog );
  
  exit( EXIT_SUCCESS );
}

/* ======================================================================
 * Function to process the command line arguments.
 */
static void ProcessCommandLine
(int iArgc_a, 
 char *sArgv_a[],
 struct private_scale_def *pScaleDef_a )
{

  int iArg, iIndex;

  /* Zero the scale, and fill in some default numbers */
  memset( pScaleDef_a, 0, sizeof(pScaleDef_a) );
  pScaleDef_a->icolcnt = 14;
  /* Default scale is a reasonable scale for dBZ */
  for (iIndex=0; iIndex<16; iIndex++)
    {
      pScaleDef_a->ilevel_seams[iIndex] = 32767 + (500*iIndex) - 1000 ;
    }
  pScaleDef_a->iflags = COLOR_SCALE_VARIABLE;
  /* Note that the top and bottom saturation flags are picked up from the file
   * pScaleDef_a->iflags = COLOR_SCALE_VARIABLE|COLOR_SCALE_TOP_SAT|COLOR_SCALE_BOT_SAT;
   */

  /* 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, "-seams", 6 ) )
	{
	  char sString[110], *sSubArg;
	  int iValue;

	  strncpy( sString, &sArg[6], 109); sString[109]=0;

	  for (iIndex=0; iIndex<16; iIndex++)
	    {
	      if (iIndex==0)
		sSubArg = strtok( sString, ":" );
	      else
		sSubArg = strtok( NULL, ":" );
	      if (sSubArg==NULL) Usage();
	      if( sscanf( sSubArg, "%d", &iValue ) != 1 )
		{
		  fprintf( stderr, "Could not parse '%s' after '%s'\n", sSubArg, sArg ) ;
		  Usage() ;
		}
	      pScaleDef_a->ilevel_seams[iIndex] = iValue;	      
	    }
	}

      else if (0 == strncmp( sArg, "-colors", 7) )
	{
	  char sString[10], *sSubArg;
	  int iValue;
	  strncpy( sString, &sArg[7], 9); sString[9]=0;
	  sSubArg = strtok( sString, ":" );
	  if (sSubArg==NULL) Usage();
	  if( sscanf( sSubArg, "%d", &iValue ) != 1 )
	    {
	      fprintf( stderr, "Could not parse '%s' after '%s'\n", sSubArg, sArg ) ;
	      Usage() ;
	    }
	  pScaleDef_a->icolcnt = MAX( 2, MIN( 14, iValue));	      
	}

      else if (0 == strncmp( sArg, "-h", 2))
	{
	  Usage();
	}
      
    }
  return;
}

/* ==================================================
 */
static void Usage( void )
{
  fprintf( stderr, "Options: -levels:N1:N2:N3:...:N16\n" );
  fprintf( stderr, "         -colors:N\n" );
  fprintf( stderr, "         -help\n" );
  fprintf( stderr, "         (all other options ignored)\n" );
  exit(EXIT_FAILURE) ;
}
/* =======================================================================
 * Check for some consistency. 
 */
static MESSAGE CheckProductHeader
(const struct product_hdr *pheader_a,
 struct convert_info *pConvert_a )
{
  char sLog[200];
  char sSitename20[20];

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

  /* Make sure that this file really looks like a product.  Check
   * the ID in the product.
   */
  if (pheader_a->hdr.id != ST_PRODUCT_HDR)
    {
      Lprintf( &CConfig_g.Logging, "File header contains invalid ID.\n" ) ;
      return USER_ERROR_SIG;
    }

  pConvert_a->bCartesianProduct = TRUE;
  /* Check that the product type is valid */
  switch( pheader_a->pcf.iptype)
    {
    case PROD_CAPPI:
      pConvert_a->iBufrProductType = BUFR_PTYPE_CAPPI;
      pConvert_a->sGtsProductType = 'C';
      if (pheader_a->pcf.ixscale <= 200000)
	pConvert_a->sGtsProductSubType = 'H';
      else if (pheader_a->pcf.ixscale <= 500000)
	pConvert_a->sGtsProductSubType = 'M';
      else
	pConvert_a->sGtsProductSubType = 'L';	
      break;
    case PROD_PPI:
      pConvert_a->iBufrProductType = BUFR_PTYPE_PPI;
      pConvert_a->sGtsProductType = 'Z';
      if (pheader_a->pcf.ixscale <= 200000)
	pConvert_a->sGtsProductSubType = 'H';
      else if (pheader_a->pcf.ixscale <= 500000)
	pConvert_a->sGtsProductSubType = 'M';
      else
	pConvert_a->sGtsProductSubType = 'L';
      break;
    case PROD_RAIN1:
      pConvert_a->iBufrProductType = BUFR_PTYPE_OTHER;
      pConvert_a->sGtsProductType = 'E';
      if (pheader_a->pcf.ixscale <= 200000)
	pConvert_a->sGtsProductSubType = 'A';
      else if (pheader_a->pcf.ixscale <= 500000)
	pConvert_a->sGtsProductSubType = 'F';
      else
	pConvert_a->sGtsProductSubType = 'K';
      break;
    case PROD_RAINN:
      pConvert_a->iBufrProductType = BUFR_PTYPE_OTHER;
      pConvert_a->sGtsProductType = 'E';
      if (pheader_a->pcf.ixscale <= 200000)
	{
	  if (pheader_a->pcf.psi.rain.irainn_hours <= 1)
	    pConvert_a->sGtsProductSubType = 'A';
	  else if (pheader_a->pcf.psi.rain.irainn_hours <= 3)
	    pConvert_a->sGtsProductSubType = 'B';
	  else if (pheader_a->pcf.psi.rain.irainn_hours <= 6)
	    pConvert_a->sGtsProductSubType = 'C';
	  else if (pheader_a->pcf.psi.rain.irainn_hours <= 12)
	    pConvert_a->sGtsProductSubType = 'D';
	  else 
	    pConvert_a->sGtsProductSubType = 'E';
	}
      else if (pheader_a->pcf.ixscale <= 500000)
	{
	  if (pheader_a->pcf.psi.rain.irainn_hours <= 1)
	    pConvert_a->sGtsProductSubType = 'F';
	  else if (pheader_a->pcf.psi.rain.irainn_hours <= 3)
	    pConvert_a->sGtsProductSubType = 'G';
	  else if (pheader_a->pcf.psi.rain.irainn_hours <= 6)
	    pConvert_a->sGtsProductSubType = 'H';
	  else if (pheader_a->pcf.psi.rain.irainn_hours <= 12)
	    pConvert_a->sGtsProductSubType = 'I';
	  else 
	    pConvert_a->sGtsProductSubType = 'J';
	}
      else
	{
	  if (pheader_a->pcf.psi.rain.irainn_hours <= 1)
	    pConvert_a->sGtsProductSubType = 'K';
	  else if (pheader_a->pcf.psi.rain.irainn_hours <= 3)
	    pConvert_a->sGtsProductSubType = 'L';
	  else if (pheader_a->pcf.psi.rain.irainn_hours <= 6)
	    pConvert_a->sGtsProductSubType = 'M';
	  else if (pheader_a->pcf.psi.rain.irainn_hours <= 12)
	    pConvert_a->sGtsProductSubType = 'N';
	  else 
	    pConvert_a->sGtsProductSubType = 'O';
	}
      break;
    case PROD_SRI:
      pConvert_a->iBufrProductType = BUFR_PTYPE_OTHER;
      pConvert_a->sGtsProductType = 'A';
      if (pheader_a->pcf.ixscale <= 200000)
	pConvert_a->sGtsProductSubType = 'H';
      else if (pheader_a->pcf.ixscale <= 500000)
	pConvert_a->sGtsProductSubType = 'M';
      else
	pConvert_a->sGtsProductSubType = 'L';
      break;
    case PROD_TOPS:
      pConvert_a->iBufrProductType = BUFR_PTYPE_OTHER;
      pConvert_a->sGtsProductType = 'D';
      if (pheader_a->pcf.ixscale <= 200000)
	pConvert_a->sGtsProductSubType = 'H';
      else if (pheader_a->pcf.ixscale <= 500000)
	pConvert_a->sGtsProductSubType = 'M';
      else
	pConvert_a->sGtsProductSubType = 'L';
      break;
    case PROD_VVP:
      pConvert_a->bCartesianProduct = FALSE;
      pConvert_a->sGtsProductType = 'G';
      pConvert_a->sGtsProductSubType = 'A';

      if (! (pheader_a->pcf.psi.vvp.ivvp_switch & (VVP_HVEL|VVP_HDIR)))  
	{
	  sprintf( sLog, "VVP Product did not incluce horizontal winds.\n" );
	  Lprintf( &CConfig_g.Logging, sLog );
	  return USER_ERROR_SIG;
	}

      break;
    default:
      sprintf( sLog, "Product type %s (%d) not supported.\n", 
	       sproduct_name5(pheader_a->pcf.iptype), pheader_a->pcf.iptype );
      Lprintf( &CConfig_g.Logging, sLog );
      return USER_ERROR_SIG;
    }

  /* Check to see if we can find the sitename in the site mapping table.  Note
   * that wildcards are allowed.
   */
  nullify_ncpy( sSitename20, pheader_a->end.sing_sitename, 16 );
  pConvert_a->pSite = FindSiteByName( sSitename20, &CConfig_g );
  if (!pConvert_a->pSite)
    {
      sprintf( sLog, "ERROR, IRIS sitename '%s' not found in config site list\n", 
	       sSitename20 );
      Lprintf( &CConfig_g.Logging, sLog );
      return USER_ERROR_SIG;
    }

  /* Checks below only needed for cartesian products */
  if (!pConvert_a->bCartesianProduct) return SS_NORMAL;

  /* Check that the data type is valid */
  switch( pheader_a->pcf.idata_out)
    {
    case DB_DBZ:
    case DB_DBZ2:
    case DB_DBZC:
    case DB_DBZC2:
      //    case DB_FLIQUID2:
    case DB_HEIGHT:
    case DB_LDRH:
    case DB_LDRH2:
    case DB_LDRV:
    case DB_LDRV2:
    case DB_RAINRATE2:
    case DB_VEL:
    case DB_VEL2:
    case DB_VIL2:
    case DB_WIDTH:
    case DB_WIDTH2:
    case DB_ZDR:
    case DB_ZDR2:
      break;

    default:
      sprintf( sLog, "Data type %s is not supported.\n", 
	       sdata_name6(pheader_a->pcf.idata_out) );
      Lprintf( &CConfig_g.Logging, sLog );
      return USER_ERROR_SIG;
    }

  /* Check that the projection type is valid */
  switch( pheader_a->pcf.iprojection_type)
    {
    case MAPROJ_AZIMUTHAL:
      pConvert_a->iBufrProjection=BUFR_PROJ_AZIMUTHAL;
      break;
    case MAPROJ_MERCATOR:
      pConvert_a->iBufrProjection=BUFR_PROJ_MERCATOR;
      break;
    case MAPROJ_POLSTEREO:
      pConvert_a->iBufrProjection=BUFR_PROJ_POLSTEREO;
      break;
    case MAPROJ_LAMBERT:
      pConvert_a->iBufrProjection=BUFR_PROJ_LAMBERTS;
      break;
    case MAPROJ_GNOMONIC:
      pConvert_a->iBufrProjection=BUFR_PROJ_GNOMONIC;
      break;
    default:
      sprintf( sLog, "Projection type %s (%d) not supported.\n", 
	       sProjectionName32(pheader_a->pcf.iprojection_type),
	       pheader_a->pcf.iprojection_type );
      Lprintf( &CConfig_g.Logging, sLog );
      return USER_ERROR_SIG;
    }

  /* Check that the line length is short enough to for OPERA software */
  if (pheader_a->pcf.ixsize>BUFR_LBUFLEN)
    {
      sprintf( sLog, "Product width of %d pixels exceeds max of %d.\n", 
	       pheader_a->pcf.ixsize, BUFR_LBUFLEN );
      Lprintf( &CConfig_g.Logging, sLog );
      return USER_ERROR_SIG;
    }

  return SS_NORMAL;
}
/* =====================================================
 * Search to find the sitename.
 */
static const struct i2b_site_table8 * FindSiteByName
( const char *sSitename16_a,             /* IRIS sitename, NULL terminated */
  const struct convert_config8 *pCConfig_a ) /* Configuration */
{
  SINT4 iSite;
  for (iSite=0; iSite<pCConfig_a->iSiteCount; iSite++)
    {
      const struct i2b_site_table8 *pSite;
      pSite = &(pCConfig_a->Sites[iSite]);
      if (sig_fnmatch( pSite->sSitename16, sSitename16_a ))
	{
	  return pSite;
	}
    }
  return NULL;
}
/* =====================================================
 * Search to find the sitename.
 */
static const struct i2b_site_table8 * FindSiteByNumber
( SINT4 iSiteNumber_a,             /* IRIS site number */
  const struct convert_config8 *pCConfig_a ) /* Configuration */
{
  SINT4 iSite;
  for (iSite=0; iSite<pCConfig_a->iSiteCount; iSite++)
    {
      const struct i2b_site_table8 *pSite;
      pSite = &(pCConfig_a->Sites[iSite]);
      if ( pSite->iSiteNumber == iSiteNumber_a )
	{
	  return pSite;
	}
    }
  return NULL;
}

#define DATA_CATEGORY_RADAR  6

/* ==============================================================
 * Convert product to BUFR.  Entered with a pointer to the beginning 
 * of the full product.  Should exit if it gets an error.
 */
static void ConvertToBufr
(const union generic_product *pProd_a,
 struct convert_info *pConvert_a,
 const struct private_scale_def *pScaleDef_a )
{
  struct dd Descr[MAXDESCS];
  varfl *Vals;
  size_t iValCount, iDescrCount;
  char *sec[6];
  size_t secl[6], iSize;
  int lOk, ii;
  char sLog[200];
  sect_1_t s1;

#if (DEBUG1_ON)
    Lprintf( &CConfig_g.Logging, "Calling ConvertToBufr\n" );
#endif

  set_sect_1(&s1, &pConvert_a->IngTime, CConfig_g.icenter_id, CConfig_g.isubcenter_id, 
             DATA_CATEGORY_RADAR, 0, CConfig_g.imaster_table, CConfig_g.ilocal_table );

  /* Load the BUFR tables */
  if (read_tables(CConfig_g.sBufrTablDir, s1.vmtab, s1.vltab, s1.subcent, s1.gencent) < 0)
  {
    sprintf( sLog, "Error reading BUFR tables, Master:%d, Local:%d\n",
	     CConfig_g.imaster_table, CConfig_g.ilocal_table );
    Lprintf( &CConfig_g.Logging, sLog );
    exit(EXIT_FAILURE);
  }

  Vals = NULL;
  iValCount = 0;
  iDescrCount = 0;

  /* Fill in the product header */
  ConvertProductHeader( &Vals, &iValCount, Descr, &iDescrCount, &pProd_a->Header,
			pConvert_a, pScaleDef_a );

  ConvertProductData( &Vals, &iValCount, Descr, &iDescrCount, pProd_a, pConvert_a );

  /* Pack data sections 3 and 4 */
  bufr_create_msg( Descr, iDescrCount, Vals, (void **)&sec[4], (void **)&sec[3],
		   &secl[4], &secl[3] );
  free( Vals );
  Vals = NULL;

  /* Fill in remaining sections */
  lOk = setup_section0125( sec, secl, &s1, CConfig_g.ibufr_edition );

  if (!lOk)
    {
      Lprintf( &CConfig_g.Logging, "Error in setup_section0125\n" );
      exit(EXIT_FAILURE);
    } 

  /* Optionaly write the desired headers */
  WriteHeaders( pConvert_a, secl );

  /* Write data out to standard output */
  for (ii = 0; ii < 6; ii++)
    {
      if (secl[ii])
	{
	  iSize = fwrite( sec[ii], 1, secl[ii], stdout);
	  if ( iSize != secl[ii])
	    {
	      sprintf( sLog, "Errno %d in fwrite\n", errno );
	      Lprintf( &CConfig_g.Logging, sLog );
	      exit(EXIT_FAILURE);
	    }
	}
    }
  WriteGtsTrailer( );
  return;
}

/* ================================================================
 * Convert the product header.
 */
static void ConvertProductHeader
( varfl **pVals_a,
  size_t *pValCount_a,
  struct dd Descr_a[MAXDESCS],
  size_t *pDescrCount_a, 
  const struct product_hdr *pPHeader_a,
  struct convert_info *pConvert_a,     /* Time gets filled in here */
  const struct private_scale_def *pScaleDef_a )
{  
  UINT2 iDays, iHour, iMinute, iSecond;
  SINT4 iTimeOffset;

#if (DEBUG1_ON)
    Lprintf( &CConfig_g.Logging, "Calling ConvertProductHeader\n" );
#endif

  /* WMO ID */
  FillDescr( &Descr_a[(*pDescrCount_a)++], 3, 1, 1 );
  val_to_array( pVals_a, pConvert_a->pSite->iBlockNumber, pValCount_a );
  val_to_array( pVals_a, pConvert_a->pSite->iStationNumber, pValCount_a );

  pConvert_a->IngTime = pPHeader_a->pcf.ing_time;
  iTimeOffset = 60*(SINT4)pPHeader_a->end.iMinutesWest;
  ymds_add_seconds( iTimeOffset, &pConvert_a->IngTime );

  /* Date of ingest data */
  FillDescr( &Descr_a[(*pDescrCount_a)++], 3, 1, 11 );
  val_to_array( pVals_a, pConvert_a->IngTime.iyear, pValCount_a );
  val_to_array( pVals_a, pConvert_a->IngTime.imon,  pValCount_a );
  val_to_array( pVals_a, pConvert_a->IngTime.iday,  pValCount_a );

  /* Time of ingest data */
  FillDescr( &Descr_a[(*pDescrCount_a)++], 3, 1, 12 );
  unpacktime( pConvert_a->IngTime.isec, &iDays, &iHour, &iMinute, &iSecond );
  val_to_array( pVals_a, iHour, pValCount_a );
  val_to_array( pVals_a, iMinute,  pValCount_a );

  if (pConvert_a->bCartesianProduct)
    ConvertCartesianHeader( pVals_a, pValCount_a, Descr_a, pDescrCount_a, pPHeader_a,
			    pConvert_a, pScaleDef_a );

  FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 2, 3 );
  val_to_array( pVals_a, (FLT8)3, pValCount_a );  /* 3 means radar */

  /* Many attributes only applicable to a single radar system */
  if (!(pPHeader_a->pcf.iflags & PF_COMPOSITED))
    {
      if (pPHeader_a->end.ilambda>0)
	{
	  FLT8 fFrequency, fWavelength;
	  fWavelength = ((FLT8)pPHeader_a->end.ilambda)/100.0;
	  fFrequency = 100000.0*FSPEED_OF_LIGHT/fWavelength;
	  /* RF Frequency */
	  FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 2, 121 );
	  val_to_array( pVals_a, fFrequency, pValCount_a );
	}

      /* PRF */
      FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 2, 125 );
      val_to_array( pVals_a, pPHeader_a->end.iprf, pValCount_a ); 

      /* Sample size */
      FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 25, 3 );
      val_to_array( pVals_a, pPHeader_a->end.isamp, pValCount_a ); 

      /* For VVP products, fill in the full position of the profile */
      if (PROD_VVP == pPHeader_a->pcf.iptype)
	{
	  FLT8 fAngle;
	  FillDescr( &Descr_a[(*pDescrCount_a)++], 3, 1, 22 );
	  fAngle = fDegFromBin4( pPHeader_a->end.ilat_center );
	  val_to_array( pVals_a, fAngle, pValCount_a );
	  fAngle = fDegFromBin4( pPHeader_a->end.ilon_center );
	  val_to_array( pVals_a, fAngle,  pValCount_a );
	  /* Height of radome */
	  val_to_array( pVals_a, (pPHeader_a->end.ignd_hgt+pPHeader_a->end.irad_hgt), 
			pValCount_a ); 
	}
      else
	{
	  /* Height of radome */
	  FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 7, 1 );
	  val_to_array( pVals_a, (pPHeader_a->end.ignd_hgt+pPHeader_a->end.irad_hgt), 
			pValCount_a ); 
	}
    }

  /* Product type specific info */
  switch( pPHeader_a->pcf.iptype)
    {
    case PROD_CAPPI:
      /* Height of CAPPI */
      FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 7, 7 );
      val_to_array( pVals_a, ((FLT8)pPHeader_a->pcf.psi.cappi.icappi_hgt)/100.0, pValCount_a ); 
      break;
    case PROD_PPI:
      /* Elevation angle of PPI */
      FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 2, 135 );
      val_to_array( pVals_a, fElDegFromBin2(pPHeader_a->pcf.psi.ppi.ippi_el), pValCount_a ); 
      break;
    case PROD_RAIN1:
    case PROD_RAINN:
      /* Means time is ending time of interval */
      FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 8, 21 );
      val_to_array( pVals_a, 3.0, pValCount_a ); 
      {
	SINT4 idwell_in_seconds, iDays, iHours, iMinutes;
	if (PROD_RAIN1 == pPHeader_a->pcf.iptype)
	  {
	    idwell_in_seconds = pPHeader_a->pcf.psi.rain.iSeconds;
	    if (idwell_in_seconds==0) idwell_in_seconds = 3600;
	  }
	else
	  {
	    idwell_in_seconds = 3600*(UINT4)pPHeader_a->pcf.psi.rain.irainn_hours;
	    idwell_in_seconds = +pPHeader_a->pcf.psi.rain.iSeconds;
	  }

	iDays = idwell_in_seconds/(24*3600);
	idwell_in_seconds -= iDays*(24*3600);
	FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 4, 23 );
	val_to_array( pVals_a, (FLT8)-iDays, pValCount_a ); 

	iHours = idwell_in_seconds/3600;
	idwell_in_seconds -= iHours*3600;
	FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 4, 24 );
	val_to_array( pVals_a, (FLT8)-iHours, pValCount_a ); 

	iMinutes = idwell_in_seconds/60;
	idwell_in_seconds -= iMinutes*60;
	FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 4, 25 );
	val_to_array( pVals_a, (FLT8)-iMinutes, pValCount_a ); 

	FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 4, 26 );
	val_to_array( pVals_a, (FLT8)-idwell_in_seconds, pValCount_a ); 
      }
      /* Number of inputs to the rain accumulation */
      FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 8, 22 );
      val_to_array( pVals_a, (FLT8)pPHeader_a->end.input_cnt, pValCount_a ); 
      break;
    case PROD_SRI:
      break;
    case PROD_TOPS:
      /* Reflectivity threshold in dBZ */
      FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 21, 1 );
      val_to_array( pVals_a, ((FLT8)pPHeader_a->pcf.psi.top.iZThreshold)/16.0, pValCount_a ); 
      break;
    }

  return;
}
/* ==========================================================
 * Fills in product header info specific to cartesian products only.
 */
static void ConvertCartesianHeader
( varfl **pVals_a,
  size_t *pValCount_a,
  struct dd Descr_a[MAXDESCS],
  size_t *pDescrCount_a, 
  const struct product_hdr *pPHeader_a,
  struct convert_info *pConvert_a, 
  const struct private_scale_def *pScaleDef_a )
{
  MESSAGE istatus;
  struct mpj_projection8 Proj;
  struct mpj_region8     Region;
  struct latlon8 Earth, EarthUL;
  struct XYPoint8 Pixels; UINT1 lValid;
  FLT8 fXScale, fYScale;
  char sLog[200];

  /* Compute projection region */
  istatus = phdr_pin_region( &Proj, &Region, pPHeader_a, NULL );
  if (istatus!=SS_NORMAL) { sig_signal( istatus ); exit(EXIT_FAILURE); }

  /* Four corners of the image */
  /* Top left */
  Pixels.fX = 0.0;
  Pixels.fY = Region.fypix;
  istatus = mpj_pixel_to_earth( &Proj, &Region, &Pixels, &Earth );
  if (istatus!=SS_NORMAL) { sig_signal( istatus ); exit(EXIT_FAILURE); }

  EarthUL = Earth;
  FillDescr( &Descr_a[(*pDescrCount_a)++], 3, 1, 23 );
  val_to_array( pVals_a, (180.0/FPI)*Earth.fLat, pValCount_a );
  val_to_array( pVals_a, (180.0/FPI)*Earth.fLon,  pValCount_a );

  /* Top Right */
  FillDescr( &Descr_a[(*pDescrCount_a)++], 3, 1, 23 );
  val_to_array( pVals_a, (180.0/FPI)*Region.Loc2.fLat, pValCount_a );
  val_to_array( pVals_a, (180.0/FPI)*Region.Loc2.fLon,  pValCount_a );

  /* Bottom Right */
  Pixels.fX = Region.fxpix;
  Pixels.fY = 0.0;
  istatus = mpj_pixel_to_earth( &Proj, &Region, &Pixels, &Earth );
  if (istatus!=SS_NORMAL) { sig_signal( istatus ); exit(EXIT_FAILURE); }

  FillDescr( &Descr_a[(*pDescrCount_a)++], 3, 1, 23 );
  val_to_array( pVals_a, (180.0/FPI)*Earth.fLat, pValCount_a );
  val_to_array( pVals_a, (180.0/FPI)*Earth.fLon,  pValCount_a );

  /* Bottom Left */
  FillDescr( &Descr_a[(*pDescrCount_a)++], 3, 1, 23 );
  val_to_array( pVals_a, (180.0/FPI)*Region.Loc0.fLat, pValCount_a );
  val_to_array( pVals_a, (180.0/FPI)*Region.Loc0.fLon,  pValCount_a );

  /* Pixel sizes (scale in meters/pixel) */
  fXScale = (2000.0*Region.fde)/Region.fxpix;
  FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 5, 33 );
  val_to_array( pVals_a, fXScale, pValCount_a );
  fYScale = (2000.0*Region.fdn)/Region.fypix;
  FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 6, 33 );
  val_to_array( pVals_a, fYScale, pValCount_a );

  /* Pixel count */
  FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 30, 21 );
  val_to_array( pVals_a, (FLT8)pPHeader_a->pcf.ixsize, pValCount_a );
  FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 30, 22 );
  val_to_array( pVals_a, (FLT8)pPHeader_a->pcf.iysize, pValCount_a );

  /* Projection type */
  FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 29, 1 );
  val_to_array( pVals_a, (FLT8)pConvert_a->iBufrProjection, pValCount_a );

  /* Projection reference point */
  {
    FLT8 fLatRef = (180.0/FPI)*Proj.Ref.fLat;
    FLT8 fLonRef = (180.0/FPI)*Proj.Ref.fLon;

    if (MAPROJ_POLSTEREO == Proj.iprojection_type) fLatRef = 90.0;
    FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 29, 193 );
    val_to_array( pVals_a, fLonRef, pValCount_a );

    FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 29, 194 );
    val_to_array( pVals_a, fLatRef, pValCount_a );
  }

  /* Insert the Offsets from the projection origin numbers */
  {
    struct XYPoint8 MapRef, MapUL;
    istatus = mpj_earth_to_map( &Proj, &(Proj.Ref), &MapRef, &lValid ) ;
    if (istatus!=SS_NORMAL) { sig_signal( istatus ); exit(EXIT_FAILURE); }

    istatus = mpj_earth_to_map( &Proj, &EarthUL, &MapUL, &lValid ) ;
    if (istatus!=SS_NORMAL) { sig_signal( istatus ); exit(EXIT_FAILURE); }

    FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 29, 195 );
    val_to_array( pVals_a, 1000.0*(MapUL.fX-MapRef.fX), pValCount_a );
    FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 29, 196 );
    val_to_array( pVals_a, 1000.0*(MapUL.fY-MapRef.fY), pValCount_a );
  }
  /* Standard parallels only specified for Lamberts projection */
  if (BUFR_PROJ_LAMBERTS == pConvert_a->iBufrProjection )
    {
      FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 29, 197 );
      val_to_array( pVals_a, (180.0/FPI)*Proj.fStdPar1, pValCount_a );
      FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 29, 198 );
      val_to_array( pVals_a, (180.0/FPI)*Proj.fStdPar2, pValCount_a );
    }
  /* Insert the Earth's semi-major and minor axes */
  {
    FLT8 fa, fb, fe, ff;
    mpj_get_geode_info( &fa, &fb, &fe, &ff, &Proj );
    FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 29, 199 );
    val_to_array( pVals_a, fa, pValCount_a );
    FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 29, 200 );
    val_to_array( pVals_a, fb, pValCount_a );
  }

  /* Image type */
  FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 30, 31 );
  if (pPHeader_a->pcf.iflags & PF_COMPOSITED)
    val_to_array( pVals_a, (FLT8)BUFR_PTYPE_COMPOSITE, pValCount_a );
  else
    val_to_array( pVals_a, (FLT8)pConvert_a->iBufrProductType, pValCount_a );

  /* Coordinate grid */
  FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 29, 2 );
  val_to_array( pVals_a, (FLT8)BUFR_GRID_CARTESIAN, pValCount_a );

  if (pPHeader_a->pcf.iflags & PF_COMPOSITED)
    {
      SINT4 iSite, iSiteCount;

      iSiteCount=0;
      /* Count the sites which are individually tagged */
      for (iSite=0; iSite<31; iSite++)
	{
	  if (1 & (pPHeader_a->end.iSiteMask>>iSite)) iSiteCount++;
	}
      if (iSiteCount==0)
	{
	  Lprintf( &CConfig_g.Logging, "Composite site mask empty.\n" );
	  exit(EXIT_FAILURE);
	}
      FillDescr( &Descr_a[(*pDescrCount_a)++], 3, 21, 250 );
      val_to_array( pVals_a, (FLT8)iSiteCount, pValCount_a );

      /* Insert data for all the sites */
      for (iSite=0; iSite<31; iSite++)
	{
	  if (1 & (pPHeader_a->end.iSiteMask>>iSite))
	    {
	      const struct i2b_site_table8 *pSite;
	      pSite = FindSiteByNumber( iSite, &CConfig_g );
	      if (pSite)
		{
		  val_to_array( pVals_a, pSite->iBlockNumber, pValCount_a );
		  val_to_array( pVals_a, pSite->iStationNumber, pValCount_a );
		  val_to_array( pVals_a, (FLT8)1, pValCount_a );  /* Data present ind. */
		  val_to_array( pVals_a, (FLT8)BUFR_QUALITY3_UNKNOWN, pValCount_a );
		}
	      else
		{
		  sprintf( sLog, "Site #%d not in site table.\n", iSite );
		  Lprintf( &CConfig_g.Logging, sLog );
		  exit(EXIT_FAILURE);
		}
	    }
	}
    }
  else
    {
      /* Quality factor */
      int iQuality;
      FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 33, 3 );
      if (pPHeader_a->end.iFaultBits & (ICFB_BITE_CRITICAL|ICFB_RCP_CRITICAL|ICFB_SYS_CRITICAL))
	iQuality = BUFR_QUALITY3_HIGHLY_SUSPECT;
      else if (pPHeader_a->end.iFaultBits)
	iQuality = BUFR_QUALITY3_SLIGHTLY_SUSPECT;
      else
	iQuality = BUFR_QUALITY3_NOT_SUSPECT;
      val_to_array( pVals_a, (FLT8)iQuality, pValCount_a );
    }

  {
    struct scale_text Scale[17];  /* Label text is here */
    int iLevel;
    FLT4 fScale, fMax, fMin, fLevel;
    /* Compute color mapping tables */
    ComputeColormap( pPHeader_a->pcf.idata_out, &pPHeader_a->end, 
		     &(pConvert_a->Level), Scale, pScaleDef_a );
    fScale = 1.0;
    switch (pPHeader_a->pcf.idata_out)
      {
      case DB_DBZ:
      case DB_DBZ2:
      case DB_DBZC:
      case DB_DBZC2:     /* Maps to 0 21 1 */
	FillDescr( &Descr_a[(*pDescrCount_a)++], 3, 13,  9 );  /* Horizontal Z, 7-bits */
	fMin = -63.0; fMax = 63.0;
	break;
      case DB_RAINRATE2: /* Maps to 0 21 36 */
	FillDescr( &Descr_a[(*pDescrCount_a)++], 3, 13, 10 );  /* Rainrate, 12-bits */
	fMin = -0.1; fMax = 409.6;
	break;
      case DB_ZDR:
      case DB_ZDR2:
	FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 21,  3 );  /* ZDR, 7-bits */
	FillDescr( &Descr_a[(*pDescrCount_a)++], 1,  1,  0 );
	FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 31,  1 );
	FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 21,  3 );
	fMin = -0.4; fMax = 12.2;
	break;
      case DB_LDRH:
      case DB_LDRH2:
      case DB_LDRV:
      case DB_LDRV2:
	FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 21,  5 );  /* LDR, 6-bits */
	FillDescr( &Descr_a[(*pDescrCount_a)++], 1,  1,  0 );
	FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 31,  1 );
	FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 21,  5 );
	fMin = -63.0; fMax = -1.0;
	break;
      case DB_VEL:
      case DB_VEL2:
	FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 21, 14 );  /* Radial velocity, 13-bits */
	FillDescr( &Descr_a[(*pDescrCount_a)++], 1,  1,  0 );
	FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 31,  1 );
	FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 21, 14 );
	fMin = -409.5; fMax = 409.5;
	break;
      case DB_VIL2:
	FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 21, 31 );  /* VIL, 7-bits */
	FillDescr( &Descr_a[(*pDescrCount_a)++], 1,  1,  0 );
	FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 31,  1 );
	FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 21, 31 );
	fMin = 1.0; fMax = 127.0;  /* Unit are kg/m**2, need a conversion */
	break;
      case DB_WIDTH:
      case DB_WIDTH2:
	FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 21, 17 );  /* Spectrum Width, 8-bits */
	FillDescr( &Descr_a[(*pDescrCount_a)++], 1,  1,  0 );
	FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 31,  1 );
	FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 21, 17 );
	fMin = 0.1; fMax = 25.5;
	break;
      case DB_HEIGHT:
	FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 21, 21 );  /* Echo Tops, 4-bits */
	FillDescr( &Descr_a[(*pDescrCount_a)++], 1,  1,  0 );
	FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 31,  1 );
	FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 21, 21 );
	fMin = 1.0; fMax = 15.0;
	fScale = 1000.0;  /* IRIS uses km, BUFR uses meters */
	break;
      default:
	      fMin = 0.0; fMax = 0.0;
      }
    /* Note that we only send the N lower seams */
    fLevel = fScale * fCheckLevel( Scale[0].fLowerSeam, fMin, fMax, 0 );
    val_to_array( pVals_a, (FLT8)fLevel, pValCount_a );
    val_to_array( pVals_a, (FLT8)(pScaleDef_a->icolcnt-1), pValCount_a );
   
    for (iLevel=1; iLevel<pScaleDef_a->icolcnt; iLevel++)
      {
	fLevel = fScale * fCheckLevel( Scale[iLevel].fLowerSeam, fMin, fMax, iLevel );
	val_to_array( pVals_a, (FLT8)fLevel, pValCount_a );
      }

  }

  /* Starting here are optional elements, not required by the OPERA
   * spec.
   */

  /* If output data is rainrate, or rainfall accumulation, then
   * include the Z/R relationship.
   */
  if (pPHeader_a->pcf.idata_out == DB_RAINRATE2 )
    {
      FillDescr( &Descr_a[(*pDescrCount_a)++], 3, 21, 8 );
      if ((pPHeader_a->pcf.idata_in == DB_KDP )||
	  (pPHeader_a->pcf.idata_in == DB_KDP2 ))
	val_to_array( pVals_a, (FLT8)BUFR_OTHER_TO_R, pValCount_a );
      else
	val_to_array( pVals_a, (FLT8)BUFR_ZH_TO_R, pValCount_a );
      val_to_array( pVals_a, ((FLT8)pPHeader_a->pcf.izr_const)/1000.0,  pValCount_a );
      val_to_array( pVals_a, ((FLT8)pPHeader_a->pcf.izr_exp)/1000.0,  pValCount_a );
    }
return;
}
/* ==============================================================
 * Check the proposed level against limits, and report any over/underflow.
 */
static FLT4 fCheckLevel
( FLT4 fProposedLevel_a,
  FLT4 fMin_a,
  FLT4 fMax_a,
  int  iLevel_a )
{
  FLT4 fLevel;
  char sLog[200];
  fLevel = fProposedLevel_a;
  if (fLevel>fMax_a)
    {
      sprintf( sLog, "Level %d value of %5.2f exceeds max of %5.2f\n", iLevel_a,
	       fLevel, fMax_a );
      Lprintf( &CConfig_g.Logging, sLog );
      fLevel = fMax_a;
    }
  else
  if (fLevel<fMin_a)
    {
      sprintf( sLog, "Level %d value of %5.2f below min of %5.2f\n", iLevel_a,
	       fLevel, fMin_a );
      Lprintf( &CConfig_g.Logging, sLog );
      fLevel = fMin_a;      
    }
  return fLevel;
}

/* ================================================================
 * Convert the product data section.
 */
static void ConvertProductData
( varfl **pVals_a,
  size_t *pValCount_a,
  struct dd Descr_a[MAXDESCS],
  size_t *pDescrCount_a, 
  const union generic_product *pProd_a,
  const struct convert_info *pConvert_a )
{
  int iLine, lOk;
  UINT1 OutBuffer[MAX_PRODUCT_SIZE];
  SINT4 iRes;
  const UINT1 *pInBuffer;
  const struct product_hdr *pPHeader;

#if (DEBUG1_ON)
    Lprintf( &CConfig_g.Logging, "Calling ConvertProductData\n" );
#endif

  /* Pull out special products here */
  switch( pProd_a->Header.pcf.iptype )
    {
    case PROD_VVP:
      ConvertVvpData( pVals_a, pValCount_a, Descr_a, pDescrCount_a, &pProd_a->Vvp, 
		      pConvert_a );
      return;
      break;
    default:
      break;
    }

  pInBuffer = pProd_a->Cartesian.Data;
  pPHeader = &(pProd_a->Header);
  iRes = idata_resolution( pPHeader->pcf.idata_out );

  FillDescr( &Descr_a[(*pDescrCount_a)++], 3, 21, 192 );  /* 4-bit data */

  /* output number of rows */
  val_to_array (pVals_a, (varfl)pPHeader->pcf.iysize, pValCount_a);  

  /* Fill in the data array, one line at a time */
  /* Now copy the data, levelizing it to 4-bits/pixel */
  for (iLine=pPHeader->pcf.iysize-1; iLine>=0; iLine--)
    {
      const UINT1 *pInLine = pInBuffer + (iLine * pPHeader->pcf.ixsize * (iRes/8));
      levelize_8bit( pInLine, pPHeader->pcf.ixsize, &(pConvert_a->Level), iRes,
		     OutBuffer );
      lOk = rlenc_compress_line((pPHeader->pcf.iysize-1)-iLine, OutBuffer, 
				pPHeader->pcf.ixsize, pVals_a, pValCount_a);
      if (!lOk)
	{
	  char sLog[80];
	  sprintf( sLog, "rlenc_compress_line faile on line %d\n", iLine );
	  Lprintf( &CConfig_g.Logging, sLog );
	  exit( EXIT_FAILURE);
	}     
    }

}
/* ================================================================
 * Convert the vvp data section.
 */
static void ConvertVvpData
( varfl **pVals_a,
  size_t *pValCount_a,
  struct dd Descr_a[MAXDESCS],
  size_t *pDescrCount_a, 
  const struct vvp_product *pVvp_a,
  const struct convert_info *pConvert_a )
{
  SINT4 iIndex;

#if (DEBUG1_ON)
  Lprintf( &CConfig_g.Logging, "Calling ConvertVvpData\n" );
#endif

  FillDescr( &Descr_a[(*pDescrCount_a)++], 1, 6, 0 );
  FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 31, 1 );
  val_to_array( pVals_a, (FLT8)pVvp_a->Header.pcf.psi.vvp.ivvp_intervals, pValCount_a );

  for (iIndex=0; iIndex<pVvp_a->Header.pcf.psi.vvp.ivvp_intervals; iIndex++)
    {
      const struct vvp_results *pVvp = &pVvp_a->Results[iIndex] ;

      /* Height in meters */
      if (iIndex==0) FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 7, 7 );
      val_to_array( pVals_a, ((FLT8)pVvp->iheight)/100.0, pValCount_a );

      /* Wind direction in degrees */
      if (iIndex==0)FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 11, 1 );
      val_to_array( pVals_a, ((FLT8)pVvp->ihdir)/10.0, pValCount_a );

      /* Horizontal wind speed in m/s */
      if (iIndex==0)FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 11, 1 );
      val_to_array( pVals_a, ((FLT8)pVvp->ihvel)/100.0, pValCount_a );

      /* Horizontal wind quality info */
      if (iIndex==0)FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 33, 2 );
      if ((pVvp->icount==0)||(pVvp->ihvel_std==32767))
	val_to_array( pVals_a, (FLT8)BUFR_QUALITY2_SUSPECT, pValCount_a );
      else
	val_to_array( pVals_a, (FLT8)BUFR_QUALITY2_NOT_SUSPECT, pValCount_a );

      /* Vertical wind speed in m/s */
      if (iIndex==0)FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 11, 6 );
      val_to_array( pVals_a, ((FLT8)pVvp->ivvel)/100.0, pValCount_a );

      /* Vertical wind Quality info */
      if (iIndex==0)FillDescr( &Descr_a[(*pDescrCount_a)++], 0, 33, 2 );
      /* Force suspect if not even recorded */
      if ( (!(pVvp_a->Header.pcf.psi.vvp.ivvp_switch & VVP_VVEL)) ||
	   (pVvp->icount==0) || (pVvp->ivvel_std==32767) )
	val_to_array( pVals_a, (FLT8)BUFR_QUALITY2_SUSPECT, pValCount_a );
      else
	val_to_array( pVals_a, (FLT8)BUFR_QUALITY2_NOT_SUSPECT, pValCount_a );
    }
  return;
}

/* ==================================================================
 * Fill one descriptor slot.
 */
static void FillDescr
( struct dd *pDescr_a,   /* Descriptor to fill in */
  int iF_a,
  int iX_a,
  int iY_a )
{
  pDescr_a->f = iF_a;
  pDescr_a->x = iX_a;
  pDescr_a->y = iY_a;
}
/*===========================================================================
 *
 *
 * This function creates sections 0, 1, 2 and 5. The function returns 1 on
 *  success, 0 on a fault.
 */
static int setup_section0125
( char *sec[6],
  size_t secl[6],
  sect_1_t *s1,
  UINT1 bufr_edition_a )
{
  int i, hand;
  long len;

/******* create section 1. */

  hand = bitio_o_open ();
  if (hand == -1)
    return 0;

  if (bufr_edition_a < 4)
  {
    bitio_o_append (hand, 24L, 24);                       /* length of section */
    bitio_o_append (hand, s1->mtab, 8);                   /* master table used */
    bitio_o_append (hand, s1->subcent, 8);                /* originating/generating subcenter */
    bitio_o_append (hand, s1->gencent, 8);                /* originating/generating center */
    bitio_o_append (hand, s1->updsequ, 8);                /* original BUFR message */
    bitio_o_append (hand, s1->opsec, 8);                  /* no optional section */
    bitio_o_append (hand, s1->dcat, 8);                   /* message type */
    bitio_o_append (hand, s1->dcatst, 8);                 /* international message subtype */
    bitio_o_append (hand, s1->vmtab, 8);                  /* version number of master table used */
    bitio_o_append (hand, s1->vltab, 8);                  /* version number of local table used */
    bitio_o_append (hand, (((s1->year-1) % 100) + 1), 8); /* Year of the century */
    bitio_o_append (hand, s1->mon, 8);                    /* month */
    bitio_o_append (hand, s1->day, 8);                    /* day */
    bitio_o_append (hand, s1->hour, 8);                   /* hour */
    bitio_o_append (hand, s1->min, 8);                    /* minute */
    bitio_o_append (hand, (long) ' ', 8);                 /* Pad to the end of the section (6 bytes) */
    bitio_o_append (hand, (long) ' ', 8);
    bitio_o_append (hand, (long) ' ', 8);
    bitio_o_append (hand, (long) ' ', 8);
    bitio_o_append (hand, (long) ' ', 8);
    bitio_o_append (hand, (long) ' ', 8);
  }
  else
  {
    bitio_o_append (hand, 24L, 24);                       /* length of section */
    bitio_o_append (hand, s1->mtab, 8);                   /* master table used */
    bitio_o_append (hand, s1->subcent, 16);               /* (!) originating/generating subcenter */
    bitio_o_append (hand, s1->gencent, 16);               /* (!) originating/generating center */
    bitio_o_append (hand, s1->updsequ, 8);                /* original BUFR message */
    bitio_o_append (hand, s1->opsec, 8);                  /* no optional section */
    bitio_o_append (hand, s1->dcat, 8);                   /* message type */
    bitio_o_append (hand, s1->dcatst, 8);                 /* international message subtype */
    bitio_o_append (hand, 0, 8);                          /* (!) local message subtype */
    bitio_o_append (hand, s1->vmtab, 8);                  /* version number of master table used */
    bitio_o_append (hand, s1->vltab, 8);                  /* version number of local table used */
    bitio_o_append (hand, s1->year, 16);                  /* (!) year */
    bitio_o_append (hand, s1->mon, 8);                    /* month */
    bitio_o_append (hand, s1->day, 8);                    /* day */
    bitio_o_append (hand, s1->hour, 8);                   /* hour */
    bitio_o_append (hand, s1->min, 8);                    /* minute */
    bitio_o_append (hand, 0, 8);                          /* (!) seconds */
    bitio_o_append (hand, (long) ' ', 8);                 /* Pad to the end of the section (1 byte) */
  }

  bitio_o_append (hand, 0L, 8);                            /* filler (0) */
  sec[1] = (char *) bitio_o_close (hand, &secl[1]);

/******* there is no section 2 */

  sec[2] = NULL;
  secl[2] = 0;

/******* create section 5 */

  hand = bitio_o_open ();
  for (i = 0; i < 4; i ++) bitio_o_append (hand, (long) '7', 8);
  sec[5] = (char *) bitio_o_close (hand, &secl[5]);

/******* calculate total length of BUFR-message */

  secl[0] = 8;     /* section 0 not yet setup */
  len = 0L;
  for (i = 0; i < 6; i ++) len += (long) secl[i];
  

/******* create section 0 */

  hand = bitio_o_open ();
  if (hand == -1) return 0;
  bitio_o_append (hand, (unsigned long) 'B', 8);
  bitio_o_append (hand, (unsigned long) 'U', 8);
  bitio_o_append (hand, (unsigned long) 'F', 8);
  bitio_o_append (hand, (unsigned long) 'R', 8);
  bitio_o_append (hand, len, 24);          /* length of BUFR-message */
  bitio_o_append (hand, 2L, 8);            /* BUFR edition number */
  sec[0] = (char *) bitio_o_close (hand, &secl[0]);
  return 1;
}

/*===========================================================================*/
static void set_sect_1 (sect_1_t * s1_a, const struct ymds_time *pTime_a,
			uint16_t center_a,     uint16_t sub_center_a,
			uint8_t  category_a,   uint8_t  sub_category_a,
			uint8_t  master_tab_a, uint8_t  local_tab_a)
{
  s1_a->mtab    = 0;
  s1_a->subcent  = sub_center_a;
  s1_a->gencent  = center_a;
  s1_a->updsequ = 0;
  s1_a->opsec   = 0;
  s1_a->dcat    = category_a;
  s1_a->dcatst  = sub_category_a;
  s1_a->vmtab   = master_tab_a;
  s1_a->vltab   = local_tab_a;
  s1_a->year    = pTime_a->iyear;
  s1_a->mon     = pTime_a->imon;
  s1_a->day     = pTime_a->iday;
  s1_a->hour    = (pTime_a->isec / 3600) % 24;
  s1_a->min     = (pTime_a->isec / 60) % 60;
}

/* ===========================================================================
 * This subroutine computes the color map and the color parameter scale
 * text from the product header.
 */
static void ComputeColormap
(UINT2 idtype_a,                    /* Data type */
 const struct product_end *pend_a,
 union level_control *plevel_a,     /* Filled in so we can levelize */
 struct scale_text newscale_a[16],  /* Label text is here */
 const struct private_scale_def *pPrivateScale_a )
{
  struct data_convert Convert;
  struct color_scale_def ScaleDef;
  SINT4 iColor;
  UINT2 iColorScale[COLOR_MAP_ZOOM][16];
  UINT1 iColorNoData, iColorThresh;

  /* ----------------------------------------------------------------------- */

  FillDataConvert( &Convert, pend_a->iprf, pend_a->itrig, pend_a->ilambda, pend_a->ipolar,
		   CONVERT_FLAGS_DEFAULT ) ;
  
  memset( &ScaleDef, 0, sizeof(ScaleDef) );
  ScaleDef.iflags  = COLOR_SCALE_VARIABLE | pPrivateScale_a->iflags;
  ScaleDef.icolcnt = pPrivateScale_a->icolcnt;

  /* Color scale is replicated because we are not using zoomed scale */
  for (iColor=0; iColor<16; iColor++)
    {
      ScaleDef.ilevel_seams[iColor] =  pPrivateScale_a->ilevel_seams[iColor];
      iColorScale[0][iColor] = MIN( iColor+1, 15 );
      iColorScale[1][iColor] = MIN( iColor+1, 15 );
    }
  iColorNoData = 15;
  iColorThresh = 0;
  generic_map( idtype_a, &ScaleDef, iColorScale, iColorThresh, iColorNoData, 
	       plevel_a, &Convert, newscale_a, NULL );

}
/* ============================================
 * Optionally write the Headers
 */
static void WriteHeaders
( const struct convert_info *pConvert_a,
  const size_t secl_a[6] )  /* Array of section lengths to follow */
{
  UINT2 iDays, iHour, iMinute, iSecond;
  int iSize;
  char sBuffer[32];
  char sLog[200];

  /* Optionally write WMO Format 00 message length header */
  if (CConfig_g.lEnableMessageLength)
    {
      int iMessageLength=0, ii;

      for (ii = 0; ii < 6; ii++)
	iMessageLength += secl_a[ii];

      if (CConfig_g.lEnableGts) iMessageLength+= 31+7;
      sprintf(sBuffer,"%08d00", iMessageLength);

      iSize = fwrite( sBuffer, 1, 10, stdout);
      if ( iSize != 10)
	{
	  sprintf( sLog, "Errno %d in fwrite\n", errno );
	  Lprintf( &CConfig_g.Logging, sLog );
	  exit(EXIT_FAILURE);
	}
    }

  /* Optionally write the GTS Header after the message length */
  if (!CConfig_g.lEnableGts) return;

  sBuffer[0]  =  1;
  sBuffer[1]  = 13;
  sBuffer[2]  = 13;
  sBuffer[3]  = 10;
  sBuffer[4]  = '0';
  sBuffer[5]  = '0';
  sBuffer[6]  = '1';
  sBuffer[7]  = 13;
  sBuffer[8]  = 13;
  sBuffer[9]  = 10;
  sBuffer[10] = 'P';
  sBuffer[11] = 'A';
  sBuffer[12] = pConvert_a->sGtsProductType;
  sBuffer[13] = pConvert_a->sGtsProductSubType;
  sBuffer[14] = pConvert_a->pSite->sGtsBulletinId[0];
  sBuffer[15] = pConvert_a->pSite->sGtsBulletinId[1];
  sBuffer[16] = ' ';
  memcpy( &(sBuffer[17]), CConfig_g.sGtsSource, 4 );
  sBuffer[21] = ' ';

  unpacktime( pConvert_a->IngTime.isec, &iDays, &iHour, &iMinute, &iSecond );
  sprintf( &(sBuffer[22]), "%02d%02d%02d", pConvert_a->IngTime.iday,
	   iHour, iMinute );
  sBuffer[28] = 13;
  sBuffer[29] = 13;
  sBuffer[30] = 10;


  iSize = fwrite( sBuffer, 1, 31, stdout);
  if ( iSize != 31)
    {
      sprintf( sLog, "Errno %d in fwrite\n", errno );
      Lprintf( &CConfig_g.Logging, sLog );
      exit(EXIT_FAILURE);
    }
  return;
}
/* ============================================
 * Optionally write the GTS Trailer
 */
static void WriteGtsTrailer(void)
{
  char sBuffer[8];
  int iSize;
  char sLog[200];

  if (!CConfig_g.lEnableGts) return;

  sBuffer[0] = 13;
  sBuffer[1] = 13;
  sBuffer[2] = 10;
  sBuffer[3] = 13;
  sBuffer[4] = 13;
  sBuffer[5] = 10;
  sBuffer[6] =  3;
  iSize = fwrite( sBuffer, 1, 7, stdout);
  if ( iSize != 7)
    {
      sprintf( sLog, "Errno %d in fwrite\n", errno );
      Lprintf( &CConfig_g.Logging, sLog );
      exit(EXIT_FAILURE);
    }
  return;
}

#define DEFAULT_BUFR_EDITION 3
#define DEFAULT_CENTER       (0xff)
#define DEFAULT_SUBCENTER    (0xff)
#define DEFAULT_MASTER_TAB   11
#define DEFAULT_LOCAL_TAB    5

/* ============================================================
 * Function to load the convert setup from a configuration file.
 */
static void LoadSetup
( struct convert_config8 *pCConfig_a,
  const char* sFile_a,                  /* Filename of config file */
  int iDevice_a )                       /* Device number */
{
  char sPath[PATHNAME_SIZE];
  char sline[81], *sptr;
  FILE* stream;
  SINT4 iline;
  UINT2 lerror;

  /* Initialize the data to the default values */
  memset( pCConfig_a, 0, sizeof(*pCConfig_a) );
  pCConfig_a->fMinReflectivity = -1000.0;
  strcpy( pCConfig_a->sGtsSource, "0000" );
  pCConfig_a->ibufr_edition = DEFAULT_BUFR_EDITION;
  pCConfig_a->icenter_id = DEFAULT_CENTER;
  pCConfig_a->isubcenter_id = DEFAULT_SUBCENTER;
  pCConfig_a->imaster_table = DEFAULT_MASTER_TAB;
  pCConfig_a->ilocal_table = DEFAULT_LOCAL_TAB;

  /* Default logging to file */
  pCConfig_a->Logging.lLogToFile=TRUE;

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

  strncpy( sPath, sptr, DIRECTORY_SIZE-1 );
  sPath[DIRECTORY_SIZE-1] = 0;

  strncpy( pCConfig_a->sBufrTablDir, sPath, DIRECTORY_SIZE - 1 );
  strcat( pCConfig_a->sBufrTablDir, "bufr" );

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

  /* Select how we get BUFR location information */
  if ( 0 == strcmp ( "BUFR_TAB_PATH", stokens[0] ) )
      strncpy( pConfig_a->sBufrTablDir, stokens[1], PATHNAME_SIZE - 1 );

  /* Turn GTS either ON or OFF */
  if ( 0 == strcmp( "GTS", stokens[0] ) )
    {
      if ( icount < 2 ) return TRUE;
      if ( 0 == strcmp( "ON", stokens[1] ) )
	{
	  pConfig_a->lEnableGts = TRUE;
	  return FALSE;
	}
      if ( 0 == strcmp( "OFF", stokens[1] ) )
	{
	  pConfig_a->lEnableGts = FALSE;
	  return FALSE;
	}
      return TRUE;
    }

  /* Set the GTS Source string */
  if ( 0 == strcmp( "GTS_SOURCE", stokens[0] ) )
    {
      int iLength;
      if ( icount < 2 ) return TRUE;
      
      /* Must be 4 characters long */
      iLength = strlen( stokens[1] );
      if (iLength != 4) return TRUE;

      strcpy( pConfig_a->sGtsSource, stokens[1] );
      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->Logging.lLogToFile=TRUE;  pConfig_a->Logging.lLogToTerm=TRUE;}
      else if ( 0 == strcmp( "FILE", stokens[1] ) ) 
	{pConfig_a->Logging.lLogToFile=TRUE;  pConfig_a->Logging.lLogToTerm=FALSE;}
      else if ( 0 == strcmp( "TERM", stokens[1] ) )
	{pConfig_a->Logging.lLogToFile=FALSE; pConfig_a->Logging.lLogToTerm=TRUE;}
      else if ( 0 == strcmp( "NONE", stokens[1] ) )
	{pConfig_a->Logging.lLogToFile=FALSE; pConfig_a->Logging.lLogToTerm=FALSE;}
      else
	return TRUE;
      return FALSE;
    }

  if ( 0 == strcmp( "BUFR_EDITION", stokens[0] ) )
    {
      long iValue;
      if ( icount < 2 ) return TRUE;
      
      iValue = strtol( stokens[1], &sptr, 10 );
      if (sptr == stokens[1]) return TRUE;
      /* BUFR Edition must be either 3 or 4 */
      if (iValue < 3 ) return TRUE;
      if (iValue > 4 ) return TRUE;
      pConfig_a->ibufr_edition = iValue;
      return FALSE;
    }

  if ( 0 == strcmp( "CENTER_ID", stokens[0] ) )
    {
      long iValue;
      if ( icount < 2 ) return TRUE;
      
      iValue = strtol( stokens[1], &sptr, 10 );
      if (sptr == stokens[1]) return TRUE;
      /* Originating center ID is a 16-bit number */
      if (iValue < 0 ) return TRUE;
      if (iValue > 65535 ) return TRUE;
      pConfig_a->icenter_id = iValue;
      return FALSE;
    }
      
  if ( 0 == strcmp( "LOCAL_TABLE", stokens[0] ) )
    {
      long iValue;
      if ( icount < 2 ) return TRUE;
      
      iValue = strtol( stokens[1], &sptr, 10 );
      if (sptr == stokens[1]) return TRUE;
      if (iValue < 0 ) return TRUE;
      if (iValue > 100 ) return TRUE;
      pConfig_a->ilocal_table = iValue;
      return FALSE;
    }
      
  if ( 0 == strcmp( "MASTER_TABLE", stokens[0] ) )
    {
      long iValue;
      if ( icount < 2 ) return TRUE;
      
      iValue = strtol( stokens[1], &sptr, 10 );
      if (sptr == stokens[1]) return TRUE;
      if (iValue < 0 ) return TRUE;
      if (iValue > 100 ) return TRUE;
      pConfig_a->imaster_table = iValue;
      return FALSE;
    }
      
  /* Turn message length header either ON or OFF */
  if ( 0 == strcmp( "MESSAGE_LENGTH", stokens[0] ) )
    {
      if ( icount < 2 ) return TRUE;
      if ( 0 == strcmp( "ON", stokens[1] ) )
	{
	  pConfig_a->lEnableMessageLength = TRUE;
	  return FALSE;
	}
      if ( 0 == strcmp( "OFF", stokens[1] ) )
	{
	  pConfig_a->lEnableMessageLength = FALSE;
	  return FALSE;
	}
      return TRUE;
    }

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

  /* Read an entry in the site table.
   * Syntax is: SITE <WMO block> <WMO Station> <IRIS Sitename> <IRIS Sitenumber> <minutes west> <lat> <lon> */
  if ( 0 == strcmp( "SITE", stokens[0] ) )
    {
      long iValue;
      struct i2b_site_table8 *pOneSite;
      if ( icount < 6 ) return TRUE;
      if (pConfig_a->iSiteCount >= MAX_CONVERT_SITES) return TRUE;

      pOneSite = &(pConfig_a->Sites[pConfig_a->iSiteCount]);
      
      /* Read Block Number */
      iValue = strtol( stokens[1], &sptr, 10 );
      if (sptr == stokens[1]) return TRUE;
      /* Block number is Unsigned 7-bit value */
      if (iValue < 0 ) return TRUE;
      if (iValue > 127 ) return TRUE;
      pOneSite->iBlockNumber = iValue;
      
      /* Read Station Number */
      iValue = strtol( stokens[2], &sptr, 10 );
      if (sptr == stokens[2]) return TRUE;
      /* Block number is Unsigned 10-bit value */
      if (iValue < 0 ) return TRUE;
      if (iValue > 1023 ) return TRUE;
      pOneSite->iStationNumber = iValue;
      
      /* Read Site Name */
      strncpy( pOneSite->sSitename16, stokens[3], 16 );
      pOneSite->sSitename16[16] = 0;
            
      /* Read Site Number */
      pOneSite->iSiteNumber = strtol( stokens[4], &sptr, 10 );
      if (sptr == stokens[4]) return TRUE;
      /* Block number is Unsigned 10-bit value */
      if (pOneSite->iSiteNumber <  0 ) return TRUE;
      if (pOneSite->iSiteNumber > 30 ) return TRUE;

      /* Read GTS Bulletin ID ii fields */
      strncpy( pOneSite->sGtsBulletinId, stokens[5], 2 );
      pOneSite->sGtsBulletinId[2] = 0;

      if (!pOneSite->sGtsBulletinId[0]) return TRUE;
      if (!pOneSite->sGtsBulletinId[1]) return TRUE;
            
      pConfig_a->iSiteCount++;
      return FALSE;
    }

  if ( 0 == strcmp( "SUBCENTER_ID", stokens[0] ) )
    {
      long iValue;
      if ( icount < 2 ) return TRUE;
      
      iValue = strtol( stokens[1], &sptr, 10 );
      if (sptr == stokens[1]) return TRUE;
      /* Originating center ID is a 16-bit number */
      if (iValue < 0 ) return TRUE;
      if (iValue > 65535 ) return TRUE;
      pConfig_a->isubcenter_id = iValue;
      return FALSE;
    }

  if ( 0 == strcmp( "VERBOSE", stokens[0] ) )
    {
      pConfig_a->Logging.lVerbose = TRUE;
      return FALSE;
    }

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

/* ======================================================================
 * 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( &CConfig_g.Logging, sMessage );
  return(SS_NORMAL);
}
