/******************************************************************************
*
*  NSSDC/CDF              Toolbox of routines for CDF Toolkit (Miscellaneous).
*
*  Version 1.5b, 29-Nov-95, Hughes STX.
*
*  Modification history:
*
*   V1.0  25-Jan-94, J Love     Original version.
*   V1.0a  6-Feb-94, J Love     DEC Alpha/OpenVMS port.
*   V1.1  10-Mar-94, J Love     Modified `ParseOptionList' to make the
*                               enclosing parentheses optional (on any
*                               operating system).
*   V1.2  13-Dec-94, J Love     CDF V2.5.
*   V1.2a 13-Jan-95, J Love     Changed `IsCDF' to allow all possible
*                               extensions on Macintosh machines also.
*   V1.2b 20-Jan-95, J Love     IRIX 6.x (64-bit).
*   V1.2c  3-Mar-95, J Love     Moved `EncodeKeyDefinitions', etc. to
*                               `windoz.c'.
*   V1.3  21-Mar-95, J Love     POSIX.
*   V1.3a 18-Apr-95, J Love     More POSIX.
*   V1.4  22-May-95, J Love     `ASSIGNx', `EQx', etc. moved here (and some
*                               calling sequences changed).
*   V1.5  14-Sep-95, J Love     Added routines used by CDFexport.  Hyper
*                               groups.
*   V1.5a 19-Sep-95, J Love	Macintosh event handling.
*   V1.5b 29-Nov-95, J Love	Made `AttemptAllocations' prototype `static'.
*
******************************************************************************/

#include "cdftools.h"

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

#if defined(dos)
#define MAXnBYTESperALLOCATION  50000L  /* Remember 64Kb limit. */
#else
#if defined(mac)
#define MAXnBYTESperALLOCATION  50000L
#else
#define MAXnBYTESperALLOCATION  500000L
#endif
#endif

/******************************************************************************
* Local function prototypes.
******************************************************************************/

static int strncmpNOCASEwild PROTOARGs((char *s1, char *s2, size_t length));
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[]
));
static Logical AttemptAllocations PROTOARGs((
  long nScalarValues, int nScalars, long nHyperValues, int nHypers,
  Byte **handles[], size_t nValueBytes[]
));

/******************************************************************************
* EncodeRecordJustify.
******************************************************************************/

void EncodeRecordJustify (string, recN, minWidth)
char *string;
long recN;
int minWidth;   /* If zero, no minimum width.  If positive, right justified.
		   If negative, left justified. */
{
  sprintf (string, "%ld", recN + 1);
  Justify (string, minWidth);
  return;
}

/******************************************************************************
* EncodeIndicesJustify.
******************************************************************************/

void EncodeIndicesJustify (string, numDims, indices, minWidth)
char *string;
long numDims;
long indices[];
int minWidth;   /* If zero, no minimum width.  If positive, right justified.
		   If negative, left justified. */
{
  int n;
  strcpyX (string, "[", 0);
  for (n = 0; n < numDims; n++) {
     if (n > 0) strcatX (string, ",", 0);
     sprintf (EofS(string), "%ld", indices[n] + 1);
  }
  strcatX (string, "]", 0);
  Justify (string, minWidth);
  return;
}

/******************************************************************************
* EncodeRecordIndicesJustify.
******************************************************************************/

void EncodeRecordIndicesJustify (string, recN, numDims, indices, minWidth)
char *string;
long recN;
long numDims;
long indices[];
int minWidth;   /* If zero, no minimum width.  If positive, right justified.
		   If negative, left justified. */
{
  EncodeRecordJustify (string, recN, 0);
  EncodeIndicesJustify (EofS(string), numDims, indices, 0);
  Justify (string, minWidth);
  return;
}

/******************************************************************************
* Justify.
******************************************************************************/

void Justify (string, minWidth)
char *string;
int minWidth;   /* If zero, no minimum width.  If positive, right justified.
		   If negative, left justified. */
{
  if (minWidth < 0) {
    int pad = -minWidth - strlen(string);
    if (pad > 0) CatNcharacters (string, pad, (int) ' ');
  }
  else {
    if (minWidth > 0) {
      int i, stringL = strlen(string);
      int shift = minWidth - stringL;
      if (shift > 0) {
	memmove (&(string[shift]), string, stringL + 1);
	for (i = 0; i < shift; i++) string[i] = ' ';
      }
    }
  }
  return;
}

/******************************************************************************
* CatString.
******************************************************************************/

void CatToString (string, cat, length, justify, more)
char *string;
char *cat;
int length;
int justify;
char *more;
{
  int catL = (int) strlen (cat);
  int moreL = (int) strlen (more);
  if (catL > length)
    if (moreL >= length)
      CatNcharacters (string, length, '*');
    else {
      strcatX (string, cat, strlen(string) + (length - moreL));
      strcatX (string, more, 0);
    }
  else
    switch (justify) {
      case LEFT_JUSTIFY:
	strcatX (string, cat, 0);
	CatNcharacters (string, length - catL, ' ');
	break;
      case CENTER_JUSTIFY: {
	int after = (length - catL) / 2;
	int before = (length - catL) - after;
	CatNcharacters (string, before, ' ');
	strcatX (string, cat, 0);
	CatNcharacters (string, after, ' ');
	break;
      }
      case RIGHT_JUSTIFY:
	CatNcharacters (string, length - catL, ' ');
	strcatX (string, cat, 0);
	break;
    }
  return;
}

