/* ******************************************************************
 * *                                                                *  
 * *  Function to convert an IRIS product to GRIB Version 1 format  *
 * *                                                                *
 * ******************************************************************
 * File: utils/grib1/IrisToGrib1.C
 *
 *                COPYRIGHT (c) 2002, 2003, 2004, 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.
 */

// #define DEBUG TRUE

#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 "dsp_lib.h"
#include "himath_lib.h"
#include "misc_lib.h"
#include "user_lib.h"

#include "siggrib1.h"

#define MAXDESCS  (80)

struct convert_config8 CConfig_c;

/* Information from the product useful in the conversion */
struct convert_info
{
  int  iGribLevelCode;       /* Table 3 entry for height of data */
  int  iGribLevelHeight;     /* Height of data in meters */
  int  iGribTimeRangeCode;   /* What kind of time range was used */
  int  iGribTimeRangeHours;  /* Length of the time range */
  int  iGribInputCount;      /* Number of inputs in accumulation */
  int  iGribProjection;      /* Projection used */
  int  iGribGdsSize;         /* Size of the GDS structure needed */
  struct convert_datatype DataType;
  struct ymds_time IngTime;  /* Grib time in UTC */
  //  const struct convert_site_table8 *pSite;
};

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

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

static void Usage( void );

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

#if 0
static const struct convert_site_table8 * FindSiteByName
( const char *sSitename_a,             /* IRIS sitename, NULL terminated */
  const struct convert_config8 *pCConfig_a ); /* Configuration */
#endif /* #if 0 */

static void ConvertToGrib1
(const struct cartesian_product *pProd_a,
 struct convert_info *pConvert_a );

static void SetupPDS
( char **ppPds_a,                    /* Fill in PDS pointer, if successful */
  size_t *pPdsSize_a,                /* Fill in size of PDS, if successful */
  const struct convert_info *pConvert_a,
  const struct product_hdr *pPHeader_a,
  size_t iGdsSize_a );               /* Size of GDS already made */

static void SetupGDS
( char **ppGds_a,                    /* Fill in GDS pointer, if successful */
  size_t *pGdsSize_a,                /* Fill in size of GDS, if successful */
  const struct convert_info *pConvert_a,
  const struct product_hdr *pPHeader_a );

static void SetupBMSAndBDS
( char   **pBitMap_a,
  size_t *pBitMapSize_a,
  char   **pData_a, 
  size_t *pDataSize_a,
  const struct convert_info *pConvert_a,
  const struct cartesian_product *pProd_a );

static void SetupBMS
( char   **pBitMap_a,        /* Pointer to bitmap, already zeroed */
  size_t *pBitMapSize_a,     /* Size of bitmap in bytes */
  UINT1  **pMappedData_a,    /* Pointer to remapped data */
  SINT4  *pMappedDataSize_a, /* Size of remapped data in bins */
  const struct cartesian_product *pProd_a );

static void SetupBDS
( char   **pBds_a, 
  size_t *pBdsSize_a,
  const UINT1 *pMappedData_a,    /* Pointer to data array with bitmaps removed */
  SINT4 iMappedDataSize_a,       /* Number of elements in the data array */
  const struct convert_info *pConvert_a,
  const struct product_hdr *pPHeader_a );

static void SetupSection0and5
( char *sec[6],
  size_t secl[6],
  const struct ymds_time *pTime_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 cartesian_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 );

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

  /* 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, "IrisToGrib1.log" );
      if (CConfig_c.lAppendToLog)
	CConfig_c.iLogFile = fopen( sLogPath, "a" );
      else
	CConfig_c.iLogFile = fopen( sLogPath, "w" );
      if (NULL == CConfig_c.iLogFile)
	{
	  fprintf( stderr, "Can not open log file: '%s'\n", sLogPath );
	  exit(EXIT_FAILURE);
	}
    }

  if (CConfig_c.lVerbose) Lprintf( &CConfig_c, "Verbose Logging On." );

  /* Starting here we can use the Lprintf function */
  sig_establish( SignalHandler, "IrisToGrib1" );
    
#if 0
  /* Check for no sites in the config file */
  if (CConfig_c.iSiteCount<1)
    {
      Lprintf( &CConfig_c, "ERROR: No SITE line in the IrisToGrib1.conf file" );
      exit(EXIT_FAILURE);
    }
