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

/* This program is a pipe which takes an input ASCII file defining the raingage
 * data from many gages and/or Z/R numbers from disdrometers and converts it 
 * into an IRIS GAGE product containing that data.
 *
 * Note if you are modifying this program, and wish to insert a diagnostic
 * printf, be sure to write it to the error output.
 */

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

#include "sigtypes.h"
#include "projection.h"
#include "headers.h"
#include "iris_task.h"
#include "product.h"
#include "dsp_lib.h"
#include "himath_lib.h"
#include "misc_lib.h"
#include "user_lib.h"


struct command_line
{
  SINT4 iDevice;   /* Device number */
};

/* This holds information to customize the convertor */
struct projection_summary
{
  BIN4  iRadarLat;                  /* Latitude of the radar */
  BIN4  iRadarLon;                  /* Longitude */
  FLT8  fRMax;                      /* Range from center to right side of screen */
  BIN4  iCenterLat;                 /* Latitude of center of product */
  BIN4  iCenterLon;                 /* Longitude */
  UINT1 iType;                      /* Type of projection */
  BIN4  iReferenceLat;              /* Latitude of reference point */
  BIN4  iReferenceLon;              /* Longitude */
  SINT4 iXSize, iYSize;             /* Sizes in pixels */
  SINT4 iXScale, iYScale;           /* Scales in cm/pixel */
  SINT4 iXRadPos, iYRadPos;         /* Radar positions in 1/1000 of pixels */
};

/* Device table is used to override values for each device */
struct device_table
{
  SINT4 iDevice;     /* Device number */
  char  sPName[16];  /* Product name */
};

#define MAX_SKIP_NAMES (16)
#define MAX_DEVICE_TABLE (16)
struct convert_config
{
  UINT1 lLogToFile;
  UINT1 lLogToTerm;
  UINT1 lVerbose;              /* Log more details */
  FILE *iLogFile;              /* Open file descriptor to log file */
  char  sIngSitename[16];      /* Ingest sitename to use, space padded */
  struct projection_summary Projection;
  SINT4 iSkipCount;            /* Number of skip names */
  char  sSkipNames[MAX_SKIP_NAMES][16]; /* Array of skip names, null terminated */
  char  sDefaultPName[16];     /* Default product name to use */
  SINT4 iDeviceCount;          /* Number of device table entries */
  struct device_table Device[MAX_DEVICE_TABLE];
  char  sRecordZone[8];
  char  sLocalZone[8];
  SINT2 iRecordMinutes;
  SINT2 iLocalMinutes;
};

struct convert_config CConfig_c;

/* Local function prototypes
 */
static void InitializeProdHeader
( struct product_hdr *ProdHeader_a,
  const struct convert_config *pCConfig_a,
  const struct command_line *pCommand_a );

static UINT1 lProcessAsciiLine
( char *sptr_a,
  struct product_hdr *pProdHeader_a,
  struct gage_results **ppData_a );

static void ProcessCommandLine
(int iArgc, 
 const char *sArgv[],
 struct command_line *pCommand_a );
  
static void Usage( void );

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 );

static void FillProjectionNumbers
( struct projection_summary *pProj_a );

#define MAX_LINELENGTH (120)
/* ===============================================================
 * Main program starts here.
 */
