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

/* This program reads in a RAIN1 product as well as a GAGE product and
 * a config file and applies a correction to write out a modified RAIN1 
 * product.
 */

#include <errno.h>
#include <locale.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "sigtypes.h"
#include "projection.h"
#include "headers.h"
#include "product.h"

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


/* This holds information to customize the convertor */
struct convert_config
{
  UINT1 lLogToFile;
  UINT1 lLogToTerm;
  UINT1 lVerbose;              /* Log more details */
  FILE *iLogFile;              /* Open file descriptor to log file */
  FLT4  fMinValidGage;
  FLT4  fMinValidRadar;
  FLT4  fMinValidCoverage;
  FLT4  fRadarAverage;         /* Radar averaging range in km */
  FLT4  fMinCorrection;        /* Limits on the span of the correction factor */
  FLT4  fMaxCorrection;
  char  sOverrideProductName[16];   /* Null terminated new product name */
};

struct path_config
{
  char sInputRain1[PATHNAME_SIZE];
  char sInputGage[PATHNAME_SIZE];
  char sOutputRain1[PATHNAME_SIZE];
};

struct gage_table
{
  UINT1 lValid;
  struct latlon8 Pos;
  struct XYPoint8 Pixel;
  FLT8 fCorrection;
};

struct convert_config CSetup_c;

/* Local function prototypes
 */
static void ComputeGageCorrections
( const struct product_hdr *pRain1Header_a,
  struct product_hdr *pGageHeader_a,
  struct gage_table *pGageTable_a,
  const struct mpj_projection8 *pProj_a,
  const struct mpj_region8 *pRegion_a );

static void FillCorrection
( struct gage_results *pGage_a,
  const struct mpj_region8 *Region_a,
  const struct product_hdr *pRain1Header_a,
  const struct data_convert *pConvert_a,
  struct gage_table *pGageT_a );

static void ApplyGageCorrections
( struct product_hdr *pRain1Header_a,
  const struct product_hdr *pGageHeader_a,
  struct gage_table *pGageT_a,
  const struct mpj_projection8 *pProj_a,
  const struct mpj_region8 *pRegion_a );

static void FillCorTable
( FLT8 *pCorTable_a,
  SINT4 iTableX_a,
  SINT4 iTableY_a,
  const struct mpj_projection8 *pProj_a,
  const struct mpj_region8 *pRegion_a,
  const struct gage_table *pGageT_a,
  SINT4 iGageCount_a );

static void ApplyCorTable
( FLT8 *pCorTable_a, 
  SINT4 iTableXSize_a,
  struct product_hdr *pRain1Header_a,
  const struct data_convert *pConvert_a );

static void ProcessCommandLine
(struct path_config *pPaths_a,
 int iArgc, 
 char *sArgv[] );

static void Usage( void );

static void LoadSetup
( struct convert_config *pCSetup_a );

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

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

static void Lprintf
( const char *sString_a );

#define MAX_LINELENGTH (120)
/* ===============================================================
 * Main program starts here.
 */
