/******************************************************************************
*
*  NSSDC/CDF                  Quick Start Test Program (INTERNAL interface/C).
*
*  cdf-to-fits:  convert native and non-native (FITS originated) CDF files to FITS.
*
*  The data mapping of CDF files depends on their origin (FITS or native)
*  Native CDF files are always mapped to a FITS BINTABLE, with the caveat that
*  variant records force the output in multiple HDU for efficiency. Each
*  BINTABLE will have a different value for NAXIS2 (the number of rows). 
*  The most common situation is one where is will result in two BINTABLEs, one
*  with NAXIS2 being 1 and the other with some non-1 number.
*  
*  Native CDF files have the following mappings:
*
*  zVars   are mapped to the columns in a BINTABLE
*  rVars   are mapped to the columns in a BINTABLE (though is the old CDF format)
*  gAttr   are mapped to keywords in the header of the BINTABLE (not pHDU)
*  vAttr   are mapped to things describing the columns
*
*  A non-native (FITS based) CDF can be recognized by the following
*  global attributes:
*
*  FITS originating CDF files (i.e. converted with fits-to-cdf) are converted
*  to a multi-HDU FITS file with as much original structure as possible, i.e.
*  a one-to-one mapping with the original FITS HDU structure to the one this
*  program creates.
*
*
*  "Original_FITS_Filename"        original filename
*  "Description"                   verbosities
*  "HDU_Type"                      IMAGE,ASCII_TBL,BINARY_TBL  
*  "FITS_Header_Keywords"          the cards from the fits header
*  "EXTNAME"                       the fits EXTNAME, if it has one
*
*
*  Code History:
*     pre 18-jun-2003:  Initial version 1.0  -    Mike Liu, Peter Teuben
*         19-jun-2003:  Version 2.0 merged code
*
*     21-May-2004:  David Han, Fixed and added the following:
*        1) Index for the 'comments' array was wrong (using totDM instead 
*           of totDT)
*        2) Global attribute with no entry used to create a blank keyword in 
*           FITS. It now creates a keyword that is the same as the global
*           CDF attribute name.
*        3) It now uses cdf_to_fits_mapping.h instead of fits_to_cdf_mapping.h.
*        4) Added a logic to create placeholder keyword(s) from the
*           cdf_to_fits_mapping.dat file.
*        5) If a CDF global attribute has multiple entries, only the first entry created 
*           a keyword in the FITS file that is the same name as the CDF attribute name,
*           and the rest of the entries had blank keywords which is not correct.  
*           All the entries now create FITS keywords that have the same CDF global 
*           attribute name.  For example,
*
*              CDF global attribute name    entry #   entry value
*              -------------------------    -------   -----------
*              MODS                            1      Original version
*                                              2      Modified by John, May 10, 2004
*
*             will be converted to
*                 MODS    = 'Original version'
*                 MODS    = 'Modified by John, May 10, 2004'
*
*             Prior to this bug fix, it used to be
*                 MODS    = 'Original version'
*                         = 'Modified by John, May 10, 2004'
*
*             NOTE:  if a keyword doesn't have a value (blank keyword), column 9 must not
*                    contain "=".  
*          
*  $Id: cdf-to-fits.c,v 1.16 2004/05/24 15:59:31 liu Exp $
*  $Revision: 1.16 $
*
******************************************************************************/

static char *c2f_revision = "$Revision: 1.16 $";
static int  cdf_fits = 20;  /* Version encoded in the CDF-FITS keyword: */
                            /* 10 * major + minor */

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

#define CDFFITS
#include "cdffits.h"
#include "cdf_to_fits_mapping.h"
#include "datatype_mapping.h"

#if defined(vms)
#include <ssdef>
#define EXIT_SUCCESS_   SS$_NORMAL
#define EXIT_FAILURE_   SS$_ABORT
#else
#define EXIT_SUCCESS_   0
#define EXIT_FAILURE_   1
#endif

#define CDF_TYPES        14

#define MAX_FITSLEN          25       /* Max length of FITS datatype name    */
#define MAX_CDFLEN           15       /* Max length of CDF  datatype name    */
#define MAX_TYPES            65       /* Number of predefined FITS datatypes */
#define MAX_REC_LENGTH     120        /* Max record length of MAPPING_FILE.  */
#define MAX_FITS_KEYWORDS  200        /* Max # of FITS mapping keywords      */
#define ELEN                24        /* EPOCH3 character length             */

/******************************************************************************
* Macros/prototypes.
******************************************************************************/

void usage(void);
void convert_ubytes(int n, char *data);
int  streq(char *a, char *b);

void WriteKey(int, long, long, char *, void *, char *);
void HandleOtherKeys(char *, char *, char *, int, void *, char *, int);
void CreateHDUs(int, long, char **, char **, char **, long *, long *);
void WriteID(void);
void ModifyDupKey(char *, char *);
void appendKey(char *, char *);
void appendNumKey(char *, int);

/*hid_t EpochFITSDataType PROTOARGs(()); */
void EquivCDFFITSDataTypes PROTOARGs((int));
/*int FindCDFMappingDataType PROTOARGs((hid_t)); */
char fits_dt[MAX_FITS_KEYWORDS][12];
char cdf_dt[MAX_FITS_KEYWORDS][CDF_ATTR_NAME_LEN];
char comments[MAX_FITS_KEYWORDS][70];

static fitsfile *fptr;
static int fstatus;

typedef struct gkey {
  char key[10];
  int  n;
  struct gkey *next;
} gkey;

static gkey *Gkeys = NULL;


/******************************************************************************
 program layout

	NG = number of global attributes (GATTR)
	NZ = number of Z variables (ZVARS)
	NV = number of variables (VATTR)
	native = (true or false) is it a native CDF file? 

block	what is does
------  ------------

B-1:	process command line options, display program title
B-2:	read (two) mapping files internally
B-3:	check fits file and open it for output

big foreach CDF file loop starts:

 B-4:	open CDF, get basic info, and allocate space for FITS tables
 B-5:	NG > 0		write Gattr and prepare for non-native FITS
 B-6:	native & NV > 0
 B-7: 	native & NZ > 0
  B-7a: special atttributes for fits columns
  B-7b: loop over all .. to gather info on the columns
  B-7c: create BINTABLE(s) layout, also writes ZVARn's
  B-7d: process dimensionality of columns (TDIM's)
  B-7e: get variable's other attribute entries. (TSCAL/BSCALE/...)
 B-8:	NZ > 0:   handle/write data scaling parameters
 B-9:	native & NZ > 0
 B-10:  NZ > 0			write the data
 B-11:  close CDF and free space

end loop

B-12:  close FITS file, end program

******************************************************************************/