int main(int iArgc, const char *sArgv[])
{
  struct product_hdr ProdHeader;
  SINT4 iLine ;
  char sLog[200], sLine[MAX_LINELENGTH], *sptr, sProdTime[TIMENAME_SIZE];
  struct gage_results *pData;
  struct command_line Command;

  setlocale( LC_ALL, "" ) ;

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

  /* 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, "AsciiToGage.log" );
      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 */
  if (CConfig_c.lVerbose) Lprintf( "Verbose Logging On.\n");
    
  /* Read in the header */
  InitializeProdHeader( &ProdHeader, &CConfig_c, &Command );

  /* now loop through the file reading and parsing lines from the standard input */
  iLine = 1;
  pData = NULL;
  sptr = fgets( sLine, MAX_LINELENGTH, stdin );

  while (NULL != sptr)
    {
      UINT1 lerror;
      if (CConfig_c.lVerbose)
	{
	  sprintf( sLog, "Line %d: %s", iLine, sptr );
	  Lprintf( sLog);
	}
      lerror = lProcessAsciiLine( sptr, &ProdHeader, &pData );
      if (lerror)
	{
	  sprintf( sLog, "Syntax error on ASCII file line %d\n", iLine );
	  Lprintf( sLog );
	  exit(EXIT_FAILURE);
	}
      iLine +=1;
      sptr = fgets( sLine, MAX_LINELENGTH, stdin );
    }

  /* Check results to see if there is a problem. */
  if (ProdHeader.pcf.ing_time.iyear==0)
    {
      Lprintf( "Error: ASCII file did not contain a TIME command\n");
      exit(EXIT_FAILURE);
    }

  ProdHeader.hdr.ibytes = PRODUCT_HDR_SIZE + 
    ProdHeader.end.iresults_count*sizeof(struct gage_results);

  /* Write out the results to the standard output */
  fwrite( &ProdHeader, PRODUCT_HDR_SIZE, 1, stdout );
  fwrite( pData, GAGE_RESULTS_SIZE, ProdHeader.end.iresults_count, stdout );

  shhmmssddmonyyyy_r( &ProdHeader.pcf.ing_time,sProdTime );
  sprintf( sLog, "GAGE product made, name:'%s', %s, %d gages\n",
	   nullify( ProdHeader.pcf.spname, 12 ), 
	   sProdTime, ProdHeader.end.iresults_count );
  Lprintf( sLog);

  if (pData) free(pData);
  exit( EXIT_SUCCESS );
}
/* ===============================================
 * Fill in the product header.
 */
static void InitializeProdHeader
( struct product_hdr *pProdHeader_a,
  const struct convert_config *pCConfig_a,
  const struct command_line *pCommand_a )
{
  SINT4 iEntry;
  const char *sPName;
  struct projection_summary *pProjSum = &CConfig_c.Projection;

  /* Figure out which product name to make: */
  sPName = pCConfig_a->sDefaultPName;
  for (iEntry=0; iEntry<pCConfig_a->iDeviceCount; iEntry++)
    {
      if (pCommand_a->iDevice == pCConfig_a->Device[iEntry].iDevice)
	sPName = pCConfig_a->Device[iEntry].sPName;
    }

  HeaderFromScratch( pProdHeader_a, NULL, pCConfig_a->sIngSitename,
		     pCConfig_a->sIngSitename, sPName );
  pProdHeader_a->pcf.iptype    = PROD_GAGE;

  pProdHeader_a->pcf.ixscale   = pProjSum->iXScale;
  pProdHeader_a->pcf.iyscale   = pProjSum->iYScale;
  pProdHeader_a->pcf.izscale   = 0;
  pProdHeader_a->pcf.ixsize    = pProjSum->iXSize;
  pProdHeader_a->pcf.iysize    = pProjSum->iYSize;
  pProdHeader_a->pcf.izsize    = 1;
  pProdHeader_a->pcf.ixrad     = pProjSum->iXRadPos;
  pProdHeader_a->pcf.iyrad     = pProjSum->iYRadPos;
  pProdHeader_a->pcf.izrad     = 0;

  pProdHeader_a->pcf.idata_out = DB_RAINRATE2;

  pProdHeader_a->pcf.iprojection_type = pProjSum->iType;

  /* Default span is 1 hour */
  pProdHeader_a->pcf.psi.Gage.iSpan = 60*60;

  pProdHeader_a->pcf.psi.Gage.iFlag = GAGE_PFLG_NORAIN;

  pProdHeader_a->end.iLocalWest      = pCConfig_a->iLocalMinutes;
  pProdHeader_a->end.iMinutesWest    = pCConfig_a->iRecordMinutes;
  pProdHeader_a->end.ilat_center = pProjSum->iCenterLat;
  pProdHeader_a->end.ilon_center = pProjSum->iCenterLon;

  pProdHeader_a->end.ilat_ref = pProjSum->iReferenceLat;
  pProdHeader_a->end.ilon_ref = pProjSum->iReferenceLon;
  strcpy( pProdHeader_a->end.sLocalTZName, pCConfig_a->sLocalZone );
}

/* ===============================================
 * Process on line of the input data.
 */