#endif  /* #if 0 */

  /* Read in the header */
  iCount =  fread( &ProdHeader, 1, sizeof(ProdHeader), stdin );
  if (iCount != sizeof(ProdHeader)) 
    {
      sprintf( sLog, "Bad read, got%d, wanted%d", iCount, sizeof(ProdHeader));
      Lprintf( &CConfig_c, sLog );
      exit(EXIT_FAILURE);
    }

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

  /* Malloc input buffer */
  pInProd  = (struct cartesian_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", iCount, iWantCount);
      Lprintf( &CConfig_c, sLog );
      exit(EXIT_FAILURE);
    }
  
  ConvertToGrib1( pInProd, &Convert );

  shhmmssddmonyyyy_r(&ProdHeader.pcf.ing_time, sProdTime );
  sprintf( sLog, "Converted %s product: %s, Date: %s", 
	   sproduct_name5(ProdHeader.pcf.iptype),
	   nullify(ProdHeader.pcf.spname,12), sProdTime );
  Lprintf( &CConfig_c, sLog );

  exit( EXIT_SUCCESS );
}

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

  int iArg;

  /* Parse command line options
   */
  for( iArg=1 ; iArg<iArgc_a ; iArg++ )
    {
      const char *sArg = sArgv_a[iArg];

      /* fprintf( stderr, "Processing %d: '%s'\n", iArg, sArg ); */
      if (0 == strncmp( sArg, "-h", 2))
	{
	  Usage();
	}
      
    }
  return;
}

/* ==================================================
 */
static void Usage( void )
{
  fprintf( stderr, "Options: -help\n" );
  fprintf( stderr, "         (all other 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];
  SINT4 iTimeOffset;

  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_c, "File header contains invalid ID." ) ;
      return USER_ERROR_SIG;
    }

  /* Based on the product type, figure out some product specific info */
  switch( pheader_a->pcf.iptype)
    {
    case PROD_RAIN1:
      pConvert_a->iGribTimeRangeCode = GRIB_TABLE5_ACCUMULATION;
      pConvert_a->iGribTimeRangeHours = 1;
      pConvert_a->iGribInputCount = pheader_a->end.input_cnt;
      pConvert_a->iGribLevelCode = GRIB_TABLE3_GROUND;
      break;
    case PROD_RAINN:
      pConvert_a->iGribTimeRangeCode = GRIB_TABLE5_ACCUMULATION;
      pConvert_a->iGribTimeRangeHours = pheader_a->pcf.psi.rain.irainn_hours;
      pConvert_a->iGribInputCount = pheader_a->end.input_cnt;
      pConvert_a->iGribLevelCode = GRIB_TABLE3_GROUND;
      break;
    case PROD_SRI:  
    case PROD_USER:
      pConvert_a->iGribLevelCode = GRIB_TABLE3_GROUND;
      break;

    case PROD_VIL:
      pConvert_a->iGribLevelCode = GRIB_TABLE3_LAYER;
      {
	SINT4 iTop=(5000+pheader_a->pcf.psi.vil.ivil_top)/10000;
	SINT4 iBot=(5000+pheader_a->pcf.psi.vil.ivil_bottom)/10000;
	pConvert_a->iGribLevelHeight = (255&iTop) | ((255&iBot)<<8);
      }
      break;

    case PROD_TOPS:
      pConvert_a->iGribLevelCode = GRIB_TABLE3_ATMOSPHERE;
      break;

    case PROD_CAPPI:
      pConvert_a->iGribLevelCode = GRIB_TABLE3_ALTITUDE;
      pConvert_a->iGribLevelHeight = (50+pheader_a->pcf.psi.cappi.icappi_hgt)/100;
      break;
    case PROD_NDOP:
      pConvert_a->iGribLevelCode = GRIB_TABLE3_ALTITUDE;
      pConvert_a->iGribLevelHeight = (50+pheader_a->pcf.psi.ndop.iCappiHeight)/100;
      break;
    }
  if (0 == pConvert_a->iGribLevelCode)
    {
      sprintf( sLog, "Product type %s (%d) not supported.", 
	       sproduct_name5(pheader_a->pcf.iptype), pheader_a->pcf.iptype );
      Lprintf( &CConfig_c, sLog );
      return USER_ERROR_SIG;
    }

  /* 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:
      pConvert_a->DataType = CConfig_c.Dbz;
      break;
    case DB_HEIGHT:
      pConvert_a->DataType = CConfig_c.Height;
      break;
    case DB_RAINRATE2:
      pConvert_a->DataType = CConfig_c.RainRate;
      break;
    case DB_FLIQUID2:
      pConvert_a->DataType = CConfig_c.RainAcc;
      break;
    case DB_VEL:
    case DB_VEL2:
    case DB_VELC:
    case DB_VELC2:
      pConvert_a->DataType = CConfig_c.Vel;
      break;
    case DB_WIDTH:
    case DB_WIDTH2:
      pConvert_a->DataType = CConfig_c.Width;
      break;
    case DB_ZDR:
    case DB_ZDR2:
      pConvert_a->DataType = CConfig_c.Zdr;
      break;
    }
  if (0==pConvert_a->DataType.iCode)
    {
      sprintf( sLog, "Data type %s is not supported.", 
	       sdata_name6(pheader_a->pcf.idata_out) );
      Lprintf( &CConfig_c, sLog );
      return USER_ERROR_SIG;
    }

  /* Check that the projection type is valid */
  switch( pheader_a->pcf.iprojection_type)
    {
    case MAPROJ_MERCATOR:
      pConvert_a->iGribProjection=GRIB_TABLE6_MERCATOR;
      pConvert_a->iGribGdsSize = 42;
      break;
    case MAPROJ_POLSTEREO:
      pConvert_a->iGribProjection=GRIB_TABLE6_POL_STEREO;
      pConvert_a->iGribGdsSize = 32;
      break;
      /*  There is no structure in the spec for UTM!
    case MAPROJ_UTM:
      pConvert_a->iGribProjection=GRIB_TABLE6_UTM;
      pConvert_a->iGribGdsSize = ??;
      break;
      */
    case MAPROJ_PERSPECTIVE:
      pConvert_a->iGribProjection=GRIB_TABLE6_PERSPECTIVE;
      pConvert_a->iGribGdsSize = 44;
      break;
    case MAPROJ_EDC:
      pConvert_a->iGribProjection=GRIB_TABLE6_EDC;
      pConvert_a->iGribGdsSize = 32;
      break;
    default:
      sprintf( sLog, "Projection type %s (%d) not supported.", 
	       sProjectionName32(pheader_a->pcf.iprojection_type),
	       pheader_a->pcf.iprojection_type );
      Lprintf( &CConfig_c, sLog );
      return USER_ERROR_SIG;
    }

  /* Check to see if this is a 3D product */
  if (pheader_a->pcf.izsize>1)
    {
      Lprintf( &CConfig_c, "3D products not supported." );
      return USER_ERROR_SIG;
    }

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

  /* Convert time to UTC */
  pConvert_a->IngTime = pheader_a->pcf.ing_time;
  iTimeOffset = 60*(SINT4)pheader_a->end.iMinutesWest;
  ymds_add_seconds( iTimeOffset, &pConvert_a->IngTime );

  return SS_NORMAL;
}

