/* *******************************************************
 * *                                                     *
 * *  Support routines for Rainbow viewer and converter  *
 * *                                                     *
 * *******************************************************
 * File: utils/rainbow/RainbowParse.C
 *
 *               COPYRIGHT (c) 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.
 */

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "sigtypes.h"
#include "user_lib.h"

#include "rainbow_h.h"

/* Local function prototypes
 */
static void ParseHeaderLabels
(const char sData_a[],
 SINT4 iDataSize_a, 
 SINT4 *pPointer_a,           /* Pointer position is updated */
 struct rainbow_info8 *pInfo_a,
 const struct convert_config *pCSetup_a  /* Info from config file */
 );

static char *sGetHeaderLabel
(const char sData_a[],      /* Data buffer */
 SINT4 iSize_a,             /* Size of buffer */
 SINT4 *iPointer_a,         /* Pointer to where we are in the buffer */
 struct rainbow_info8 *pCheckup_a,
 char sBuffer_a[256]);

static char *LabelParse
( char *sHeader_a );

static UINT1 ParseHLabels
( char *sHeader_a,
  rainbow_info8 *pInfo_a );

static UINT1 ParseNLabels
( char *sHeader_a,
  rainbow_info8 *pInfo_a );

static UINT1 ParseALabels
( char *sHeader_a,
  rainbow_info8 *pInfo_a );

static UINT1 ParsePLabels
( char *sHeader_a,
  rainbow_info8 *pInfo_a,
  const struct convert_config *pCSetup_a ); /* Info from config file */

static UINT1 ParseWLabels
( char *sHeader_a,
  rainbow_info8 *pInfo_a,
  const struct convert_config *pCSetup_a ); /* Info from config file */

static UINT1 ParseFLabels
( char *sHeader_a,
  rainbow_info8 *pInfo_a,
  const struct convert_config *pCSetup_a ); /* Info from config file */

static UINT1 ParseRLabels
( char *sHeader_a,
  rainbow_info8 *pInfo_a );

static void RainbowParseCleanup
( struct rainbow_info8 *pInfo_a, 
  const struct convert_config *pCSetup_a );

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

static UINT1 ParseDataList
(struct convert_task_list *pOneTask_a,
 char *sString_a );

static UINT1 iDataFromRainbow
( UINT1 iRainbowData_a );

/* ===============================================================
 * Parse the rainbow file.
 */
void RainbowParse
( const char *sData_a,               /* Rainbow data pointer */
  SINT4 iDataSize_a,                 /* Size of rainbow data */
  struct rainbow_info8 *pInfo_a,     /* Fill in this structure */
  const struct convert_config *pCSetup_a /* Info from config file */
  )
{
  SINT4 iPointer, iLineCount;

  iPointer=0;
  iLineCount=0;

  if (pCSetup_a->lVerbose)
    {
      char sString[FILENAME_SIZE+50];
      sprintf( sString, "File size is %d bytes\n", iDataSize_a );
      Lprintf( pCSetup_a, sString );
    }

  /* Initialize the info structure to default values */
  memset( pInfo_a, 0, sizeof(*pInfo_a) );
  pInfo_a->iFileSize = iDataSize_a;
  pInfo_a->iSweepCount = 1;

  /* Store first byte, if any */
  if (iDataSize_a>0) pInfo_a->iFirstByte = sData_a[0];

  /* Loop reading in lines, and displaying the results */
  while( (!pInfo_a->lParsingDone) && (iPointer<iDataSize_a) )
    {
      iLineCount++;
      switch( sData_a[iPointer] )
	{
	  /* SOH only as the first character of the file */
	case 001:
	  if (iPointer==0)
	    pInfo_a->lSohFound=TRUE;
	  else
	    pInfo_a->lSohInWrongPlace=TRUE;
	  iPointer++;
	  break;

	  /* ETX as the last character of the header */
	case 003:
	  pInfo_a->iEtxOffset=iPointer+1;
	  pInfo_a->lParsingDone=TRUE;
	  iPointer++;
	  break;

	  /* ETB appears twice in the header */
	case 027:
	  pInfo_a->iEtbCount++;
	  if (pInfo_a->iEtbCount>2) pInfo_a->lParsingDone=TRUE;
	  iPointer++;
	  break;

	  /* Note that the ETB character has a \r after it, so ignore
	   * all extra \r's in the file.
	   */
	case '\r':
	  iPointer++;
	  break;

	default:
	  ParseHeaderLabels( sData_a, iDataSize_a, &iPointer, pInfo_a, pCSetup_a );
	  break;
	}
    }
  RainbowParseCleanup( pInfo_a, pCSetup_a );
  return;
}

/* =================================================================
 * Parse the known header labels.
 */