static UINT1 lProcessAsciiLine
( char *sline_a,
  struct product_hdr *pProdHeader_a,
  struct gage_results **ppData_a )  /* Pointer will be updated */
{
  char *sToken;
  UINT4 ioffset;
  SINT4 iTokenCount;  /* TRUE if this line contains data from a single raingage */
  UINT1 lGageLine;
  struct gage_parse_state
  {
    struct gage_results Results;
    UINT1 lLongLatSet;
    UINT1 lRainRateSet;
    UINT1 lRainFallSet;
    UINT1 lZrSet;
  };
  struct gage_parse_state State;
  struct data_convert Convert ;

  /* Fill in convert structure.  Note that we use mm/hour here because
   * the Z/R equations are in mm/hour. */
  PendFillDataConvert( &Convert, &pProdHeader_a->end, CONVERT_FLAGS_DEFAULT );


  ioffset = 0;
  iTokenCount = 0;
  lGageLine = FALSE;
  memset( &State, 0, sizeof(State) );
  State.Results.iRainRate = DV_NOSCAN2;

 LOOP:
  sToken = sigtok( sline_a, &ioffset );
  iTokenCount++;

  /* Blank lines are allowed and skipped */
  if (NULL == sToken)
    {
      if (iTokenCount==1)
	return FALSE;
      else if (!lGageLine)
	return FALSE;
      else
	{
	  SINT4 iSize, iSkip, iGage;
	  struct gage_results *pNewData;
	  
	  /* If we have started a gage line, then check to make sure
	   * all the data is there.
	   */
	  if (0 == State.Results.sName[0])
	    {
	      Lprintf( "Gage line missing the CODE command\n" );
	      return TRUE;
	    }
	  if (!State.lLongLatSet)
	    {
	      Lprintf( "Gage line missing the LONLAT command\n" );
	      return TRUE;
	    }
	  /* Check that either a rainrate of Z/R value was reported, also
	   * set bits as appropriate. */
	  if ((State.lRainRateSet)||(State.lRainFallSet))
	    {
	      pProdHeader_a->pcf.psi.Gage.iFlag &= ~GAGE_PFLG_NORAIN;
	      if (State.lZrSet)
		{
		  pProdHeader_a->pcf.psi.Gage.iFlag |= GAGE_PFLG_DISDRO;
		  State.Results.iFlag |= GAGE_FLG_DISDRO;
		}
	    }
	  else
	    {
	      State.Results.iFlag |= GAGE_FLG_NORAIN;
	      if (State.lZrSet)
		{
		  pProdHeader_a->pcf.psi.Gage.iFlag |= GAGE_PFLG_DISDRO;
		  State.Results.iFlag |= GAGE_FLG_DISDRO;
		}
	      else
		{
		  Lprintf( "Gage line missing RAIN or Z/R commands\n" );
		  return TRUE;
		}
	    }

	  /* Check name to see if it is a duplicate */
	  for (iGage=0; iGage<pProdHeader_a->end.iresults_count; iGage++)
	    {
	      const struct gage_results *pOneGage;
	      pOneGage=&((*ppData_a)[iGage]);
	      if (0 == strcmp(pOneGage->sName, State.Results.sName))
		{
		  char sString[80];
		  sprintf( sString, "Duplicate gage name '%s'\n", State.Results.sName );
		  Lprintf( sString );
		  return TRUE;
		}
	    }

	  /* Check name against skip list */
	  for (iSkip=0; iSkip<CConfig_c.iSkipCount; iSkip++)
	    {
	      if ( 0 == strcmp( CConfig_c.sSkipNames[iSkip], State.Results.sName))
		State.Results.iFlag |= GAGE_FLG_SKIP;
	      
	    }

	  /* Copy data into array */
	  iSize = (pProdHeader_a->end.iresults_count+1) * sizeof(struct gage_results);
	  pNewData = (struct gage_results *)realloc( *ppData_a, iSize );
	  if (pNewData)
	    {
	      *ppData_a = pNewData;
	      pNewData[pProdHeader_a->end.iresults_count] = State.Results;
	      pProdHeader_a->end.iresults_count++;
	    }
	  return FALSE;
	}
    }
  
  /* Skip commented lines */
  if ((iTokenCount==1) &&(sToken[0] == '#')) return FALSE;

