/* #define CDFMISC */
#include "cdffits.h"
#include "fitsio.h"

#define MAX_LINES_WHEN_PAGING         22
#define MAX_PROGRAM_NAME_LEN            25

Logical pagingOn = TRUE;        /* So online help will be paged. */
int soLineCount = 0;
char pgmName[MAX_PROGRAM_NAME_LEN+1] = "";

static QOP *ParseSlashStyle PROTOARGs((
  int argc, char *argv[], char *validQuals[], int optRequired[]
));
static QOP *ParseHyphenStyle PROTOARGs((
  int argc, char *argv[], char *validQuals[], int optRequired[]
));
static void FreeArgvT PROTOARGs((int lastArgN, char *argvT[]));
static QOP *BuildQOP PROTOARGs((
  int Nparms, char *parms[], char *validQuals[], int *qualEntered,
  char *qualOpt[]
));

void BreakdownCard(char *, long, char *, char *, char *, Logical *);
/******************************************************************************
* WriteStringStdOut.
******************************************************************************/

void WriteStringStdOut (string, length)
char *string;
size_t length;
{
  fprintf (stdout, "%*.*s", (int) length, (int) length, string);
  return;
}

/******************************************************************************
* strchrIgCase.
******************************************************************************/

int strncmpIgCase (string1, string2, count)
char *string1;
char *string2;
size_t count;
{
  int string1L = (int) strlen(string1);
  int string2L = (int) strlen(string2);
  int maxL, i;
  maxL = MAXIMUM(string1L,string2L);
  maxL = MINIMUM(maxL,(int)count);
  for (i = 0; i < maxL; i++) {
     if (MakeLower(string1[i]) != MakeLower(string2[i])) {
       return ((int) (string1[i] - string2[i]));
     }
  }
  return 0;
}

/******************************************************************************
* ReadCharStdIn.
******************************************************************************/

#if !defined(mac)
void ReadCharStdIn (key)
char *key;
{
  *key = (char) fgetc (stdin);
  return;
}
#endif

/******************************************************************************
* WriteOut.
* Returns number of characters output.
******************************************************************************/

int WriteOut (fp, text)
FILE *fp;
char *text;
{
  /****************************************************************************
  * If the output is not going to `stdout', then assume it is going to a file
  * (and ignore paging if requested).
  ****************************************************************************/
  if (fp != stdout)
    fprintf (fp, "%s", text);
  else {
    /**************************************************************************
    * The output is going to `stdout'.  Assume that `stdout' has not been
    * redirected away from the user's terminal (to a file).  If `stdout' has
    * been redirected to a file, then hopefully the user had enough sense not
    * to request paging.
    **************************************************************************/
#if defined(mac) || defined(win32)
#if defined(mac)
    WriteOutMacSO (text);
#else
#if defined(SO)             /* Jeff doesn't like this.  This function should */
                            /* not be compiled if an FSI application. */
        WriteOutWin32 (text);
#endif
#endif
#else
    char *ptr = text, *ptrNL; size_t len;
    /**************************************************************************
    * If paging is on, first prompt for `more...' if the end of the standard
    * output screen has been reached.
    **************************************************************************/
    for (;;) {

       /***********************************************************************
       * If paging on, check if the maximum number of lines have been written.
       ***********************************************************************/
       if (pagingOn) {
         if (soLineCount == MAX_LINES_WHEN_PAGING) {
           char key = NUL; static char prompt[] = "\nEnter RETURN for more...";
           WriteStringStdOut (prompt, strlen(prompt));
           ReadCharStdIn (&key);
           if (key != '\n') pagingOn = FALSE;
           WriteStringStdOut ("\n\n", 2);
           soLineCount = 0;
         }
       }
       /***********************************************************************
       * Write the string up to the next newline character.  If there aren't
       * any(more) newline characters, write the rest of the string.
       ***********************************************************************/
       ptrNL = (char *) strchr (ptr, Nl);
       if (ptrNL == NULL) {
         len = strlen (ptr);
         WriteStringStdOut (ptr, len);
         break;
       }
       else {
         len = (int) (ptrNL - ptr + 1);
         WriteStringStdOut (ptr, len);
         if (pagingOn) soLineCount++;
         ptr = ptrNL + 1;
         if (*ptr == NUL) break;
       }
    }
    fflush (stdout);
#endif
  }
  return strlen(text);
}

/******************************************************************************
* FatalError.
******************************************************************************/

void FatalError (message)
char *message;
{
#if defined(mac)
  MacMessageDialog ("FATAL ERROR!", message);
#else
#if defined(win32)
  WinMessageDialog ("FATAL ERROR!", message);
#else
  WriteOut (stdout, "FATAL ERROR!  ");
  WriteOut (stdout, message);
  WriteOut (stdout, "\n");
#endif
#endif
  ExitBAD;
}

/******************************************************************************
* DisplayError.
******************************************************************************/

void DisplayError (message)
char *message;
{
#if defined(mac)
  MacMessageDialog ("Error!", message);
#else
#if defined(win32)
  WinMessageDialog ("Error!", message);
#else
  WriteOut (stdout, "Error!  ");
  WriteOut (stdout, message);
  WriteOut (stdout, "\n");
#endif
#endif
  return;
}

/******************************************************************************
* Qop.
******************************************************************************/

QOP *Qop (argc, argv, validQuals, optRequired)
int argc;
char *argv[];
char *validQuals[];
int optRequired[];
{
  /****************************************************************************
  * Check to see if a style definition has been specified.  (A slash or hyphen
  * as the first command line argument.)
  ****************************************************************************/
  if (argc > 1) {
    if (!strcmp(argv[1],"/")) return ParseSlashStyle (argc - 1, &argv[1],
                                                      validQuals, optRequired);
    if (!strcmp(argv[1],"-")) return ParseHyphenStyle (argc - 1, &argv[1],
                                                       validQuals,optRequired);
  }
  /****************************************************************************
  * No style definition, parse according to default for operating system
  * being used.
  ****************************************************************************/
#if defined(vms)
  return ParseSlashStyle (argc, argv, validQuals, optRequired);
#else
  return ParseHyphenStyle (argc, argv, validQuals, optRequired);
#endif
}