static void ParseHeaderLabels
(const char sData_a[],
 SINT4 iDataSize_a, 
 SINT4 *pPointer_a,           /* Pointer position is updated */
 struct rainbow_info8 *pInfo_a,
 const struct convert_config *pCSetup_a /* Info from config file */
 )
{
  char *sHeader, sBuffer[256];
  UINT1 iError;
  
  sHeader = sGetHeaderLabel( &sData_a[*pPointer_a], iDataSize_a, pPointer_a, pInfo_a, 
			     sBuffer );
  if (!sHeader) 
    {
      pInfo_a->lParsingDone=TRUE;
      return;
    }
  if (pCSetup_a->lRainbowView) fprintf( stdout, "%s\n", sHeader );
  /* Search again on the first letter of the header */
  switch (sHeader[0])
    {
    case 'H':
      iError = ParseHLabels( sHeader, pInfo_a );
      if (iError) 
	{
	  pInfo_a->lHeaderSyntaxError=TRUE;
	  if (pCSetup_a->lRainbowView) fprintf( stdout, "Syntax Error\n" );
	}
      break;
    case 'N':
      iError = ParseNLabels( sHeader, pInfo_a );
      if (iError) 
	{
	  pInfo_a->lHeaderSyntaxError=TRUE;
	  if (pCSetup_a->lRainbowView) fprintf( stdout, "Syntax Error\n" );
	}
      break;
    case 'A':
      iError = ParseALabels( sHeader, pInfo_a );
      if (iError) 
	{
	  pInfo_a->lHeaderSyntaxError=TRUE;
	  if (pCSetup_a->lRainbowView) fprintf( stdout, "Syntax Error\n" );
	}
      break;
    case 'P':
      iError = ParsePLabels( sHeader, pInfo_a, pCSetup_a );
      if (iError) 
	{
	  pInfo_a->lHeaderSyntaxError=TRUE;
	  if (pCSetup_a->lRainbowView) fprintf( stdout, "Syntax Error\n" );
	}
      break;
    case 'W':
      iError = ParseWLabels( sHeader, pInfo_a, pCSetup_a );
      if (iError) 
	{
	  pInfo_a->lHeaderSyntaxError=TRUE;
	  if (pCSetup_a->lRainbowView) fprintf( stdout, "Syntax Error\n" );
	}
      break;
    case 'F':
      iError = ParseFLabels( sHeader, pInfo_a, pCSetup_a );
      if (iError) 
	{
	  pInfo_a->lHeaderSyntaxError=TRUE;
	  if (pCSetup_a->lRainbowView) fprintf( stdout, "Syntax Error\n" );
	}
      break;
    case 'R':
      iError = ParseRLabels( sHeader, pInfo_a );
      if (iError) 
	{
	  pInfo_a->lHeaderSyntaxError=TRUE;
	  if (pCSetup_a->lRainbowView) fprintf( stdout, "Syntax Error\n" );
	}
      break;

    case '#':
    case 'X':
      break;
    default:
      pInfo_a->lUnknownLabel=TRUE;
      pInfo_a->sUnknownLabel=sData_a[*pPointer_a];
      pInfo_a->lParsingDone=TRUE;
      *pPointer_a++;
      break;
    }
  return;
}
/* ====================================================================
 * Get string holding one header.
 * Returns NULL on a syntax error.
 */
static char *sGetHeaderLabel
(const char sData_a[],      /* Data buffer */
 SINT4 iSize_a,             /* Size of full buffer */
 SINT4 *iPointer_a,         /* Pointer to where we are in the buffer */
 struct rainbow_info8 *pCheckup_a,
 char sBuffer_a[256])
{
  char *sEnd;
  SINT4 iLength, iRemainingSize;

  iRemainingSize = iSize_a-*iPointer_a;
  /* First figure out how long the line is */
  sEnd = (char *)memchr( sData_a, '\r', iRemainingSize );

  if (sEnd==NULL) 
    {
      pCheckup_a->lLabelOffEnd=TRUE;
      return NULL;
    }

  iLength = (sEnd-sData_a);
  if (iLength>254)
    {
      pCheckup_a->lLabelTooLong=TRUE;
      return NULL;
    }

  strncpy( sBuffer_a, sData_a, iLength );
  sBuffer_a[iLength]=0;

  *iPointer_a += (iLength+1);  /* Include the \r */

  return sBuffer_a;
}
/* ==========================================================
 * Parse a header line and attempt to break it into two
 * tokens.  The syntax is string, followed by any number of
 * spaces followed by a colon followed by any number of spaces,
 * followed by the second token.  This function will put a null
 * at the end of the first token, and return a pointer to the
 * second token.  It returns NULL if it cannot find all it wants.
 */
static char *LabelParse
( char *sHeader_a )
{
  char *sPtr;
  /* Search for the first space */
  sPtr = strchr( sHeader_a, ' ' );
  if (!sPtr) return NULL;
  *sPtr++ = 0;  /* Null terminate the first token */

  /* Search for the next colon */
  sPtr = strchr( sPtr, ':' );
  if (!sPtr) return NULL;

  /* Search for the next character which is not a space */
  sPtr++;
  while (*sPtr)
    {
      if (' ' !=*sPtr) return sPtr;
      sPtr++;
    }
  /* Hit the zero at the end of the string */
  return NULL;
}

/* =======================================================================
 * Special parsing for the H labels.  Returns true on error.
 */
static UINT1 ParseHLabels
( char *sHeader_a,
  rainbow_info8 *pInfo_a )
{
  char *sValue, *sEnd;
  /* Break first token off : */
  sValue = LabelParse( sHeader_a );
  if (!sValue) return TRUE;

  if (0==strcmp(sHeader_a, "H3" ) )
    {
      pInfo_a->iFileType = strtol( sValue, &sEnd, 10 );
      if (sValue==sEnd) return TRUE;
    }
  else if (0==strcmp(sHeader_a, "H8" ) )
    {
      strncpy( pInfo_a->sRadarId, sValue, 15 );
    }

  return FALSE;
};
/* =======================================================================
 * Special parsing for the N labels.  Returns true on error.
 */