/******************************************************************************
* RemoveTrailingBlanks.
******************************************************************************/

void RemoveTrailingBlanks (string)
char *string;
{
  int i;
  for (i = strlen(string) - 1; i >= 0; i--) {
     if (string[i] == ' ') string[i] = NUL;
  }
  return;
}

/******************************************************************************
* RemoveWhiteSpace.
******************************************************************************/

void RemoveWhiteSpace (string)
char *string;
{
  char *c = string;
  while (*c != NUL) {
    if (IsSpaceChr(*c))
      memmove (c, c + 1, strlen(c+1) + 1);
    else
      c++;
  }  
  return;
}

/******************************************************************************
* CopyToField.
*    Copies a string to a field and blank pads to the end of the field.
******************************************************************************/

void CopyToField (string, field, length)
char *string;
char *field;
int length;
{
  strcpyX (field, string, length);
  BlankPadRight (field, length - strlen(string));
  return;
}

/******************************************************************************
* CopyFromField.
*    Copies from a field to a string and removes trailing blanks.  It is
* assumed that the string is large enough.
******************************************************************************/

void CopyFromField (field, length, string)
char *field;
int length;
char *string;
{
  strcpyX (string, field, length);
  RemoveTrailingBlanks (string);
  return;
}

/******************************************************************************
* SystemClock.
******************************************************************************/

double SystemClock () {
  return ((double) time(NULL));
}

/******************************************************************************
* ParsePath.
******************************************************************************/

void ParsePath (path, dir, name)
char *path;
char dir[DU_MAX_DIR_LEN+1];
char name[DU_MAX_NAME_LEN+1];
{
  char *cp;
  size_t len;
  char dirT[DU_MAX_DIR_LEN+1];
  char nameT[DU_MAX_NAME_LEN+1];
#if defined(unix) || defined(posixSHELL)
  if (IsDir(path)) {
    strcpyX (dirT, path, DU_MAX_DIR_LEN);
    nameT[0] = NUL;
  }
  else {
    cp = strrchr (path, '/');
    if (cp == NULL) {
      dirT[0] = NUL;
      strcpyX (nameT, path, DU_MAX_NAME_LEN);
    }
    else {
      len = cp - path;                          /* No trailing '/'. */
      strcpyX (dirT, path, MINIMUM(len,DU_MAX_DIR_LEN));
      strcpyX (nameT, cp + 1, DU_MAX_NAME_LEN);
    }
  }
#endif
#if defined(dos)
  if (IsDir(path)) {
    strcpyX (dirT, path, DU_MAX_DIR_LEN);
    nameT[0] = NUL;
  }
  else {
    char *cp1 = strrchr (path, ':');
    char *cp2 = strrchr (path, '\\');
    cp = (cp1 > cp2 ? cp1 : cp2);
    if (cp == NULL) {
      dirT[0] = NUL;
      strcpyX (nameT, path, DU_MAX_NAME_LEN);
    }
    else {
      if (cp1 == NULL) {
	len = (size_t) (cp2 - path);            /* No trailing '\'. */
	strcpyX (dirT, path, MINIMUM(len,DU_MAX_DIR_LEN));
      }
      else {
	if (cp2 == NULL) {
	  len = (size_t) (cp1 - path + 1);      /* Trailing ':'. */
	  strcpyX (dirT, path, MINIMUM(len,DU_MAX_DIR_LEN));
	}
	else {
	  if (cp1 + 1 == cp2)
	    len = (size_t) (cp2 - path + 1);    /* Trailing ':'. */
	  else
	    len = (size_t) (cp2 - path);        /* No trailing ':'. */
	  strcpyX (dirT, path, MINIMUM(len,DU_MAX_DIR_LEN));
	}
      }
      strcpyX (nameT, cp + 1, DU_MAX_NAME_LEN);
    }
  }
#endif
#if defined(vms)
  char *cp1 = strrchr (path, ':');
  char *cp2 = strrchr (path, ']');
  cp = (cp1 > cp2 ? cp1 : cp2);
  if (cp == NULL) {
    dirT[0] = NUL;
    strcpyX (nameT, path, DU_MAX_NAME_LEN);
  }
  else {
    len = cp - path + 1;                        /* Trailing ':' or ']' */
    strcpyX (dirT, path, MINIMUM(len,DU_MAX_DIR_LEN));
    strcpyX (nameT, cp + 1, DU_MAX_NAME_LEN);
  }
#endif
#if defined(mac)
  if (IsDir(path)) {
    strcpyX (dirT, path, DU_MAX_DIR_LEN);
    nameT[0] = NUL;
  }
  else {
    cp = strrchr (path, ':');
    if (cp == NULL) {
      dirT[0] = NUL;
      strcpyX (nameT, path, DU_MAX_NAME_LEN);
    }
    else {
      len = cp - path + 1;                      /* Trailing ':'. */
      strcpyX (dirT, path, MINIMUM(len,DU_MAX_DIR_LEN));
      strcpyX (nameT, cp + 1, DU_MAX_NAME_LEN);
    }
  }
#endif
  if (dir != NULL) strcpyX (dir, dirT, DU_MAX_DIR_LEN);
  if (name != NULL) strcpyX (name, nameT, DU_MAX_NAME_LEN);
  return;
}