/******************************************************************************
* ParseSlashStyle.
* Assume qualifiers begin with a slash (/).
******************************************************************************/

static QOP *ParseSlashStyle (argc, argv, validQuals, optRequired)
int argc;
char *argv[];
char *validQuals[];
int optRequired[];
{
  QOP *qop;                     /* Pointer to QOP structure. */
  int argN,                     /* Argument number. */
      qualNv,                   /* Qualifier number (from list of valid
                                   qualifiers). */
      qualNe,                   /* Qualifier number (from list of qualifiers
                                   entered). */
      Nparms = 0,               /* Number of parameters entered. */
      Nquals = 0,               /* Number of qualifiers entered. */
      matchQualNv,              /* Matching qualifier number (from list of
                                   valid qualifiers). */
      qualEntered[QOP_MAX_QUALs];
                                /* TRUE if the corresponding qualifier was
                                   entered. */
  char *parms[QOP_MAX_PARMs],   /* Pointers to the parameters entered. */
       *qualOpt[QOP_MAX_QUALs], /* Pointers to the options of the qualifiers
                                   entered (indexed to correspond to the list
                                   of valid qualifiers). */
       *argvT[QOP_MAX_ARGVs],   /* Pointers to temporary `argv' strings. */
       *quals[QOP_MAX_QUALs],   /* Pointers to the qualifiers entered (in the
                                   order they were entered). */
       *opts[QOP_MAX_QUALs],    /* Pointers to the options entered (in the
                                   order they were entered). */
       *ptr;                    /* Pointer into character string. */
  enum modes { UNKNOWN_,
               START_OF_PARM_,
               FIND_PARM_END_,
               START_OF_QUAL_,
               FIND_QUAL_END_,
               START_OF_OPT_,
               FIND_OPT_END_ } mode;  /* The current mode when scanning the
                                         list of command line arguments. */
  /****************************************************************************
  * Determine parameters and qualifiers/options.
  ****************************************************************************/
  for (argN = 1; argN < argc; argN++) {
     char nChars = strlen (argv[argN]);
     argvT[argN] = (char *) cdf_AllocateMemory (nChars + 1, FatalError);
     strcpyX (argvT[argN], argv[argN], nChars);
     mode = UNKNOWN_;
     for (ptr = argvT[argN]; *ptr != NUL;) {
        switch (mode) {
          case UNKNOWN_:
            if (*ptr == '/') {
              mode = START_OF_QUAL_;
              ptr++;
            }
            else
              mode = START_OF_PARM_;
            break;
          case START_OF_PARM_:
            Nparms++;
            if (Nparms <= QOP_MAX_PARMs)
              parms[Nparms-1] = ptr;
            else {
              DisplayError ("Too many parameters.");
              FreeArgvT (argN, argvT);
              return NULL;
            }
            mode = FIND_PARM_END_;
            ptr++;
            break;
          case FIND_PARM_END_:
            if (*ptr == '/')
              if (*(ptr+1) == '/')
                memmove (ptr, ptr+1, strlen(ptr+1) + 1);
              else {
                *ptr = NUL;
                mode = START_OF_QUAL_;
              }
            ptr++;
            break;
          case START_OF_QUAL_:
            if (*ptr == '/')
              mode = START_OF_PARM_;
            else {
              Nquals++;
              if (Nquals <= QOP_MAX_QUALs)
                quals[Nquals-1] = ptr;
              else {
                DisplayError ("Too many qualifiers.");
                FreeArgvT (argN, argvT);
                return NULL;
              }
              mode = FIND_QUAL_END_;
              ptr++;
            }
            break;
          case FIND_QUAL_END_:
            switch (*ptr) {
              case '/':
                *ptr = NUL;
                opts[Nquals-1] = NULL;
                mode = START_OF_QUAL_;
                break;
              case '=':
                *ptr = NUL;
                mode = START_OF_OPT_;
                break;
            }
            ptr++;
            break;
          case START_OF_OPT_:
            if (*ptr == '/')
              if (*(ptr+1) == '/') {
                opts[Nquals-1] = ptr + 1;
                ptr += 2;
                mode = FIND_OPT_END_;
              }
              else {
                char tempS[MAX_MESSAGE_TEXT_LEN+1];
                sprintf (tempS, "Missing option for qualifier `/%s'.",
                                 quals[Nquals-1]);
                DisplayError (tempS);
                FreeArgvT (argN, argvT);
                return NULL;
              }
            else {
              opts[Nquals-1] = ptr;
              mode = FIND_OPT_END_;
              ptr++;
            }
            break;
          case FIND_OPT_END_:
            if (*ptr == '/')
              if (*(ptr+1) == '/')
                memmove (ptr, ptr+1, strlen(ptr+1) + 1);
              else {
                *ptr = NUL;
                mode = START_OF_QUAL_;
              }
            ptr++;
            break;
        }
     }
     switch (mode) {
       case UNKNOWN_:
         /* Logic error. */
         break;
       case START_OF_PARM_:
         /* Logic error. */
         break;
       case START_OF_QUAL_:
         DisplayError ("Missing qualifier body (/).");
         FreeArgvT (argN, argvT);
         return NULL;
       case FIND_QUAL_END_:
         /* Qualifier already NUL-terminated */
         opts[Nquals-1] = NULL;
         break;
       case FIND_PARM_END_:
         /* Parameter already NUL-terminated */
         break;
       case START_OF_OPT_: {
         char tempS[MAX_MESSAGE_TEXT_LEN+1];
         sprintf (tempS,"Missing option for qualifier `/%s'.",quals[Nquals-1]);
         DisplayError (tempS);
         FreeArgvT (argN, argvT);
         return NULL;
       }
       case FIND_OPT_END_:
         /* Option already NUL-terminated */
         break;
     }
  }
  /****************************************************************************
  * Set up to scan command line arguments (while checking number of valid
  * qualifiers).
  ****************************************************************************/
  for (qualNv = 0; validQuals[qualNv] != NULL; qualNv++) {
     if (qualNv < QOP_MAX_QUALs) {
       qualEntered[qualNv] = FALSE;
       qualOpt[qualNv] = NULL;
     }
     else {
       WriteOut (stdout, "Too many valid qualifiers.\n");
       FreeArgvT (argc - 1, argvT);
       return NULL;
     }
  }
  /****************************************************************************
  * Determine which qualifiers/options were entered.
  ****************************************************************************/
  for (qualNe = 0; qualNe < Nquals; qualNe++) {
     matchQualNv = FindUniqueMatch (quals[qualNe], validQuals);
     switch (matchQualNv) {
       case NOMATCH: {
         char tempS[MAX_MESSAGE_TEXT_LEN+1];
         sprintf (tempS, "Unknown qualifier (/%s).", quals[qualNe]);
         DisplayError (tempS);
         FreeArgvT (argc - 1, argvT);
         return NULL;
       }
       case MATCHES: {
         char tempS[MAX_MESSAGE_TEXT_LEN+1];
         sprintf (tempS, "Ambiguous qualifier (/%s).", quals[qualNe]);
         DisplayError (tempS);
         FreeArgvT (argc - 1, argvT);
         return NULL;
       }
       default: {
         if (optRequired[matchQualNv] && opts[qualNe] == NULL) {
           char tempS[MAX_MESSAGE_TEXT_LEN+1];
           sprintf (tempS, "Option missing for qualifier `/%s'.",
                    validQuals[matchQualNv]);
           DisplayError (tempS);
           FreeArgvT (argc - 1, argvT);
           return NULL;
         }
         if (!optRequired[matchQualNv] && opts[qualNe] != NULL) {
           char tempS[MAX_MESSAGE_TEXT_LEN+1];
           sprintf (tempS, "Extraneous option for qualifier `/%s'.",
                    validQuals[matchQualNv]);
           DisplayError (tempS);
           FreeArgvT (argc - 1, argvT);
           return NULL;
         }
         if (qualEntered[matchQualNv]) {
           char tempS[MAX_MESSAGE_TEXT_LEN+1];
           sprintf (tempS, "Redundant qualifier (/%s).",
                    validQuals[matchQualNv]);
           DisplayError (tempS);
           FreeArgvT (argc - 1, argvT);
           return NULL;
         }
         qualEntered[matchQualNv] = TRUE;
         qualOpt[matchQualNv] = opts[qualNe];
         break;
       }
     }
  }
  /****************************************************************************
  * Build QOP structure.
  ****************************************************************************/
  qop = BuildQOP (Nparms, parms, validQuals, qualEntered, qualOpt);
  /****************************************************************************
  * Free memory used and return QOP structure.
  ****************************************************************************/
  FreeArgvT (argc - 1, argvT);
  return qop;
}