  /* See if the line contains the CODE command */
  if ( 0 == strcmp( "CODE", sToken ) )
    {
      lGageLine = TRUE;
      char *sCode;
      sCode = sigtok( sline_a, &ioffset );
      if (NULL == sCode) return TRUE;
      
      strncpy( State.Results.sName, sCode, 15 );
    }

  /* See if the line contains the LONLAT command */
  else if ( 0 == strcmp( "LONLAT", sToken ) )
    {
      lGageLine = TRUE;
      char *sPos, *sptr;
      FLT8 fPos;

      sPos = sigtok( sline_a, &ioffset );
      if (NULL == sPos) return TRUE;

      fPos = strtod( sPos, &sptr );
      if (sPos==sptr) return TRUE;
      if ((fPos < -180.0)||(fPos > 180.0)) return TRUE;

      State.Results.iLabelLon = iBin4FromFDeg( fPos );

      sPos = sigtok( sline_a, &ioffset );
      if (NULL == sPos) return TRUE;

      fPos = strtod( sPos, &sptr );
      if (sPos==sptr) return TRUE;
      if ((fPos < -90.0)||(fPos > 90.0)) return TRUE;

      State.Results.iLabelLat = iBin4FromFDeg( fPos );
      State.lLongLatSet = TRUE;
    }

  /* See if the line contains the RFALL command */
  else if ( 0 == strcmp( "RFALL", sToken ) )
    {
      lGageLine = TRUE;
      char *sAccum, *sptr;
      FLT8 fAccum, fSpan;

      sAccum = sigtok( sline_a, &ioffset );
      if (NULL == sAccum) return TRUE;

      fAccum = strtod( sAccum, &sptr );
      if (sAccum==sptr) return TRUE;
      if (fAccum < 0.0) return TRUE;

      fSpan = ((FLT8)pProdHeader_a->pcf.psi.Gage.iSpan)/3600.0;
      State.Results.iRainRate = iuser_to_data( fAccum/fSpan, DB_RAINRATE2, &Convert );

      State.lRainRateSet = TRUE;
    }

  /* See if the line contains the QUAL command */
  else if ( 0 == strcmp( "QUAL", sToken ) )
    {
      char *sQuality, *sptr;
      FLT8 fQuality;
      sQuality = sigtok( sline_a, &ioffset );
      if (NULL == sQuality) return TRUE;
      fQuality = strtod( sQuality, &sptr );
      if (sQuality==sptr) return TRUE;
      if (fQuality < 0.0) return TRUE;
      if (fQuality > 10.0) return TRUE;
      State.Results.iDataQuality = NINT(fQuality);
    }

  /* See if the line contains the REM command */
  else if ( 0 == strcmp( "REM", sToken ) )
    {
      /* For a comment, simply read and ignore the next token */
      sigtok( sline_a, &ioffset );
    }

  /* See if the line contains the RRATE command */
  else if ( 0 == strcmp( "RRATE", sToken ) )
    {
      lGageLine = TRUE;
      char *sRate, *sptr;
      FLT8 fRate;

      sRate = sigtok( sline_a, &ioffset );
      if (NULL == sRate) return TRUE;

      fRate = strtod( sRate, &sptr );
      if (sRate==sptr) return TRUE;
      if (fRate < 0.0) return TRUE;

      State.Results.iRainRate = iuser_to_data( fRate, DB_RAINRATE2, &Convert );

      State.lRainRateSet = TRUE;
    }

  /* See if the line contains the SPAN command */
  else if ( 0 == strcmp( "SPAN", sToken ) )
    {
      char *sSpan, *sptr;
      FLT8 fMinutes;

      sSpan = sigtok( sline_a, &ioffset );
      if (NULL == sSpan) return TRUE;

      fMinutes = strtod( sSpan, &sptr );
      if (sSpan==sptr) return TRUE;

      if (fMinutes < 1.0) return TRUE;
      if (fMinutes > 24.0 * 60.0) return TRUE;
      pProdHeader_a->pcf.psi.Gage.iSpan = NINT(60.0*fMinutes);
    }