#if 0
/* =====================================================
 * Search to find the sitename.
 */
static const struct convert_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 convert_site_table8 *pSite;
      pSite = &(pCConfig_a->Sites[iSite]);
      if (sig_fnmatch( pSite->sSitename16, sSitename16_a ))
	{
	  return pSite;
	}
    }
  return NULL;
}
#endif  /* #if 0 */

/* ==============================================================
 * Convert product to GRIB.  Entered with a pointer to the beginning 
 * of the full product.  Should exit if it gets an error.
 */
static void ConvertToGrib1
(const struct cartesian_product *pProd_a,
 struct convert_info *pConvert_a )
{
  char *sec[6];
  size_t secl[6], iSize;
  int ii;
  char sLog[200];

#ifdef DEBUG
  fprintf( stderr, "ConvertToGrib1 called." );
#endif

  memset(  sec, 0, 6*sizeof(char*) );
  memset( secl, 0, 6*sizeof(size_t) );

  /* Fill in section 2, Grid Definition Section */
  SetupGDS( &sec[2], &secl[2], pConvert_a, &pProd_a->Header );

  /* Fill in sections 3 and 4, bitmap and data */
  SetupBMSAndBDS( &sec[3], &secl[3], &sec[4], &secl[4], pConvert_a, pProd_a );

  /* Fill in section 1, Product Definition Section */
  SetupPDS( &sec[1], &secl[1], pConvert_a, &pProd_a->Header, secl[3] );

  /* Fill in remaining sections */
  SetupSection0and5( sec, secl, &pConvert_a->IngTime );

  /* Write data out to standard output */
  for (ii = 0; ii < 6; ii++)
    {
      if (secl[ii])
	{
	  if (CConfig_c.lVerbose)
	    {
	      sprintf( sLog, "Section %d, size:%d, address:0x%X.", ii, secl[ii], sec[ii] );
	      Lprintf( &CConfig_c, sLog );
	    }
	  iSize = fwrite( sec[ii], 1, secl[ii], stdout);
	  if ( iSize != secl[ii])
	    {
	      sprintf( sLog, "Errno %d in fwrite.", errno );
	      Lprintf( &CConfig_c, sLog );
	      exit(EXIT_FAILURE);
	    }
	}
    }
  return;
}
/* ========================================================
 * Setup and fill in the PDS section of the GRIB file.
 */