int main (int argC, char *argV[]) 
{
  CDFid id;
  CDFstatus status;
  int i, j, totDM = 0, totDT = 0;
  long startRec;
  Logical deleteOld = DEFAULTdelete;
  Logical noHeader = DEFAULTnoheader;
  Logical showMap = FALSE;
  char CDFpath[QOP_MAX_PARMs][DU_MAX_PATH_LEN+1], tFITSpath[DU_MAX_PATH_LEN+1];
  char mapping[DU_MAX_PATH_LEN+1];
  char oFile[DU_MAX_PATH_LEN+1];
  int fitsDataType;
  int ii, jj, kk, ix;
  int parms = 0;
  int unitsNum;
  size_t bufferSize, strSize;
  long numAttrs, numGAttrs, numVAttrs, numZvars, dataType, numElems, maxEntry;
  long numEntries, scope, majority, maxRec, maxRecNum, numRecs, numDims;
  long phyValues, phySize, recNum, recAlloc, recVary;
  long dimSizes[CDF_MAX_DIMS], dimIndices[CDF_MAX_DIMS], 
       dimIntervals[CDF_MAX_DIMS]; 
  long dimVarys[CDF_MAX_DIMS], tDims[CDF_MAX_DIMS];
  char DATATYPE_TOKEN[CDF_TYPES][12];
  char tempS[MAX_REC_LENGTH];
  long DATATYPE[CDF_TYPES];
  double TZERO[CDF_TYPES], TSCALE[CDF_TYPES];
  int BITPIX[CDF_TYPES];
  char TFORM[CDF_TYPES][3];
  char keyword[10], keywordValue[80], comment[80];
  int keyDataType, keySize, keyNum;
  void *value = NULL;
  long ep_yr, ep_mo, ep_dy, ep_hr, ep_mn, ep_se;
  double ep_ms, ep_msX;
  char attrName[CDF_ATTR_NAME_LEN+1], newAttrName[CDF_ATTR_NAME_LEN+1];
  char **varAttr = NULL;
  char colNumStr[5];
  char tmpStr[201];
  char floatFormat[20];
  char varName[CDF_VAR_NAME_LEN];
  char *ptr, rec[MAX_REC_LENGTH];
  QOP *qop; 
  Logical qopError = FALSE, foundMapping = FALSE, DEBUG = FALSE;
  static char *validQuals[] = {
    "help", "delete", "no-header", "show", "map", "debug",  
    NULL
  };
  static int optRequired[] = {
    FALSE, FALSE, FALSE, FALSE, TRUE, FALSE, 0
  };
  FILE  *inFile;
  Logical endExt = FALSE;
  Logical nativeCDF, firstEntry;

  /* fits things 'fitsfile *fptr' is now static global for local functions */

  int hduType;

  int bitpix;
  int naxis, size, extend = -99;
  int addOne;
  long naxes[10], pcount = -99, gcount = -99;
  int extension;
  int conformation, whichHeader;

  /****************************************************************************
  * B-1: Determine qualifiers/options/parameters from command line
  *      and display the title of the program
  ****************************************************************************/
  switch (argC) {
    case 1:
      usage();
      return TRUE;
    default:
      qop = Qop (argC, argV, validQuals, optRequired);
      if (qop == NULL) return FALSE;
      /************************************************************************
      * Check for `help' qualifier.
      ************************************************************************/
      if (qop->qualEntered[HELPqual]) {
        usage();
        cdf_FreeMemory (qop, FatalError);
        return TRUE;
      }
      /************************************************************************
      * Check for 'delete' qualifier.
      ************************************************************************/
      if (qop->qualEntered[DELETEqual]) {
	deleteOld = TRUE;
      } 
      /************************************************************************
      * Check for 'no-header' qualifier.
      ************************************************************************/
      if (qop->qualEntered[NOHEADERqual]) {
        noHeader = TRUE;
      } 
      /************************************************************************
      * Check for 'mapping' file qualifier.  TODO: fix this  (see below)
      ************************************************************************/
      if (qop->qualEntered[MAPqual]) {
        strcpyX (mapping, qop->qualOpt[MAPqual], DU_MAX_PATH_LEN);
      } else
	mapping[0] = 0;
      /************************************************************************
      * Check for 'show' qualifier.
      ************************************************************************/
      if (qop->qualEntered[SHOWqual]) {
	showMap = TRUE;
      }
      /************************************************************************
      * Check for backdoor 'debug' qualifier.
      ************************************************************************/
      if (qop->qualEntered[DEBUGqual]) {
        DEBUG = TRUE;
      }
      /************************************************************************
      * Check for CDF path parameter(s).
      ************************************************************************/
      if (qop->Nparms < 1) {
	if (!showMap) {
	  DisplayError ("Missing parameter for FITS or CDF file(s).");
	  qopError = TRUE;
	} 
      } else if (qop->Nparms == 1) { /* special case : only 1 cdf input file */
        parms = 2;
	oFile[0] = 0;
	strcpyX (CDFpath[0], qop->parms[0], DU_MAX_PATH_LEN);
      } else {                    /* generic case:   fits_out cdf_in_file(s) */
        parms = qop->Nparms;
        strcpyX (oFile, qop->parms[0], DU_MAX_PATH_LEN);
        for (i = 1; i < qop->Nparms; i++)
          strcpyX (CDFpath[i-1], qop->parms[i], DU_MAX_PATH_LEN);
      }
      /************************************************************************
      * Free QOP memory and check for an error.
      ************************************************************************/
      cdf_FreeMemory (qop, FatalError);
      if (qopError) return FALSE;
      break;
  } /* argC */

  if (!showMap)
    printf ("Converting CDF(s) to FITS...\nCVS Revision: %s\n",c2f_revision);


  /****************************************************************************
  * B-2: Read mapping files. By default internally build, though can also be
  *      be read from an external text file
  ****************************************************************************/

  totDM = 0;
  if (mapping[0]) {     
    inFile = fopen(mapping, "r");
    if (inFile == NULL) {
      printf ("**  Cannot open keywords mapping file: %s  **\n", mapping);
      exit(1);
    }
    while (fgets(rec, MAX_REC_LENGTH, inFile) != NULL) {
      if (showMap) printf("%s",rec);
      if (rec[0] != '#'  &&  rec[0] != ' ') { /* Not a comment */
        rec[strlen(rec)-1] = '\0';    /* Remove the newline character. */
        sscanf(rec, "%s %s", fits_dt[totDM], cdf_dt[totDM]);
        ptr = (char *) strstr(rec, "//");
        if (ptr != NULL) {
	  ptr += 3;
	  strcpy (comments[totDM], ptr);
        }
        if (DEBUG) 
	  printf("fits:%s <===> cdf:%s\n", fits_dt[totDM],cdf_dt[totDM]);
        totDM++;
      }
    }
    fclose(inFile);
  } else {
    for (ii=0; fits_to_cdf_mapping[ii]; ii++) {
      strcpy(rec,fits_to_cdf_mapping[ii]);
      if (showMap) printf("%s\n",rec);
      if (rec[0] != '#'  &&  rec[0] != ' ') {       /* Not a comment */
	if (totDM == MAX_FITS_KEYWORDS) {
	  printf("Too many fits_to_cdf mappings. Can only handle %d\n",
                 MAX_FITS_KEYWORDS);
	  exit(1);
	}
	sscanf(rec, "%s %s", fits_dt[totDM], cdf_dt[totDM]);
	ptr = (char *) strstr(rec, "//");
	if (ptr != NULL) {
	  ptr += 3;
	  strcpy (comments[totDM], ptr);
	}
	if (DEBUG) printf("fits_to_cdf_mapping  fits= %s <===> cdf=%s\n", 
                          fits_dt[totDM], cdf_dt[totDM]);
	totDM++;
      }
    } /* for(ii) */
  } 
  if (showMap) exit(0);

  totDT = 0;
  for(ii=0; datatype_mapping[ii]; ii++) {
    strcpy(rec,datatype_mapping[ii]);
    if (rec[0] != '#'  &&  rec[0] != ' ') {      /* Not a comment. */
      if (totDT == CDF_TYPES) {
	printf("Too many datatype mappings.. can only handle %d\n",CDF_TYPES);
	exit(1);
      }
      sscanf(rec, "%s %lf %lf %d %s", DATATYPE_TOKEN[totDT], &TZERO[totDT], 
	     &TSCALE[totDT], &BITPIX[totDT], TFORM[totDT]);
      DATATYPE[totDT] = WhichDataType(DATATYPE_TOKEN[totDT]);
      if (DEBUG) 
        printf("datatype_mapping     cdf: %-12s (%ld) <===> fits: %lF %lF %d %s\n",
	       DATATYPE_TOKEN[ii], DATATYPE[ii], TZERO[ii], 
     	       TSCALE[totDT], BITPIX[totDT], TFORM[totDT]);
      totDT++;
    }
  } /* for(ii) */

  /**************************************************************************
  * B-3: Check the existence of the FITS file if "nodelete" is chosen.
  *      then create the new FITS file, using the CFITSIO library
  **************************************************************************/
  if (oFile[0] == 0) {    /* FITS file name needs to be constructed */
    char *cp = NULL;

    cp = strrchr(CDFpath[0], '/');
    if (cp == NULL) cp = strrchr(CDFpath[0], '\\');
    if (cp) {
      cp++;
      strcpy(tFITSpath, cp);
    } else {
      strcpy(tFITSpath, CDFpath[0]);
    }

    cp = strrchr(tFITSpath, '.');
    if (cp) {
      cp++; 
      strcpy(cp, "fits");
    } else {
      strcat(tFITSpath, ".fits");
    }

  } else {
    strcpyX (tmpStr, oFile, 0);
    MakeLowerString (tmpStr);
    if (strchr(tmpStr, '.') != NULL) 
      strcpyX (tFITSpath, oFile, 0);
    else { 
      strcpy (tFITSpath, oFile);
      strcat (tFITSpath, ".fits");
    }
  }
/*   
  endExt = Trailer (tmpStr, ".fits");
  if (!endExt) 
    endExt = Trailer (tmpStr, ".fit");
  if (!endExt)
    endExt = Trailer (tmpStr, ".fts");
*/
/*  if (!endExt) strcpyX (tFITSpath, oFile, 0);          */
/*  else strcpyX (tFITSpath, oFile, strlen(oFile) - 5);  */
/*  strcatX (tFITSpath, ".fits", DU_MAX_PATH_LEN);       */
  printf("  Output FITS=%s\n",tFITSpath); 

  if ((inFile = fopen(tFITSpath, "r")) != NULL) {
    fclose (inFile);
    if (!deleteOld) {
      QuitFITS ("10.0 ",
                "**  Cannot create FITS... as it already exists. **") ;
    } else  /* Delete it. */
      if (!DeleteFile (tFITSpath)) 
        QuitFITS ("12.0H", "** Error deleting the FITS...");
  }

  fptr = 0;
  fstatus = 0;
  fits_create_file(&fptr, tFITSpath, &fstatus);            
  PrintFITSerror(fstatus, "1.0");
  for (i = 0; i < 10; i++) naxes[i] = 0;

  /****************************************************************************
  * A big loop processing each CDF 
  ****************************************************************************/
  for (i = 0; i < parms - 1; i++) {

    int group;
    char **ttype = NULL;                      /* fits column name      */
    char **tform = NULL;                      /* fits column type      */
    char **tunit = NULL;                      /* fits column unit      */
    char *extName = NULL;                     /* fits external name    */
    double *tzero = NULL;                     /* fits column tzero     */
    double *tscale = NULL;                    /* fits column tscale    */
    int *newNumDims = NULL;                   /* new numDims           */
    long **newDimSizes = NULL;                /* new dimSizes          */
    long *tDataType = NULL;                   /* data type array       */
    long *tNumElems = NULL;                   /* numElements array     */
    long *attrNums = NULL;                    /* attribute num array   */
    long *recVarys = NULL;                    /* record variance array */
    long *varMaxRec = NULL;                   /* var max record  array */
    char **varNames = NULL;                   /* variable names        */
    char **attrNames = NULL;                  /* attribute names       */
    long *hduGroup = NULL;                    /* HDU group             */
    int *varColumn = NULL;                    /* variable column in HDU*/

    /**************************************************************************
    * B-4: Open CDF, aquire some basic info and allocate FITS table space
    **************************************************************************/
    printf ("  Process file no: %d  CDF= %s\n", i+1, CDFpath[i]);

    id = OpenCDF(CDFpath[i]);
    GetCDFInfo(id, &majority, &numAttrs, &numGAttrs, &numVAttrs, &numZvars, 
               &maxRecNum);

    if (DEBUG) {
       printf("<=== CDF: majority:%s Num of Max records: %ld \n", 
                      MajorityToken(majority), maxRecNum+1);
       printf("          Gattrs:%ld  Vattrs:%ld  Vars:%ld\n", numGAttrs, 
              numVAttrs, numZvars);
    }

#if 1
    /* this hack is to get past testprog_hdu6.cdf */
    if (numZvars == 0) {
      numZvars = 1;
      ttype = (char **) malloc(sizeof(char *)*numZvars);  /* fits column name*/
      tform = (char **) malloc(sizeof(char *)*numZvars);  /* fits column type*/
      tunit = (char **) malloc(sizeof(char *)*numZvars);  /* fits column unit*/
      tzero = (double *) malloc(sizeof(double)*numZvars);  /* fits column tzero*/
      tscale= (double *) malloc(sizeof(double)*numZvars); /* fits column tscale*/
      newNumDims= (int *) malloc(sizeof(int)*numZvars);  /* new numDims      */
      newDimSizes= (long **) malloc(sizeof(long *)*numZvars); /* new dimSizes*/
      tDataType= (long *) malloc(sizeof(long)*numZvars); /* data type array*/
      tNumElems= (long *) malloc(sizeof(long)*numZvars); /* numElements array*/
      recVarys = (long *) malloc(sizeof(long)*numZvars); /* rec-variance array*/
      varMaxRec = (long *) malloc(sizeof(long)*numZvars); /* rec-max array */
      varNames = (char **) malloc(sizeof(char *)*numZvars);  /* variable names */
      hduGroup= (long *) malloc(sizeof(long)*numZvars); /* HDU group array*/
      varColumn = (int *) malloc(sizeof(int)*numZvars); /* var column array*/

      for (ii = 0; ii < numZvars; ii++) {
        tzero[ii] = 0.0;
        tscale[ii] = 1.0;
        tunit[ii] = (char *) strdup("");
      }
      numZvars = 0;
    }
#endif

    if (numZvars > 0) {

      ttype = (char **) malloc(sizeof(char *)*numZvars);  /* fits column name*/
      tform = (char **) malloc(sizeof(char *)*numZvars);  /* fits column type*/
      tunit = (char **) malloc(sizeof(char *)*numZvars);  /* fits column unit*/
      tzero = (double *) malloc(sizeof(double)*numZvars);  /* fits column tzero*/
      tscale= (double *) malloc(sizeof(double)*numZvars); /* fits column tscale*/
      newNumDims= (int *) malloc(sizeof(int)*numZvars);  /* new numDims      */
      newDimSizes= (long **) malloc(sizeof(long *)*numZvars); /* new dimSizes*/
      tDataType= (long *) malloc(sizeof(long)*numZvars); /* data type array*/
      tNumElems= (long *) malloc(sizeof(long)*numZvars); /* numElements array*/
      recVarys = (long *) malloc(sizeof(long)*numZvars); /* rec-variance array*/
      varMaxRec = (long *) malloc(sizeof(long)*numZvars); /* rec-max array */
      varNames = (char **) malloc(sizeof(char *)*numZvars);  /* variable names */
      hduGroup= (long *) malloc(sizeof(long)*numZvars); /* HDU group array*/
      varColumn = (int *) malloc(sizeof(int)*numZvars); /* var column array*/

      for (ii = 0; ii < numZvars; ii++) {
        tzero[ii] = 0.0;
        tscale[ii] = 1.0;
        tunit[ii] = (char *) strdup("");
      }

      group = GetVarsRecordNumbers(id, varNames, varMaxRec, hduGroup, varColumn);

      if (DEBUG) {
        printf("  Total group(s): %d\n", group);
        for (ii = 0; ii < numZvars; ii++) {
          printf("  varNames=%s varMax=%d HDU#=%d column=%d\n", varNames[ii], 
                 varMaxRec[ii], hduGroup[ii], varColumn[ii]);
        }
      }
      printf("  Columns: %d   Rows: %d  HDUgroups: %d; with rows [",numZvars,
             maxRecNum+1,group);
      for(jj=1; jj<=group; jj++) {
	for(ii=0; ii<numZvars; ii++) {
	  if (hduGroup[ii] == jj) {
	    printf(" %d ",varMaxRec[ii]);
	    break;
	  }
	}
      }
      printf("]\n");
     } else {
      printf("  No DATA\n");
    }   /* if (numZvars > 0)  */


    /**************************************************************************
    * B-5: Process the global attributes.  (keywords for the FITS header)
    *      this is also the place where we decide if the CDF file is native
    *      or FITS-non-native.
    **************************************************************************/

    if (DEBUG) 
      printf("<=== Total num of Global Attributes: %ld ===>\n", numGAttrs);

    if (numGAttrs == 0) { /* Must be a native CDF. Just create an FITS header*/
      naxes[0] = 0;
      nativeCDF = TRUE;
      fits_write_imghdr(fptr, 8, 0, naxes, &fstatus);
      WriteID();
    } else { /* Could be a native or non-native CDF. */
      attrNums = (long *) malloc(sizeof(long) * numGAttrs);

      GetGlobalAttributeNumbers(id, numAttrs, attrNums);

      for (ii = 0; ii < numGAttrs; ii++) {

        /**********************************************************************
        * Acquire each global attribute's info.
        **********************************************************************/
	GetGlobalAttributeInfo(id, attrNums[ii], attrName, &numEntries,
                               &maxEntry);
	if (ii == 0) {
          if (streq(attrName, "Original_FITS_Filename"))
	    nativeCDF = FALSE; /* A CDF converted from FITS-to-CDF program. */
	  else {
	    nativeCDF = TRUE;  /* A native CDF. */
            naxes[0] = 0;
            if (i == 0) {      /* very first CDF, first GATTR */
	      fits_write_imghdr(fptr, 8, 0, naxes, &fstatus);
	      WriteID();
	    }
          }
        }

        if (!nativeCDF && streq(attrName, "FITS_Header_Keywords")) {
          char *extName1 = malloc(80);
          /******************************************************************
          * Needs two passes for the non-native CDF.
          * The first pass is to collect the needed info to create the proper
          * header record.
          ******************************************************************/

          FirstPass(id, attrNums[ii], numEntries, &conformation,
                    &whichHeader, &extension, &bitpix, &naxis,
                    naxes, &pcount, &gcount, &extend, ttype,
                    tform, tunit, extName1);

          if (extension == IMAGE) {
            if (ii == 0) {
              if (gcount != -99)
                fits_write_grphdr(fptr, conformation, bitpix, naxis,
                                  naxes, pcount, gcount, extend, &fstatus);
              else 
                fits_write_imghdr(fptr, bitpix, naxis, naxes, &fstatus);
            } else 
              fits_create_img(fptr, bitpix, naxis, naxes, &fstatus);
          } else if (extension == BINTABLE) {
              fits_create_tbl(fptr, BINARY_TBL, 0, (int) numZvars, ttype, 
                              tform, tunit, extName1, &fstatus);
          } else if (extension == TABLE) {
            fits_create_tbl(fptr, ASCII_TBL, 0, (int) numZvars, ttype,
                            tform, tunit, extName1, &fstatus);
          } else {
	    printf("Extension %d not supported yet\n",extension);
	    exit(1);
	  }
	  
	  if (i==0) WriteID();

          PrintFITSerror(fstatus, "1.5");
          free (extName1);
        } /* !nativeCDF */

        /**********************************************************************
        * For non-native CDF, only process the global attribute, with name 
        * "FITS_Header_Keywords", and skip others. 
        **********************************************************************/
	if (!nativeCDF && !streq(attrName, "FITS_Header_Keywords")) continue;

        if (DEBUG) printf("Global attribute #%d: %s #_of_entries=%ld (%ld)\n",
                          ii, attrName, numEntries, maxEntry);

	kk = 0;
        firstEntry = TRUE;

        /**********************************************************************
        * For non-native CDF, the second pass to add the remaining
        * fields to the header.
        **********************************************************************/

        foundMapping = FALSE;
        strcpy(newAttrName, attrName);
        for (jj = 0; jj < totDM; jj++) {
          if (streq(attrName, cdf_dt[jj])) { /* Found the mapping. */
            foundMapping = TRUE;
            strcpy(newAttrName, fits_dt[jj]);
            break;
          }
        }
	if (numEntries == 0) {     /* No global attribute entry data */
	  if (foundMapping)
	    strcpy(keyword, newAttrName);
	  else
	    /* strcpy(keyword, "");     */
	    strcpy(keyword, attrName);    /* Bug fixed by David Han */
 
/*	  sprintf(comment, "GATTR[%d][%d]=%s (has no data)",ii+1,1,attrName); */
          sprintf(comment, "%s (has no data)",attrName);
	  fits_write_key_str(fptr, keyword, "", comment,&fstatus);
	}
                                                                                               
        for (jj = 0; jj <= maxEntry; jj++) {
          /*******************************************************************
          * Acquire each global attribute entry's info.
          *******************************************************************/

	  status = GetGlobalAttributeEntryInfo(id, attrNums[ii], (long) jj,
                                               &dataType, &numElems);

	  if (status == CDF_OK) { /* The entry exists. */
            void *entry, *keywordValue;
            size = CDFelemSize(dataType) * numElems;
            entry = (void *) malloc ((size_t) size);
            if (dataType == CDF_CHAR || dataType == CDF_UCHAR)
              keywordValue = (void *) malloc ((size_t) size + 1);
            else
              keywordValue = (void *) malloc (25 * numElems);
            /*****************************************************************
            * Acquire a global attribute entry's data.
            *****************************************************************/
	    GetGlobalAttributeEntryData(id, attrNums[ii], (long) jj, 
                                        entry);

            if (DEBUG) {
              printf("  entry: %ld ", jj);
              DumpAttributeEntry(dataType, numElems, entry);
            }

	    if (streq(attrName, "FITS_Header_Keywords")) { 
              /***************************************************************
              * Acquire entry data from the "FITS_Header_Keywords" attribute.
              * This is a non-native CDF.
              ***************************************************************/
              if (streq(entry, "END")) continue;
	      if (value == NULL) value = (void *) malloc(80);
              *(char *)value = '\0';
              ParseKeyword((char *)entry, numElems, keyword, 
                           keywordValue, comment, &keyDataType, 
                           &keyNum, &keySize, floatFormat, value);

	      if (keySize > 0) { /* The keyword value is returned. */

                if (DEBUG) 
                  DumpFITSCard(keyword, keywordValue, comment, keyNum,
                               keyDataType, value, floatFormat);

                /***********************************************************
                * Skip those keys that have already been processed in 
                * FirstPass call.
                ***********************************************************/
                if (SkipCard(keyword)) {
                  kk++;
                  continue;
                } 

                if (CheckScaleZeroNull(keyword, keywordValue, comment,
                                       keyDataType, value,
                                       tscale, tzero)) continue;

                HandleOtherKeys(keyword, keywordValue, comment,
                                keyDataType, value, floatFormat, keyNum);
              } else { /* No keyword value. A COMMENT, HISTORY or CONTINUE. */
                if (DEBUG) { 
                  printf("   ** Keyword=%s ",keyword);
                  if (strlen(comment) > 0) printf("COMMENT=%s\n",comment);
                  else printf("\n");
                }
                if (streq(keyword, "COMMENT") || streq(keyword, "comment")) {
                  if (!streq(comment, "FITS (Flexible Image Transport System)") 
                      &&
                      !streq(comment, "and Astrophysics', volume 376, page 35"))
		    fits_write_comment(fptr, comment, &fstatus);
                } else if (streq(keyword, "HISTORY") || 
                           streq(keyword, "history")) {
                  fits_write_history(fptr, comment, &fstatus);
                } else if (streq(keyword, "CONTINUE") || 
                           streq(keyword, "continue")) {
                  if (strlen(keywordValue) > 68)
                    fits_write_key_longstr(fptr, keyword, keywordValue, comment,
                                           &fstatus);
                  else
                    fits_write_key_str(fptr, keyword, keywordValue, comment,
                                       &fstatus);
                } else /* A null value.*/
                  fits_write_key_null(fptr, keyword, comment, &fstatus);
                PrintFITSerror(fstatus, "2.0");
              }

            } else { /* A global attribute entry from a native CDF. */
                     /* Convert the entry data into string.         */
              /***************************************************************
              * Acquire entry data from a native CDF.
              ***************************************************************/
              char *outString;
              if (dataType == CDF_CHAR || dataType == CDF_UCHAR) 
                outString = (char *) malloc((size_t)numElems + 1);
              else
                outString = (char *) malloc(25 * numElems);

              /* Print the value of the entry data to 'outString' */
              PrintData(entry, dataType, numElems, outString);

              strcpyX(keywordValue, outString, strlen(outString));
              if (strlen(newAttrName) > 8) /* Show attribute name in comment */
/*		sprintf(comment, "GATTR[%d][%d]=%s",ii+1,jj+1,attrName); */
                sprintf(comment, "%s",attrName);
              else
                if (foundMapping) {
/*                  sprintf(comment, "GATTR[%d][%d]=%s",ii+1,jj+1,attrName); */
                  sprintf(comment, "%s",attrName);
                } else
                  comment[0] = (char) 0;
/*                  sprintf(comment, "GATTR[%d][%d]=%s",ii+1,jj+1,attrName); */
/*                  sprintf(comment, "%s",attrName); */

              if (firstEntry) { /* Output the attribute name for the first  */
                char tmpKey[8];
                int nr;
                strcpyX(tmpKey, newAttrName, 8);
                nr = FindGkey(tmpKey);
                if (nr == 0) 
                  strcpyX(keyword, newAttrName, 8); /* occurrence.          */
                else {
                  appendNumKey(tmpKey, nr);
                  strcpyX(keyword, tmpKey, 8);
                }
                firstEntry = FALSE;
              } else { /* Any following entries will have blanks as the     */
                /* strcpyX(keyword, "        ", 8); */
                comment[0] = (char) 0;
              }
	      /* TODO: ??   comment and history are handled differently
	       *            from other attributes. 
	       */
              if (streq(attrName, "COMMENT") || streq(attrName, "comment")) {
                fits_write_comment(fptr, (char *) outString, &fstatus); 
/*		sprintf(comment, "GATTR[%d][%d]=%s",ii+1,jj+1,attrName); */
/*                sprintf(comment, "%s",attrName); */
/*		fits_write_comment(fptr, comment, &fstatus); */
              } else if (streq(attrName, "HISTORY") || 
                         streq(attrName, "history")) {
                fits_write_history(fptr, (char *) outString, &fstatus);
/*		sprintf(comment, "GATTR[%d][%d]=%s",ii+1,jj+1,attrName); */
/*                sprintf(comment, "%s",attrName); */
/*		fits_write_comment(fptr, comment, &fstatus); */
              } else {
                int ik = strlen(keywordValue);
                int ic = strlen(comment);
/*
		int nr = FindGkey(keyword);  
		if (nr > 0 && strlen(keyword) > 8) {
		  if (nr < 10) 
		    sprintf(&keyword[7],"%d",nr);
		  else if (nr < 100) 
		    sprintf(&keyword[6],"%d",nr);
		  else if (nr < 1000) 
		    sprintf(&keyword[5],"%d",nr);
		}
*/
                if (ik > 68) {
                  int ir = ik - (ik / 68) * 68;
                  if ((ir + ic ) <= 65 && (ic < 48)) {  
                    fits_write_key_longstr(fptr, keyword, keywordValue, comment,
                                           &fstatus);
                  } else {
                    fits_write_key_longstr(fptr, keyword, keywordValue, "", 
                                           &fstatus);
                    fits_write_comment(fptr, comment, &fstatus);
                  }
                } else {
                  if ((ik + ic ) <= 65 && (ic < 48)) {
                    fits_write_key_str(fptr, keyword, keywordValue, comment,
                                       &fstatus);
                  } else {
                    fits_write_key_str(fptr, keyword, keywordValue, "",
                                       &fstatus);
                    fits_write_comment(fptr, comment, &fstatus);
                  }
                }
              }
            } /* native / non-native keyword */
	    kk++;
            free (entry);
	  } /* status OK */
	} /* for(jj) */
      } /* for(ii) */
    }  /* (numGAttrs > 0)  */


    /**************************************************************************
    * Create placeholder attribute(s) - by David Han
    **************************************************************************/
    for (jj = 0; jj < totDM; jj++) { 
        if (strcmp(cdf_dt[jj], "*") == 0)     /* placeholder indicator */
            fits_write_key_str(fptr, 
                               fits_dt[jj],    /* keyword       */
                               comments[jj],   /* keyword value */
                               "",             /* comment       */
                               &fstatus);
    }

    /**************************************************************************
    * B-6: Acquire variable attributes. 
    **************************************************************************/

    if (DEBUG) printf("<=== Total num of Variable Attributes=%ld ===>\n",
                      numVAttrs);

    if (nativeCDF && numVAttrs > 0) {

      attrNums = (long *) malloc(sizeof(long) * numVAttrs);
      attrNames = (char **) malloc(sizeof(char *) * numVAttrs);

      for (ii = 0; ii < numVAttrs; ii++) 
         attrNames[ii] = (char *) malloc((size_t) DU_MAX_PATH_LEN);

      /************************************************************************
      * Acquire number of variable attributes, their attribute numbers and 
      * variable names.
      ************************************************************************/
      GetVarAttributes(id, numAttrs, attrNums, attrNames);
      
      if (DEBUG) 
        for (ii = 0; ii < numVAttrs; ii++) 
           printf(" Variable attribute #%d: name=%s\n", ii, attrNames[ii]);

    }    /* if (nativeCDF && numVAttrs > 0) */

    /**************************************************************************
    * B-7: process variables for native files
    **************************************************************************/

    if (DEBUG) 
      printf("<=== Total num of Variables=%ld (attribute entries) ===>\n",
             numZvars);

    if (nativeCDF && numZvars > 0) {

      /*
       * B-7a:  Find special CDF attributes that have a meaning for FITS columns
       */
      unitsNum = -1;
      for (ii = 0; ii < numVAttrs; ii++) {
        if (streq(attrNames[ii], "UNITS") || streq(attrNames[ii], "units")) {
          unitsNum = (int)attrNums[ii];
          break;
        }
      } /* for(ii) */

      /*
       * B-7b:  Acquire each variable info and gather info for FITS columns
       */
      for (ii = 0; ii < numZvars; ii++) {
        int jk;
        GetVarInfo(id, (long) ii, varName, &dataType, &numElems, &numDims, 
                   dimSizes, &recVary, &maxRec);

        if (DEBUG) printf("var(%d):%s (building the HDU(%d))\n", ii+1,
                          varName, hduGroup[ii]+1);

        tDataType[ii] = dataType;
        tNumElems[ii] = numElems;
        recVarys[ii] = recVary;
        *(((int *)newNumDims)+ii) = (int) numDims;

        if (numDims > 0) 
          newDimSizes[ii] = (long *) malloc(sizeof(long) * numDims);
        if (majority == ROW_MAJOR) { /* Switch row to column. */
          for (jk = 0; jk < numDims; jk++)
            newDimSizes[ii][jk] = dimSizes[numDims-jk-1];
        } else {
          for (jk = 0; jk < numDims; jk++)
            newDimSizes[ii][jk] = dimSizes[jk];
        }
        /**********************************************************************
        * For a complex variable, identified by an attribute "IsComplexNum_",
        * the number of dimensions is deducted by one (1).
        **********************************************************************/
        fitsDataType = -1;
        if (ComplexVariable(id, (long) ii, attrNames, attrNums, numVAttrs)) {
          *(((int *)newNumDims)+ii) = (int) numDims - 1;
          if (dataType == CDF_FLOAT || dataType == CDF_REAL4)
            fitsDataType = TCOMPLEX;
          else
            fitsDataType = TDBLCOMPLEX;
        }

	ttype[ii] = (char *) strdup(varName);
	for (jj = 0, size = 1; jj < *(((int *)newNumDims)+ii); jj++)
	  size *= newDimSizes[ii][jj];
	tform[ii] = (char *) malloc(32);
        for (jj = 0; jj < totDT; jj++) {
          if (dataType == DATATYPE[jj]) {
            tzero[ii] = TZERO[jj];
            tscale[ii] = TSCALE[jj];
            if (dataType == CDF_EPOCH)
              sprintf(tform[ii],"%d%s", size*ELEN, TFORM[jj]);
            else if (dataType == CDF_CHAR || dataType == CDF_UCHAR)
              sprintf(tform[ii],"%d%s%d", size*numElems, TFORM[jj], numElems);
            else if (dataType == CDF_FLOAT || dataType == CDF_REAL4 ||
                     dataType == CDF_DOUBLE || dataType == CDF_REAL8) {
                 if (fitsDataType == -1) 
                   sprintf(tform[ii],"%d%s", size, TFORM[jj]);
                 else {
                   if (fitsDataType == TCOMPLEX)
                     sprintf(tform[ii],"%dC", size);
                   else
                     sprintf(tform[ii],"%dM", size);
                 }
            } else if (dataType == CDF_INT1 || dataType == CDF_BYTE) 
              sprintf(tform[ii],"%dI", size);
            else 
              sprintf(tform[ii],"%d%s", size, TFORM[jj]);
            break;
          }
        } /* for(jj) */

        if (unitsNum > -1) {
          /********************************************************************
          * Acquire variable attribute's, "UNITS", entry info, if it exists.
          ********************************************************************/
          status = GetVarAttributeEntryInfo(id, (long) unitsNum, (long) ii, 
                                            &dataType, &numElems);
          if (status == CDF_OK) {
            void *entry;
            size = CDFelemSize(dataType) * numElems;
            entry = malloc(size);
            GetVarAttributeEntryData(id, (long) unitsNum, (long) ii, entry);
            tunit[ii] = (char *) malloc(size+1);
            memcpy(tunit[ii], (char *) entry, size);
            *(((char *) tunit[ii])+size) = (char) 0;
            free (entry);
          } else {
            tunit[ii] = malloc(1);
            *((char *) tunit[ii]) = (char) 0;
          }
        }

      } /* for(ii) */

      /************************************************************************
      * B-7c: Create BINTABLE(s) layout for variables from a native CDF
      ************************************************************************/

      CreateHDUs(group, numZvars, ttype, tform, tunit, hduGroup, varMaxRec);

      /************************************************************************
      * B-7d: Write out the dimensionality of each column if it's greater than 0.
      ************************************************************************/

      for (ii = 0; ii < numZvars; ii++) {
        if (*(((int *)newNumDims)+ii) > 0) {
          if (tDataType[ii] == CDF_CHAR || tDataType[ii] == CDF_UCHAR ||
              tDataType[ii] == CDF_EPOCH) {
            int ya;
            int yb = *(((int *)newNumDims)+ii) + 1;
            long *yc = (long *) malloc(sizeof(long) * yb);
            for (ya = 0; ya < yb; ya++) {
              if (ya == 0) {
                if (tDataType[ii] != CDF_EPOCH) yc[0] = tNumElems[ii];
                else yc[0] = ELEN;
              } else 
                yc[ya] = newDimSizes[ii][ya-1];
            }
	    fits_movabs_hdu(fptr, (int) hduGroup[ii]+1, &hduType, &fstatus);
	    fits_write_tdim(fptr, varColumn[ii], yb, yc, &fstatus);
          } else {
	    fits_movabs_hdu(fptr, (int) hduGroup[ii]+1, &hduType, &fstatus);
	    fits_write_tdim(fptr, varColumn[ii], *(((int *)newNumDims)+ii), 
                            newDimSizes[ii], &fstatus);
          }
          PrintFITSerror(fstatus, "4.0");
        }
      } /* for(ii) */

      /************************************************************************
      * B-7e: Acquire each variable's other attribute entries.
      ************************************************************************/

      for (ii = 0; ii < numZvars; ii++) {

        if (DEBUG) {
	  GetVarInfo(id, (long) ii, varName, &dataType, &numElems, &numDims,
                     dimSizes, &recVary, &maxRec);
          printf("var: %s (attribute entries)\n", varName);
        }

        for (jj = 0; jj < numVAttrs; jj++) {
          if (attrNums[jj] == unitsNum) continue; /* Skip processed UNITS. */
          /********************************************************************
          * Acquire each variable attribute entry info, if it exists.
          ********************************************************************/
	  status = GetVarAttributeEntryInfo(id, attrNums[jj], (long) ii, 
                                            &dataType, &numElems);
          if (status == CDF_OK) {
            void *entry;
            size = CDFelemSize(dataType) * numElems;
            if (dataType == CDF_CHAR || dataType == CDF_UCHAR) size++;
            entry = (void *) malloc((size_t)size);

            foundMapping = FALSE;
            strcpy(newAttrName, attrNames[jj]);
            for (kk = 0; kk < totDM; kk++) {
              if (streq(attrNames[jj], cdf_dt[kk])) { /* Found the mapping.*/
                foundMapping = TRUE;
                strcpy(newAttrName, fits_dt[kk]);
                break;
              }
            }
	    GetVarAttributeEntryData(id, attrNums[jj], (long) ii, entry);

            if (DEBUG) {
              printf("  entry: (attribute:%s) ",attrNames[jj]);
              DumpAttributeEntry(dataType, numElems, entry);
            }

	    if (tDataType[ii] == CDF_EPOCH && foundMapping && 
                streq(newAttrName,"TDISP")) {
	      free(entry);
	      entry = strdup("A24"); /* ELEN */
	    }

#if 0
	    /* (sort of) old style  (see v1) */
/*	    sprintf(comment,"VATTR[%d][%d]=%s",ii+1,jj+1,newAttrName); */
            sprintf(comment,"%s",newAttrName);
#else
	    /* new style, index them all (also does e.g. TDISP) */
            sprintf(colNumStr, "%d", varColumn[ii]);
            if (ii < 9 ) colNumStr[1] = (char) 0;
            else if (ii < 98) colNumStr[2] = (char) 0;
            else if (ii < 998) colNumStr[3] = (char) 0;
            else if (ii < 9998) colNumStr[4] = (char) 0;

            strcatX(newAttrName, colNumStr, CDF_ATTR_NAME_LEN);

            if (streq(attrNames[jj], "DEPEND_") || 
                streq(attrNames[jj], "LABL_PTR_")) {
              ModifyDupKey(attrNames[jj], newAttrName);
              strcatX(newAttrName, colNumStr, 8);
            }

            if (strlen(newAttrName) > 8) {
/*	      sprintf(comment,"VATTR[%d][%d]=%s_%s",ii+1,jj+1,attrNames[jj],colNumStr); */
              sprintf(comment, "%s(%s)", attrNames[jj], colNumStr);
              if (ii < 9 ) {
                newAttrName[7] = colNumStr[0];
              } else if (ii < 98) {
                newAttrName[6] = colNumStr[0];
                newAttrName[7] = colNumStr[1];
              } else if (ii < 998) {
                newAttrName[5] = colNumStr[0];
                newAttrName[6] = colNumStr[1];
                newAttrName[7] = colNumStr[2];
              } else if (ii < 9998) {
                newAttrName[4] = colNumStr[0];
                newAttrName[5] = colNumStr[1];
                newAttrName[6] = colNumStr[2];
                newAttrName[7] = colNumStr[3];
              }
              newAttrName[8] = (char) 0;
            } else
              if (foundMapping)
/*		sprintf(comment,"VATTR[%d][%d]=%s",ii+1,jj+1,attrName); */
                sprintf(comment,"%s(%s)",attrNames[jj],colNumStr);
              else
/*		sprintf(comment,"VATTR[%d][%d]",ii+1,jj+1,attrNames[jj]); */
                sprintf(comment,"%s(%s)",attrNames[jj],colNumStr);
#endif

            WriteKey((int) hduGroup[ii], dataType, numElems, newAttrName, entry, 
                     comment);
            free (entry);
	  } /* status OK */
	} /* for(jj) */
      } /* for(ii) */
    } /*     if (nativeCDF && numZvars > 0)  */

    /**************************************************************************
    * B-8: For native CDF, set the BSCALE/BZERO or TSCALE/TZERO for 
    *      unsigned 2 and 4 bytes intgers.
    *      For non-native ...
    **************************************************************************/

    if (numZvars > 0) {
      if (nativeCDF) {
        char forScale[9], forZero[9], tmpInd[5];
        for (ii = 0; ii < numZvars; ii++) {
          if (tzero[ii] == 0.0 && tscale[ii] == 1.0) continue;
          strcpy(forScale, "TSCAL");
          strcpy(forZero, "TZERO");
          sprintf(tmpInd, "%d", varColumn[ii]);
          strcat(forScale, tmpInd);
          strcat(forZero, tmpInd);
          fits_movabs_hdu(fptr, (int) hduGroup[ii]+1, &hduType, &fstatus);
          fits_write_key(fptr, TDOUBLE, forScale, &tscale[ii], "TSCALE",
                         &fstatus);
          fits_write_key(fptr, TDOUBLE, forZero, &tzero[ii], "TZERO",
                         &fstatus);
          fits_set_tscale(fptr, varColumn[ii], tscale[ii], tzero[ii], &fstatus);
          PrintFITSerror(fstatus, "4.53");
        } /* for(ii) */
      } else { /* non-native CDF. */
        for (ii = 0; ii < numZvars; ii++) {
          if (tzero[ii] == 0.0 && tscale[ii] == 1.0) continue;
          fits_set_tscale(fptr, ii+1, tscale[ii], tzero[ii], &fstatus);
          PrintFITSerror(fstatus, "4.55");
        }  /* for(ii) */
      } /* native/non-native  */
    } /* if (numZvars > 0) */


    /**************************************************************************
    * B-9: For native CDF, set the TNULLn for integer type data from the pad 
    *      value.
    *      TODO: do we even have these? since we do multiHDU
    **************************************************************************/

    if (nativeCDF && numZvars > 0) {
      long status;
      int dd;
      char forTNULL[9], tmpInd[5];
      for (ii = 0; ii < numZvars; ii++) {
        if (tDataType[ii] == CDF_INT1 || tDataType[ii] == CDF_BYTE || 
            tDataType[ii] == CDF_INT2 || tDataType[ii] == CDF_UINT2 ||
            tDataType[ii] == CDF_INT4 || tDataType[ii] == CDF_UINT4 ||
            tDataType[ii] == CDF_UINT1) {
          void *padValue = (void *) malloc(CDFelemSize(tDataType[ii]));
          status = GetVarPadValue(id, (long) ii, padValue);
          if (status == CDF_OK) {
            if (tDataType[ii] == CDF_INT1 || tDataType[ii] == CDF_BYTE) {
              dd = *(signed char *) padValue;
              if (dd < 0) dd = 0;
            } else if (tDataType[ii] == CDF_UINT1) 
              dd = *(unsigned *) padValue;
            else if (tDataType[ii] == CDF_INT2)
              dd = *(short *) padValue;
            else if (tDataType[ii] == CDF_INT4)
              dd = *(int *) padValue;
            else if (tDataType[ii] == CDF_UINT2)
              dd = *(unsigned short *) padValue;
            else if (tDataType[ii] == CDF_UINT4)
              dd = *(unsigned int *) padValue;
            free (padValue);
            strcpy(forTNULL, "TNULL");
            sprintf(tmpInd, "%d", varColumn[ii]);
            strcat(forTNULL, tmpInd);
            fits_movabs_hdu(fptr, (int) hduGroup[ii]+1, &hduType, &fstatus);
            fits_write_key(fptr, TINT, forTNULL, &dd, "Undefined value", 
                           &fstatus);
            PrintFITSerror(fstatus, "4.55");
          }
        }
      } /* for(ii) */
    } /*     if (nativeCDF && numZvars > 0) */

    /**************************************************************************
    * B-10: Acquire each variable's record data. (Fill to the FITS columns)
    **************************************************************************/

    if (numZvars > 0) {
      int jk;
      long recAlloc;
      int typecode, fitsstatus;
      long repeat, width;
      int hduNum;

      if (DEBUG) 
        printf("<=== Total num of Variables=%ld (variable records) ===>\n",
               numZvars);

      ix = 1;
      while (ix <= group) {

        if (nativeCDF) 
          fits_movabs_hdu(fptr, (int) ix+1, &hduType, &fstatus);

        for (ii = 0; ii < numZvars; ii++) {

          if (nativeCDF) {
            if (hduGroup[ii] != ix ) continue;

            /********************************************************************
            * Get variable's attribute entries.
            ********************************************************************/
            foundMapping = FALSE;
            for (jj = 0; jj < numVAttrs; jj++) {
              for (kk = 0; kk < totDM; kk++) {
                if (streq(attrNames[jj], cdf_dt[kk])) { /* Found the mapping.*/
                  foundMapping = TRUE;

                  /**************************************************************
                  * Acquire variable attribute entry info, if it exists, for the
                  * keyword field.
                  **************************************************************/
                  status = GetVarAttributeEntryInfo(id, attrNums[jj], (long) ii,
                                                    &dataType, &numElems);
                  if (status == CDF_OK) {
                    void *entry;
                    size = CDFelemSize(dataType) * numElems;
                    entry = malloc(size);
                    /************************************************************
                    * Acquire a variable attribute's entry data.
                    ************************************************************/
                    GetVarAttributeEntryData(id, attrNums[jj], (long) ii, entry);

                    if (DEBUG) PrintData(entry, dataType, numElems, tempS); 
                    free (entry);
                    break;
	          }
                }
              }
            }
          } else /* non-native CDF. */

            if (extension != IMAGE) 
              typecode = GetFITSdataType(tform[ii]);

          /**********************************************************************
          * Acquire each variable's info.
          **********************************************************************/
          GetVarInfo(id, (long) ii, varName, &dataType, &numElems, &numDims,
                     dimSizes, &recVary, &maxRec);

          size = CDFelemSize(dataType) * numElems;
          phyValues = 1;
          if (numDims > 0) 
            for (jk = 0; jk < numDims; jk++) phyValues *= dimSizes[jk];
          phySize = phyValues * size;

          if (DEBUG) {
            printf("var=%s(%d) numDims=%ld",varName, ii, numDims);
            if (numDims > 0) {
              printf(":["); 
              for (jk = 0; jk < numDims; jk++) {
                printf("%d",dimSizes[jk]);
                if (jk != (numDims-1)) printf(",");
              }
              printf("]");
            }
            printf("  dataType=%s",DataTypeToken(dataType));
            if (dataType == CDF_CHAR || dataType == CDF_UCHAR)
              printf("(%ld)",numElems);
            printf("  maxRecNum=%ld\n", maxRec);

          }

	  if (maxRec < 0) continue;
          recNum = 0;
          startRec = 1;

          /*********************************************************************
          * Acquire record values. Try as many records as possible for a read.
          *********************************************************************/
          while (recNum <= maxRec) {
            void *outData;
            recAlloc = maxRec - recNum + 1;
            while (TRUE) { /* Acquire a buffer as large as possible. */
              size = recAlloc * phySize;
              outData = (void *) malloc(size);
              if (outData != NULL) break;
              else
                recAlloc = recAlloc / 2; /* Half the space request. */
            }
            GetVarRecordData(id, (long) ii, recNum, recAlloc, 
                             numDims, dimSizes, outData);

            if (dataType == CDF_EPOCH) { /* Convert epoch in double to string. */
              char **newTmp = malloc(sizeof(char *));
              newTmp[0] = malloc(ELEN+1); 
    	      for (jk = 0; jk < recAlloc; jk++) {
                 for (jj = 0; jj < phyValues; jj++) {
	  	   encodeEPOCH3(*(((double *)outData)+jk*phyValues+jj), newTmp[0]);
                   fits_write_col(fptr, TSTRING, varColumn[ii], startRec, jj+1, 1,
                                  newTmp, &fstatus);
                 }
                 startRec++;
              }
            } else { /* For non-EPOCH data.... */
              if (!nativeCDF && streq(varName, "Image")) { 
                /* FITS image extension originally */
                fits_write_img(fptr, ConvertCDFDataType(dataType), startRec, 
                               recAlloc * phyValues, outData, &fstatus);
              } else { /* other types of native/non-native CDF */ 
                if (dataType == CDF_CHAR || dataType == CDF_UCHAR) {
                  char **newTmp = malloc(phyValues*sizeof(char *));
                  for (jk = 0; jk < phyValues; jk++)
                    newTmp[jk] = malloc(numElems);
                  for (jj = 0; jj < recAlloc; jj++) {
                    for (jk = 0; jk < phyValues; jk++)
                      memcpy(newTmp[jk], ((char *)outData)+jj*numElems*phyValues+
                             jk*numElems, numElems);
                    if (nativeCDF)
                      fits_write_col(fptr, TSTRING, varColumn[ii], startRec,
                                     (long) 1, (long) phyValues, newTmp, 
                                     &fstatus);
                      else
                        fits_write_col(fptr, TSTRING, ii+1, startRec, (long) 1, 
                                       (long) phyValues, newTmp, &fstatus);
                      startRec++;
                  }
                } else if (dataType == CDF_INT1 || dataType == CDF_BYTE) {
                  if (nativeCDF) {
                    short *newOut;
                    newOut = malloc(sizeof(short) * recAlloc * phyValues);
                    if (newOut != NULL) {
                      for (jk = 0; jk < recAlloc * phyValues; jk++) 
                        *(((short *)newOut)+jk) = (short)
                                             *(((signed char *) outData)+jk); 
                      fits_write_col(fptr, TSHORT, varColumn[ii], startRec, 
                                     (long) 1, (long) recAlloc * phyValues, 
                                     newOut, &fstatus);
                    } else {
                      int jl, jm;
                      newOut = malloc(2);
                      for (jk = 0; jk < recAlloc * phyValues; jk++) {
                        *newOut = (short) *(((signed char *)outData)+jk);
                        jl = startRec + jk / phyValues;
                        jm = jk - jl * phyValues + 1;
                        fits_write_col(fptr, TSHORT, varColumn[ii], (long) jl, 
                                       (long) jm, (long) 1, newOut, &fstatus);
                      }
                    }
                  } else { /* non-native CDF */
                    if (extension == IMAGE)
                      fits_write_col(fptr, ConvertCDFDataType(dataType), ii+1, 
                                     startRec, (long) 1, 
                                     (long) recAlloc * phyValues, outData,
                                     &fstatus);
                    else 
                      fits_write_col(fptr, typecode, ii+1, startRec, (long) 1,
                                     (long) recAlloc * phyValues, outData,
                                     &fstatus);
                  }
                } else { /* other integer types or simple/complex float/double */
                  if (nativeCDF) {
                    fits_write_col(fptr, ConvertCDFDataType(dataType), 
                                   varColumn[ii], startRec, (long) 1, 
                                   (long) recAlloc * phyValues, 
                                   outData, &fstatus);
                  } else {
                    fits_write_col(fptr, ConvertCDFDataType(dataType), ii+1, 
                                   startRec, (long) 1, 
                                   (long) recAlloc * phyValues, 
                                   outData, &fstatus);
		  }
                }
              }
              PrintFITSerror(fstatus, "4.8");
              startRec += recAlloc;
            }  /* epoch/non-epoch */


            if (DEBUG) 
              printf("  Done a read/write %d record(s) (rec:%d to rec:%d)\n",
                     recAlloc, recNum, (recNum+recAlloc-1));

	    recNum += recAlloc; /* a following read(s) are needed if records */
                                /* are still left.                           */
            free (outData);
          } /* while (recNum <= maxRec) */
        } /* for(ii) */
        ix++;
      } /* (ix <= group) */
    } /*     if (numZvars > 0)  */

    /* 
     * B-11: close the CDF file, and free any memory we allocated 
     */

    CloseCDF(id);

    if (numZvars > 0) { 
      free (ttype);
      free (tform);
      free (tunit);      
      free (tzero);
      free (tscale);
      free (newNumDims);
      free (newDimSizes);
      free (tDataType);
      free (tNumElems);
      free (recVarys);
      free (varMaxRec);
      free (varNames);
      free (hduGroup);
      free (varColumn);
    }

    if (numVAttrs > 0) {

      if (attrNums != NULL) free (attrNums);
      if (attrNames != NULL) free (attrNames);
    }

    FindGkey("END");    /* free memory */

  } /* for (i) loop over all CDF's */

  /*
   * B-12: close fits file, end program
   */

  fits_close_file(fptr, &fstatus);
  PrintFITSerror(fstatus, "5.0");

  return EXIT_SUCCESS_;
} /* main() */