/******************************************************************************
* IsDir (directory).
*    `stat' on MS-DOS systems will indicate a directory if the pathname
* contains a wildcard character.
******************************************************************************/

Logical IsDir (path)
char *path;
{
  char pathX[DU_MAX_PATH_LEN+1];
  size_t len;
#if !defined(unix) && !defined(posixSHELL)
  int lastChar;
#endif
#if defined(unix) || defined(dos) || defined(posixSHELL)
  struct stat st;
#endif
  ExpandPath (path, pathX);
  len = strlen (pathX);
  if (len == 0) return FALSE;
#if !defined(unix) && !defined(posixSHELL)
  lastChar = len - 1;
#endif
#if defined(unix) || defined(dos) || defined(posixSHELL)
  /****************************************************************************
  * UNIX/POSIXshell and MS-DOS.
  ****************************************************************************/
#if defined(dos)
  /****************************************************************************
  * MS-DOS: Check for wildcard character in the pathname.
  ****************************************************************************/
  if (strchr(pathX,'*') != NULL) return FALSE;
  if (strchr(pathX,'?') != NULL) return FALSE;
  /****************************************************************************
  * MS-DOS: Strip trailing backslash (if present).
  ****************************************************************************/
  if (pathX[lastChar] == '\\') {
    pathX[lastChar] = NUL;         /* Strip trailing '\' */
    len--;
  }
  /****************************************************************************
  * MS-DOS: Check for disk drive specification.
  ****************************************************************************/
  if (len == 2)
    if (IsAlphaChr(pathX[0]) && pathX[1] == ':') return TRUE;
#endif
#if defined(unix) || defined(posixSHELL)
  /****************************************************************************
  * UNIX: Check for wildcard character in the pathname.
  ****************************************************************************/
  if (strchr(pathX,'*') != NULL) return FALSE;
  if (strchr(pathX,'?') != NULL) return FALSE;
#endif
  /****************************************************************************
  * Call `stat' to determine type of file.
  ****************************************************************************/
  if (stat(pathX, &st) == 0) {
#if defined(SALFORDC)           /* Salford's `stat' is broken. */
    return (st.st_size == 0);
#else
#if defined(S_ISDIR)
    return S_ISDIR(st.st_mode);
#else
    return (st.st_mode & S_IFDIR);
#endif
#endif
  }
  else
    return FALSE;
#endif
#if defined(vms)
  /****************************************************************************
  * VMS.
  ****************************************************************************/
  /****************************************************************************
  * Check for wildcard character in the pathname.
  ****************************************************************************/
  if (strchr(pathX,'*') != NULL) return FALSE;
  if (strchr(pathX,'%') != NULL) return FALSE;
  /****************************************************************************
  * Check for possible logical name defined to be a full file specification
  * (not just a directory).
  ****************************************************************************/
  if (IsReg(pathX))
    return FALSE;
  else
    /**************************************************************************
    * If not, check if the path ends with a ':' (logical name) or ']'
    * (directory).
    **************************************************************************/
    if (pathX[lastChar] == ':' || pathX[lastChar] == ']')
      return TRUE;
    else
      return FALSE;
#endif
#if defined(mac)
  /****************************************************************************
  * MAC: Check to see if specification ends with a `:' (and assume it to be a
  * directory without actually checking).  If specification doesn't end with
  * a `:', check if it is a directory anyway.
  ****************************************************************************/
  if (pathX[lastChar] == ':')
    return TRUE;
  else {
    return MacDirSpecified (pathX, NULL, NULL);
  }
#endif
}

/******************************************************************************
* IsWild (Wildcard specification in name).
******************************************************************************/

Logical IsWild (path)
char *path;
{
  char dir[DU_MAX_DIR_LEN+1];
  char name[DU_MAX_NAME_LEN+1];
  ParsePath (path, dir, name);
#if defined(vms)
  if (strchr(name,'*') != NULL || strchr(name,'%') != NULL) return TRUE;
#endif
#if defined(dos) || defined(unix) || defined(mac) || defined(posixSHELL)
  if (strchr(name,'*') != NULL || strchr(name,'?') != NULL) return TRUE;
#endif
  return FALSE;
}

/******************************************************************************
* IsCDF.
*    Determines if the specified pathname is that of a CDF.  Because of
* inconsistent behavior of CD-ROM drivers on various UNIX/POSIXshell machines
* and the Macintosh, the following extensions are tried: `.cdf', `.CDF',
* `.cdf;1', and `.CDF;1'.
******************************************************************************/