static void SetupPDS
( char **ppPds_a,                    /* Fill in PDS pointer, if successful */
  size_t *pPdsSize_a,                /* Fill in size of PDS, if successful */
  const struct convert_info *pConvert_a,
  const struct product_hdr *pPHeader_a,
  size_t iGdsSize_a )                /* Size of GDS already made */
{
#define GRIB_PDS_SIZE (28)
#define GRIB_TABLE_VERSION (3)

  /* Allocate the PDS buffer */
  char *pPds;
  pPds = (char *)malloc(GRIB_PDS_SIZE);
  if (!pPds) { sig_signal( errno_to_message(errno)); exit(EXIT_FAILURE); }

  /* Fill in the PDS fields */
  Pack3( &pPds[0], GRIB_PDS_SIZE );
  pPds[3] = GRIB_TABLE_VERSION;
  pPds[4] = CConfig_c.iGenCenterId;
  pPds[5] = CConfig_c.iGenProcessId;
  pPds[6] = 255;   /* Means that grid is defined in section 2 */
  pPds[7] = 0x80;     /* Means sections 2 present */
  if (iGdsSize_a) pPds[7] |= 0x40;
  pPds[8] = pConvert_a->DataType.iCode;
  pPds[9] = pConvert_a->iGribLevelCode;
  Pack2( &pPds[10], pConvert_a->iGribLevelHeight );

  /* Time related PDS fields */
  {
    int iCentury, iYearOfCentury;
    UINT2 iDays, iHours, iMinutes, iSeconds;
    iYearOfCentury = pConvert_a->IngTime.iyear % 100;
    if (iYearOfCentury==0) iYearOfCentury=100;
    iCentury = 1 + ((pConvert_a->IngTime.iyear-iYearOfCentury)/100);
    /* Note that both year and century are origin 1.  So, for example, the
     * year 2000 is the last (100th) year in the 20th century.
     */
    unpacktime( pConvert_a->IngTime.isec, &iDays, &iHours, &iMinutes, &iSeconds );

    pPds[24] = iCentury;
    pPds[12] = iYearOfCentury;
    pPds[13] = pConvert_a->IngTime.imon;
    pPds[14] = pConvert_a->IngTime.iday;
    pPds[15] = iHours;
    pPds[16] = iMinutes;
  }
  pPds[17] = GRIB_TABLE4_HOURS;  /* Accumulations are always in hours */
  pPds[18] = 0;                  /* P1 Start time is always 0 */
  pPds[19] = pConvert_a->iGribTimeRangeHours; /* P2 End time */
  pPds[20] = pConvert_a->iGribTimeRangeCode;
  Pack2( &pPds[21], pConvert_a->iGribInputCount );
  pPds[23] = 0;     /* No record of missing inputs */
  pPds[25] = CConfig_c.iSubCenterId;
  {
    int iDecScaleFactor = abs(pConvert_a->DataType.iDecScaleFactor);
    if (pConvert_a->DataType.iDecScaleFactor<0) iDecScaleFactor |= 0x8000; /* Sign magnitude */
    Pack2( &pPds[26], iDecScaleFactor );   /* Decimal scale factor */
  }

  /* Return PDS info */
  *ppPds_a = pPds;
  *pPdsSize_a = GRIB_PDS_SIZE;
  return;
}
/* ========================================================
 * Setup and fill in the GDS section of the GRIB file.
 */