int main(int iArgc, char *sArgv[])
{
  MESSAGE istatus;
  struct path_config Paths;
  const struct product_hdr *pInputRain1;
  SINT4 iInputRain1Size, iInputRain1Channel;
  int iExitStatus;

  setlocale( LC_ALL, "" ) ;

  /* Process the command line arguments, if any */
  ProcessCommandLine( &Paths, iArgc, sArgv );

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

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

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

  /* Open Input Rain1 */
  iExitStatus = EXIT_FAILURE;
  istatus = imapopen( Paths.sInputRain1, FALSE, (void **)&pInputRain1, &iInputRain1Size,
		      &iInputRain1Channel );
  if (istatus == SS_NORMAL)
    {
      struct product_hdr *pInputGage;
      SINT4 iInputGageSize, iInputGageChannel;

      /* Open Input GAGE */
      istatus = imapopen( Paths.sInputGage, TRUE, (void **)&pInputGage, &iInputGageSize,
			  &iInputGageChannel );
      if (istatus == SS_NORMAL)
	{
	  struct product_hdr *pOutputRain1;
	  SINT4 iOutputRain1Size, iOutputRain1Channel;

	  /* Open Output RAIN1 */
	  istatus = imapcreate( Paths.sOutputRain1, iInputRain1Size, (void **)&pOutputRain1,
				&iOutputRain1Size, &iOutputRain1Channel );
	  if (istatus == SS_NORMAL)
	    {
	      struct gage_table *pGageTable;
	      iExitStatus = EXIT_SUCCESS;

	      /* Copy input to output InputRain1 */
	      memcpy( pOutputRain1, pInputRain1, iInputRain1Size );
	      
	      /* Compute the gage correction factors, and place in InputGage file,
	       * and table */
	      pGageTable = (struct gage_table *)
		malloc( pInputGage->end.iresults_count*sizeof(*pGageTable));
	      if (pGageTable) 
		{
		  struct mpj_projection8 Proj;
		  struct mpj_region8 Region;
		    
		  phdr_pin_region( &Proj, &Region, pInputRain1, NULL );
		  ComputeGageCorrections( pInputRain1, pInputGage, pGageTable,
					  &Proj, &Region );
		  
		  /* Apply the correction */
		  ApplyGageCorrections( pOutputRain1, pInputGage, pGageTable,
					&Proj, &Region );
		  
		  /* Close the Output RAIN1 */
		  imapclose( pOutputRain1, iOutputRain1Size, iOutputRain1Channel );
		}
	      else
		{
		  istatus = errno_to_message( errno );
		  istatus = iMessageMod_va( istatus, 1, "malloc" );
		  sig_signal( istatus );
		}
	    }
	  else
	    {
	      sig_signal( istatus );
	    }

	  /* Close the InputGage */
	  imapclose( pInputGage, iInputGageSize, iInputGageChannel );
	}
      else
	{
	  sig_signal( istatus );
	}
      /* Close the InputRain1 */
      imapclose( pInputRain1, iInputRain1Size, iInputRain1Channel );
    }
  else
    {
      sig_signal( istatus );
    }
  /* Done */
  exit( iExitStatus );
}
/* ==============================================
 * Compute the raingage correction factors.
 */
static void ComputeGageCorrections
( const struct product_hdr *pRain1Header_a,
  struct product_hdr *pGageHeader_a,
  struct gage_table *pGageTable_a,
  const struct mpj_projection8 *pProj_a,
  const struct mpj_region8 *pRegion_a )
{
  struct gage_results *pGageData;
  struct data_convert Convert;
  SINT4 iGage;

  pGageData = (struct gage_results *)(void *)(pGageHeader_a+1);

  PendFillDataConvert( &Convert, &pRain1Header_a->end, CONVERT_FLAGS_DEFAULT );

  for (iGage=0; iGage<pGageHeader_a->end.iresults_count; iGage++)
    {
      struct gage_results *pGage;
      struct gage_table   *pGageT; UINT1 lValid;
      pGage = &pGageData[iGage];
      pGageT = &pGageTable_a[iGage];
      memset( pGageT, 0, sizeof(*pGageT) );

      pGageT->Pos.fLat = fRadianFromBin4( pGage->iLabelLat );
      pGageT->Pos.fLon = fRadianFromBin4( pGage->iLabelLon );
      mpj_earth_to_pixel( pProj_a, pRegion_a, &pGageT->Pos, &pGageT->Pixel, &lValid );
      pGage->iCorrection = DV_NOSCAN2;
      if ( (0==(pGage->iFlag&GAGE_FLG_SKIP)) &&
	   (pGageT->Pixel.fX>0.0)&&(pGageT->Pixel.fX<pRegion_a->fxpix)&&
	   (pGageT->Pixel.fY>0.0)&&(pGageT->Pixel.fY<pRegion_a->fypix))
	FillCorrection( pGage, pRegion_a, pRain1Header_a, &Convert, pGageT );

      if (pGageT->lValid)
	{
	  FLT8 fLogCor;
	  /* Store corrections away in dB */
	  fLogCor = 10.0 * log10( pGageT->fCorrection );
	  pGage->iCorrection = iuser_to_data( fLogCor, DB_DBZ2, &Convert );
	  if (CSetup_c.lVerbose)
	    {
	      char sLog[100];
	      sprintf( sLog, "Correction: %5.2f\n", pGageT->fCorrection );
	      Lprintf( sLog );
	    }
	}
      else
	{
	  if (CSetup_c.lVerbose)
	    {
	      Lprintf( "Correction: Invalid\n" );
	    }

	}
    }
  return;
}
/* ===============================================
 * Fill the gage correction based on the rainrates.
 */