/******************************************************************************
* Check whether to skip the previously processed card.
******************************************************************************/
Logical SkipCard(char *keyWord) {

    if (streq(keyWord, "SIMPLE") ||
        streq(keyWord, "XTENSION") ||
        streq(keyWord, "BITPIX") ||
        streq(keyWord, "NAXIS") ||
        streq(keyWord, "PCOUNT") ||
        streq(keyWord, "GCOUNT") ||
        streq(keyWord, "EXTEND") ||
        streq(keyWord, "TTYPE") ||
        streq(keyWord, "TFORM") ||
        streq(keyWord, "TUNIT") ||
        streq(keyWord, "EXTNAME") ||
        streq(keyWord, "TFIELDS"))
      return TRUE;
    else
      return FALSE;

}
 
/******************************************************************************
* Check whether this is a TSCALn/TZEROn/TNULLn card.
******************************************************************************/
Logical CheckScaleZeroNull(char *keyWord, char *keywordValue, char *comment,
                           int keyDataType, void *value,
                           double *tscale, double *tzero) {
    
    if (streq(keyWord, "TSCAL")) {
      int tmpInt;
      sscanf(keyWord, "TSCAL%d", &tmpInt);
      if (keyDataType == TINT32BIT)
        tscale[tmpInt-1] = (double) *(int *) value;
      else if (keyDataType == TFLOAT)
        tscale[tmpInt-1] = (double) *(float *) value;
      else if (keyDataType == TDOUBLE)
        tscale[tmpInt-1] = *(double *) value;
      fits_write_key(fptr, TDOUBLE, keyWord, &tscale[tmpInt-1],
                     "TSCALE", &fstatus);
      return TRUE;
    } else if (streq(keyWord, "TZERO")) {
      int tmpInt;
      sscanf(keyWord, "TZERO%d", &tmpInt);
      if (keyDataType == TINT32BIT)
        tzero[tmpInt-1] = (double) *(int *) value;
      else if (keyDataType == TFLOAT)
        tzero[tmpInt-1] = (double) *(float *) value;
      else if (keyDataType == TDOUBLE)
        tzero[tmpInt-1] = *(double *) value;
      fits_write_key(fptr, TDOUBLE, keyWord, &tzero[tmpInt-1],
                     "TZERO", &fstatus);
      return TRUE;
    } else if (streq(keyWord, "TNULL")) {
      if (keyDataType == TSTRING)
        fits_write_key(fptr, keyDataType, keyWord, keywordValue,
                       comment, &fstatus);
      else
        fits_write_key(fptr, TINT, keyWord, value,
                       comment, &fstatus);
      return TRUE;
    }

    return FALSE;
}