static void SetupGDS
( char **ppGds_a,                    /* Fill in GDS pointer, if successful */
  size_t *pGdsSize_a,                /* Fill in size of GDS, if successful */
  const struct convert_info *pConvert_a,
  const struct product_hdr *pPHeader_a )
{
  MESSAGE iStatus;
  char *pGds;
  struct mpj_projection8 Proj;
  struct mpj_region8     Region;

#ifdef DEBUG
  fprintf( stderr, "SetupGDS called." );
#endif

  /* Allocate the PDS buffer */
  pGds = (char *)malloc(pConvert_a->iGribGdsSize);
  if (!pGds) { sig_signal( errno_to_message(errno)); exit(EXIT_FAILURE); }
  memset( pGds, 0, pConvert_a->iGribGdsSize );

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

  /* Fill in the GDS fields */
  Pack3( &pGds[0], pConvert_a->iGribGdsSize );
  pGds[3] = 0;     /* No vertical coordinate parameters */
  pGds[4] = 255;   /* Means the projection is specified here */
  pGds[5] = pConvert_a->iGribProjection;
  Pack2( &pGds[6], pPHeader_a->pcf.ixsize );
  Pack2( &pGds[8], pPHeader_a->pcf.iysize );

  /* The remainder of the structure is a descriminated union, dependent
   * on the projection type.  Note that the checking for unsupported
   * projections is already done.
   */
  switch( pConvert_a->iGribProjection )
    {
    case GRIB_TABLE6_EDC:
      Pack3( &pGds[10], NINT( (180000.0/FPI)*Region.Loc0.fLat ) );
      Pack3( &pGds[13], NINT( (180000.0/FPI)*Region.Loc0.fLon ) );
      pGds[16] = 0;  /* Code table 7 */
      Pack3( &pGds[17], NINT( (180000.0/FPI)*Region.Loc2.fLat ) );
      Pack3( &pGds[20], NINT( (180000.0/FPI)*Region.Loc2.fLon ) );
      {
	SINT4 iRes = NINT((180000.0/FPI)*(Region.Loc2.fLat-Region.Loc0.fLat)
			  /(FLT8)pPHeader_a->pcf.iysize);
	Pack2( &pGds[23], iRes );
	Pack2( &pGds[25], iRes );
      }
      pGds[27] = 0;  /* Scanning mode */
      break;
    case GRIB_TABLE6_MERCATOR:
      Pack3( &pGds[10], NINT( (180000.0/FPI)*Region.Loc0.fLat ) );
      Pack3( &pGds[13], NINT( (180000.0/FPI)*Region.Loc0.fLon ) );
      pGds[16] = 0;  /* Code table 7 */
      Pack3( &pGds[17], NINT( (180000.0/FPI)*Region.Loc2.fLat ) );
      Pack3( &pGds[20], NINT( (180000.0/FPI)*Region.Loc2.fLon ) );
      Pack3( &pGds[23], 0 );  /* Intersection at equator */
      pGds[26] = 0;
      pGds[27] = 0;  /* Scanning mode */
      Pack3( &pGds[28], (50+pPHeader_a->pcf.ixscale)/100 );
      Pack3( &pGds[31], (50+pPHeader_a->pcf.iyscale)/100  );
      break;
    case GRIB_TABLE6_POL_STEREO:
      Pack3( &pGds[10], NINT( (180000.0/FPI)*Region.Loc0.fLat ) );
      Pack3( &pGds[13], NINT( (180000.0/FPI)*Region.Loc0.fLon ) );
      pGds[16] = 0;  /* Code table 7 */
      /* Reference longitude */
      Pack3( &pGds[17], NINT( (180000.0/FPI)*Proj.Ref.fLon ) );
      Pack3( &pGds[20], (50+pPHeader_a->pcf.ixscale)/100 );
      Pack3( &pGds[23], (50+pPHeader_a->pcf.iyscale)/100  );
      Pack3( &pGds[23], 0 );  /* Intersection at equator */
      pGds[26] = 0;           /* Only supports north pole */
      pGds[27] = 0;           /* Scanning mode */
      break;
    case GRIB_TABLE6_PERSPECTIVE:
      /* Reference point */
      Pack3( &pGds[10], NINT( (180000.0/FPI)*Proj.Ref.fLat ) );
      Pack3( &pGds[13], NINT( (180000.0/FPI)*Proj.Ref.fLon ) );
      pGds[16] = 0;  /* Code table 7 */
      /* Compute radius of the earth in pixels */
      {
	FLT8 fP = FEARTH_GEO_HEIGHT/FEARTH_MEAN_RADIUS;
	FLT8 fRho = sqrt( (fP-1.0) / (fP+1.0) );
	FLT8 fRadius = (FEARTH_MEAN_RADIUS/Region.fde)*Region.fxpix;
	fRadius *= fRho;
	Pack3( &pGds[17], NINT(fRadius) );
	Pack3( &pGds[20], NINT(fRadius) );
      }

      {
	struct XYPoint8 Pixel; UINT1 lValid;
	FLT8 fNr = FEARTH_GEO_HEIGHT / FEARTH_EQUATORIAL_RADIUS;
	/* Computer sub-satellite point */
	iStatus = mpj_earth_to_pixel( &Proj, &Region, &Proj.Ref, &Pixel, &lValid );
	if (iStatus!=SS_NORMAL)
	  sig_signal( iStatus );
	else
	  {
	    Pack2( &pGds[23], NINT(Pixel.fX) );
	    Pack2( &pGds[25], NINT(Pixel.fY) );
	  }
	pGds[27] = 0;           /* Scanning mode */
	Pack3( &pGds[28], 0 );  /* Orientation is zero */
	Pack3( &pGds[31], NINT(1000000.0*fNr) );  /* Altitude of the camera */
      }
      break;
    }

  /* Return GDS info */
  *ppGds_a = pGds;
  *pGdsSize_a = pConvert_a->iGribGdsSize;

  return;
}
/* ==============================================================
 * This function builds the binary data section, and the optional
 * bitmap.  The bitmap section is skipped if there is no area-not-scanned
 * data in the product.
 */