static UINT1 ParseNLabels
( char *sHeader_a,
  rainbow_info8 *pInfo_a )
{
  char *sValue, *sEnd;
  /* Break first token off: */
  sValue = LabelParse( sHeader_a );
  if (!sValue) return TRUE;

  pInfo_a->iProductType = strtol( sValue, &sEnd, 10 );
  if (sValue==sEnd) return TRUE;

  return FALSE;
};
/* =======================================================================
 * Special parsing for the A labels.  Returns true on error.
 */
static UINT1 ParseALabels
( char *sHeader_a,
  rainbow_info8 *pInfo_a )
{
  char *sValue, *sEnd;
  /* Break first token off: */
  sValue = LabelParse( sHeader_a );
  if (!sValue) return TRUE;

  if (0==strcmp(sHeader_a, "A9" ) )
    {
      pInfo_a->iSweepCount = strtol( sValue, &sEnd, 10 );
      if (sValue==sEnd) return TRUE;
    }

  return FALSE;
};

/* =======================================================================
 * Special parsing for the P labels.  Returns true on error.
 */
static UINT1 ParsePLabels
( char *sHeader_a,
  rainbow_info8 *pInfo_a,
  const struct convert_config *pCSetup_a ) /* Info from config file */
{
  char *sValue, *sValues, *sEnd, *sTokens[4], *sState;
  /* Break first token off: */
  sValues = LabelParse( sHeader_a );
  if (!sValues) return TRUE;

  /* Parse 3 tokens on the line */
  sTokens[0] = strtok_r( sValues, " ", &sState );
  sTokens[1] = strtok_r(    NULL, " ", &sState );
  sTokens[2] = strtok_r(    NULL, " ", &sState );
  sTokens[3] = strtok_r(    NULL, " ", &sState );
  sValue= sTokens[0];

  if (0==strcmp(sHeader_a, "P1" ) )
    {
      /* Location uses 3 or 4 tokens */
      if (!sTokens[2]) return TRUE;
      if (!pCSetup_a->lNoZdrSupport)
	{
	  if (sTokens[3])
	    {
	      pInfo_a->fMinZdr = strtod( sTokens[3], &sEnd );
	      if (sTokens[3]==sEnd) return TRUE;
	    }
	  else
	    {
	      pInfo_a->lHeaderSyntaxError=TRUE;
	      Lprintf( pCSetup_a,"P1 Error, try NO_ZDR_SUPPORT in RainbowToIris.conf\n");
	    }
	}
    }
  else if (0==strcmp(sHeader_a, "P2" ) )
    {
      /* Location uses 3 or 4 tokens */
      pInfo_a->fNyquistVel = strtod( sTokens[2], &sEnd );
      if (sTokens[2]==sEnd) return TRUE;

      if (!pCSetup_a->lNoZdrSupport)
	{
	  if (sTokens[3])
	    {
	      pInfo_a->fMaxZdr = strtod( sTokens[3], &sEnd );
	      if (sTokens[3]==sEnd) return TRUE;
	    }
	  else
	    {
	      pInfo_a->lHeaderSyntaxError=TRUE;
	      Lprintf( pCSetup_a, "P2 Error, try NO_ZDR_SUPPORT in RainbowToIris.conf\n");
	    }
	}
    }
  else if (0==strcmp(sHeader_a, "P3" ) )
    {
      pInfo_a->fRangeStart = strtod( sValue, &sEnd );
      if (sValue==sEnd) return TRUE;
    }
  else if (0==strcmp(sHeader_a, "P4" ) )
    {
      pInfo_a->fRangeStop = strtod( sValue, &sEnd );
      if (sValue==sEnd) return TRUE;
    }
  else if (0==strcmp(sHeader_a, "P6" ) )
    {
      pInfo_a->iSampleSize = strtol( sValue, &sEnd, 10 );
      if (sValue==sEnd) return TRUE;
    }
  else if (0==strcmp(sHeader_a, "P7" ) )
    {
      pInfo_a->iRangeAvg = strtol( sValue, &sEnd, 10 );
      if (sValue==sEnd) return TRUE;
      if (pInfo_a->iRangeAvg<1) pInfo_a->iRangeAvg=1;
      if (pInfo_a->iRangeAvg>8) pInfo_a->iRangeAvg=8;
    }
  else if (0==strcmp(sHeader_a, "P12" ) )
    {
      pInfo_a->iClutterFilter = strtol( sValue, &sEnd, 10 );
      if (sValue==sEnd) return TRUE;
    }
  else if (0==strcmp(sHeader_a, "P19" ) )
    {
      pInfo_a->lLogSpeckle = (0 != strtol( sValue, &sEnd, 10 ));
      if (sValue==sEnd) return TRUE;
    }
  else if (0==strcmp(sHeader_a, "P20" ) )
    {
      pInfo_a->lDopSpeckle = (0 != strtol( sValue, &sEnd, 10 ));
      if (sValue==sEnd) return TRUE;
    }
  else if (0==strcmp(sHeader_a, "P21" ) )
    {
      pInfo_a->fSqiThreshold = strtod( sValue, &sEnd);
      if (sValue==sEnd) return TRUE;
    }
  else if (0==strcmp(sHeader_a, "P22" ) )
    {
      pInfo_a->fCsrThreshold = strtod( sValue, &sEnd);
      if (sValue==sEnd) return TRUE;
    }
  else if (0==strcmp(sHeader_a, "P92" ) )
    {
      pInfo_a->fLogThreshold = strtod( sValue, &sEnd);
      if (sValue==sEnd) return TRUE;
    }

  return FALSE;
};