/******************************************************************************
* ParseHyphenStyle.
* Assume qualifiers begin with a hyphen (-).
******************************************************************************/

static QOP *ParseHyphenStyle (argc, argv, validQuals, optRequired)
int argc;
char *argv[];
char *validQuals[];
int optRequired[];
{
  QOP *qop;                     /* Pointer to a QOP structure. */
  int argN,                     /* Command line argument number. */
      qualNv,                   /* Qualifier number from `valids' list. */
      Nparms = 0,               /* Number of parameters entered. */
      matchQualNv,              /* Matching qualifier number (from list of
                                   valid qualifiers). */
      qualEntered[QOP_MAX_QUALs];
                                /* TRUE if the corresponding qualifier was
                                   entered. */
  char *parms[QOP_MAX_PARMs],   /* Pointers to the parameters entered. */
       *qualOpt[QOP_MAX_QUALs]; /* Pointers to the options of the qualifiers
                                   entered (indexed to correspond to the list
                                   of valid qualifiers). */
  /****************************************************************************
  * Set up to scan command line arguments (while checking number of valid
  * qualifiers).
  ****************************************************************************/
  for (qualNv = 0; validQuals[qualNv] != NULL; qualNv++) {
     if (qualNv < QOP_MAX_QUALs) {
       qualEntered[qualNv] = FALSE;
       qualOpt[qualNv] = NULL;
     }
     else {
       DisplayError ("Too many valid qualifiers.");
       return NULL;
     }
  }
  /****************************************************************************
  * Determine which qualifiers were entered (the rest being parameters).
  ****************************************************************************/
  for (argN = 1; argN < argc; argN++) {
     if (argv[argN][0] == '-' && argv[argN][1] != '-') {
       matchQualNv = FindUniqueMatch (&argv[argN][1], validQuals);
       switch (matchQualNv) {
         case NOMATCH: {
           char tempS[MAX_MESSAGE_TEXT_LEN+1];
           sprintf (tempS, "Unknown qualifier (-%s).", &argv[argN][1]);
           DisplayError (tempS);
           return NULL;
         }
         case MATCHES: {
           char tempS[MAX_MESSAGE_TEXT_LEN+1];
           sprintf (tempS, "Ambiguous qualifier (-%s).", &argv[argN][1]);
           DisplayError (tempS);
           return NULL;
         }
         default: {
           if (qualEntered[matchQualNv]) {
             char tempS[MAX_MESSAGE_TEXT_LEN+1];
             sprintf (tempS, "Redundant qualifier (-%s).",
                      validQuals[matchQualNv]);
             DisplayError (tempS);
             return NULL;
           }
           else
             qualEntered[matchQualNv] = TRUE;
           if (optRequired[matchQualNv]) {
             if (argN+1 < argc) {
               qualOpt[matchQualNv] = argv[argN+1];
               argN++;
             }
             else {
               char tempS[MAX_MESSAGE_TEXT_LEN+1];
               sprintf (tempS, "Option missing for qualifier `-%s'.",
                        validQuals[matchQualNv]);
               DisplayError (tempS);
               return NULL;
             }
           }
         }
       }
     }
     else {
       Nparms++;
       if (Nparms <= QOP_MAX_PARMs)
         parms[Nparms-1] = BOO(strncmp(argv[argN], "--", 2),
                               argv[argN], argv[argN] + 1);
       else {
         DisplayError ("Too many parameters.");
         return NULL;
       }
     }
  }
  /****************************************************************************
  * Build QOP structure.
  ****************************************************************************/
  qop = BuildQOP (Nparms, parms, validQuals, qualEntered, qualOpt);
  /****************************************************************************
  * Return QOP structure.
  ****************************************************************************/
  return qop;
}

/******************************************************************************
* BuildQOP.
******************************************************************************/