static void FillCorrection
( struct gage_results *pGage_a,
  const struct mpj_region8 *pRegion_a,
  const struct product_hdr *pRain1Header_a,
  const struct data_convert *pConvert_a,
  struct gage_table *pGageT_a )
{
  FLT4 fGageRain, fRadarRain, fOneRadarRain;
  FLT8 fXScale, fYScale, fWidth, fHeight, fCoverage;
  SINT4 iMinX, iMaxX, iMinY, iMaxY, iX, iTotalCount, iValidCount;
  const UINT2 *pRain1Data;
  char sLog[100];

  if (CSetup_c.lVerbose)
    {
      sprintf( sLog, "Gage: %12s: %5.1f,%5.1f\n", pGage_a->sName,
	       pGageT_a->Pixel.fX, pGageT_a->Pixel.fY );
      Lprintf( sLog );
    }
  pRain1Data = (const UINT2*)(pRain1Header_a+1);

  fGageRain = fdata_to_user( pGage_a->iRainRate, DB_RAINRATE2, pConvert_a );
  if (fGageRain < CSetup_c.fMinValidGage ) return;

  /* Scale factors are in km/pixel */
  fXScale = (2.0 * pRegion_a->fde) / pRegion_a->fxpix;
  fYScale = (2.0 * pRegion_a->fdn) / pRegion_a->fypix;

  /* Compute half size of the region to integrate */
  fWidth = CSetup_c.fRadarAverage /fXScale;
  fHeight = CSetup_c.fRadarAverage /fYScale;

  /* Compute integer span of pixels to process */
  iMinX = NINT( pGageT_a->Pixel.fX-fWidth );
  iMaxX = NINT( pGageT_a->Pixel.fX+fWidth );
  iMinY = NINT( pGageT_a->Pixel.fY-fHeight );
  iMaxY = NINT( pGageT_a->Pixel.fY+fHeight );

  /* Loop though and accumulate all the data */
  iTotalCount = 0, iValidCount=0;
  fRadarRain = 0.0;
  for (iX=iMinX; iX<=iMaxX; iX++)
    {
      SINT4 iY;
      for (iY=iMinY; iY<=iMaxY; iY++)
	{
	  iTotalCount++;
	  /* First check that the position is inside the window */
	  if ((iX>=0)&&(iX<pRain1Header_a->pcf.ixsize)&&(iY>=0)&&
	      (iY<pRain1Header_a->pcf.iysize))
	    {
	      UINT2 iData;
	      iData = pRain1Data[ iX + (iY*pRain1Header_a->pcf.ixsize) ];
	      /* Now check that the data is valid. */
	      if ( (iData!=DV_NOSCAN2)&&(iData!=DV_THRESH2))
		{
		  fOneRadarRain = fdata_to_user( iData, DB_FLIQUID2, pConvert_a );
		  fRadarRain += fOneRadarRain;
		  iValidCount++;
		}
	    }
	}
    }
  /* Invalid if no pixels */
  if (iValidCount==0) return;

  /* Invalid if below threshold */
  fRadarRain /= (FLT4)iValidCount;
  if (fRadarRain < CSetup_c.fMinValidRadar ) return;

  /* Invalid if below percent coverage */
  fCoverage = 100.0 * (((FLT8)iValidCount)/((FLT8)iTotalCount));
  if (fCoverage < CSetup_c.fMinValidCoverage) return;

/* Note that neither number can be zero */
  pGageT_a->fCorrection = fGageRain/fRadarRain;
  if (pGageT_a->fCorrection<CSetup_c.fMinCorrection)
    pGageT_a->fCorrection = CSetup_c.fMinCorrection;
  if (pGageT_a->fCorrection>CSetup_c.fMaxCorrection) 
    pGageT_a->fCorrection = CSetup_c.fMaxCorrection;

  pGageT_a->lValid = TRUE;

  return;
  
}
#define GAGE_SKIP_FACTOR (10)
/* ===============================================
 * Apply the gage corrections to the data array.
 */