static void SetupBMSAndBDS
( char   **pBitMap_a,
  size_t *pBitMapSize_a,
  char   **pData_a, 
  size_t *pDataSize_a,
  const struct convert_info *pConvert_a,
  const struct cartesian_product *pProd_a )
{

  UINT1 *pMappedData;    /* Pointer to data array with bitmaps removed */
  SINT4 iMappedDataSize; /* Number of elements in the data array */

  /* Our GRIB data format convention is to used bitmap section to
   * flag area-not-scanned, and to use the all bits set value to
   * flag thresholded data.  The GRIB documentation does not discuss
   * this in detail.
   */

  SetupBMS( pBitMap_a, pBitMapSize_a, &pMappedData, &iMappedDataSize, pProd_a );

  SetupBDS( pData_a, pDataSize_a, pMappedData, iMappedDataSize, pConvert_a, 
	    &pProd_a->Header );

  if (pMappedData) free( pMappedData );

  return;
}
/* =================================================
 * Build the Bit-map section.  Possibly make none if all data is scanned.
 */
static void SetupBMS
( char   **pBms_a,           /* Pointer to BMS section, already zeroed */
  size_t *pBmsSize_a,        /* Size of BMS section in bytes */
  UINT1  **pMappedData_a,    /* Pointer to remapped data */
  SINT4  *pMappedDataSize_a, /* Size of remapped data in bins */
  const struct cartesian_product *pProd_a )
{
  SINT4 iCount, iRes, iMapIndex;
  size_t iBmsSize, iBitMapSize, iMappedDataSize;
  char  *pBms, *pBitMap; UINT1 *pMappedData;
  UINT1 lAllSet=TRUE;

#ifdef DEBUG
  fprintf( stderr, "SetupBMS called." );
#endif

  iCount = pProd_a->Header.pcf.ixsize*pProd_a->Header.pcf.iysize;
  iRes = idata_resolution( pProd_a->Header.pcf.idata_out );

  /* Allocate bitmap */
  iBitMapSize = 2* ((iCount+15)/16);  /* Size must be even */
  iBmsSize = 6 + iBitMapSize;
  pBms = (char *)malloc( iBmsSize );
  if (!pBms) { sig_signal( errno_to_message(errno)); exit(EXIT_FAILURE); }
  pBitMap = pBms+6;
  memset( pBitMap, -1, iBitMapSize );  /* Assume most data scanned */

  /* Allocate a full buffer for the remapped data.  Some will not be used */
  iMappedDataSize = (iCount*iRes)/8;
  pMappedData = (UINT1 *)malloc( iMappedDataSize );
  if (!pMappedData_a) { sig_signal( errno_to_message(errno)); exit(EXIT_FAILURE); }

  if (iRes==16)
    {
      UINT2 iData;
      const UINT2 *pInData2 = (UINT2*)pProd_a->Data;
      UINT2 *pMappedData2 = (UINT2*)pMappedData;
      SINT4 iIndex;
      iMapIndex=0;
      for( iIndex=0; iIndex<iCount; iIndex++ )
	{
	  iData = pMappedData2[iMapIndex] = pInData2[iIndex];
	  if (iData==DV_NOSCAN2)
	    {
	      GribBitClear( pBitMap, iIndex );
	      lAllSet=FALSE;
	    }
	  else
	    {
	      iMapIndex++;
	    }
	}
    }
  else
    {
      UINT1 iData;
      const UINT1 *pInData = pProd_a->Data;
      SINT4 iIndex;
      iMapIndex=0;
      if ( DB_HEIGHT == pProd_a->Header.pcf.idata_out )
	{
	  for( iIndex=0; iIndex<iCount; iIndex++ )
	    {
	      iData = pMappedData[iMapIndex] = pInData[iIndex];
	      if ((iData==DV_NOSCAN1)||(iData==DV_HEIGHT1_UNKNOWN))
		{
		  GribBitClear( pBitMap, iIndex );
		  lAllSet=FALSE;
		}
	      else
		{
		  iMapIndex++;
		}
	    }
	}
      else
	{
	  for( iIndex=0; iIndex<iCount; iIndex++ )
	    {
	      iData = pMappedData[iMapIndex] = pInData[iIndex];
	      if (iData==DV_NOSCAN1)
		{
		  GribBitClear( pBitMap, iIndex );
		  lAllSet=FALSE;
		}
	      else
		{
		  iMapIndex++;
		}
	    }
	}
    }

  /* Return the bitmap if used, else free it */
  if (!lAllSet)
    {
      /* Fill in the header */
      Pack3( &pBms[0], iBmsSize );
      /* compute the number of unused bits */
      pBms[3] = (8*iBitMapSize) - iCount;
      pBms[4] = 0;
      pBms[5] = 0;
      *pBms_a = pBms;
      *pBmsSize_a = iBmsSize;
    }
  else
    {
      free( pBitMap ); 
    }
  /* Return the remapped data */
  *pMappedData_a = pMappedData;
  *pMappedDataSize_a = iMapIndex;
  return;
}
#define DV_THRESH_F4 (-1000000.0)
/* =========================================================================
 * Setup the Binary Data Section
 */