  /* See if the line contains the TIME command */
  else if ( 0 == strcmp( "TIME", sToken ) )
    {
      char *sTime;
      int iCount, iYear, iMonth, iDay, iHour, iMinute;
      struct ymds_time Time;

      sTime = sigtok( sline_a, &ioffset );
      if (NULL == sTime) return TRUE;

      memset( &Time, 0, sizeof(Time) );
      iCount = sscanf( sTime, "%4d%2d%2d%2d%2d", &iYear, &iMonth, &iDay,
		       &iHour, &iMinute );
      if (iCount<5) return TRUE;

      Time.iyear = iYear;
      Time.imon = iMonth;
      Time.iday = iDay;
      Time.isec = iPackTime( iHour, iMinute, 0 );
      if (Time.iyear<1950) return TRUE;
      if (Time.iyear>3000) return TRUE;
      if (Time.imon<1) return TRUE;
      if (Time.imon>12) return TRUE;
      if (Time.iday<1) return TRUE;
      if (Time.iday>31) return TRUE;
      if (Time.isec>1+24*3600) return TRUE;

      /* Set the UTC flag, if desired */
      if (0 == strcmp(CConfig_c.sRecordZone, "UTC" )) Time.imills |= YMDS_FLG_UTC;

      pProdHeader_a->pcf.ing_time = Time;
      pProdHeader_a->pcf.epoch    = Time;
      pProdHeader_a->end.old_ing  = Time;
    }

  /* See if the line contains a Z/R command */
  else if ( 0 == strcmp( "Z/R", sToken ) )
    {
      lGageLine = TRUE;
      char *sValue, *sEnd;
      FLT8 fValue;

      sValue = sigtok( sline_a, &ioffset );
      if (NULL == sValue) return TRUE;

      fValue = strtod( sValue, &sEnd );
      if (sValue==sEnd) return TRUE;
      if ((fValue < 0.0)||(fValue > 10000.0)) return TRUE;

      State.Results.iZrConst = NINT( 10.0*fValue );

      sValue = sigtok( sline_a, &ioffset );
      if (NULL == sValue) return TRUE;

      fValue = strtod( sValue, &sEnd );
      if (sValue==sEnd) return TRUE;
      if ((fValue < 0.0)||(fValue > 10.0)) return TRUE;

      State.Results.iZrExp = NINT( 1000.0*fValue );
      State.lZrSet = TRUE;
    }

  /* All unrecognized commands are just skipped.  This allows adding new options
   * without breaking old code readers. */

  goto LOOP;

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

  int iArg;
  char *sEnd;

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

  /* 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();
	}
      else if (0 == strncmp( sArg, "-device:", 8 ))
	{
	  pCommand_a->iDevice = strtol( &sArg[8], &sEnd, 10 );
	}
      
    }
  return;
}
/* ==================================================
 */
static void Usage( void )
{
  fprintf( stderr, "Options: -help         Print this list\n" );
  fprintf( stderr, "         -device:#     Specify device number\n" );
  fprintf( stderr, "         (all other arguments ignored)\n" );
  exit(EXIT_FAILURE) ;
}

/* ============================================================
 * Function to load the convert setup from a configuration file.
 */
static void LoadSetup
( struct convert_config *pCSetup_a )
{
  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) );
  spacify_ncpy( pCSetup_a->sIngSitename, "UNKNOWN", 16 );
  /* Default location is 0 0 latlon, AED, max range=100 km */
  pCSetup_a->Projection.fRMax = 100.0;
  pCSetup_a->Projection.iType = MAPROJ_AZIMUTHAL;
  pCSetup_a->Projection.iXSize = 480;
  strcpy( pCSetup_a->sDefaultPName, "DEFAULT" );
  strcpy( pCSetup_a->sRecordZone, "UTC" );
  strcpy( pCSetup_a->sLocalZone, "UTC" );
  
  /* 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, "AsciiToGage.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 config file line %d\n", iline );
      iline +=1;
      sptr = fgets( sline, 81, stream );
    }

  fclose( stream );

  FillProjectionNumbers( &pCSetup_a->Projection );
  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>
 * SITENAME      <name>
 * VERBOSE
 *
 */