/******************************************************************************
* Handle other keywords that have not yet been processed.
******************************************************************************/
void HandleOtherKeys(char *keyWord, char *keywordValue, char *comment,
                     int keyDataType, void *value, char *floatFormat, 
                     int keyNum) {

    if (keyDataType == TFLOAT || keyDataType == TDOUBLE) {
      /***********************************************************
      * Check how the float/double value(s) should be keyed in
      * to the FITS file.
      ***********************************************************/
      int len1, len2, num;
      char fl[3];
      num = sscanf(floatFormat, "%%%d.%d%s", &len1, &len2, fl);
      if (keyNum == 1) { /* Single number key value */
        if (fl[0] == 'F' || streq(fl, "lF")) {
          if (len2 <= 6)
            fits_write_key_fixflt(fptr, keyWord, *(float *)value,
                                  len2, comment, &fstatus);
          else
            fits_write_key_fixdbl(fptr, keyWord, *(double *)value,
                                  len2, comment, &fstatus);
        } else { /* fl == "E" || "lE" */
          if (len2 <= 6)
            fits_write_key_flt(fptr, keyWord, *(float *)value,
                               len2, comment, &fstatus);
          else
            fits_write_key_dbl(fptr, keyWord, *(double *)value,
                               len2, comment, &fstatus);
        }
      } else { /* Multiple-number key values */
        if (fl[0] == 'F' || streq(fl, "lF")) {
          if (len2 <= 6)
            fits_write_key_fixcmp(fptr, keyWord, value, len2,
                                  comment, &fstatus);
          else
            fits_write_key_fixdblcmp(fptr, keyWord, value, len2,
                                     comment, &fstatus);
        } else {
          if (len2 <= 6)
            fits_write_key_cmp(fptr, keyWord, value, len2,
                               comment, &fstatus);
          else
            fits_write_key_dblcmp(fptr, keyWord, value, len2,
                                  comment, &fstatus);
        }
      }
    } else {
      if (strchr((char *)value, ',') != NULL) {
        char *tmpS = malloc(strlen(value)+3);
        tmpS[0] = '(';
        memcpy(tmpS+1, value, strlen(value));
        tmpS[strlen(value)+1] = ')';
        tmpS[strlen(value)+2] = '\0';
        fits_write_key(fptr, keyDataType, keyWord, tmpS, comment,
                       &fstatus);
      } else
        fits_write_key(fptr, keyDataType, keyWord, value, comment,
                       &fstatus);
    }

}