/* =======================================================================
 * Special parsing for the W labels.  Returns true on error.
 */
static UINT1 ParseWLabels
( char *sHeader_a,
  rainbow_info8 *pInfo_a,
  const struct convert_config *pCSetup_a ) /* Info from config file */
{
  char *sValues, *sEnd, *sTokens[15], *sState;
  UINT1 lOnly14;
  int iSweep, iToken;

  /* Break first token off: */
  sValues = LabelParse( sHeader_a );
  if (!sValues) return TRUE;

  /* Get the sweep number, check for invalid numbers.
   * RHI not supported yet.
   */
  iSweep = strtol( sHeader_a+1, &sEnd, 10 );
  if ( (sHeader_a+1) == sEnd) return TRUE;

  if ((iSweep<1)||(iSweep>RAINBOW_MAX_SWEEPS)) return TRUE;
  iSweep-=1;  /* Make origin 0 */

  /* Parse all the 15 tokens on the line.  There is a bug in some versions, so
   * sometimes value number 9 is dropped.  Therefore, if we find the last one
   * missing, set a flag.
   */
  lOnly14=FALSE;
  sTokens[0] = strtok_r( sValues, " ", &sState );
  if (!sTokens[0]) return TRUE;
  for (iToken=1; iToken<15; iToken++ )
    {
      sTokens[iToken] = strtok_r( NULL, " ", &sState );
      if (!sTokens[iToken])
	{
	  if (iToken==14)
	    {
	      if (!pCSetup_a->lNoZdrSupport) lOnly14=TRUE;
	    }
	  else 
	    return TRUE;
	}
    }

  pInfo_a->Sweep[iSweep].fFixedAngle  = strtod( sTokens[0], &sEnd );
  if (sTokens[0]==sEnd) return TRUE;

  pInfo_a->Sweep[iSweep].fSectorStart = strtod( sTokens[1], &sEnd );
  if (sTokens[1]==sEnd) return TRUE;

  pInfo_a->Sweep[iSweep].fSectorEnd   = strtod( sTokens[2], &sEnd );
  if (sTokens[2]==sEnd) return TRUE;

  pInfo_a->Sweep[iSweep].fSectorStep  = strtod( sTokens[3], &sEnd );
  if (sTokens[3]==sEnd) return TRUE;

  pInfo_a->Sweep[iSweep].fScanSpeed   = strtod( sTokens[4], &sEnd );
  if (sTokens[4]==sEnd) return TRUE;

  pInfo_a->Sweep[iSweep].iPulseWidth  = strtol( sTokens[5], &sEnd, 10 );
  if (sTokens[5]==sEnd) return TRUE;

  pInfo_a->Sweep[iSweep].iPrfHigh     = strtol( sTokens[6], &sEnd, 10 );
  if (sTokens[6]==sEnd) return TRUE;

  pInfo_a->Sweep[iSweep].iPrfLow      = strtol( sTokens[7], &sEnd, 10 );
  if (sTokens[7]==sEnd) return TRUE;

  if (!lOnly14)
    {
      pInfo_a->Sweep[iSweep].lZData   = strtol( sTokens[8], &sEnd, 10 );
      if (sTokens[8]==sEnd) return TRUE;
    }

  if (lOnly14) iToken=8; else iToken=9;
  pInfo_a->Sweep[iSweep].lVData       = strtol( sTokens[iToken], &sEnd, 10 );
  if (sTokens[iToken]==sEnd) return TRUE;

  if (lOnly14) iToken=9; else iToken=10;
  pInfo_a->Sweep[iSweep].lWData       = strtol( sTokens[iToken], &sEnd, 10 );
  if (sTokens[iToken]==sEnd) return TRUE;

  if (lOnly14) iToken=10; else iToken=11;
  pInfo_a->Sweep[iSweep].iDualCase    = strtol( sTokens[iToken], &sEnd, 10 );
  if (sTokens[iToken]==sEnd) return TRUE;

  /* Skip if no ZDR support */
  if (!pCSetup_a->lNoZdrSupport)
    {
      if (lOnly14) iToken=13; else iToken=14;
      pInfo_a->Sweep[iSweep].lZdrData     = strtol( sTokens[iToken], &sEnd, 10 );
      if (sTokens[iToken]==sEnd) return TRUE;
    }

  return FALSE;
};
/* =======================================================================
 * Special parsing for the F labels.  Returns true on error.
 */