Logical IsCDF (pathname)
char *pathname;
{
  char pathnameX[DU_MAX_PATH_LEN+1];
  ExpandPath (pathname, pathnameX);
  strcatX (pathnameX, ".cdf", DU_MAX_PATH_LEN);
  if (IsReg(pathnameX)) return TRUE;
#if defined(unix) || defined(mac) || defined(posixSHELL)
  ExpandPath (pathname, pathnameX);
  strcatX (pathnameX, ".cdf;1", DU_MAX_PATH_LEN);
  if (IsReg(pathnameX)) return TRUE;
  ExpandPath (pathname, pathnameX);
  strcatX (pathnameX, ".CDF", DU_MAX_PATH_LEN);
  if (IsReg(pathnameX)) return TRUE;
  ExpandPath (pathname, pathnameX);
  strcatX (pathnameX, ".CDF;1", DU_MAX_PATH_LEN);
  if (IsReg(pathnameX)) return TRUE;
#endif
  return FALSE;
}

/******************************************************************************
* 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 *) 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;
     }
  }

  /****************************************************************************
  * Determine which qualifiers/options were entered.
  ****************************************************************************/

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

  for (qualNe = 0; qualNe < Nquals; qualNe++) {
     matchQualNv = -1;
     for (qualNv = 0; validQuals[qualNv] != NULL; qualNv++)
	if (strncmpNOCASEwild(quals[qualNe],
	    validQuals[qualNv], strlen(quals[qualNe])) == 0)
	  if (matchQualNv != -1) {
	    char tempS[MAX_MESSAGE_TEXT_LEN+1];
	    sprintf (tempS, "Ambiguous qualifier (/%s).", quals[qualNe]);
	    DisplayError (tempS);
	    FreeArgvT (argc - 1, argvT);
	    return NULL;
	  }
	  else
	    matchQualNv = qualNv;
     if (matchQualNv != -1)
       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;
       }
       else
	 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;
	 }
	 else
	   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;
	   }
	   else {
	     qualEntered[matchQualNv] = TRUE;
	     qualOpt[matchQualNv] = opts[qualNe];
	   }
	 else {
	   char tempS[MAX_MESSAGE_TEXT_LEN+1];
	   sprintf (tempS, "Unknown qualifier (/%s).", quals[qualNe]);
	   DisplayError (tempS);
	   FreeArgvT (argc - 1, argvT);
	   return NULL;
	 }
  }

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

       for (qualNv = 0; validQuals[qualNv] != NULL; qualNv++)
	  if (strncmpNOCASEwild(&argv[argN][1],
				validQuals[qualNv],
				strlen(&argv[argN][1])) == 0)
	    if (matchQualNv != -1) {
	      char tempS[MAX_MESSAGE_TEXT_LEN+1];
	      sprintf (tempS, "Ambiguous qualifier (-%s).", &argv[argN][1]);
	      DisplayError (tempS);
	      return NULL;
	    }
	    else
	      matchQualNv = qualNv;

       if (matchQualNv != -1) {
	 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 {
	 char tempS[MAX_MESSAGE_TEXT_LEN+1];
	 sprintf (tempS, "Unknown qualifier (-%s).", &argv[argN][1]);
	 DisplayError (tempS);
	 return NULL;
       }
     }
     else {
       Nparms++;
       if (Nparms <= QOP_MAX_PARMs) {
	 parms[Nparms-1] = (strncmp(argv[argN],"--",2) != 0 ? 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 *) 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;
}


/******************************************************************************
* strncmpNOCASEwild.
******************************************************************************/

static int strncmpNOCASEwild (s1, s2, len)
char *s1;
char *s2;
size_t len;
{
  int i;
  for (i = 0; i < len; i++)
     switch (s1[i]) {
       case NUL:
	 if (s2[i] == '*')
	   return 0;
	 else
	   return -s2[i];
       case '*':
	 return 0;
       default:
	 switch (s2[i]) {
	   case NUL:
	     return s1[i];
	   case '*':
	     return 0;
	   default:
	     if (ToLowerChr(s1[i]) != ToLowerChr(s2[i]))
	       return (s1[i] - s2[i]);
	     break;
	 }
	 break;
     }
  return 0;
}


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

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


/******************************************************************************
* ASSIGNx.
******************************************************************************/

void ASSIGNx (dst, src, dataType, numElems)
void *dst;
void *src;
long dataType;
long numElems;
{
  memmove (dst, src, (size_t) (CDFelemSize(dataType) * numElems));
  return;
}

/******************************************************************************
* EQx.
******************************************************************************/

Logical EQx (a, b, dataType, numElems)
void *a;
void *b;
long dataType;
long numElems;
{
  return BOO(memcmp(a,b,(size_t)(CDFelemSize(dataType)*numElems)),FALSE,TRUE);
}

/******************************************************************************
* NEx.
******************************************************************************/

Logical NEx (a, b, dataType, numElems)
void *a;
void *b;
long dataType;
long numElems;
{
  return BOO(memcmp(a,b,(size_t)(CDFelemSize(dataType)*numElems)),TRUE,FALSE);
}

/******************************************************************************
* LEx.
******************************************************************************/

Logical LEx (a, b, dataType, numElems)
void *a;
void *b;
long dataType;
long numElems;
{
switch (dataType) {
  case CDF_BYTE:
  case CDF_INT1:
    return (*((Schar *) a) <= *((Schar *) b));
  case CDF_UINT1:
    return (*((Uchar *) a) <= *((Uchar *) b));
  case CDF_INT2:
    return (*((Int16 *) a) <= *((Int16 *) b));
  case CDF_UINT2:
    return (*((uInt16 *) a) <= *((uInt16 *) b));
  case CDF_INT4:
    return (*((Int32 *) a) <= *((Int32 *) b));
  case CDF_UINT4:
    return (*((uInt32 *) a) <= *((uInt32 *) b));
  case CDF_REAL4:
  case CDF_FLOAT:
    return (*((float *) a) <= *((float *) b));
  case CDF_REAL8:
  case CDF_DOUBLE:
  case CDF_EPOCH:
    return (*((double *) a) <= *((double *) b));
  case CDF_CHAR:
  case CDF_UCHAR:
    return BOO(memcmp(a,b,(size_t)numElems) <= 0,TRUE,FALSE);
}
return FALSE;
}

/******************************************************************************
* LTx.
******************************************************************************/

Logical LTx (a, b, dataType, numElems)
void *a;
void *b;
long dataType;
long numElems;
{
switch (dataType) {
  case CDF_BYTE:
  case CDF_INT1:
    return (*((Schar *) a) < *((Schar *) b));
  case CDF_UINT1:
    return (*((Uchar *) a) < *((Uchar *) b));
  case CDF_INT2:
    return (*((Int16 *) a) < *((Int16 *) b));
  case CDF_UINT2:
    return (*((uInt16 *) a) < *((uInt16 *) b));
  case CDF_INT4:
    return (*((Int32 *) a) < *((Int32 *) b));
  case CDF_UINT4:
    return (*((uInt32 *) a) < *((uInt32 *) b));
  case CDF_REAL4:
  case CDF_FLOAT:
    return (*((float *) a) < *((float *) b));
  case CDF_REAL8:
  case CDF_DOUBLE:
  case CDF_EPOCH:
    return (*((double *) a) < *((double *) b));
  case CDF_CHAR:
  case CDF_UCHAR:
    return BOO(memcmp(a,b,(size_t)numElems) < 0,TRUE,FALSE);
}
return FALSE;
}

/******************************************************************************
* GEx.
******************************************************************************/

Logical GEx (a, b, dataType, numElems)
void *a;
void *b;
long dataType;
long numElems;
{
switch (dataType) {
  case CDF_BYTE:
  case CDF_INT1:
    return (*((Schar *) a) >= *((Schar *) b));
  case CDF_UINT1:
    return (*((Uchar *) a) >= *((Uchar *) b));
  case CDF_INT2:
    return (*((Int16 *) a) >= *((Int16 *) b));
  case CDF_UINT2:
    return (*((uInt16 *) a) >= *((uInt16 *) b));
  case CDF_INT4:
    return (*((Int32 *) a) >= *((Int32 *) b));
  case CDF_UINT4:
    return (*((uInt32 *) a) >= *((uInt32 *) b));
  case CDF_REAL4:
  case CDF_FLOAT:
    return (*((float *) a) >= *((float *) b));
  case CDF_REAL8:
  case CDF_DOUBLE:
  case CDF_EPOCH:
    return (*((double *) a) >= *((double *) b));
  case CDF_CHAR:
  case CDF_UCHAR:
    return BOO(memcmp(a,b,(size_t)numElems) >= 0,TRUE,FALSE);
}
return FALSE;
}

/******************************************************************************
* GTx.
******************************************************************************/

Logical GTx (a, b, dataType, numElems)
void *a;
void *b;
long dataType;
long numElems;
{
switch (dataType) {
  case CDF_BYTE:
  case CDF_INT1:
    return (*((Schar *) a) > *((Schar *) b));
  case CDF_UINT1:
    return (*((Uchar *) a) > *((Uchar *) b));
  case CDF_INT2:
    return (*((Int16 *) a) > *((Int16 *) b));
  case CDF_UINT2:
    return (*((uInt16 *) a) > *((uInt16 *) b));
  case CDF_INT4:
    return (*((Int32 *) a) > *((Int32 *) b));
  case CDF_UINT4:
    return (*((uInt32 *) a) > *((uInt32 *) b));
  case CDF_REAL4:
  case CDF_FLOAT:
    return (*((float *) a) > *((float *) b));
  case CDF_REAL8:
  case CDF_DOUBLE:
  case CDF_EPOCH:
    return (*((double *) a) > *((double *) b));
  case CDF_CHAR:
  case CDF_UCHAR:
    return BOO(memcmp(a,b,(size_t)numElems) > 0,TRUE,FALSE);
}
return FALSE;
}

/******************************************************************************
* IncrIndicesFirstLastRow.
*   Rolls over to `first' indices if at `last' indices.
******************************************************************************/

void IncrIndicesFirstLastRow (numDims, first, last, indices)
long numDims;
long first[];
long last[];
long indices[];
{
  int dimN;
  for (dimN = (int) (numDims - 1); dimN >= 0; dimN--) {
     if (indices[dimN] == last[dimN])
       indices[dimN] = first[dimN];
     else {
       indices[dimN]++;
       break;
     }
  }
  return;
}

/******************************************************************************
* IncrIndicesFirstLastCol.
*   Rolls over to `first' indices if at `last' indices.
******************************************************************************/

void IncrIndicesFirstLastCol (numDims, first, last, indices)
long numDims;
long first[];
long last[];
long indices[];
{
  int dimN;
  for (dimN = 0; dimN < numDims; dimN++) {
     if (indices[dimN] == last[dimN])
       indices[dimN] = first[dimN];
     else {
       indices[dimN]++;
       break;
     }
  }
  return;
}

/******************************************************************************
* AllocateBuffers.
******************************************************************************/

Logical AllocateBuffers (nRecords, numDims, dimSizes, groups, nScalarBuffers,
			 nHyperBuffers, handles, nValueBytes, rowMajor,
			 minNofHypers, fatalFnc)
long nRecords;
long numDims;
long dimSizes[];
struct GroupStruct *groups;
int nScalarBuffers;
int nHyperBuffers;
Byte **handles[];
size_t nValueBytes[];
Logical rowMajor;
int minNofHypers;
void (*fatalFnc) PROTOARGs((char *msg));
{
  int lsDimN;           /* Least significant dimension number. */
  int msDimN;           /* Most significant dimension number. */
  int dimStep;          /* How to increment dimension number from most
			   significant to least significant. */
  int dimN, dN;
  long nValuesPerRec, nValuesPerDim[CDF_MAX_DIMS], count, nHypersSoFar;
  /****************************************************************************
  * Setup for majority.
  ****************************************************************************/
  lsDimN = BOO(rowMajor,(int)(numDims-1),0);
  msDimN = BOO(rowMajor,0,(int)(numDims-1));
  dimStep = BOO(rowMajor,1,-1);
  /****************************************************************************
  * Calculate number of values per record/dimension.
  ****************************************************************************/
  if (numDims > 0) {
    nValuesPerDim[lsDimN] = 1;
    for (dimN = lsDimN - dimStep; dimN != msDimN - dimStep; dimN -= dimStep) {
       nValuesPerDim[dimN] = dimSizes[dimN+dimStep] *
			     nValuesPerDim[dimN+dimStep];
    }
    nValuesPerRec = dimSizes[msDimN] * nValuesPerDim[msDimN];
  }
  else
    nValuesPerRec = 1;
  /****************************************************************************
  * Check if full records can be allocated.
  ****************************************************************************/
  count = nRecords;
  while (count > 1) {
     long nRecGroups = nRecords/count + BOO(nRecords % count == 0,0,1);
     if (nRecGroups >= minNofHypers) {
       if (AttemptAllocations(count,nScalarBuffers,count*nValuesPerRec,
			      nHyperBuffers,handles,nValueBytes)) {
         groups->nRecGroups = nRecGroups;
         groups->firstRecCount = count;
         groups->lastRecCount = BOO(nRecords % count == 0,
				    count,nRecords % count);
         groups->recGroupN = 1;
         for (dimN = msDimN; dimN != lsDimN + dimStep; dimN += dimStep) {
	    groups->nDimGroups[dimN] = 1;
	    groups->firstDimCount[dimN] = dimSizes[dimN];
	    groups->lastDimCount[dimN] = dimSizes[dimN];
	    groups->dimGroupN[dimN] = 1;
         }
         return TRUE;
       }
     }
     count = (count + 1) / 2;
  }
  groups->nRecGroups = nRecords;
  groups->firstRecCount = 1;
  groups->lastRecCount = 1;
  groups->recGroupN = 1;
  nHypersSoFar = nRecords;
  /****************************************************************************
  * Check how much of each dimension can be allocated.
  ****************************************************************************/
  for (dimN = msDimN; dimN != lsDimN + dimStep; dimN += dimStep) {
     count = dimSizes[dimN];
     while (count > 1) {
	long nDimGroups = (dimSizes[dimN] / count) +
			  BOO(dimSizes[dimN] % count == 0,0,1);
	if (nHypersSoFar * nDimGroups >= minNofHypers) {
	  if (AttemptAllocations(1L,nScalarBuffers,count*nValuesPerDim[dimN],
			         nHyperBuffers,handles,nValueBytes)) {
	    groups->nDimGroups[dimN] = nDimGroups;
	    groups->firstDimCount[dimN] = count;
	    groups->lastDimCount[dimN] = BOO(dimSizes[dimN] % count == 0,
					     count,dimSizes[dimN] % count);
	    groups->dimGroupN[dimN] = 1;
	    for (dN = dimN + dimStep; dN != lsDimN + dimStep; dN += dimStep) {
	       groups->nDimGroups[dN] = 1;
	       groups->firstDimCount[dN] = dimSizes[dN];
	       groups->lastDimCount[dN] = dimSizes[dN];
	       groups->dimGroupN[dN] = 1;
	    }
	    return TRUE;
	  }
	}
        count = (count + 1) / 2;
     }
     groups->nDimGroups[dimN] = dimSizes[dimN];
     groups->firstDimCount[dimN] = 1;
     groups->lastDimCount[dimN] = 1;
     groups->dimGroupN[dimN] = 1;
     nHypersSoFar *= dimSizes[dimN];
  }
  if (AttemptAllocations(1L,nScalarBuffers,1L,nHyperBuffers,
			 handles,nValueBytes)) return TRUE;
  /****************************************************************************
  * Not all of the buffers can be allocated even at only one value per buffer.
  ****************************************************************************/
  if (fatalFnc != NULL) (*fatalFnc)("Unable to allocate memory buffers.");
  return FALSE;
}

/******************************************************************************
* AttemptAllocations.
******************************************************************************/

static Logical AttemptAllocations (nScalarValues, nScalarBuffers, nHyperValues,
				   nHyperBuffers, handles, nValueBytes)
long nScalarValues;
int nScalarBuffers;
long nHyperValues;
int nHyperBuffers;
Byte **handles[];
size_t nValueBytes[];
{
  int bN, bNt; unsigned long maxBytes = 1, nBytes;
#if defined(mac) || defined(dos)
  void *ptr;
#endif
  /****************************************************************************
  * First allocate scalar buffers.
  ****************************************************************************/
  for (bN = 0; bN < nScalarBuffers; bN++) {
     nBytes = nScalarValues * nValueBytes[bN];
     if (nBytes > MAXnBYTESperALLOCATION)
       *handles[bN] = NULL;
     else
       *handles[bN] = AllocateMemory ((size_t) nBytes, NULL);
     if (*handles[bN] == NULL) {
       for (bNt = bN - 1; bNt >= 0; bNt--) FreeMemory (*handles[bNt], NULL);
       return FALSE;
     }
     maxBytes = MAXIMUM(maxBytes,nBytes);
  }
  /****************************************************************************
  * Then allocate hyper buffers.
  ****************************************************************************/
  for (bN = nScalarBuffers; bN < nScalarBuffers + nHyperBuffers; bN++) {
     nBytes = nHyperValues * nValueBytes[bN];
     if (nBytes > MAXnBYTESperALLOCATION)
       *handles[bN] = NULL;
     else
       *handles[bN] = AllocateMemory ((size_t) nBytes, NULL);
     if (*handles[bN] == NULL) {
       for (bNt = bN - 1; bNt >= 0; bNt--) FreeMemory (*handles[bNt], NULL);
       return FALSE;
     }
     maxBytes = MAXIMUM(maxBytes,nBytes);
  }
  /****************************************************************************
  * Allocate and then free a buffer twice the size of the largest buffer
  * allocated.  This will save some space on the heap for other uses.
  ****************************************************************************/
#if defined(mac) || defined(dos)
  ptr = AllocateMemory ((size_t) (2 * maxBytes), NULL);
  if (ptr == NULL) {
    for (bNt = nScalarBuffers + nHyperBuffers - 1; bNt >= 0; bNt--) {
       FreeMemory (*handles[bNt], NULL);
    }
    return FALSE;
  }
  FreeMemory (ptr, NULL);
#endif
  /****************************************************************************
  * All allocations succeeded, return TRUE.
  ****************************************************************************/
  return TRUE;
}

/******************************************************************************
* HyperFullRecord.
******************************************************************************/

Logical HyperFullRecord (groups, numDims)
struct GroupStruct *groups;
long numDims;
{
  int dimN;
  for (dimN = 0; dimN < numDims; dimN++) {
     if (groups->nDimGroups[dimN] > 1) return FALSE;
  }
  return TRUE;
}

/******************************************************************************
* HypersPerRecord.
******************************************************************************/

long HypersPerRecord (groups, numDims)
struct GroupStruct *groups;
long numDims;
{
  int dimN; long nHypers = 1;
  for (dimN = 0; dimN < numDims; dimN++) {
     nHypers *= groups->nDimGroups[dimN];
  }
  return nHypers;
}

/******************************************************************************
* HyperStartRecord.
******************************************************************************/

Logical HyperStartOfRecord (hyper, numDims)
struct HyperStruct *hyper;
long numDims;
{
  int dimN;
  for (dimN = 0; dimN < numDims; dimN++) {
     if (hyper->dimIndices[dimN] > 0) return FALSE;
  }
  return TRUE;
}

/******************************************************************************
* InitHyperParms.
******************************************************************************/

void InitHyperParms (hyper, groups, numDims, nHypers, nValues)
struct HyperStruct *hyper;
struct GroupStruct *groups;
long numDims;
long *nHypers;
long *nValues;
{
  int dimN;
  *nHypers = groups->nRecGroups;
  for (dimN = 0; dimN < numDims; dimN++) *nHypers *= groups->nDimGroups[dimN];
  hyper->recNumber = 0;
  hyper->recCount = groups->firstRecCount;
  hyper->recInterval = 1;
  for (dimN = 0; dimN < numDims; dimN++) {
     hyper->dimIndices[dimN] = 0;
     hyper->dimCounts[dimN] = groups->firstDimCount[dimN];
     hyper->dimIntervals[dimN] = 1;
  }
  *nValues = hyper->recCount;
  for (dimN = 0; dimN < numDims; dimN++) *nValues *= hyper->dimCounts[dimN];
  return;
}

/******************************************************************************
* IncrHyperParms.
******************************************************************************/

void IncrHyperParms (hyper, groups, numDims, rowMajor, nValues)
struct HyperStruct *hyper;
struct GroupStruct *groups;
long numDims;
Logical rowMajor;
long *nValues;
{
  int lsDimN;           /* Least significant dimension number. */
  int msDimN;           /* Most significant dimension number. */
  int dimStep;          /* How to increment dimension number from most
			   significant to least significant. */
  int dimN;
  /****************************************************************************
  * Setup for majority.
  ****************************************************************************/
  lsDimN = BOO(rowMajor,(int)(numDims-1),0);
  msDimN = BOO(rowMajor,0,(int)(numDims-1));
  dimStep = BOO(rowMajor,1,-1);
  /****************************************************************************
  * Increment indices.
  ****************************************************************************/
  for (dimN = lsDimN; dimN != msDimN - dimStep; dimN -= dimStep) {
     if (groups->dimGroupN[dimN] < groups->nDimGroups[dimN]) {
       groups->dimGroupN[dimN]++;
       if (groups->dimGroupN[dimN] < groups->nDimGroups[dimN]) {
	 hyper->dimIndices[dimN] += groups->firstDimCount[dimN];
	 hyper->dimCounts[dimN] = groups->firstDimCount[dimN];
       }
       else {
	 hyper->dimIndices[dimN] += groups->firstDimCount[dimN];
	 hyper->dimCounts[dimN] = groups->lastDimCount[dimN];
       }
       *nValues = hyper->recCount;
       for (dimN = 0; dimN < numDims; dimN++) {
	  *nValues *= hyper->dimCounts[dimN];
       }
       return;
     }
     else {
       groups->dimGroupN[dimN] = 1;
       hyper->dimIndices[dimN] = 0;
       hyper->dimCounts[dimN] = groups->firstDimCount[dimN];
     }
  }
  /****************************************************************************
  * Increment record.
  ****************************************************************************/
  groups->recGroupN++;
  hyper->recNumber += groups->firstRecCount;
  hyper->recCount = BOO(groups->recGroupN < groups->nRecGroups,
			groups->firstRecCount,groups->lastRecCount);
  *nValues = hyper->recCount;
  for (dimN = 0; dimN < numDims; dimN++) *nValues *= hyper->dimCounts[dimN];
  return;
}

/******************************************************************************
* WriteOutPct.
******************************************************************************/

void WriteOutPct (pct)
int pct;
{
  char text[8+1];
  strcpyX (text, "\b\b\b\b", 8);
  if (pct < 10) strcatX (text, ".", 8);
  if (pct < 100) strcatX (text, ".", 8);
  sprintf (EofS(text), "%d", pct);
  strcatX (text, "%", 8);
  WriteOut (stdout, text);
  return;
}

/******************************************************************************
* SwitchMajority.
******************************************************************************/

Logical SwitchMajority (buffer, rowMajor, numDims, dimSizes, recCount,
			nValueBytes)
Byte *buffer;
Logical rowMajor;
long numDims;
long dimSizes[];
long recCount;
size_t nValueBytes;
{
  int dimN; long nRecordBytes, recX; Byte *offset; void *tBuffer;
  /****************************************************************************
  * If less than two dimensions no switching needs to be done.
  ****************************************************************************/
  if (numDims < 2) return TRUE;
  /****************************************************************************
  * Calculate number of bytes per record.
  ****************************************************************************/
  nRecordBytes = nValueBytes;
  for (dimN = 0; dimN < numDims; dimN++) nRecordBytes *= dimSizes[dimN];
  /****************************************************************************
  * Attempt to allocate a temporary buffer for one record.
  ****************************************************************************/
#if defined(dos)
  if (TOObigIBMpc(nRecordBytes))
    tBuffer = NULL;
  else
#endif
    tBuffer = AllocateMemory ((size_t) nRecordBytes, NULL);
  if (tBuffer == NULL) return FALSE;
  /****************************************************************************
  * Switch majority.
  ****************************************************************************/
  for (recX = 0, offset = buffer; recX < recCount;
       recX++, offset += (size_t) nRecordBytes) {
    if (rowMajor)
      ROWtoCOL (offset, tBuffer, numDims, dimSizes, (long) nValueBytes);
    else
      COLtoROW (offset, tBuffer, numDims, dimSizes, (long) nValueBytes);
    memmove (offset, tBuffer, (size_t) nRecordBytes);
  }
  /****************************************************************************
  * Free temporary buffer and return success.
  ****************************************************************************/
  FreeMemory (tBuffer, FatalError);
  return TRUE;
}