/******************************************************************************
* Write the variable attribute entry to the HDU as a key.
* TODO: there is a lot of code duplication in here from previous parsing,
*       see e.g. GATTR section
******************************************************************************/
void WriteKey(int hduNum, long dataType, long numElems, char *keyWord, 
              void *entry, char *comment) {

    int hduType, fitsDataType;

    fits_movabs_hdu(fptr, (int) hduNum+1, &hduType, &fstatus);

    fitsDataType = ConvertCDFDataType(dataType);
    if (dataType == CDF_CHAR || dataType == CDF_UCHAR) {
      *(((char *)entry)+numElems) = '\0';
      if (streq(keyWord, "COMMENT") || streq(keyWord, "comment") )
        fits_write_comment(fptr, (char *) entry, &fstatus);
      else if (streq(keyWord, "HISTORY") || streq(keyWord, "history"))
        fits_write_history(fptr, (char *) entry, &fstatus);
      else {
        int ik = strlen((char *)entry);
        int ic = strlen(comment);
        if (ik > 68) {
          int ir = ik - (ik / 68) * 68;
          if ((ir + ic ) <= 65 && (ic < 48)) {
            fits_write_key_longstr(fptr, keyWord, (char *) entry, comment, 
                                   &fstatus);
          } else {
            fits_write_key_longstr(fptr, keyWord, (char *) entry, "", 
                                   &fstatus);
            fits_write_comment(fptr, comment, &fstatus);
          }
        } else {
          if ((ik + ic ) <= 65 && (ic < 48)) {
            fits_write_key_str(fptr, keyWord, (char *) entry, comment, 
                               &fstatus);
          } else {
            fits_write_key_str(fptr, keyWord, (char *) entry, "", &fstatus);
            fits_write_comment(fptr, comment, &fstatus);
          }
        }
      }
    } else if (dataType == CDF_EPOCH) {
      char tmp[ELEN+1];
      encodeEPOCH3(*(double *)entry, tmp);
      fits_write_key(fptr, fitsDataType, keyWord, tmp, comment,
                     &fstatus);
    } else { /* Number data type. */
      if (numElems > 1) {
        int ik;
        int ic = strlen(comment);
        char *tmp = malloc(20 * numElems);
        PrintData(entry, dataType, numElems, tmp);
        ik = strlen(tmp);
        if (ik > 68) {
          int ir = ik - (ik / 68) * 68;
          if ((ir + ic ) <= 65 && (ic < 48)) {
            fits_write_key_longstr(fptr, keyWord, tmp, comment, &fstatus);
          } else {
            fits_write_key_longstr(fptr, keyWord, tmp, "", &fstatus);
            fits_write_comment(fptr, comment, &fstatus);
          }
        } else {
          if ((ik + ic ) <= 65 && (ic < 48)) {
            fits_write_key_str(fptr, keyWord, tmp, comment, &fstatus);
          } else {
            fits_write_key_str(fptr, keyWord, tmp, "", &fstatus);
            fits_write_comment(fptr, comment, &fstatus);
          }
        }
      } else {
        fits_write_key(fptr, fitsDataType, keyWord, entry, comment,
                       &fstatus);

      }
    }
    PrintFITSerror(fstatus, "6.0");

}