static UINT1 ParseFLabels
( char *sHeader_a,
  rainbow_info8 *pInfo_a,
  const struct convert_config *pCSetup_a ) /* Info from config file */
{
  char *sValues, *sEnd, *sTokens[3], *sState;

  /* Break first token off: */
  sValues = LabelParse( sHeader_a );
  if (!sValues) return TRUE;

  /* Parse 3 tokens on the line */
  sTokens[0] = strtok_r( sValues, " ", &sState );
  sTokens[1] = strtok_r(    NULL, " ", &sState );
  sTokens[2] = strtok_r(    NULL, " ", &sState );


  if (0==strcmp(sHeader_a, "F4" ) )
    {
      FLT8 fArg1, fArg2;
      /* Location uses 2 tokens */
      if (!sTokens[0]) return TRUE;
      if (!sTokens[1]) return TRUE;
      fArg1 = strtod( sTokens[0], &sEnd );
      if (sTokens[0]==sEnd) return TRUE;
      fArg2 = strtod( sTokens[1], &sEnd );
      if (sTokens[1]==sEnd) return TRUE;

      if (pCSetup_a->lF4LatitudeFirst)
	{
	  pInfo_a->fRadarLatitude  = fArg1;
	  pInfo_a->fRadarLongitude = fArg2;
	}
      else
	{
	  pInfo_a->fRadarLongitude = fArg1;
	  pInfo_a->fRadarLatitude  = fArg2;
	}

      if (pInfo_a->fRadarLongitude< -180.0) return TRUE;
      if (pInfo_a->fRadarLongitude>  180.0) return TRUE;
      if (pInfo_a->fRadarLatitude< -90.0) return TRUE;
      if (pInfo_a->fRadarLatitude>  90.0) return TRUE;
    }
  else if (0==strcmp(sHeader_a, "F5" ) )
    {
      /* Date uses 3 tokens */
      if (!sTokens[0]) return TRUE;
      if (!sTokens[1]) return TRUE;
      if (!sTokens[2]) return TRUE;
      pInfo_a->iDay   = strtol( sTokens[0], &sEnd, 10 );
      if (sTokens[0]==sEnd) return TRUE;
      if (pInfo_a->iDay < 1 ) return TRUE;
      if (pInfo_a->iDay > 31 ) return TRUE;
      pInfo_a->iMonth = strtol( sTokens[1], &sEnd, 10 );
      if (sTokens[1]==sEnd) return TRUE;
      if (pInfo_a->iMonth < 1 ) return TRUE;
      if (pInfo_a->iMonth > 12 ) return TRUE;
      pInfo_a->iYear  = strtol( sTokens[2], &sEnd, 10 );
      if (sTokens[2]==sEnd) return TRUE;
      /* Some systems use only 2 digit year, others use 4 digit.  Try to
       * detect the difference here:
       */
      if (pInfo_a->iYear < 70 )
	pInfo_a->iYear += 2000;
      else if (pInfo_a->iYear < 1000 )
	pInfo_a->iYear += 1900;
      
      if (pInfo_a->iYear < 1970 ) return TRUE;
      if (pInfo_a->iYear > 3000 ) return TRUE;
    }
  else if (0==strcmp(sHeader_a, "F6" ) )
    {
      /* Time uses 3 tokens */
      if (!sTokens[0]) return TRUE;
      if (!sTokens[1]) return TRUE;
      if (!sTokens[2]) return TRUE;
      pInfo_a->iSecond  = strtol( sTokens[0], &sEnd, 10 );
      if (sTokens[0]==sEnd) return TRUE;
      if (pInfo_a->iSecond < 0 ) return TRUE;
      if (pInfo_a->iSecond > 60 ) return TRUE;
      pInfo_a->iMinute = strtol( sTokens[1], &sEnd, 10 );
      if (sTokens[1]==sEnd) return TRUE;
      if (pInfo_a->iMinute < 0 ) return TRUE;
      if (pInfo_a->iMinute > 59 ) return TRUE;
      pInfo_a->iHour  = strtol( sTokens[2], &sEnd, 10 );
      if (sTokens[2]==sEnd) return TRUE;
      if (pInfo_a->iHour < 0 ) return TRUE;
      if (pInfo_a->iHour > 23 ) return TRUE;
    }
  else if (0==strcmp(sHeader_a, "F8" ) )
    {
      if (!sTokens[0]) return TRUE;
      strncpy( pInfo_a->sScanFile, sTokens[0], FILENAME_SIZE-1 );
    }
  else if (0==strcmp(sHeader_a, "F9" ) )
    {
      char sMessage[100];
      pInfo_a->iRbDataType = strtol( sTokens[0], &sEnd, 10 );
      if (sTokens[0]==sEnd) return TRUE;

      /* Convert to IRIS data type */
      pInfo_a->iIrisDataType = iDataFromRainbow( pInfo_a->iRbDataType );
      switch (pInfo_a->iIrisDataType)
	{
	case DB_NULL:
	  sprintf( sMessage, "ERROR, Rainbow Data type %d (F9) does not convert to IRIS.\n", 
		   pInfo_a->iRbDataType );
	  Lprintf( pCSetup_a, sMessage );
	  pInfo_a->lHeaderSyntaxError=TRUE;
	  break;
	case DB_ZDR:
	  if (pCSetup_a->lNoZdrSupport)
	    {
	      Lprintf( pCSetup_a,
		       "ERROR, File contains ZDR, consider removing NO_ZDR_SUPPORT from RainbowToIris.conf.\n" );
	      pInfo_a->lHeaderSyntaxError=TRUE;
	    }
	  break;

	}
    }

  return FALSE;
};


/* =======================================================================
 * Special parsing for the R labels.  Returns true on error.
 */