static QOP *BuildQOP (Nparms, parms, validQuals, qualEntered, qualOpt)
int Nparms;
char *parms[];
char *validQuals[];
int *qualEntered;
char *qualOpt[];
{
  QOP *qop;             /* Pointer to a QOP structure. */
  int parmN,            /* Parameter number. */
      qualNv,           /* Qualifier number (in list of valid qualifiers. */
      strCount = 0;     /* Number of characters in the strings to be contained
                           in the QOP structure. */
  char *strOffset;      /* Offset into the QOP structure of a string
                           (parameter/option). */
  for (parmN = 0; parmN < Nparms; parmN++)
     strCount += strlen(parms[parmN]) + 1;
  for (qualNv = 0; validQuals[qualNv] != NULL; qualNv++)
     if (qualEntered[qualNv])
       if (qualOpt[qualNv] != NULL) strCount += strlen(qualOpt[qualNv]) + 1;
  qop = (QOP *) cdf_AllocateMemory (sizeof(QOP) + strCount,
                                FatalError);
  strOffset = (char *) qop + sizeof(QOP);
  qop->Nparms = Nparms;
  for (parmN = 0; parmN < Nparms; parmN++) {
     qop->parms[parmN] = strOffset;
     strOffset += strlen(parms[parmN]) + 1;
     strcpyX (qop->parms[parmN], parms[parmN], 0);
  }
  for (qualNv = 0; validQuals[qualNv] != NULL; qualNv++) {
     qop->qualEntered[qualNv] = qualEntered[qualNv];
     if (qualEntered[qualNv]) {
       if (qualOpt[qualNv] != NULL) {
         qop->qualOpt[qualNv] = strOffset;
         strOffset += strlen(qualOpt[qualNv]) + 1;
         strcpyX (qop->qualOpt[qualNv], qualOpt[qualNv], 0);
       }
       else
         qop->qualOpt[qualNv] = NULL;
     }
  }
  return qop;
}

/******************************************************************************
* FreeArgvT.
******************************************************************************/

static void FreeArgvT (lastArgN, argvT)
int lastArgN;
char *argvT[];
{
  int argN;
  for (argN = 1; argN <= lastArgN; argN++) cdf_FreeMemory (argvT[argN],
                                                       FatalError);
  return;
}

/******************************************************************************
* FindUniqueMatch.
* Returns: NOMATCH if a match was not found,
*          MATCHES if more than one match,
*          `index' if only one match.
******************************************************************************/

int FindUniqueMatch (target, strings)
char *target;           /* Searching for this string. */
char *strings[];        /* Terminated by a NULL pointer. */
{
  int match, beyond; size_t targetL = strlen(target);
  for (match = 0; strings[match] != NULL; match++) {
     if (!strncmpIgCase(target,strings[match],targetL)) {
       for (beyond = match + 1; strings[beyond] != NULL; beyond++) {
          if (!strncmpIgCase(target,strings[beyond],targetL)) return MATCHES;
       }
       return match;
     }
  }
  return NOMATCH;
}

/******************************************************************************
* TFqualifier.
******************************************************************************/

Logical TFqualifier (qop, variable, Tx, Fx, defaultTF, conflictText)
QOP *qop;
Logical *variable;
int Tx;
int Fx;
Logical defaultTF;
char *conflictText;
{
  int count = 0;
  if (qop->qualEntered[Tx]) count++;
  if (qop->qualEntered[Fx]) count++;
  switch (count) {
    case 0:
      *variable = defaultTF;
      return TRUE;
    case 1:
      *variable = (qop->qualEntered[Tx] ? TRUE : FALSE);
      return TRUE;
    default: {
      char tempS[MAX_MESSAGE_TEXT_LEN+1];
      sprintf (tempS, "Conflicting qualifiers (%s & no%s).",
               conflictText, conflictText);
      DisplayError (tempS);
      return FALSE;
    }
  }
}
/******************************************************************************
* DataTypeToken.
*   Returns address of character string for data type.
******************************************************************************/

char *DataTypeToken (dataType)
long dataType;
{
  switch (dataType) {
    case CDF_BYTE: return "CDF_BYTE";
    case CDF_INT1: return "CDF_INT1";
    case CDF_INT2: return "CDF_INT2";
    case CDF_INT4: return "CDF_INT4";
    case CDF_UINT1: return "CDF_UINT1";
    case CDF_UINT2: return "CDF_UINT2";
    case CDF_UINT4: return "CDF_UINT4";
    case CDF_REAL4: return "CDF_REAL4";
    case CDF_REAL8: return "CDF_REAL8";
    case CDF_FLOAT: return "CDF_FLOAT";
    case CDF_DOUBLE: return "CDF_DOUBLE";
    case CDF_EPOCH: return "CDF_EPOCH";
    case CDF_CHAR: return "CDF_CHAR";
    case CDF_UCHAR: return "CDF_UCHAR";
    default: return "?";
  }
}

/******************************************************************************
* MajorityToken.
*   Returns address of character string for majority.
******************************************************************************/

char *MajorityToken (majority)
long majority;
{
  switch (majority) {
    case ROW_MAJOR: return "ROW_MAJOR";
    case COLUMN_MAJOR: return "COLUMN_MAJOR";
    default: return "?";
  }
}

/******************************************************************************
* ConvertFormat.
*   Convert the format to Fortran-style if a C-style is passed. Otherwise, 
*   return as is or a NULL. 
******************************************************************************/

Logical ConvertFormat (char *format, long dataType, long numElemens,
                       char *Fformat) {

    if (format == NULL)
      return FALSE;
    else 
      if ((char *) strchr(format,'%') == NULL) { /* A Fortran format assumed. */
        memcpy(Fformat, format, strlen(format));
        return TRUE;
      } else { /* A C format is detected. Convert it to corresponding Fortran.*/
        if (CtoFformat(format, Fformat, dataType, numElemens))
          return TRUE;
        else
          return FALSE;
      }
}

/******************************************************************************
* CtoFformat.
* Returns TRUE if valid Fortran format specification is returned.
******************************************************************************/