static void ApplyGageCorrections
( struct product_hdr *pRain1Header_a,
  const struct product_hdr *pGageHeader_a,
  struct gage_table *pGageT_a,
  const struct mpj_projection8 *pProj_a,
  const struct mpj_region8 *pRegion_a)
{
  struct data_convert Convert;
  SINT4 iGage, iGageCount;
  SINT4 iTableX, iTableY, iTableSize;
  FLT4 fCorrection;
  FLT8 fLogCor;
  FLT8 *pCorTable;

  /* Optionally override the product name */
  if (CSetup_c.sOverrideProductName[0])
    {
      spacify_ncpy( pRain1Header_a->pcf.spname, CSetup_c.sOverrideProductName, 12 );
    }

  /* Count up and pack the gage data */
  iGageCount=0;
  for (iGage=0; iGage<pGageHeader_a->end.iresults_count; iGage++)
    {
      if (pGageT_a[iGage].lValid)
	{
	  if (iGage!=iGageCount) pGageT_a[iGageCount] = pGageT_a[iGage];
	  iGageCount++;
	}
    }

  if (iGageCount<=0) return;

  pRain1Header_a->pcf.psi.rain.irain_flags |= RAIN_FLG_GAG_COR_OK;

  PendFillDataConvert( &Convert, &pRain1Header_a->end, CONVERT_FLAGS_DEFAULT );

  /* Compute average correction factor */
  fCorrection = 0.0;
  for (iGage=0; iGage<iGageCount; iGage++)
    {
      fCorrection += pGageT_a[iGage].fCorrection;
    }
  fCorrection /= (FLT4)iGageCount;

  /* Put it in the product header */
  fLogCor = 10.0 * log10( fCorrection );
  pRain1Header_a->pcf.psi.rain.iAverageGageFactor = 
    iuser_to_data( fLogCor, DB_DBZ2, &Convert );

  /* Compute an array of correction factors  based on a skip factor */
  iTableX = 1 + pRain1Header_a->pcf.ixsize/GAGE_SKIP_FACTOR;
  if (iTableX<1) iTableX=1;
  iTableY = 1 + pRain1Header_a->pcf.iysize/GAGE_SKIP_FACTOR;
  if (iTableY<1) iTableY=1;
  iTableSize = iTableX*iTableY;

  pCorTable = (FLT8 *)malloc( iTableSize*sizeof(*pCorTable));

  if (pCorTable)
    {
      FillCorTable( pCorTable, iTableX, iTableY, pProj_a, pRegion_a, 
		    pGageT_a, iGageCount );
      ApplyCorTable( pCorTable, iTableX, pRain1Header_a, &Convert );
      free(pCorTable);
    }

  return;
}
/* =================================================
 * Fill the correction table
 */
static void FillCorTable
( FLT8 *pCorTable_a,
  SINT4 iTableX_a,
  SINT4 iTableY_a,
  const struct mpj_projection8 *pProj_a,
  const struct mpj_region8 *pRegion_a,
  const struct gage_table *pGageT_a,
  SINT4 iGageCount_a
 )
{
  SINT4 iX, iY, iXPix, iYPix;
  struct latlon8 Earth;

  for (iX=0; iX<iTableX_a; iX++)
    {
      struct XYPoint8 Pix;
      iXPix = iX*GAGE_SKIP_FACTOR;
      Pix.fX = (FLT8)iXPix;
      for (iY=0; iY<iTableY_a; iY++)
	{
	  FLT8 fSumCor, fSumDis;
	  SINT4 iGage;
	  iYPix = iY*GAGE_SKIP_FACTOR;
	  Pix.fY = (FLT8)iYPix;
	  mpj_pixel_to_earth( pProj_a, pRegion_a, &Pix, &Earth );
	  fSumCor=0, fSumDis=0;
	  for (iGage=0; iGage<iGageCount_a; iGage++)
	    {
	      const struct gage_table *pGage;
	      FLT8 fDistance, fAz, fAz2;
	      pGage = &pGageT_a[iGage];
	      earth_to_polar( &Earth, &pGage->Pos, &fDistance, &fAz, &fAz2 );
	      if (fDistance<1.0) fDistance=1.0;
	      fSumCor += pGage->fCorrection/fDistance;
	      fSumDis += 1/fDistance;
	    }
	  pCorTable_a[iX+iY*iTableX_a] = fSumCor/fSumDis;
	}
    }
  return;
}
/* ==================================================
 * Apply the correction table.
 */