static UINT1 ParseRLabels
( char *sHeader_a,
  rainbow_info8 *pInfo_a )
{
  char *sValue, *sEnd;
  /* Break first token off: */
  sValue = LabelParse( sHeader_a );
  if (!sValue) return TRUE;

  if (0==strcmp(sHeader_a, "R1" ) )
    {
      pInfo_a->iRangeBinCount = strtol( sValue, &sEnd, 10 );
      if (sValue==sEnd) return TRUE;
    }
  else if (0==strcmp(sHeader_a, "R2" ) )
    {
      pInfo_a->fRangeStep = strtod( sValue, &sEnd );
      if (sValue==sEnd) return TRUE;
    }
  else if (0==strcmp(sHeader_a, "R8" ) )
    {
      pInfo_a->iDataBytes = strtol( sValue, &sEnd, 10 );
      if (sValue==sEnd) return TRUE;
    }

  return FALSE;
};

/* =======================================================================
 * Clean up the parsed information after all the labels have been parsed.
 */
static void RainbowParseCleanup
( struct rainbow_info8 *pInfo_a, 
  const struct convert_config *pCSetup_a )
{
  SINT4 iSweep;

  /* If so flagged, and the clutter filter (P12) is zero, and the current data
   * type is not Z (F9),
   * then change all sweep expected Z data to T data.
   */
  if ((pCSetup_a->lAllowTData)&&(0==pInfo_a->iClutterFilter)&&(1!=pInfo_a->iRbDataType))
    {
      for (iSweep=0; iSweep<RAINBOW_MAX_SWEEPS; iSweep++)
	{
	  if (pInfo_a->Sweep[iSweep].lZData)
	    {
	      pInfo_a->Sweep[iSweep].lZData=FALSE;
	      pInfo_a->Sweep[iSweep].lTData=TRUE;
	    }
	}
    }

}
/* =======================================================================
 * Check for and print out warnings based on the parsed info.
 */