Logical CtoFformat (char *Cformat, char *Fformat, long dataType, 
                    long numElems) {
  int nFound, wpFound;
  int w, p, d;
  char m, cv, fv;
  int loc, len, iw, ip;

  w = p = d = iw = ip = 0;
  m = cv = fv = ' ';
  /****************************************************************************
  * Change `Cformat' to point to first non-blank, non-digit, non-`('
  * character.  
  ****************************************************************************/
  while (*Cformat != NUL && Spacing(*Cformat)) Cformat++;
  if (*Cformat == NUL) return FALSE;
  /****************************************************************************
  * Encode Fortran format specification.
  * The C-style format: %<flags><width><.prec><modifier><conversion>
  ****************************************************************************/
  len = strlen(Cformat);
  loc = 1;
  /****************************************************************************
  * Check flags.
  ****************************************************************************/
  if (Cformat[loc] == '-' || Cformat[loc] == '+' || Cformat[loc] == '#') {
    loc++;
    len--;
  }
  if (Cformat[loc] == '0') {
    loc++;
    len--;
  }
  /****************************************************************************
  * Check width and precision.
  ****************************************************************************/
  if (Cformat[loc] == '.') 
    wpFound = sscanf (&Cformat[loc], ".%d", &w);
  else
    wpFound = sscanf (&Cformat[loc], "%d.%d", &w, &p);
  switch (wpFound) {
      case 1:
	 if (w < 10) {iw = 1; loc++;}
         else if (w < 100) {iw = 2; loc += 2;}
         else if (w < 1000) {iw = 3; loc += 3;}
         else if (w < 10000) {iw = 4; loc += 4;}
         break;
      case 2:
         if (w < 10) {iw = 1; loc++;}
         else if (w < 100) {iw = 2; loc += 2;}
         else if (w < 1000) {iw = 3; loc += 3;}
         else if (w < 10000) {iw = 4; loc += 4;}
         loc++;
         if (p < 10) {ip = 1; loc++;}
         else if (p < 100) {ip = 2; loc += 2;}
         else if (p < 1000) {ip = 3; loc += 3;}
         else if (p < 10000) {ip = 4; loc += 4;}
         break;
      default:
         break;
  }
  
  if (loc == (len - 2)) { 
     /*************************************************************************
     * Check modifier.
     *************************************************************************/
     nFound = sscanf (&Cformat[loc], "%c", &m);
     if (nFound > 0) loc++;
  }
  /****************************************************************************
  * Check conversion.
  ****************************************************************************/
  nFound = sscanf (&Cformat[loc], "%c", &cv);

  switch (cv) {
    case 'u':
	fv = 'I';
	break;
    case 'd':
        fv = 'I';
        break;
    case 'o':
	fv = 'O';
	break;
    case 'i':
	fv = 'I';
	break;
    case 'x':
    case 'X':
        fv = 'Z';
        break;
    case 'e':
    case 'E':
        fv = 'D';
        break;
    case 'f':
        fv = 'F';
        break;
    case 'g':
    case 'G':
        fv = 'G';
        break;
    case 'c': 
        fv = 'A';
        break;
    case 's':
        fv = 'A';
        break;
    default:
        break;
  }

  if (fv == ' ') 
    return FALSE;
  else {
    Fformat[0] = fv;
    if (wpFound > 0) {
      sprintf(Fformat+1, "%d", w);
      if (wpFound > 1) {
        Fformat[1+iw] = '.';
        sprintf(Fformat+1+iw+1, "%d", p);
        Fformat[1+iw+ip+1] = (char) 0;
      } else
        Fformat[1+iw] = (char) 0;
    } else {
      if (numElems == 1)
        Fformat[1] = (char) 0;
      else {
        sprintf(Fformat+1, "%ld", numElems);
        if (numElems < 10) Fformat[2] = (char) 0;
        else if (numElems < 100) Fformat[3] = (char) 0;
        else if (numElems < 1000) Fformat[4] = (char) 0;
        else if (numElems < 10000) Fformat[5] = (char) 0;
      }
    }
    return TRUE;
  } 
}

/******************************************************************************
* WhichDataType.
******************************************************************************/

long WhichDataType (token)
char *token;
{
  if (!strncmp(token,"BYTE",4)) return CDF_BYTE;
  if (!strncmp(token,"INT1",4)) return CDF_INT1;
  if (!strncmp(token,"UINT1",5)) return CDF_UINT1;
  if (!strncmp(token,"INT2",4)) return CDF_INT2;
  if (!strncmp(token,"UINT2",5)) return CDF_UINT2;
  if (!strncmp(token,"INT4",4)) return CDF_INT4;
  if (!strncmp(token,"UINT4",5)) return CDF_UINT4;
  if (!strncmp(token,"REAL4",5)) return CDF_REAL4;
  if (!strncmp(token,"FLOAT",5)) return CDF_FLOAT;
  if (!strncmp(token,"REAL8",5)) return CDF_REAL8;
  if (!strncmp(token,"DOUBLE",6)) return CDF_DOUBLE;
  if (!strncmp(token,"CHAR",4)) return CDF_CHAR;
  if (!strncmp(token,"UCHAR",5)) return CDF_UCHAR;
  if (!strncmp(token,"EPOCH",5)) return CDF_EPOCH;
  if (!strncmp(token,"CDF_BYTE",8)) return CDF_BYTE;
  if (!strncmp(token,"CDF_INT1",8)) return CDF_INT1;
  if (!strncmp(token,"CDF_UINT1",9)) return CDF_UINT1;
  if (!strncmp(token,"CDF_INT2",8)) return CDF_INT2;
  if (!strncmp(token,"CDF_UINT2",9)) return CDF_UINT2;
  if (!strncmp(token,"CDF_INT4",8)) return CDF_INT4;
  if (!strncmp(token,"CDF_UINT4",9)) return CDF_UINT4;
  if (!strncmp(token,"CDF_REAL4",9)) return CDF_REAL4;
  if (!strncmp(token,"CDF_FLOAT",9)) return CDF_FLOAT;
  if (!strncmp(token,"CDF_REAL8",9)) return CDF_REAL8;
  if (!strncmp(token,"CDF_DOUBLE",10)) return CDF_DOUBLE;
  if (!strncmp(token,"CDF_CHAR",8)) return CDF_CHAR;
  if (!strncmp(token,"CDF_UCHAR",9)) return CDF_UCHAR;
  if (!strncmp(token,"CDF_EPOCH",9)) return CDF_EPOCH;
  return -1;
}