static UINT2 ProcessConfigLine
(char *sline_a,
 struct convert_config *pConfig_a )
{
  char *stokens[9], *sptr, *sEnd;
  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 CENTER_LATLON command */
  if ( 0 == strcmp( "CENTER_LATLON", stokens[0] ) )
    {
      FLT8 fLatitude, fLongitude;
      if ( icount < 3 ) return TRUE;
      
      fLatitude = strtod( stokens[1], &sptr );
      if (sptr == stokens[1]) return TRUE;

      fLongitude = strtod( stokens[2], &sptr );
      if (sptr == stokens[2]) return TRUE;

      if (fLatitude  <  -90.0 ) return TRUE;
      if (fLatitude  >   90.0 ) return TRUE;
      if (fLongitude < -180.0 ) return TRUE;
      if (fLongitude >  180.0 ) return TRUE;

      pConfig_a->Projection.iCenterLat = iBin4FromFDeg(fLatitude);
      pConfig_a->Projection.iCenterLon = iBin4FromFDeg(fLongitude);
      return FALSE;
    }

  /* See if the line contains the Default product name command */
  if ( 0 == strcmp( "DEFAULT_PNAME", stokens[0] ) )
    {
      if ( icount < 2 ) return TRUE;
      strncpy( pConfig_a->sDefaultPName, stokens[1], 15 );
      pConfig_a->sDefaultPName[15]=0;
      return FALSE;
    }
  
  /* Add an entry to the device table */
  if ( 0 == strcmp( "DEVICE", stokens[0] ) )
    {
      struct device_table *pDevice=&pConfig_a->Device[pConfig_a->iDeviceCount];
      if ( icount < 3 ) return TRUE;
      
      if (pConfig_a->iDeviceCount>=MAX_DEVICE_TABLE) return TRUE;

      pDevice->iDevice = strtol( stokens[1], &sEnd, 10 )-1;
      if ( stokens[1]==sEnd ) return TRUE;
      strncpy( pDevice->sPName, stokens[2], 15 );
      pDevice->sPName[15]=0;
      pConfig_a->iDeviceCount++;
      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;
    }

  /* See if the line contains the MAX_RANGE command */
  if ( 0 == strcmp( "MAX_RANGE", stokens[0] ) )
    {
      FLT8 fRMax;
      if ( icount < 2 ) return TRUE;
      
      fRMax = strtod( stokens[1], &sptr );
      if (sptr == stokens[1]) return TRUE;

      if (fRMax <    1.0 ) return TRUE;
      if (fRMax > 1000.0 ) return TRUE;

      pConfig_a->Projection.fRMax = fRMax;
      return FALSE;
    }

  /* See if the line contains the PROJECTION file command */
  if ( 0 == strcmp( "PROJECTION", stokens[0] ) )
    {
      if ( icount < 2 ) return TRUE;
      
      pConfig_a->Projection.iType = iProjectionFromName16( stokens[1] );
      if (pConfig_a->Projection.iType >= MAPROJ_COUNT ) return TRUE;
      return FALSE;
    }

  /* See if the line contains the RADAR_LATLON command */
  if ( 0 == strcmp( "RADAR_LATLON", stokens[0] ) )
    {
      FLT8 fLatitude, fLongitude;
      if ( icount < 3 ) return TRUE;
      
      fLatitude = strtod( stokens[1], &sptr );
      if (sptr == stokens[1]) return TRUE;

      fLongitude = strtod( stokens[2], &sptr );
      if (sptr == stokens[2]) return TRUE;

      if (fLatitude  <  -90.0 ) return TRUE;
      if (fLatitude  >   90.0 ) return TRUE;
      if (fLongitude < -180.0 ) return TRUE;
      if (fLongitude >  180.0 ) return TRUE;

      pConfig_a->Projection.iRadarLat = iBin4FromFDeg(fLatitude);
      pConfig_a->Projection.iRadarLon = iBin4FromFDeg(fLongitude);
      return FALSE;
    }

  /* See if the line contains the SITENAME command */
  if ( 0 == strcmp( "SITENAME", stokens[0] ) )
    {
      if ( icount < 2 ) return TRUE;
      
      spacify_ncpy( pConfig_a->sIngSitename, stokens[1], 16 );
      return FALSE;
    }
  