/******************************************************************************
* Create FITS HDU(s), based on the number of groups, for variables from a
* native CDF.
******************************************************************************/
void CreateHDUs(int groups, long numZvars, char **ttype, char **tform,
                char **tunit, long *hduGroup, long *varMaxRec) {
                                                                                 
   int i, j, curGrp, numVars;
   long rows;
   char extName[80], keyword[9];

   curGrp = 1;
                                                                                 
   while (curGrp <= groups) {
     char **tmpType, **tmpForm, **tmpUnit;
     int *tmpzVar;
     j = 0;
     numVars = 0;
     for (i = 0; i < numZvars; i++)
       if (hduGroup[i] == curGrp) numVars++;
     tmpType = (char **) malloc(sizeof(char *) * numVars);
     tmpForm = (char **) malloc(sizeof(char *) * numVars);
     tmpUnit = (char **) malloc(sizeof(char *) * numVars);
     tmpzVar = (int *) malloc(sizeof(int) * numVars);
     sprintf(extName, "cdffits%d", curGrp);
     for (i = 0; i < numZvars; i++) {
       if (hduGroup[i] == curGrp) {
         tmpType[j] = (char *) strdup(ttype[i]);
         tmpForm[j] = (char *) strdup(tform[i]);
         tmpUnit[j] = (char *) strdup(tunit[i]);
	 tmpzVar[j] = i+1;
         if (j == 0) rows = varMaxRec[i];
         j++;
       }
     }
     fits_create_tbl(fptr, BINARY_TBL, rows, j,
                     tmpType, tmpForm, tmpUnit, extName, &fstatus);
     PrintFITSerror(fstatus, "3.08");
#if 0
     /* print out the ZVARn keyword -- probably not needed for most humans ? */
     for (i=0; i<j; i++) {
       sprintf(keyword,"ZVAR%d",i+1);
       fits_write_key(fptr,TINT,keyword,&tmpzVar[i],"Original zVar 'column'",
                      &fstatus);
     }
#endif
     PrintFITSerror(fstatus, "3.09");
                                                                                 
     curGrp++;
   }

}