static void ApplyCorTable
( FLT8 *pCorTable_a, 
  SINT4 iTableXSize_a,
  struct product_hdr *pRain1Header_a,
  const struct data_convert *pConvert_a )
{
  SINT4 iX;
  FLT8 fWeights[GAGE_SKIP_FACTOR];
  UINT2 *pRain1Data;

  pRain1Data = (UINT2 *)(pRain1Header_a+1);
  /* Precompute the weight tables */
  for (iX=0; iX<GAGE_SKIP_FACTOR; iX++)
    {
      fWeights[iX] = ((FLT8)(GAGE_SKIP_FACTOR-iX))/((FLT8)GAGE_SKIP_FACTOR);
    }

  for (iX=0; iX<pRain1Header_a->pcf.ixsize; iX++)
    {
      SINT4 iY, iTableX1, iXrem;
      FLT8 fWeightX1, fWeightX2;
      iTableX1 = iX/GAGE_SKIP_FACTOR;
      iXrem    = iX%GAGE_SKIP_FACTOR;
      fWeightX1 = fWeights[iXrem];
      fWeightX2 = 1.0 - fWeightX1;
      for (iY=0; iY<pRain1Header_a->pcf.iysize; iY++)
	{
	  UINT2 *pData = pRain1Data+iX+(iY*pRain1Header_a->pcf.ixsize);
	  if ((*pData!=DV_NOSCAN2)&&(*pData!=DV_THRESH2))
	    {
	      SINT4 iTableY1, iYrem;
	      FLT8 fWeightY1, fWeightY2;
	      FLT4 fData, fCorrection;
	      iTableY1 = iY/GAGE_SKIP_FACTOR;
	      iYrem    = iY%GAGE_SKIP_FACTOR;
	      fWeightY1 = fWeights[iYrem];
	      fWeightY2 = 1.0 - fWeightY1;
	      fCorrection  = fWeightX1*fWeightY1*pCorTable_a[iTableX1    +iTableY1*iTableXSize_a];
	      fCorrection += fWeightX2*fWeightY1*pCorTable_a[(iTableX1+1)+(iTableY1+1)*iTableXSize_a];
	      fCorrection += fWeightX1*fWeightY2*pCorTable_a[iTableX1    +iTableY1*iTableXSize_a];
	      fCorrection += fWeightX2*fWeightY2*pCorTable_a[(iTableX1+1)+(iTableY1+1)*iTableXSize_a];
	      fData = fdata_to_user( *pData, DB_FLIQUID2, pConvert_a );
	      *pData = iuser_to_data( fData*fCorrection, DB_FLIQUID2, pConvert_a );
	    }
	}
    }
  return;
}
/* ======================================================================
 * Function to process the command line arguments.  First three strings not
 * starting with "-" are assumed to be file names.
 */
static void ProcessCommandLine
(struct path_config *pPaths_a,
 int iArgc_a, 
 char *sArgv_a[] )
{

  int iArg, iPathCount;
  memset( pPaths_a, 0, sizeof(*pPaths_a) );
  iPathCount = 0;

  /* 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 ( sArg[0] == '-')
	{
	  if (0 == strncmp( sArg, "-h", 2 ))
	    {
	      Usage();
	      exit(EXIT_FAILURE);	  
	    }
	  /* Ignore unrecognized command line options */
	}
      else
	{
	  if (iPathCount==0)
	    {
	      strncpy( pPaths_a->sInputRain1, sArg, PATHNAME_SIZE-1 );
	      iPathCount = 1;
	    }
	  else if (iPathCount==1)
	    {
	      strncpy( pPaths_a->sInputGage, sArg, PATHNAME_SIZE-1 );
	      iPathCount = 2;
	    }
	  else if (iPathCount==2)
	    {
	      strncpy( pPaths_a->sOutputRain1, sArg, PATHNAME_SIZE-1 );
	      iPathCount = 3;
	    }
	}
    }
  if (iPathCount!=3)
    {
      fprintf( stderr, "ERROR: Rain1GageCor requires 3 filenames!\n" );
      exit( EXIT_FAILURE);
    }

  return;
}
/* ==================================================
 */