/******************************************************************************
* BreakdownCard.
******************************************************************************/

void BreakdownCard (char *cardIn, long numElems, char *keyword, 
                    char *keywordValue, char *comment, 
                    Logical *strKeywordValue) {

     char *card;
     char value[80];
     char *ptr1, *ptr2;
     int jj;
     int fstatus;
     int ii;
     char *ptr;

     card = (char *) malloc((size_t) numElems + 1);
     memcpy(card, cardIn, (size_t) numElems);
     card[(size_t) numElems] = '\0';

     keyword[0] = (char) 0;
     keywordValue[0] = (char) 0;
     comment[0] = (char) 0;
     *strKeywordValue = FALSE;
     if (numElems == 0) return;

     if (numElems <= 8) {
       ii = sscanf(card, "%s", keyword);
       return;
     }

     if (card[8] == '=' && strncmp(card, "COMMENT", 7)) {
       if (numElems == 9) return;
       memcpy(keyword, card, 8);
       ptr1 = (char *) strchr(keyword, ' ');
       if (ptr1 != NULL) keyword[ptr1-keyword] = (char) 0;
       else keyword[8] = (char) 0;
       if (card[10] == '\'') {
	 ptr1 = (char *) strrchr(card, '\'');
         ptr2 = (char *) strchr(card+30, '/');
	 if (ptr2 != NULL && ptr2 > ptr1) {
           if (!strncmp(keyword, "TFORM", 5)) {
             memcpy(keywordValue, card+11, ptr1-card-12);
             keywordValue[ptr1-card-12] = (char) 0;
           } else {
             char *ptr3;
             ptr3 = (char *) strchr(card+10, '(');
             if (ptr3 != NULL && ptr3 < ptr1) {
               char *ptr4 = (char *) strchr(ptr3, ')');
               memcpy(keywordValue, ptr3+1, ptr4-ptr3-1);
               keywordValue[ptr4-ptr3-1] = (char) 0;
             } else {
	       memcpy(keywordValue, card+11, ptr1-card-11);
               keywordValue[ptr1-card-11] = (char) 0;
             }
           } 
	 } else {
	   if (ptr2 == NULL) {
	     memcpy(keywordValue, card+11, ptr1-card-11);
             keywordValue[ptr1-card-11] = (char) 0;	
	   } else {
	     memcpy(value, card, ptr1-card-1);
	     ptr2 = (char *) strchr(value, '\'');
	     memcpy(keywordValue, ptr2+1, ptr1-card-11);
	     keywordValue[ptr1-card-11] = (char) 0;
	   }
         }
         *strKeywordValue = TRUE;
       }  else if (card[10] == '(') {
         ptr1 = (char *) strchr(card+10, '(');
	 ptr2 = (char *) strchr(card+10, ')');
	 memcpy(keywordValue, ptr1+1, ptr2-ptr1-1);
	 keywordValue[ptr2-ptr1-1] = (char) 0;
       } else if (sscanf(card+9, "%s", keywordValue) == 1) {
	 ptr1 = (char *) strrchr(keywordValue, '/');
         if (ptr1 != NULL) keywordValue[ptr1-keywordValue] = (char) 0;
         if (!strncmp(keywordValue, "/", 1)) keywordValue[0] = (char) 0;
       }

       ptr1 = (char *) strchr(card+31, '/');
       if (ptr1 != NULL) {
	 int len = ptr1+1-card;
         if (len < numElems) {
           memcpy(comment, ptr1+1, (size_t)(numElems - len));
	   comment[numElems - len] = (char) 0;
	 }
       }
     } else { /* Got to be a CONTINUE, COMMENT or HISTORY. */
       memcpy(keyword, card, 8);
       ptr1 = (char *) strchr(keyword, ' ');
       if (ptr1 != NULL) keyword[ptr1-keyword] = (char) 0;
       else keyword[8] = (char) 0;
       if (strncmp(keyword, "CONTINUE", 8)) { /* It is COMMENT or HISTORY. */
         if (card[8] != '=') {
           memcpy(comment, card+8, (size_t)(numElems - 8));
           comment[numElems - 8] = (char) 0;
         } else {
           if (numElems == 9) comment = (char *) strdup("");
           else {  
             memcpy(comment, card+9, (size_t)(numElems - 9));
             comment[numElems - 9] = (char) 0;
           }
         }
       } else { /* A CONTINUE card. */
	 *strKeywordValue = TRUE;
	 ptr1 = (char *) strchr(card, '\'');
         ptr2 = (char *) strrchr(card, '\'');
	 memcpy(keywordValue, ptr1+1, ptr2-ptr1-1);
	 keywordValue[ptr2-ptr1-1] = (char) 0;
         ptr1 = (char *) strchr(card, '/');
	 if (ptr1 != NULL) {
	   memcpy(comment, ptr1+1, numElems-(ptr1-card)-1);
           comment[numElems-(ptr1-card)-1] = (char) 0;
         } else
           comment[0] = (char) 0;
       }
     }
     
     return;
}

/******************************************************************************
* CheckKeywordValue.
******************************************************************************/