  /* Add a gage to the skip table */
  if ( 0 == strcmp( "SKIP", stokens[0] ) )
    {
      if ( icount < 2 ) return TRUE;
      
      if (pConfig_a->iSkipCount>=MAX_SKIP_NAMES) return TRUE;
      strncpy( pConfig_a->sSkipNames[pConfig_a->iSkipCount++], stokens[1], 15 );
      return FALSE;
    }

  if ( 0 == strcmp( "TIMEZONE", stokens[0] ) )
    {
      if ( icount < 5 ) return TRUE;
      strncpy( pConfig_a->sRecordZone, stokens[1], 7 );
      pConfig_a->iRecordMinutes = strtol( stokens[2], &sptr, 10 );
      strncpy( pConfig_a->sLocalZone, stokens[3], 7 );
      pConfig_a->iLocalMinutes = strtol( stokens[4], &sptr, 10 );
      return FALSE;
    }  
  
  if ( 0 == strcmp( "VERBOSE", stokens[0] ) )
    {
      pConfig_a->lVerbose = TRUE;
      return FALSE;
    }

  if ( 0 == strcmp( "X_SIZE", stokens[0] ) )
    {
      SINT4 iXSize;
      if ( icount < 2 ) return TRUE;
      iXSize = strtol( stokens[1], &sptr, 10 );
      /* Invalid parse is detected here */
      if (iXSize<MIN_PRODUCT_SIZE) return TRUE;
      if (iXSize>MAX_PRODUCT_SIZE) return TRUE;
      pConfig_a->Projection.iXSize = iXSize;
      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 );
    }
}


/* ===============================================
 * Fill in the radar position in pixels*1000, etc., from other information
 * in the setup file.
 */
static void FillProjectionNumbers
( struct projection_summary *pProj_a )
{ 
  MESSAGE istatus;
  struct mpj_projection8 mpj;
  struct mpj_region8     region;

  /* As a default, set reference to center */
  pProj_a->iReferenceLat = pProj_a->iCenterLat;
  pProj_a->iReferenceLon = pProj_a->iCenterLon;

  /* Initialize outputs in case of an error return
   */
  pProj_a->iXRadPos = 0;
  pProj_a->iYRadPos = 0;
  pProj_a->iYSize  = pProj_a->iXSize;
  pProj_a->iXScale = 100000;
  pProj_a->iYScale = 100000;

  mpj.iprojection_type = pProj_a->iType;
  mpj.Ref.fLat         = fRadianFromBin4( pProj_a->iReferenceLat );
  mpj.Ref.fLon         = fRadianFromBin4( pProj_a->iReferenceLon );

  region.Loc1.fLat = fRadianFromBin4( pProj_a->iCenterLat );
  region.Loc1.fLon = fRadianFromBin4( pProj_a->iCenterLon );
  region.fde  = pProj_a->fRMax;
  region.fdn  = pProj_a->fRMax;
  region.fxpix = (FLT8)pProj_a->iXSize;
  istatus = mpj_pin_region( &mpj, &region, MPJ_LOC1|MPJ_FDE|MPJ_FDN );
  if (istatus != SS_NORMAL)
    {
      sig_signal( istatus );
      return;
    }

  /* Fill in the final X & Y sizes and scales
   */
  pProj_a->iXSize  = NINT( region.fxpix );
  pProj_a->iYSize  = NINT( region.fypix );
  pProj_a->iXScale = NINT( 200000.0*region.fde/region.fxpix);
  pProj_a->iYScale = NINT( 200000.0*region.fdn/region.fypix);
  
  /* Fill in the radar location in fractional pixels within the
   * data array.  If pointer is NULL, then use the center.
   */
  {
    struct latlon8 RadarEarth;
    struct XYPoint8 Pix; UINT1 lValid;
    RadarEarth.fLat = fRadianFromBin4( pProj_a->iRadarLat ) ;
    RadarEarth.fLon = fRadianFromBin4( pProj_a->iRadarLon ) ;
    
    mpj_earth_to_pixel( &mpj, &region, &RadarEarth, &Pix, &lValid ) ;
    
    pProj_a->iXRadPos = NINT( 1000.0 * Pix.fX ) ;
    pProj_a->iYRadPos = NINT( 1000.0 * Pix.fY) ;
  }
  return;
}