UINT1 RainbowWarnings
( const struct rainbow_info8 *pInfo_a, 
  const struct convert_config *pCSetup_a 
)
{
  char sLine[120];
  UINT1 lError=FALSE;
  SINT4 iSweep, iExpectedSize;

  if (pInfo_a->iFileSize<=0)
    {
      sprintf( sLine, "WARNING: Not Rainbow scan file, file size is %d.\n",
	       pInfo_a->iFileSize );
      Lprintf( pCSetup_a, sLine );
      lError=TRUE;
    }
  if (!pInfo_a->lSohFound)
    {
      sprintf( sLine, "WARNING: Not Rainbow scan file, first char 0x%02d not SOH.\n",
	       pInfo_a->iFirstByte );
      Lprintf( pCSetup_a, sLine );
      lError=TRUE;
    }
  if (pInfo_a->lSohInWrongPlace)
    {
      Lprintf( pCSetup_a, "WARNING: Not Rainbow scan file, extra SOH.\n" );
      lError=TRUE;
    }
  if (!pInfo_a->iEtxOffset)
    {
      Lprintf( pCSetup_a, "WARNING: Not Rainbow scan file, no ETX.\n" );
      lError=TRUE;
    }
  if (2 != pInfo_a->iEtbCount)
    {
      sprintf( sLine, "WARNING: Not Rainbow scan file, ETB count:%d.\n",
	       pInfo_a->iEtbCount );
      Lprintf( pCSetup_a, sLine );
      lError=TRUE;
    }
  if (pInfo_a->lUnknownLabel)
    {
      if (isprint(pInfo_a->sUnknownLabel) )
	sprintf( sLine, "WARNING: Not Rainbow scan file, unknown label '%c'.\n",
		 pInfo_a->sUnknownLabel );
      else
	sprintf( sLine, "WARNING: Not Rainbow scan file, unknown label 0%o.\n",
		 pInfo_a->sUnknownLabel );
      Lprintf( pCSetup_a, sLine );
      lError=TRUE;
    }
  if (pInfo_a->lLabelOffEnd)
    {
      Lprintf( pCSetup_a, "WARNING: Not Rainbow scan file, label goes off the end.\n" );
      lError=TRUE;
    }
  if (pInfo_a->lLabelTooLong)
    {
      Lprintf( pCSetup_a, "WARNING: Not Rainbow scan file, label too long.\n" );
      lError=TRUE;
    }

  if ( pInfo_a->lHeaderSyntaxError)
    {
      if (!pCSetup_a->lRainbowView)
	Lprintf( pCSetup_a, "WARNING: Syntax error in header, see RainbowView for details.\n");
      lError=TRUE;
    }

  /* Check the file size for volume scan data  */
  iExpectedSize = pInfo_a->iEtxOffset + pInfo_a->iSweepCount*360*pInfo_a->iRangeBinCount;
  /* Note that the file size is sometimes one to big */
  if (( pInfo_a->iFileSize != iExpectedSize ) &&
      ( pInfo_a->iFileSize != (iExpectedSize+1) ) )
    {
      sprintf( sLine, "WARNING: File size of %d does not match %d header plus %d sweeps of %d bins.\n",
	pInfo_a->iFileSize, pInfo_a->iEtxOffset, pInfo_a->iSweepCount, 
	       pInfo_a->iRangeBinCount );
      Lprintf( pCSetup_a, sLine );
      lError=TRUE;
    }

  if (pCSetup_a->lRainbowView)
    {
      fprintf( stdout, "Total Header size is %d bytes.\n", pInfo_a->iEtxOffset );
    }

  /* Check that important parameters remain constant between sweeps */
  for (iSweep=0; iSweep<pInfo_a->iSweepCount; iSweep++)
    {
      if (pInfo_a->Sweep[iSweep].iPrfHigh != pInfo_a->Sweep[0].iPrfHigh )
	{
	  Lprintf( pCSetup_a, "WARNING: PRF changes between sweeps.\n" );
	  lError=TRUE;
	}
      if (pInfo_a->Sweep[iSweep].fSectorStart != pInfo_a->Sweep[0].fSectorStart )
	{
	  Lprintf( pCSetup_a, "WARNING: Sector start changes between sweeps.\n" );
	  lError=TRUE;
	}
      if (pInfo_a->Sweep[iSweep].fSectorEnd != pInfo_a->Sweep[0].fSectorEnd )
	{
	  Lprintf( pCSetup_a, "WARNING: Sector end changes between sweeps.\n" );
	  lError=TRUE;
	}
      if (pInfo_a->Sweep[iSweep].fSectorStep != pInfo_a->Sweep[0].fSectorStep )
	{
	  Lprintf( pCSetup_a, "WARNING: Sector step changes between sweeps.\n" );
	  lError=TRUE;
	}
    }

  return lError;
}

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

  /* Initialize the data to the default values */
  memset( pCSetup_a, 0, sizeof(*pCSetup_a) );
  /* Default pulsewidth list */
  pCSetup_a->fPwList[0] = 0.5;
  pCSetup_a->fPwList[1] = 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, PATHNAME_SIZE-20 );
  strcat( sPath, "RainbowToIris.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, MAX_LINE_LENGTH, 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, MAX_LINE_LENGTH, 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>
 * SITENAME      <name>
 * VERBOSE
 *
 */
static UINT2 ProcessConfigLine
(char *sline_a,
 struct convert_config *pConfig_a )
{
  char *stokens[9];
  UINT4 icount, ioffset;
  
  ioffset = 0;
  icount  = 0;
  /* Process up to 9 tokens, stop when the line is exhausted. */
  while (icount<9) 
    {
      stokens[icount] = sigtok( sline_a, &ioffset );
      if (NULL == stokens[icount])
	break;
      else
	icount++;
    }
  
  /* Blank lines are allowed and skipped */
  if (icount == 0) return FALSE;
  
  /* Skip commented lines */
  if (stokens[0][0] == '#') return FALSE;

  if ( 0 == strcmp( "APPEND_TO_LOG", stokens[0] ) )
    {
      pConfig_a->lAppendToLog=TRUE;
      return FALSE;
    }

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

      if      ( 0 == strcmp( "LATITUDE_FIRST", stokens[1] ) ) 
	{pConfig_a->lF4LatitudeFirst = TRUE;}
      else if ( 0 == strcmp( "LONGITUDE_FIRST", stokens[1] ) ) 
	{pConfig_a->lF4LatitudeFirst = FALSE;}
      else
	return TRUE;

      return FALSE;
    }

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

  if ( 0 == strcmp( "NO_ZDR_SUPPORT", stokens[0] ) )
    {
      pConfig_a->lNoZdrSupport=TRUE;
      return FALSE;
    }

  if ( 0 == strcmp( "ALLOW_T_DATA", stokens[0] ) )
    {
      pConfig_a->lAllowTData=TRUE;
      return FALSE;
    }

  /* Shows list of pulse widths available */
  if ( 0 == strcmp( "PULSEWIDTHS", stokens[0] ) )
    {
      char *sptr;
      if ( icount < 3 ) return TRUE;

      pConfig_a->fPwList[0] = strtod( stokens[1], &sptr );
      if (sptr == stokens[1]) return TRUE;

      pConfig_a->fPwList[1] = strtod( stokens[2], &sptr );
      if (sptr == stokens[2]) return TRUE;

      return FALSE;
    }

  if ( 0 == strcmp( "SITE", stokens[0] ) )
    {
      struct convert_site_list *pOneSite;
      char *sptr;
      if ( icount < 8 ) return TRUE;
      if ( pConfig_a->iSiteCount >= MAX_CONVERT_SITES) return TRUE;
      pOneSite = &pConfig_a->Sites[pConfig_a->iSiteCount];
      strncpy( pOneSite->sRainbowSitename, stokens[1], 15 );
      pOneSite->sRainbowSitename[15] = 0;
      spacify_ncpy( pOneSite->sIrisSitename, stokens[2], 16 );

      pOneSite->iLocalWest = strtol( stokens[3], &sptr, 10 );
      if (sptr == stokens[1]) return TRUE;

      /* Allow 16 hours either way */
      if (pOneSite->iLocalWest < -16*60 ) return TRUE;
      if (pOneSite->iLocalWest >  16*60 ) return TRUE;

      /* Local TZ name */
      strncpy( pOneSite->sLocalTZName, stokens[4], TZNAME_SIZE-1 );

      /* IRIS TZ output flag */
      if      ( 0 == strcmp( "LOCAL", stokens[5] ) ) 
	{}
      else if ( 0 == strcmp( "UTC", stokens[5] ) ) 
	{pOneSite->lIrisUtc=TRUE;}
      else
	return TRUE;

      /* Rainbow TZ input flag */
      if      ( 0 == strcmp( "LOCAL", stokens[6] ) ) 
	{}
      else if ( 0 == strcmp( "UTC", stokens[6] ) ) 
	{pOneSite->lRainbowUtc=TRUE;}
      else
	return TRUE;

      /* Altitude of the site */
      pOneSite->fAltitude = strtod( stokens[7], &sptr );
      if (sptr == stokens[7]) return TRUE;

      pConfig_a->iSiteCount++;
      return FALSE;
    }

  /* See if the line contains a TASK command */
  /* Format is something like: TASK  "<RBsitename>" PPI 0.5 Z,V PPI_VOL <fMinZ>*/
  if ( 0 == strcmp( "TASK", stokens[0] ) )
    {
      struct convert_task_list *pOneTask;
      char *sptr;
      if ( icount < 8 ) return TRUE;
      if ( pConfig_a->iTaskCount >= MAX_CONVERT_TASKS) return TRUE;
      pOneTask = &pConfig_a->Tasks[pConfig_a->iTaskCount];
      strncpy( pOneTask->sRainbowSitename, stokens[1], 15 );
      pOneTask->sRainbowSitename[15] = 0;


      if (0 == strcmp( "PPI", stokens[2]))
	pOneTask->iProductType = RB_PROD_AZ_SCAN;
      else if (0 == strcmp( "RHI", stokens[2]))
	pOneTask->iProductType = RB_PROD_EL_SCAN;
      else
	return TRUE;

      pOneTask->fFirstFixedAngle = strtod( stokens[3], &sptr );
      if (sptr == stokens[3]) return TRUE;

      if (ParseDataList( pOneTask, stokens[4] )) return TRUE;

      strncpy( pOneTask->sRainbowScanFile, stokens[5], FILENAME_SIZE-1 );
      pOneTask->sRainbowScanFile[FILENAME_SIZE-1] = 0;

      strncpy( pOneTask->sIrisTaskName, stokens[6], 12 );
      pOneTask->sIrisTaskName[12] = 0;

      /* seventh field fZCalib */
      pOneTask->fIrisZCalib = strtod( stokens[7], &sptr );
      if (sptr == stokens[7]) return TRUE;
      if ((pOneTask->fIrisZCalib > 50.0) || (pOneTask->fIrisZCalib<-100)) return TRUE;

      pConfig_a->iTaskCount++;
      return FALSE;
    }
  
  if ( 0 == strcmp( "VERBOSE", stokens[0] ) )
    {
      pConfig_a->lVerbose = TRUE;
      return FALSE;
    }
  
  /* Unrecognized lines will generate a syntax error */
  return TRUE;
}