void GetKeywordValue (char *keywordValue, void *value, int *dataType, 
                      char *floatFormat) {

     int ii, jj;
     long ll;
     float ff;
     double dd;
     char *ptr1, *ptr2;
     int len1, len2;

     floatFormat[0] = (char) 0;
     if (strlen(keywordValue) == 1) {
       if (keywordValue[0] == 'T') {
	 *(int *)value = 1;
	 *dataType = TLOGICAL;
	 return;
       } else if (keywordValue[0] == 'F') {
	 *(int *)value = 0;
	 *dataType = TLOGICAL;
	 return;
       }
       if (sscanf(keywordValue, "%d", &ii) == 1) {
         *(int *)value = ii;
         *dataType = TINT32BIT;
         return;
       }
     } 

     ptr1 = (char *) strchr(keywordValue, '.');
     if (ptr1 != NULL) {
     /*************************************
     * A floating number.
     *************************************/
       len1 = strlen(keywordValue);
       ptr2 = (char *) strchr(keywordValue, 'E');
       if (ptr2 == NULL) ptr2 = (char *) strchr(keywordValue, 'e');
       if (ptr2 != NULL) 
       /*************************************
       * A floating number with exponential.
       *************************************/
         len2 = ptr2 - ptr1 - 1;
       /*************************************
       * A floating number w/t exponential.
       *************************************/
       else
         len2 = len1 - (ptr1 - keywordValue) - 1;
       if (len2 <= 6) {
	 if (ptr2 == NULL) {
	  sprintf(floatFormat, "%%%d.%dF",len1, len2);
	  sscanf(keywordValue, "%f", value);
         } else {
          sprintf(floatFormat, "%%%d.%dE",len1, len2);
	  sscanf(keywordValue, "%e", value);
         }
	 *dataType = TFLOAT;
         return;
       } else {
         if (ptr2 == NULL) {
	   sprintf(floatFormat, "%%%d.%dlF",len1, len2);
	   sscanf(keywordValue, "%lf", value);
         } else {
	   sprintf(floatFormat, "%%%d.%dlE",len1, len2);
	   sscanf(keywordValue, "%le", value);
	 }
         *dataType = TDOUBLE;
         return;
       }
     } else {
       ptr1 = (char *) strchr(keywordValue, 'E');
       if (ptr1 == NULL) ptr1 = (char *) strchr(keywordValue, 'e');
       if (ptr1 != NULL) { /* eg., 1E05 */
       /*************************************
       * A floating number.
       *************************************/
         len1 = strlen(keywordValue);
         if (len1 > (ptr1 - keywordValue + 1)) {
           if (sscanf(ptr1+1, "%d", ii) == 1) {
             if (ii > -125 && ii < 125) {
	       sscanf(keywordValue, "%e", value);
               *dataType = TFLOAT;
               return;
             } else {
	       sscanf(keywordValue, "%le", value);
               *dataType = TDOUBLE;
               return;
             }
	   }
         } else {
           sscanf(keywordValue, "%e", value);
           *dataType = TFLOAT;
           return;
         }
       }
     }

     len1 = strlen(keywordValue);
     if (len1 < 10) {
       if (sscanf(keywordValue, "%d", value) == 1) {
         *dataType = TINT32BIT;
         return;
       }
     } else {
       if (sscanf(keywordValue, "%ld", value) == 1) {
         *dataType = TLONG;
         return;
       }
     }
}

/******************************************************************************
* ComplexVariable.
******************************************************************************/

Logical ComplexVariable (CDFid id, long varNum, char **attrName, 
                         long *attrNums, long numAttrs) {

     int ii;
     long dataType, numElems;
     CDFstatus status;

     for (ii = 0; ii < numAttrs; ii++) {
        if (!strcmp(attrName[ii], "IsComplexNum_")) {
          status = GetVarAttributeEntryInfo(id, attrNums[ii], varNum, 
                                            &dataType, &numElems);
	  if (status == CDF_OK) return TRUE;
	  else return FALSE;
        }
     }
     return FALSE;

}

/******************************************************************************
* ParseKeyword. This is for a non-native CDF, originally converted from FITS.
* It parses the card and returns the keyword, keyword value and comment in
* char string. It also checks for and sets the keyword value data type, its
* size and returns its binary value, if a non-string type.
******************************************************************************/

void ParseKeyword(char *entry, long lengthEntry, char *keyword,
                  char *keywordValue, char *comment, int *keyDataType,
                  int *num, int *size, char *floatFormat, void *value) {

  Logical strKeywordValue;

  floatFormat[0] = (char) 0;

  /*****************************************************************
  * Breakdown the card for the CDF originally converted from FITS.
  *****************************************************************/
  BreakdownCard(entry, lengthEntry, keyword, keywordValue, comment,
                &strKeywordValue);

  *size = 0;
  *num = 1;

  if (strlen(keywordValue) == 0 && strlen(comment) == 0) {
    *((char *)value) = '\0';
    return;
  }

  if (strKeywordValue == FALSE && keywordValue != NULL &&
      strlen(keywordValue) > 0) {
    if ((char *) strchr(keywordValue, ',') != NULL) {
      void *tmp = malloc(80);
      int offset = 0, numFields = 1, inArray = 0, itemSize = 0;
      int len = strlen(keywordValue);
      char *ptr1;
      int fieldLength;
      ptr1 = (char *) strchr(keywordValue, ',');
      fieldLength = ptr1 - keywordValue;
      while (ptr1 != NULL) {
        numFields++;
        ptr1 = (char *) strchr(ptr1+1, ',');
      }
      offset = 0;
      do {
        memcpy(tmp, keywordValue+offset, fieldLength);
        ((char *)tmp)[fieldLength] = (char) 0;
        GetKeywordValue(tmp, ((char *)value)+inArray*itemSize, keyDataType, 
                        floatFormat);
        offset += (fieldLength+2);
        if (*keyDataType == TFLOAT) itemSize = 4;
        else itemSize = 8;
        inArray++;
      } while (offset < len);
      *size = numFields * itemSize;
      *num = numFields;
    } else {
      GetKeywordValue(keywordValue, value, keyDataType, floatFormat);
      if (*keyDataType == TINT32BIT || *keyDataType == TLONG ||
          *keyDataType == TLOGICAL || *keyDataType == TFLOAT) *size = 4;
      else if (*keyDataType == TDOUBLE) *size = 8;
      *num = 1;
    }
  } else if (strKeywordValue == TRUE && (strlen(keywordValue) > 0)) {
    *keyDataType = TSTRING;
    *size = strlen(keywordValue);
    *num = 1;
    strcpy(value, keywordValue);
  }

}

/******************************************************************************
* Print out error message from CFITSIO function call.
******************************************************************************/
void PrintFITSerror(int status, char *msg) {

    /*****************************************************/
    /* Print out cfitsio error messages and exit program */
    /*****************************************************/

    if (status) {
       printf("\n*** at %s\n",msg);
       fits_report_error(stderr, status); /* print error report */
       exit( status );    /* terminate the program, returning error status */
    }
    return;
}