static void SetupBDS
( char   **pBds_a,               /* Return malloc'ed BDS data here */
  size_t *pBdsSize_a,
  const UINT1 *pMappedData_a,    /* Pointer to data array with bitmaps removed */
  SINT4 iMappedDataSize_a,       /* Number of elements in the data array */
  const struct convert_info *pConvert_a,
  const struct product_hdr *pPHeader_a )
{

  FLT4 *pfArray;    /* This will hold an array of Y * 10**D values before packing */
  FLT4 fMaximum, fMinimum, f10toD, f2toE, fReference;
  UINT1 lDataFound;
  SINT4 iRes, iBitsPerValue, iTotalBits, iBdsSize, iUnusedBits, iIndex;
  char *pBds, *pBdsMap;   
  struct data_convert Convert;
  char sMessage[100];

#ifdef DEBUG
  fprintf( stderr, "SetupBDS called, mapped size:%d.", iMappedDataSize_a );
#endif

  /* We could have no data which passed the bitmap.  Consider this an error.
   */

  FillDataConvert( &Convert, pPHeader_a->end.iprf, pPHeader_a->end.itrig, 
		   pPHeader_a->end.ilambda, pPHeader_a->end.ipolar,
		   CONVERT_FLAGS_DEFAULT ) ;

  /* Allocate a floating array to store the values to be encoded */
  
  pfArray = (FLT4*)malloc(iMappedDataSize_a*sizeof(FLT4) );
  if (!pfArray) { sig_signal( errno_to_message(errno)); exit(EXIT_FAILURE); }

  iRes = idata_resolution( pPHeader_a->pcf.idata_out );

  /* Note that the packing formula is:
   *
   *   Y * 10**D = R + ( X * 2**E )
   * Where:
   *  Y is the user format data value.  
   *  D is the decimal scale factor.
   *  E is the binary scale factor.
   *  R is the reference value (minimum data value).
   */

  f10toD = (FLT4)pow( 10.0, (double)pConvert_a->DataType.iDecScaleFactor );

  /* Include in here the GRIB scale factor also. */
  f10toD *= pConvert_a->DataType.fGribScaleFactor;  

  f2toE  = (FLT4)pow(  2.0, (double)pConvert_a->DataType.iBinScaleFactor );

  /* Fill in the floating array, and search for maximum and minimum values */
  fMaximum=0.0; fMinimum=0.0; lDataFound=FALSE;
  if (iRes==16)
    {
      const UINT2 *pMappedData = (UINT2*)pMappedData_a;
      UINT2 iData;
      for (iIndex=0; iIndex<iMappedDataSize_a; iIndex++)
	{
	  iData = pMappedData[iIndex];
	  if (iData == DV_THRESH2)
	    {
	      pfArray[iIndex] = DV_THRESH_F4;
	    }
	  else
	    {
	      FLT4 fValue = fdata_to_user( iData, pPHeader_a->pcf.idata_out, &Convert );
	      fValue *= f10toD;
	      pfArray[iIndex] = fValue;
	      if (lDataFound)
		{
		  if (fMinimum>fValue) fMinimum=fValue;
		  if (fMaximum<fValue) fMaximum=fValue;
		}
	      else
		{
		  fMinimum=fValue;
		  fMaximum=fValue;
		  lDataFound=TRUE;
		}
	    }
	}
    }
  else
    {
      UINT1 iData;
      for (iIndex=0; iIndex<iMappedDataSize_a; iIndex++)
	{
	  iData = pMappedData_a[iIndex];
	  if (iData == DV_THRESH1)
	    {
	      pfArray[iIndex] = DV_THRESH_F4;
	    }
	  else
	    {
	      FLT4 fValue = fdata_to_user( iData, pPHeader_a->pcf.idata_out, &Convert );
	      fValue *= f10toD;
	      pfArray[iIndex] = fValue;
	      if (lDataFound)
		{
		  if (fMinimum>fValue) fMinimum=fValue;
		  if (fMaximum<fValue) fMaximum=fValue;
		}
	      else
		{
		  fMinimum=fValue;
		  fMaximum=fValue;
		  lDataFound=TRUE;
		}
	    }
	}
    }

  /* Now calculate various derived parameters, like the number of bits
   * required to store the numbers.
   */

  {
    FLT4 fSpan;
    fSpan = (fMaximum-fMinimum) / f2toE ;

    /* Reserve an extra slot for the all ones case for thresholded data */
    iBitsPerValue = 1 + (int)floor( log(fSpan+1.0) / log(2.0) ); 
  }

  if ((iBitsPerValue<1)||(iBitsPerValue>30))
    {
      sprintf( sMessage, "DATA SCALING ERROR: DSF:%d, BSF:%d, Min:%5.2f, Max:%5.2f, Bits:%d.",
	       pConvert_a->DataType.iDecScaleFactor, pConvert_a->DataType.iBinScaleFactor,
	       fMinimum, fMaximum, iBitsPerValue );
	       
      Lprintf( &CConfig_c, sMessage );
      exit(EXIT_FAILURE);
    }

  /* To get nearest integer rounding, raise the reference value by half
   * the step size.  The data storage will then use truncation.
   */
  fReference = fMinimum /* + (f2toE/2.0)*/ ;

  iTotalBits  = 11*8 + iBitsPerValue*iMappedDataSize_a;
  iBdsSize    = 2*((iTotalBits+15)/16);  /* Size of BDS in bytes, must be even */
  iUnusedBits = (8*iBdsSize) - iTotalBits;


  /* Malloc the BDS buffer, and fill it in */
  pBds = (char*)malloc(iBdsSize);
  if (!pBds)
    {
      char sMessage[100];
      sprintf( sMessage, "BDS Malloc of %d bytes failed.", iBdsSize );
      Lprintf( &CConfig_c, sMessage );
      exit(EXIT_FAILURE);
    }

  /* Fill in the BDS header section in the first 11 bytes */

  Pack3( &pBds[0], iBdsSize );
  pBds[3] = iUnusedBits;
  {
    int iBinScaleFactor = abs(pConvert_a->DataType.iBinScaleFactor);
    if (pConvert_a->DataType.iBinScaleFactor<0) iBinScaleFactor |= 0x8000;  /* Sign magnitude */
    Pack2( &pBds[4], iBinScaleFactor );   /* Binary scale factor */
  }
  PackIbmFloat4( &pBds[6], fReference );
  pBds[10] = iBitsPerValue;

  pBdsMap = &pBds[11];
  /* Now loop through packing the data */
  for (iIndex=0; iIndex<iMappedDataSize_a; iIndex++)
    {
      FLT4 fValue;
      SINT4 iValue, iBit;
      SINT4 iOffset=iIndex*iBitsPerValue;
      fValue = pfArray[iIndex];
      if (fValue == DV_THRESH_F4)
	{
	  iValue = -1;
	}
      else
	{
	  iValue = (SINT4) ( (fValue-fMinimum) / f2toE );
	}
      /* Pack in 1 bit at a time */
      for (iBit=0; iBit<iBitsPerValue; iBit++)
	{
	  GribBitPut( pBdsMap, iOffset+iBit, 1&(iValue>>((iBitsPerValue-iBit)-1)) );
	}
    }
  
  /* Copy back data */
  *pBds_a = pBds;   
  *pBdsSize_a = iBdsSize;

  sprintf( sMessage, "BDS contains %d values, %d bits each, Minimum: %5.2f, Maximum: %5.2f",
	   iMappedDataSize_a, iBitsPerValue, fMinimum, fMaximum );
  Lprintf( &CConfig_c, sMessage );

  if (CConfig_c.lVerbose )
    {
      sprintf( sMessage, "Grib Scaling:%6.5f, DSF:%d, BSF:%d",
	       pConvert_a->DataType.fGribScaleFactor, 
	       pConvert_a->DataType.iDecScaleFactor,
	       pConvert_a->DataType.iBinScaleFactor );
      Lprintf( &CConfig_c, sMessage );
    }

  return;

}