/* ===================================================
 * Parse the list of data types in the token.  Return
 * TRUE on a syntax error.
 */
static UINT1 ParseDataList
(struct convert_task_list *pOneTask_a,
 char *sString_a )
{
  char *sToken, *sState;
  sToken = strtok_r( sString_a, ",", &sState );
  if (NULL == sToken) return TRUE;  /* Must be at least one token */
  while( sToken )
    {
      switch( sToken[0] )
	{
	case '*':
	  pOneTask_a->lWildData=TRUE; return FALSE; break;  /* Wildcard exits now */
	case 'Z':
	  pOneTask_a->lZData=TRUE; break;
	case 'V':
	  pOneTask_a->lVData=TRUE; break;
	case 'W':
	  pOneTask_a->lWData=TRUE; break;
	case 'T':
	  pOneTask_a->lTData=TRUE; break;
	case 'D':
	  pOneTask_a->lZdrData=TRUE; break;
	default:
	  return TRUE;      /* Unknown letter is error */
	  break;
	}
      sToken = strtok_r( NULL, ",", &sState );
    }
  return FALSE;

}
/* ================================================================
 * Get the IRIS data type from the Rainbow type
 */
static UINT1 iDataFromRainbow( UINT1 iRainbowData_a )
{
  UINT1 iIrisData;
  switch( iRainbowData_a )
    {
    case RB_DATA_V:  iIrisData = DB_VEL;   break;
    case RB_DATA_Z:  iIrisData = DB_DBZ;   break;
    case RB_DATA_W:  iIrisData = DB_WIDTH; break;
    case RB_DATA_T:  iIrisData = DB_DBT;   break;
    case RB_DATA_D:  iIrisData = DB_ZDR;   break;
    default:         iIrisData = DB_NULL;  break;
    }
  return iIrisData;

}

/* ===========================================
 * Print string to log and terminal, if desired.
 */
void Lprintf
( const struct convert_config *pCSetup_a,
  const char *sString_a )
{
  UINT1 lLogToTerm, lLogToFile;
  char sTimeBuf[TIMENAME_SIZE];
  struct ymds_time Ymds;
  GetTimeYmds( &Ymds, TIME_FLG_LOCAL );

  if (pCSetup_a)
    {
      lLogToTerm=pCSetup_a->lLogToTerm;
      lLogToFile=pCSetup_a->lLogToFile;
    }
  else
    {
      lLogToTerm=TRUE;
      lLogToFile=FALSE;
    }

  if (lLogToTerm)
    {
      fputs( sString_a, stderr );
    }
  if (lLogToFile)
    {
      shhmmssmmm_ddmonyyyy( &Ymds, sTimeBuf );
      fprintf( pCSetup_a->iLogFile, "%s:", sTimeBuf );
      fputs( sString_a, pCSetup_a->iLogFile );
      fflush( pCSetup_a->iLogFile );
    }

  return;
}