static void Usage( void )
{
  fprintf( stderr, "Options: -help\n" );
  fprintf( stderr, "         (all other arguments ignored)\n" );
  exit(EXIT_FAILURE) ;
}

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

  /* Initialize the data to the default values */
  memset( pConfig_a, 0, sizeof(*pConfig_a) );
  pConfig_a->fMinValidGage     = 3.0;
  pConfig_a->fMinValidRadar    = 3.0;
  pConfig_a->fMinValidCoverage = 50.0;
  pConfig_a->fRadarAverage     = 2.0;
  pConfig_a->fMinCorrection    = 0.5;
  pConfig_a->fMaxCorrection    = 2.0;
  
  /* 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, "Rain1GageCor.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, pConfig_a );
      if (lerror) fprintf( stderr, "Syntax error on config file line %d\n", iline );
      iline +=1;
      sptr = fgets( sline, 81, stream );
    }

  fclose( stream );

  return;
}

/* ===============================================================
 * Process one line from the configuration file.  Returns nonzero if there
 * is a syntax error.
 *
 * 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>
 * VERBOSE
 *
 */
static UINT2 ProcessConfigLine
(char *sline_a,
 struct convert_config *pConfig_a )
{
  char *stokens[9], *sptr;
  UINT4 icount, ioffset;
  
  ioffset = 0;
  icount  = 0;
  /* Process up to 9 tokens, stop when the line is exhausted. */
  while (icount<9) 
    {
      stokens[icount] = sigtok( sline_a, &ioffset );
      if (NULL == stokens[icount])
	break;
      else
	icount++;
    }
  
  /* Blank lines are allowed and skipped */
  if (icount == 0) return FALSE;
  
  /* Skip commented lines */
  if (stokens[0][0] == '#') return FALSE;

  /* See if the line contains the Override product name command */
  if ( 0 == strcmp( "OVERRIDE_PRODUCT_NAME", stokens[0] ) )
    {
      if ( icount < 2 ) return TRUE;
      
      strncpy( pConfig_a->sOverrideProductName, stokens[1], 15 );
      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( "MAX_CORRECTION", stokens[0] ) )
    {
      FLT4 fCorrection;
      if (icount<2) return TRUE;

      fCorrection = strtod( stokens[1], &sptr );
      if (stokens[1]==sptr) return TRUE;

      if (fCorrection <   1.00) return TRUE;
      if (fCorrection > 100.00) return TRUE;

      pConfig_a->fMaxCorrection = fCorrection;
      return FALSE;
    }

  if ( 0 == strcmp( "MIN_CORRECTION", stokens[0] ) )
    {
      FLT4 fCorrection;
      if (icount<2) return TRUE;

      fCorrection = strtod( stokens[1], &sptr );
      if (stokens[1]==sptr) return TRUE;

      if (fCorrection < 0.01) return TRUE;
      if (fCorrection > 1.00) return TRUE;

      pConfig_a->fMinCorrection = fCorrection;
      return FALSE;
    }

  if ( 0 == strcmp( "MIN_VALID_COVERAGE", stokens[0] ) )
    {
      FLT4 fPercent;
      if (icount<2) return TRUE;

      fPercent = strtod( stokens[1], &sptr );
      if (stokens[1]==sptr) return TRUE;

      if (fPercent <= 0.0) return TRUE;
      if (fPercent > 100.0) return TRUE;

      pConfig_a->fMinValidCoverage = fPercent;
      return FALSE;
    }
  
  if ( 0 == strcmp( "MIN_VALID_GAGE", stokens[0] ) )
    {
      FLT4 fRain;
      if (icount<2) return TRUE;

      fRain = strtod( stokens[1], &sptr );
      if (stokens[1]==sptr) return TRUE;

      if (fRain <= 0.0) return TRUE;
      if (fRain > 1000.0) return TRUE;

      pConfig_a->fMinValidGage = fRain;
      return FALSE;
    }

  if ( 0 == strcmp( "MIN_VALID_RADAR", stokens[0] ) )
    {
      FLT4 fRain;
      if (icount<2) return TRUE;

      fRain = strtod( stokens[1], &sptr );
      if (stokens[1]==sptr) return TRUE;

      if (fRain <= 0.0) return TRUE;
      if (fRain > 1000.0) return TRUE;

      pConfig_a->fMinValidRadar = fRain;
      return FALSE;
    }

  if ( 0 == strcmp( "RADAR_AVERAGE", stokens[0] ) )
    {
      FLT4 fKm;
      if (icount<2) return TRUE;

      fKm = strtod( stokens[1], &sptr );
      if (stokens[1]==sptr) return TRUE;

      if (fKm < 1.0) return TRUE;
      if (fKm > 100.0) return TRUE;

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

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

/* ======================================================================
 * Function to process the command line arguments.  First three strings not
 * starting with "-" are assumed to be file names.
 */
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 );
  strcat( sMessage, "\n" );

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