/*===========================================================================
 *
 *
 * This function creates sections 0 and 5. The function returns 1 on
 * success, 0 on a fault.
 */
static void SetupSection0and5
( char *sec[6],
  size_t secl[6],
  const struct ymds_time *pTime_a )
{
  int ii, iLength;

#ifdef DEBUG
  fprintf( stderr, "SetupSection0and5 called." );
#endif

  /* Allocate section 5 */
  sec[5] = (char *)malloc(4);
  if (!sec[5]) { sig_signal( errno_to_message(errno)); exit(EXIT_FAILURE); }
  secl[5] = 4;

  /* Fill in section 5 */
  sec[5][0] = '7';
  sec[5][1] = '7';
  sec[5][2] = '7';
  sec[5][3] = '7';

  /* Allocate section 0 */
  sec[0] = (char *)malloc(8);
  if (!sec[0]) { sig_signal( errno_to_message(errno)); exit(EXIT_FAILURE); }
  secl[0] = 8;

  /* Calculate total length of the GRIB-message */

  iLength = 0;
  for (ii=0; ii<6; ii++) iLength += secl[ii];
  
  /* Fill in section 0 */
  sec[0][0] = 'G';
  sec[0][1] = 'R';
  sec[0][2] = 'I';
  sec[0][3] = 'B';
  Pack3( &(sec[0][4]), iLength );
  sec[0][7] = 1;

  return;
}

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

}
#endif  // #if 0

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

  Lprintf( &CConfig_c, sMessage );
  return(SS_NORMAL);
}