/******************************************************************************
 * This function writes a set of comments, related to CDF, to a FITS file.
 ******************************************************************************/
void WriteID(void) {

  char comment[80], keywordValue[80];
  int timeref;

  sprintf(comment," cdf-to-fits translator: %s  (FORMAT version %d)",
	  c2f_revision,cdf_fits);
  fits_write_comment(fptr, comment, &fstatus);
/*
  sprintf(comment," Special cdf-to-fits keywords:");
  fits_write_comment(fptr, comment, &fstatus);

  sprintf(comment,"    GATTR[][]     Global Attributes  (in pHDU)");
  sprintf(comment,"    Global Attributes  (in pHDU)");
  fits_write_comment(fptr, comment, &fstatus);

  sprintf(comment,"    VATTR[][]     Variable Attributes (in extension HDUs)");
  sprintf(comment,"    Variable Attributes (in extension HDUs)");
  fits_write_comment(fptr, comment, &fstatus);
*/
  fits_get_system_time(keywordValue, &timeref, &fstatus);   
  sprintf(comment,  " Date: %s (cdf-to-fits file creation date in %s)", 
          keywordValue, timeref ? "Localtime" : "UTC/GMT");
/*  fits_write_key_longstr(fptr, "DATE", keywordValue, comment, &fstatus); */
  fits_write_comment(fptr, comment, &fstatus);

  PrintFITSerror(fstatus, "3.10");
}