/******************************************************************************
* Convert CDF data type to its corresponding FITS data type.
******************************************************************************/
int ConvertCDFDataType(long dataType) {

  switch (dataType) {
    case CDF_BYTE: return TSHORT;
    case CDF_INT1: return TSHORT;
    case CDF_INT2: return TSHORT;
    case CDF_INT4: return TINT;
    case CDF_UINT1: return TBYTE;
    case CDF_UINT2: return TUSHORT;
    case CDF_UINT4: return TUINT;
    case CDF_REAL4: return TFLOAT;
    case CDF_REAL8: return TDOUBLE;
    case CDF_FLOAT: return TFLOAT;
    case CDF_DOUBLE: return TDOUBLE;
    case CDF_EPOCH: return TSTRING;
    case CDF_CHAR: return TSTRING;
    case CDF_UCHAR: return TSTRING;
    default: return -1;
  }
    return -1;
}

/******************************************************************************
* Get FITS data type from its header information, TFORMn. 
******************************************************************************/
int GetFITSdataType(char *tform) {

  char *ptr; 

  ptr = (char *) strchr(tform, 'A');
  if (ptr != NULL) return TSTRING;
  ptr = (char *) strchr(tform, 'L');
  if (ptr != NULL) return TLOGICAL;
  ptr = (char *) strchr(tform, 'X');
  if (ptr != NULL) return TBIT;
  ptr = (char *) strchr(tform, 'B');
  if (ptr != NULL) return TBYTE;
  ptr = (char *) strchr(tform, 'I');
  if (ptr != NULL) return TSHORT;
  ptr = (char *) strchr(tform, 'J');
  if (ptr != NULL) return TINT;
  ptr = (char *) strchr(tform, 'E');
  if (ptr != NULL) return TFLOAT;
  ptr = (char *) strchr(tform, 'F');
  if (ptr != NULL) return TFLOAT;
  ptr = (char *) strchr(tform, 'D');
  if (ptr != NULL) return TDOUBLE;
  ptr = (char *) strchr(tform, 'C');
  if (ptr != NULL) return TCOMPLEX;
  ptr = (char *) strchr(tform, 'M');
  if (ptr != NULL) return TDBLCOMPLEX;

  return -999;
}

/******************************************************************************
*  The first pass for the global attribute "FITS_Header_Keywords" in a 
*  non-native CDF to collect information that is needed for header creation 
*  later.
******************************************************************************/
void FirstPass(CDFid id, long attrNum, long numEntries, int *conformation,
               int *whichHeader, int *extension, int *bitpix, int *naxis, 
               long *naxes, long *pcount, long *gcount, int *extend, 
               char **ttype, char **tform, char **tunit, char *extName) {

  int jj;
  long dataType, numElems;
  CDFstatus status;
  char keyword[8], keywordValue[80], comment[80], floatFormat[40];
  int keyNum, keySize, keyDataType;
  long kk;
  void *value;
 
  kk = 0;
  value = (void *) malloc(80);

  for (jj = 0; jj < numEntries; jj++) {
    /**********************************************************************
    * Acquire each global attribute entry's info.
    **********************************************************************/
    status = GetGlobalAttributeEntryInfo(id, attrNum, kk,
                                         &dataType, &numElems);
    if (status == CDF_OK) { /* The entry exists. */
      void *entry;
      int size = CDFelemSize(dataType) * numElems;
      entry = (void *) malloc ((size_t) size);
      /********************************************************************
      * Acquire its entry's data.
      ********************************************************************/
      GetGlobalAttributeEntryData(id, attrNum, kk, entry);
      /******************************************************************
      * Acquire entry data from the "FITS_Header_Keywords" attribute.
      * This is a non-native CDF.
      ******************************************************************/
      ParseKeyword(entry, numElems, keyword, keywordValue, comment, 
                   &keyDataType, &keyNum, &keySize, floatFormat, value);

      if (keySize > 0) { /* The keyword value is returned. */
        if (!strncmp(keyword, "SIMPLE", 6)) {
          *whichHeader = PRIMARY;
          *extension = IMAGE;
          if (!strcmp(keywordValue, "T"))
            *conformation = TRUE;
          else
            *conformation = FALSE;
        } if (!strncmp(keyword, "XTENSION", 8)) {
          *whichHeader = XTENSION;
          if (!strcmp(keywordValue, "IMAGE   "))
            *extension = IMAGE;
          else if (!strcmp(keywordValue, "BINTABLE"))
            *extension = BINTABLE;
          else if (!strcmp(keywordValue, "TABLE   "))
            *extension = TABLE;
        } if (!strncmp(keyword, "BITPIX", 6)) {
          sscanf(keywordValue, "%d", bitpix);
        } else if (!strncmp(keyword, "NAXIS", 5)) {
          int tmpInt1, tmpInt2;
          tmpInt2 = sscanf(keyword, "NAXIS%d", &tmpInt1);
          if (tmpInt2 != 1) 
            sscanf(keywordValue, "%d", naxis);
          else {
            sscanf(keywordValue, "%d", &tmpInt2);
            *(((long *)naxes)+tmpInt1-1) = tmpInt2;
          }
        } else if (!strncmp(keyword, "PCOUNT", 6)) {
          sscanf(keywordValue, "%d", pcount);
        } else if (!strncmp(keyword, "GCOUNT", 6)) {
          sscanf(keywordValue, "%d", gcount);
        } else if (!strncmp(keyword, "EXTEND", 6)) {
          if (!strcmp(keywordValue,"T"))
            *extend = 1;
          else
            *extend = 0;
        } else if (!strncmp(keyword, "TTYPE", 5)) {
          int tmpInt;
          sscanf(keyword, "TTYPE%d", &tmpInt);
          ttype[tmpInt-1] = (char *) strdup(keywordValue);
        } else if (!strncmp(keyword, "TFORM", 5)) {
          int tmpInt;
          sscanf(keyword, "TFORM%d", &tmpInt);
          tform[tmpInt-1] = (char *) strdup(keywordValue);
        } else if (!strncmp(keyword, "TUNIT", 5)) {
          int tmpInt;
          sscanf(keyword, "TUNIT%d", &tmpInt);
          tunit[tmpInt-1] = (char *) strdup(keywordValue);
        } else if (!strncmp(keyword, "EXTNAME", 7)) {
          strcpy(extName, keywordValue);
        }
      }
      free (entry);
    }
    kk++;
  }
}