/******************************************************************************
 * FindGkey: maintain a linked list of all GATTR keywords; if they are 
 *           duplicated, report the index it should receive. Returns 0 if not 
 *           duplicated yet.
 *
 *     if "END" is given as the keyword, it will free the list from global space
 *     for the next HDU (normally GATTR's are only written in the pHDU, so this
 *     should actually "never" happen).
 ******************************************************************************/

int FindGkey(char *keyword) {
  gkey *g;
  char *cp;
  int n;

  if (streq(keyword,"END")) {      /* free the list */
    if (Gkeys) {
      int i;
      gkey *f;

      g = Gkeys;
      f = g->next;
      while (f) {
	free(g);
	g = f;
	f = g->next;
      }
      Gkeys = NULL;
      return 0;
    }
  }

  if (*keyword == ' ') return 0;    /* ignore blank keywords */

  for (cp=keyword; *cp; cp++)
    if (islower(*cp)) *cp = toupper(*cp);

  if (Gkeys == NULL) {
    Gkeys = g = (gkey *) malloc(sizeof(gkey));
    strcpy(g->key,keyword);
    g->n = 0;
    g->next = NULL;
    return 0;
  }

  for (g=Gkeys, n=0; ; g = g->next, n++) {
    if (streq(keyword,g->key)) {
      g->n++;
      return g->n;
    }
    if (g->next == NULL) break;
  }

  g->next = (gkey *) malloc(sizeof(gkey));
  g = g->next;
  strcpy(g->key,keyword);
  g->n = 0;
  g->next = NULL;
  
  return 0;
}

/******************************************************************************
 * This function compare two strings. 
 ******************************************************************************/

int streq(char *a, char *b) {
  return !strncmp(a, b, strlen(b));
}

/******************************************************************************
 * This function converts a (signed) INT1 to a UINT1 array, such that
 * with TSCAL=1 TZERO=-128 the original INT1 values are recovered
 ******************************************************************************/
 
void convert_ubytes(int n, char *data) {
  while (n > 0)
    data[--n] ^= 0x80;
}

/******************************************************************************
 * This function changes the keyword for an attribute so it will not duplicate.
 * It involves attributes starting with "DEPEND_" or "LABL_PTR_".
 * For "DEPEND_" case, it would change the keyword to the form of "DPxx_".
 * For "LABL_PTR_", it changes to "LPTxx_".
 ******************************************************************************/
                                                                                 
void ModifyDupKey(char *attrName, char *keyword) {
  int ix;
 
  if (streq(attrName, "DEPEND_")) {
    sscanf(attrName, "DEPEND_%d", &ix);
    sprintf(keyword, "DP%02d_", ix);
    *(((char *) keyword)+5) = (char) 0;
  } else {
    sscanf(attrName, "LABL_PTR_%d", &ix);
    sprintf(keyword, "LPT%02d_", ix);
    *(((char *) keyword)+6) = (char) 0;
  }
}
                                                                                 
/******************************************************************************
 * This function appends an item to the keyword. It makes sure that the
 * appended keyword is less than 8 char long. Otherwise, the original keyword
 * is cut from the tail before the item is appended.
 ******************************************************************************/

void appendKey(char *srcKey, char *item) {

    int ik = strlen(srcKey);
    int ic = strlen(item);
    int ilen = ik + ic;
    if (ilen <= 8) {
      sprintf(&srcKey[ik], "%s", item);
    } else {
      if (ic < 10)
        sprintf(&srcKey[7],"%s",item);
      else if (ic < 100)
        sprintf(&srcKey[6],"%s",item);
      else if (ic < 1000)
        sprintf(&srcKey[5],"%s",item);
    }
}

/******************************************************************************
 * This function appends an integer to the keyword. It makes sure that the
 * appended keyword is less than 8 char long. Otherwise, the original keyword
 * is cut from the tail before the integer is appended.
 ******************************************************************************/

void appendNumKey(char *srcKey, int item) {

    int ik = strlen(srcKey);
    int ic = 0; 
    int ilen;
    if (item < 10) ic = 1;
    else if (item < 100) ic = 2;
    else if (item < 1000) ic = 3; 
    ilen = ik + ic;
    if (ilen <= 8) {
      sprintf(&srcKey[ik], "%d", item);
    } else {
      if (ic == 1)
        sprintf(&srcKey[7],"%d",item);
      else if (ic == 2)
        sprintf(&srcKey[6],"%d",item);
      else if (ic == 3)
        sprintf(&srcKey[5],"%d",item);
    }
}

/******************************************************************************
* usage.
******************************************************************************/

void usage(void) {
  printf("\n");
  printf("Usage: cdf-to-fits [-Options] <FITS_Name> <CDF_Name(s)>  or\n");
  printf("       cdf-to-fits [-Options] <CDF_Name>\n");
  printf("\n");
/*  printf("%s\n",c2f_revision); */
/*  printf("\n");                */
  printf("Description:\n");
  printf("  This program converts one or more native CDF files into a single FITS\n");
  printf("  file with one (or possibly multiple) BINTABLE extensions\n");
  printf("  All CDF files are assembled (in the order given on the command line)\n");
  printf("  (FITS originating) non-native CDF files support a number of FITS extensions:\n");
  printf("  IMAGE, TABLE and BINTABLE, though one has to be careful in what order the \n");
  printf("  CDF files are given on the command line.\n");
  printf("\n");
  printf("Options:\n");
  printf("-help      Displays the help information.\n");
  printf("-debug     Show lots of debug info on contents of CDF file(s)\n");
  printf("-delete    Delete the FITS file if it already exists.\n");
  printf("-map <mapfile>  Use an alternate CDF<->FITS keyword mapping file.\n");
  printf("-show      Show the mapping file of FITS to/from CDF keywords.\n");
  printf("           If the map option is provided ahead of the show option, the\n");
  printf("           alternate mapping file is shown.\n");
  printf("\n");
  printf("Examples of usage:\n");
  printf("1) cdf-to-fits a.cdf \n");
  printf("   will convert a single 'a.cdf' into 'a.fits'. \n");
  printf("2) cdf-to-fits my.fits a.cdf \n");
  printf("   will convert a single 'a.cdf' into 'my.fits'. \n");
  printf("3) cdf-to-fits a.fits a*.cdf \n");
  printf("   will convert many 'a*.cdf' files into single (multiHDU) 'a.fits'. \n");
  printf("4) cdf-to-fits -show > my.map\n");
  printf("   vi my.map                       <-- add or delete mappings.\n");
  printf("   cdf-to-fits -map my.map  a.cdf\n");
  printf("   will convert 'a.cdf' into 'a.fits' using the mapping\n");
  printf("   information in the my.map file.\n\n");
}

