/*********************************************************************
 *   Copyright 2018, University Corporation for Atmospheric Research
 *   See netcdf/README file for copying and redistribution conditions.
 *   $Header: /upc/share/CVS/netcdf-3/ncdump/dumplib.c,v 1.85 2010/05/05 22:15:39 dmh Exp $
 *********************************************************************/

/*
 * We potentially include <stdarg.h> before <stdio.h> in order to obtain a
 * definition for va_list from the GNU C compiler.
 */

#include "config.h"
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
#ifndef NO_FLOAT_H
#include <float.h>		/* for FLT_EPSILON, DBL_EPSILON */
#endif /* NO_FLOAT_H */
#include <math.h>
#include <netcdf.h>
#include "utils.h"
#include "nccomps.h"
#include "dumplib.h"
#include "ncdump.h"
#include "isnan.h"
#include "nctime0.h"

static float float_eps;
static double double_eps;

extern fspec_t formatting_specs; /* set from command-line options */

static float
float_epsilon(void)
{
    float float_eps;
#ifndef NO_FLOAT_H
    float_eps = FLT_EPSILON;
#else /* NO_FLOAT_H */
    {
	float etop, ebot, eps;
	float one = 1.0;
	float two = 2.0;
	etop = 1.0;
	ebot = 0.0;
	eps = ebot + (etop - ebot)/two;
	while (eps != ebot && eps != etop) {
	    float epsp1;

	    epsp1 = one + eps;
	    if (epsp1 > one)
		etop = eps;
	    else
		ebot = eps;
	    eps = ebot + (etop - ebot)/two;
	}
	float_eps = two * etop;
    }
#endif /* NO_FLOAT_H */
    return float_eps;
}


static double
double_epsilon(void)
{
    double double_eps;
#ifndef NO_FLOAT_H
    double_eps = DBL_EPSILON;
#else /* NO_FLOAT_H */
    {
	double etop, ebot, eps;
	double one = 1.0;
	double two = 2.0;
	etop = 1.0;
	ebot = 0.0;
	eps = ebot + (etop - ebot)/two;
	while (eps != ebot && eps != etop) {
	    double epsp1;

	    epsp1 = one + eps;
	    if (epsp1 > one)
		etop = eps;
	    else
		ebot = eps;
	    eps = ebot + (etop - ebot)/two;
	}
	double_eps = two * etop;
    }
#endif /* NO_FLOAT_H */
    return double_eps;
}


void
init_epsilons(void)
{
    float_eps = float_epsilon();
    double_eps = double_epsilon();
}


static char* has_c_format_att(int ncid, int varid);

int float_precision_specified = 0; /* -p option specified float precision */
int double_precision_specified = 0; /* -p option specified double precision */
char float_var_fmt[] = "%.NNg";
char double_var_fmt[] = "%.NNg";
char float_att_fmt[] = "%#.NNgf";
char float_attx_fmt[] = "%#.NNg";
char double_att_fmt[] = "%#.NNg";

/* magic number stored in a safebuf and checked, hoping it will be
 * changed if buffer was overwritten inadvertently */
#define SAFEBUF_CERT 2147114711

/* expression for where SAFEBUF_CERT is stored within safebuf (at end
 * of buffer, after data) */
#define SAFEBUF_EXPR(sbuf) (*(int *)((sbuf)->buf + (sbuf)->len))

/* expression to be checked whenever a safebuf is used */
#define SAFEBUF_CHECK(sbuf) (SAFEBUF_EXPR(sbuf) == SAFEBUF_CERT)

/* somewhat arbitrary initial size of safebufs, grow as needed */
#define SAFEBUF_INIT_LEN 128

/* initialize safe buffer */
safebuf_t *
sbuf_new() {
    size_t len = SAFEBUF_INIT_LEN;
    safebuf_t *sb;
    sb = (safebuf_t *) emalloc(sizeof(safebuf_t));
    sb->buf = (char *)emalloc(len + sizeof(int));
    sb->len = len;
    /* write a "stamp" in last 4 bytes of buffer for id and to check for overflow */
    SAFEBUF_EXPR(sb) = SAFEBUF_CERT;
    sb->buf[0] = 0;
    sb->cl = strlen(sb->buf);
    assert(SAFEBUF_CHECK(sb));
    return sb;
}


/* grow buffer to at least len bytes, copying previous contents if
 * necessary */
void
sbuf_grow(safebuf_t *sb, size_t len) {
    size_t m = sb->len;
    void *tmp;
    assert(SAFEBUF_CHECK(sb));
    if (len <= m)
	return;

    /* Make sure we at least double size of buffer to get what's
     * needed.  If we just used realloc(), no guarantee that length
     * would be expanded by a multiple, which we want. */
    while(len > m) {
	m *= 2;
    }
    tmp = emalloc(m + sizeof(int));
    memcpy(tmp, sb->buf, sb->len);
    sb->len = m;
    free(sb->buf);
    sb->buf = tmp;
    SAFEBUF_EXPR(sb) = SAFEBUF_CERT;
    assert(SAFEBUF_CHECK(sb));
}

/* Copy string s2 to safe buffer, growing if necessary */
void
sbuf_cpy(safebuf_t *sb, const char *s2) {
    size_t s2len;
    assert(SAFEBUF_CHECK(sb));
    s2len = strlen(s2);
    sbuf_grow(sb, 1 + s2len);
    strncpy(sb->buf, s2, sb->len);
    sb->cl = s2len;
    assert(SAFEBUF_CHECK(sb));
}

/* Concatenate string s2 to end of string in safe buffer, growing if necessary */
void
sbuf_cat(safebuf_t *sb, const char *s2) {
    size_t s2len;
    size_t res;
    assert(SAFEBUF_CHECK(sb));
    s2len = strlen(s2);
    sbuf_grow(sb, 1 + sb->cl + s2len);
    res = strlcat(sb->buf + sb->cl, s2, sb->len - sb->cl);
    assert( res < sb->len );
    sb->cl += s2len;
    assert(SAFEBUF_CHECK(sb));
}

/* Concatenate string in safebuf s2 to end of string in safebuf s1,
 * growing if necessary */
void
sbuf_catb(safebuf_t *s1, const safebuf_t *s2) {
    size_t s2len;
    size_t res;
    assert(SAFEBUF_CHECK(s1));
    assert(SAFEBUF_CHECK(s2));
    s2len = (size_t)sbuf_len(s2);
    sbuf_grow(s1, 1 + s1->cl + s2len);
    res = strlcat(s1->buf + s1->cl, s2->buf, s1->len - s1->cl);
    assert( res < s1->len );
    s1->cl += s2len;
    assert(SAFEBUF_CHECK(s1));
}

/* Return length of string in sbuf */
int
sbuf_len(const safebuf_t *sb) {
    assert(SAFEBUF_CHECK(sb));
    return (int)sb->cl;
}

/* Return C string in an sbuf */
char *
sbuf_str(const safebuf_t *sb) {
    assert(SAFEBUF_CHECK(sb));
    return sb->buf;
}

/* free safe buffer */
void
sbuf_free(safebuf_t *sb) {
    assert(SAFEBUF_CHECK(sb));
    free(sb->buf);
    free(sb);
}


/* In case different formats specified with -d option, set them here. */
void
set_formats(int float_digits, int double_digits)
{
    int res;
    res = snprintf(float_var_fmt, sizeof float_var_fmt, "%%.%dg",
		   float_digits) + 1;
    assert(res <= sizeof(float_var_fmt));
    res = snprintf(double_var_fmt, sizeof double_var_fmt, "%%.%dg",
		   double_digits) + 1;
    assert(res <= sizeof(double_var_fmt));
    res = snprintf(float_att_fmt, sizeof float_att_fmt, "%%#.%dgf",
		   float_digits) + 1;
    assert(res <= sizeof(float_att_fmt));
    res = snprintf(float_attx_fmt, sizeof float_attx_fmt, "%%#.%dg",
		   float_digits) + 1;
    assert(res <= sizeof(float_attx_fmt));
    res = snprintf(double_att_fmt, sizeof double_att_fmt, "%%#.%dg",
		   double_digits) + 1;
    assert(res <= sizeof(double_att_fmt));
}


static char *
has_c_format_att(
    int ncid,			/* netcdf id */
    int varid			/* variable id */
    )
{
    nc_type cfmt_type;
    size_t cfmt_len;
#define C_FMT_NAME	"C_format" /* name of C format attribute */
#define	MAX_CFMT_LEN	100	/* max length of C format attribute */
    static char cfmt[MAX_CFMT_LEN];

    /* we expect nc_inq_att to fail if there is no "C_format" attribute */
    int nc_stat = nc_inq_att(ncid, varid, "C_format", &cfmt_type, &cfmt_len);

    switch(nc_stat) {
    case NC_NOERR:
	if (cfmt_type == NC_CHAR && cfmt_len != 0 && cfmt_len < MAX_CFMT_LEN) {
	    int nc_stat = nc_get_att_text(ncid, varid, "C_format", cfmt);
	    if(nc_stat != NC_NOERR) {
		fprintf(stderr, "Getting 'C_format' attribute %s\n",
			nc_strerror(nc_stat));
		(void) fflush(stderr);
	    }
	    cfmt[cfmt_len] = '\0';
	    return &cfmt[0];
	}
	break;
    case NC_ENOTATT:
	break;
    default:
	fprintf(stderr, "Inquiring about 'C_format' attribute %s\n",
		nc_strerror(nc_stat));
	(void) fflush(stderr);
	break;
    }
    return 0;
}


/* Return default format to use for a primitive type */
const char *
get_default_fmt(nc_type typeid) {
    /* Otherwise return sensible default. */
    switch (typeid) {
    case NC_BYTE:
	return "%d";
    case NC_CHAR:
	return "%s";
    case NC_SHORT:
	return "%d";
    case NC_INT:
	return "%d";
    case NC_FLOAT:
	return float_var_fmt;
    case NC_DOUBLE:
	return double_var_fmt;
    case NC_UBYTE:
	return "%u";
    case NC_USHORT:
	return "%u";
    case NC_UINT:
	return "%u";
    case NC_INT64:
	return "%lld";
    case NC_UINT64:
	return "%llu";
    case NC_STRING:
	return "\"%s\"";
    default:
	break;
    }
    return "";		/* user-defined types don't use fmt member */
}

/*
 * Determine print format to use for each primitive value for this
 * variable.  Use value of attribute C_format if it exists, otherwise
 * a sensible default.
 */
const char *
get_fmt(
     int ncid,
     int varid,
     nc_type typeid
     )
{
    char *c_format_att;

    /* float or double precision specified with -p option overrides any
       C_format attribute value, so check for that first. */
    if (float_precision_specified && typeid == NC_FLOAT)
	return float_var_fmt;
    if (double_precision_specified && typeid == NC_DOUBLE)
	return double_var_fmt;
    /* If C_format attribute exists, return it */
    c_format_att = has_c_format_att(ncid, varid);
    if (c_format_att)
      return c_format_att;
    return get_default_fmt(typeid);
}

/* Return primitive type name */
static const char *
prim_type_name(nc_type type)
{
    switch (type) {
      case NC_BYTE:
	return "byte";
      case NC_CHAR:
	return "char";
      case NC_SHORT:
	return "short";
      case NC_INT:
	return "int";
      case NC_FLOAT:
	return "float";
      case NC_DOUBLE:
	return "double";
      case NC_UBYTE:
	return "ubyte";
      case NC_USHORT:
	return "ushort";
      case NC_UINT:
	return "uint";
      case NC_INT64:
	return "int64";
      case NC_UINT64:
	return "uint64";
      case NC_STRING:
	return "string";
      default:
	error("prim_type_name: bad type %d", type);
	return "bogus";
    }
}

static int max_type = 0;
static int max_atomic_type = 0;
static nctype_t **nctypes = 0;	/* holds all types in a netCDF dataset */


#ifdef USE_NETCDF4
/* return number of user-defined types in a group and all its subgroups */
static int
count_udtypes(int ncid) {
    int ntypes = 0;
    int numgrps;
    int *ncids;
    int i;
    int format;

    NC_CHECK( nc_inq_format(ncid, &format) );

    if (format == NC_FORMAT_NETCDF4) {
	/* Get number of types in this group */
	NC_CHECK( nc_inq_typeids(ncid, &ntypes, NULL) ) ;
	NC_CHECK( nc_inq_grps(ncid, &numgrps, NULL) ) ;
	ncids = (int *) emalloc(sizeof(int) * (size_t)(numgrps + 1));
	NC_CHECK( nc_inq_grps(ncid, NULL, ncids) ) ;
	/* Add number of types in each subgroup, if any */
	for (i=0; i < numgrps; i++) {
	    ntypes += count_udtypes(ncids[i]);
	}
	free(ncids);
    }
    return ntypes;
}
#endif /*USE_NETCDF4*/

/* This routine really is intended to return the max atomic typeid */
static int
max_typeid(int ncid) {
    int maxtypes = NC_NAT;
    int maxatomictypes = NC_NAT;
    int format = 0;
    int err = NC_NOERR;

    /* get the file type */
    err = nc_inq_format(ncid,&format);
    if(err) {
	fprintf(stderr,"%s: Cannot get file format.\n",nc_strerror(err));
	return 0;
    }
    switch (format) {
    case NC_FORMAT_CLASSIC:
    case NC_FORMAT_NETCDF4_CLASSIC:
    case NC_FORMAT_64BIT_OFFSET:
        maxatomictypes = (maxtypes = NC_DOUBLE); /*ignore NC_NAT?*/
	break;
    case NC_FORMAT_64BIT_DATA:
	maxatomictypes = (maxtypes = NC_UINT64);
	break;
    case NC_FORMAT_NETCDF4:
#ifdef USE_NETCDF4
	{
        int nuser = 0;
        maxatomictypes = (maxtypes = NC_STRING); /* extra netCDF-4 primitive types */
        maxtypes += 4;		/* user-defined classes */
        nuser = count_udtypes(ncid);
        if(nuser > 0)
	    maxtypes = NC_FIRSTUSERTYPEID + (nuser - 1);
	} break;
#else
	/* fallthru */
#endif
    default:
	fprintf(stderr,"Unexpected file format: %d\n",format);
	return 0;
    }
    max_type = maxtypes;
    max_atomic_type = maxatomictypes;
    return maxtypes;
}

void typeadd(nctype_t *typep) {
    nctypes[typep->tid] = typep;
}

/* From type id, get full type info */
nctype_t *
get_typeinfo ( int typeid ) {
    if(typeid < 0 || typeid > max_type)
	error("ncdump: %d is an invalid type id", typeid);
    return nctypes[typeid];
}

/* void */
/* xfree_typeinfo(int ncid) { */
/*     int i; */
/*     for (i = 0; i < number_of_types; i++) { */
/* 	nctype_t *tinfop = nctypes[i]; */
/* 	if (tinfop) { */
/* 	    if(tinfop->name) */
/* 		free(tinfop->name); */
/* 	    if(tinfop->grps) */
/* 		free(tinfop->grps); */
/* 	    free(tinfop); */
/* 	} */
/*     } */
/* } */


bool_t
ncbyte_val_equals(const nctype_t *this,
		  const void *v1p, const void *v2p) {
    return ( *(signed char* )v1p == *(signed char* )v2p);
}

bool_t
ncchar_val_equals(const nctype_t *this,
		  const void *v1p, const void *v2p) {
    return ( *(char* )v1p == *(char* )v2p);
}

bool_t
ncshort_val_equals(const nctype_t *this,
		   const void *v1p, const void *v2p) {
    return ( *(short* )v1p == *(short* )v2p);
}

bool_t
ncint_val_equals(const nctype_t *this,
		 const void *v1p, const void *v2p) {
    return ( *(int* )v1p == *(int* )v2p);
}

#define absval(x)  ( (x) < 0 ? -(x) : (x) )

/*
 * Return ( *(float* )v1p == *(float* )v2p);
 * except use floating epsilon to compare very close vals as equal
 * and handle IEEE NaNs and infinities.
 */
bool_t
ncfloat_val_equals(const nctype_t *this,
		   const void *v1p, const void *v2p) {
    float v1 = *(float* )v1p;
    float v2 = *(float* )v2p;
    if((v1 > 0.0F) != (v2 > 0.0F))	/* avoid overflow */
	return false;
    if(isfinite(v1) && isfinite(v2))
	return (absval(v1 - v2) <= absval(float_eps * v2)) ;
    if(isnan(v1) && isnan(v2))
	return true;
    if(isinf(v1) && isinf(v2))
	return true;
    return false;
}

/*
 * Return ( *(double* )v1p == *(double* )v2p);
 * except use floating epsilon to compare very close vals as equal
 * and handle IEEE NaNs and infinities.
 */
bool_t
ncdouble_val_equals(const nctype_t *this,
		    const void *v1p, const void *v2p) {
    double v1 = *(double* )v1p;
    double v2 = *(double* )v2p;
    if((v1 > 0.0) != (v2 > 0.0))	/* avoid overflow */
	return false;
    if(isfinite(v1) && isfinite(v2))
	return (absval(v1 - v2) <= absval(double_eps * v2)) ;
    if(isnan(v1) && isnan(v2))
	return true;
    if(isinf(v1) && isinf(v2))
	return true;
    return false;
}

bool_t
ncubyte_val_equals(const nctype_t *this,
		   const void *v1p, const void *v2p) {
    return ( *(unsigned char* )v1p == *(unsigned char* )v2p);
}

bool_t
ncushort_val_equals(const nctype_t *this,
		    const void *v1p, const void *v2p) {
    return ( *(unsigned short* )v1p == *(unsigned short* )v2p);
}

bool_t
ncuint_val_equals(const nctype_t *this,
		  const void *v1p, const void *v2p) {
    return ( *(unsigned int* )v1p == *(unsigned int* )v2p);
}

bool_t
ncint64_val_equals(const nctype_t *this,
		   const void *v1p, const void *v2p) {
    return ( *(long long* )v1p == *(long long* )v2p);
}

bool_t
ncuint64_val_equals(const nctype_t *this,
		    const void *v1p, const void *v2p) {
    return ( *(unsigned long long* )v1p == *(unsigned long long* )v2p);
}

bool_t
ncstring_val_equals(const nctype_t *this,
		    const void *v1p, const void *v2p) {
    if (NULL == *((char **)v1p) && NULL == *((char **)v2p))
        return(1);
    else if (NULL != *((char **)v1p) && NULL == *((char **)v2p))
        return(0);
    else if (NULL == *((char **)v1p) && NULL != *((char **)v2p))
        return(0);
    return (strcmp(*((char **)v1p), *((char **)v2p)) == 0);
}

#ifdef USE_NETCDF4
bool_t
ncopaque_val_equals(const nctype_t *this,
		    const void *v1p, const void *v2p) {
    size_t nbytes = this->size;
    const char *c1p = (const char *) v1p;
    const char *c2p = (const char *) v2p;
    int i;
    for (i=0; i < nbytes; i++) {
	if (*c1p++ != *c2p++)
	    return false;
    }
    return true;
}

bool_t
ncvlen_val_equals(const nctype_t *this,
		  const void *v1p, const void *v2p) {
    size_t v1len = ((nc_vlen_t *)v1p)->len;
    size_t v2len = ((nc_vlen_t *)v2p)->len;
    if (v1len != v2len)
	return false;
    {
	size_t base_size = this->size;
	nc_type base_type = this->base_tid;
	nctype_t *base_info = get_typeinfo(base_type);
	val_equals_func base_val_equals = base_info->val_equals;
	const char *v1dat = ((nc_vlen_t *)v1p)->p;
	const char *v2dat = ((nc_vlen_t *)v2p)->p;
	size_t i;
	for(i = 0; i < v1len; i++) {
	    if (base_val_equals(base_info, (const void *)v1dat,
				(const void *)v2dat) != true)
		return false;
	    v1dat += base_size;
	    v2dat += base_size;
	}
    }
    return true;
}

/* Determine if two compound values are equal, by testing equality of
 * each member field. */
bool_t
nccomp_val_equals(const nctype_t *this,
		  const void *v1p, const void *v2p) {
    size_t nfields = this->nfields;
    size_t fidx;			/* field id */

    for (fidx = 0; fidx < nfields; fidx++) {
	size_t offset = this->offsets[fidx];
	nc_type fid = this->fids[fidx];		/* field type id */
	nctype_t *finfo = get_typeinfo(fid);
	if(finfo->ranks == 0 || finfo->ranks[fidx] == 0) {
	    if(! finfo->val_equals(finfo,
				   (char *)v1p + offset, (char *)v2p + offset))
		return false;
	} else {		/* this field is an array */
	    int i;		/* array element counter when rank > 0 */
	    void *v1elem = (char *)v1p + offset;
	    void *v2elem = (char *)v2p + offset;
	    for(i = 0; i < finfo->nvals[fidx]; i++) {
		if(! finfo->val_equals(finfo, v1elem, v2elem))
		    return false;
		v1elem = (char *)v1elem + finfo->size;
		v2elem = (char *)v1elem + finfo->size;
	    }
	}
    }
    return true;
}
#endif /* USE_NETCDF4 */

int
ncbyte_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
    char sout[PRIM_LEN];
    int res;
    res = snprintf(sout, PRIM_LEN, typ->fmt, *(signed char *)valp);
    assert(res < PRIM_LEN);
    sbuf_cpy(sfbf, sout);
    return sbuf_len(sfbf);
}

int
ncchar_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
    char sout[PRIM_LEN];
    char cstr[2];
    int res;
cstr[0] = *(char*)valp;
cstr[1] = '\0';
    res = snprintf(sout, PRIM_LEN, typ->fmt, cstr);
    assert(res < PRIM_LEN);
    sbuf_cpy(sfbf, sout);
    return sbuf_len(sfbf);
}

int
ncshort_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
    char sout[PRIM_LEN];
    int res;
    res = snprintf(sout, PRIM_LEN, typ->fmt, *(short *)valp);
    assert(res < PRIM_LEN);
    sbuf_cpy(sfbf, sout);
    return sbuf_len(sfbf);
}

int
ncint_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
    char sout[PRIM_LEN];
    int res;
    res = snprintf(sout, PRIM_LEN, typ->fmt, *(int *)valp);
    assert(res < PRIM_LEN);
    sbuf_cpy(sfbf, sout);
    return sbuf_len(sfbf);
}

/* CDL canonical representations of some special floating point values */
#define NCDL_NANF "NaNf"
#define NCDL_NAN "NaN"
#define NCDL_INFF "Infinityf"
#define NCDL_INF "Infinity"

/* Convert a float NaN or Infinity to an allocated string large enough
 * to hold it (at least PRIM_LEN chars) */
static void
float_special_tostring(float vv, char *sout) {
    if(isnan(vv)) {
	snprintf(sout, PRIM_LEN, "%s", NCDL_NANF);
    } else if(isinf(vv)) {
	if(vv < 0.0) {
	    snprintf(sout, PRIM_LEN, "-%s", NCDL_INFF);
	} else {
	    snprintf(sout, PRIM_LEN, "%s", NCDL_INFF);
	}
    } else
	assert(false);		/* vv was finite */
}

/* Convert a double NaN or Infinity to an allocated string large enough
 * to hold it (at least PRIM_LEN chars) */
static void
double_special_tostring(double vv, char *sout) {
    if(isnan(vv)) {
	    snprintf(sout, PRIM_LEN, "%s", NCDL_NAN);
    } else if(isinf(vv)) {
	if(vv < 0.0) {
	    snprintf(sout, PRIM_LEN, "-%s", NCDL_INF);
	} else {
	    snprintf(sout, PRIM_LEN, "%s", NCDL_INF);
	}
    } else
	assert(false);		/* vv was finite */
}

int
ncfloat_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
    char sout[PRIM_LEN];
    float vv = *(float *)valp;
    if(isfinite(vv)) {
	int res;
	res = snprintf(sout, PRIM_LEN, typ->fmt, vv);
	assert(res < PRIM_LEN);
    } else {
	float_special_tostring(vv, sout);
    }
    sbuf_cpy(sfbf, sout);
    return sbuf_len(sfbf);
}

int
ncdouble_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
    char sout[PRIM_LEN];
    double vv = *(double *)valp;
    if(isfinite(vv)) {
	int res;
	res = snprintf(sout, PRIM_LEN, typ->fmt, vv);
	assert(res < PRIM_LEN);
    } else {
	double_special_tostring(vv, sout);
    }
    sbuf_cpy(sfbf, sout);
    return sbuf_len(sfbf);
}

int
ncubyte_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
    char sout[PRIM_LEN];
    int res;
    res = snprintf(sout, PRIM_LEN, typ->fmt, *(unsigned char *)valp);
    assert(res < PRIM_LEN);
    sbuf_cpy(sfbf, sout);
    return sbuf_len(sfbf);
}

int
ncushort_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
    char sout[PRIM_LEN];
    int res;
    res = snprintf(sout, PRIM_LEN, typ->fmt, *(unsigned short *)valp);
    assert(res < PRIM_LEN);
    sbuf_cpy(sfbf, sout);
    return sbuf_len(sfbf);
}

int
ncuint_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
    char sout[PRIM_LEN];
    int res;
    res = snprintf(sout, PRIM_LEN, typ->fmt, *(unsigned int *)valp);
    assert(res < PRIM_LEN);
    sbuf_cpy(sfbf, sout);
    return sbuf_len(sfbf);
}

int
ncint64_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
    char sout[PRIM_LEN];
    int res;
    res = snprintf(sout, PRIM_LEN, typ->fmt, *(long long *)valp);
    assert(res < PRIM_LEN);
    sbuf_cpy(sfbf, sout);
    return sbuf_len(sfbf);
}

int
ncuint64_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
    char sout[PRIM_LEN];
    int res;
    res = snprintf(sout, PRIM_LEN, typ->fmt, *(unsigned long long *)valp);
    assert(res < PRIM_LEN);
    sbuf_cpy(sfbf, sout);
    return sbuf_len(sfbf);
}

int ncstring_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp)
{
    const char *cp;

    cp = ((char **)valp)[0];
    if(cp) {
        size_t slen;
        char *sout;
        char *sp;
        unsigned char uc;

        slen = 4 + 5 * strlen(cp); /* need "'s around string, and extra space to escape control characters */
	slen++; /* nul term */
        sout = emalloc(slen);
        sp = sout;
        *sp++ = '"' ;
        while(*cp) {
            switch (uc = (unsigned char)*cp++ & 0377) {
            case '\b':
                *sp++ = '\\';
                *sp++ = 'b' ;
                break;
            case '\f':
                *sp++ = '\\';
                *sp++ = 'f';
                break;
            case '\n':
                *sp++ = '\\';
                *sp++ = 'n';
                break;
            case '\r':
                *sp++ = '\\';
                *sp++ = 'r';
                break;
            case '\t':
                *sp++ = '\\';
                *sp++ = 't';
                break;
            case '\v':
                *sp++ = '\\';
                *sp++ = 'n';
                break;
            case '\\':
                *sp++ = '\\';
                *sp++ = '\\';
                break;
            case '\'':
                *sp++ = '\\';
                *sp++ = '\'';
                break;
            case '\"':
                *sp++ = '\\';
                *sp++ = '\"';
                break;
            default:
                if (iscntrl(uc)) {
                    snprintf(sp,4+1,"\\%03o",uc); /* +1 for nul */
                    sp += 4;
                }
                else
                    *sp++ = (char)uc;
                break;
            }
        }
        *sp++ = '"' ;
        *sp = '\0' ;
        sbuf_cpy(sfbf, sout);
        free(sout);
    }
    else {
        sbuf_cpy(sfbf, "NIL");
    }
    return sbuf_len(sfbf);
}

#ifdef USE_NETCDF4
int
ncenum_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
    char symbol[NC_MAX_NAME + 1];
    long long val = 0;

    switch (typ->base_tid) {
    case NC_BYTE:
	val = *(signed char *)valp;
	break;
    case NC_UBYTE:
	val = *(unsigned char *)valp;
	break;
    case NC_SHORT:
	val = *(short *)valp;
	break;
    case NC_USHORT:
	val = *(unsigned short *)valp;
	break;
    case NC_INT:
	val = *(int *)valp;
	break;
    case NC_UINT:
	val = *(unsigned int *)valp;
	break;
    case NC_INT64:
	val = *(long long *)valp;
	break;
    case NC_UINT64:
	val = *(long long *)valp;
	break;
    default:
	error("bad base type for enum");
	break;
    }
    NC_CHECK( nc_inq_enum_ident(typ->ncid, typ->tid, val, symbol));
    sbuf_cpy(sfbf, symbol);
    return sbuf_len(sfbf);
}

/* Given an opaque type size and opaque value, convert to a string,
 * represented as hexadecimal characters, returning number of chars in
 * output string */
int
ncopaque_val_as_hex(size_t size, char *sout, const void *valp) {
    const unsigned char *cp = valp;
    char *sp = sout;
    int i;
    char *prefix = "0X";
    size_t prelen = strlen(prefix);

    snprintf(sp, prelen + 1, "%s", prefix);
    sp += prelen;
    for(i = 0; i < size; i++) {
	int res;
	res = snprintf(sp, prelen + 1, "%.2X", *cp++);
	assert (res == 2);
	sp += 2;
    }
    *sp = '\0';
    return (int)(2*size + prelen);
}

/* Convert an opaque value to a string, represented as hexadecimal
 * characters */
int
ncopaque_typ_tostring(const nctype_t *typ, safebuf_t *sfbf,
		      const void *valp) {
    char* sout = (char *) emalloc(2 * typ->size + strlen("0X") + 1);
    (void) ncopaque_val_as_hex(typ->size, sout, valp);
    sbuf_cpy(sfbf, sout);
    free(sout);
    return sbuf_len(sfbf);
}

/* Convert a vlen value to a string, by using tostring function for base type */
int
ncvlen_typ_tostring(const nctype_t *tinfo, safebuf_t *sfbf, const void *valp) {
    nc_type base_type = tinfo->base_tid;
    nctype_t *base_info = get_typeinfo(base_type);
    size_t base_size = base_info->size;
    size_t len = ((nc_vlen_t *)valp)->len;
    typ_tostring_func base_typ_tostring = base_info->typ_tostring;
    size_t i;
    const char *vp;		/* instead of void* so can increment to next */
    safebuf_t* sout2 = sbuf_new();

    sbuf_cpy(sfbf, "{");
    /* put each val in sout2, then append sout2 to sfbf */
    vp = ((nc_vlen_t *)valp)->p;
    for(i = 0; i < len; i++) {
	(void) base_typ_tostring(base_info, sout2, vp);
	sbuf_catb(sfbf, sout2);
	if(i < len - 1) {
	    sbuf_cat(sfbf, ", ");
	}
	vp += base_size;
    }
    sbuf_cat(sfbf, "}");
    sbuf_free(sout2);
    return sbuf_len(sfbf);
}

/*
 * Print a number of char values as a text string.
 */
static int
chars_tostring(
    safebuf_t *sbuf,		/* for output */
    size_t len,			/* number of characters */
    const char *vals		/* pointer to block of values */
    )
{
    long iel;
    const char *sp;
    size_t sout_size = 4*len + 5; /* max len of string */
    char *sout = (char *)emalloc(sout_size);
    char *cp = sout;
    *cp++ = '"';

    /* adjust len so trailing nulls don't get printed */
    sp = vals + len;
    while (len != 0 && *--sp == '\0')
	len--;
    for (iel = 0; iel < len; iel++) {
	unsigned char uc;
	switch (uc = (unsigned char)(*vals++ & 0377)) {
	case '\b':
	case '\f':
	case '\n':
	case '\r':
	case '\t':
	case '\v':
	case '\\':
	case '\'':
	case '\"':
	    *cp++ = '\\';
	    *cp++ = *(char *)&uc; /* just copy, even if char is signed */
	    break;
	default:
	    if (isprint(uc))
		*cp++ = *(char *)&uc; /* just copy, even if char is signed */
	    else {
		size_t remaining = sout_size - (size_t)(cp - sout);
		snprintf(cp,remaining,"\\%.3o",uc);
		cp += 4;
	    }
	    break;
	}
    }
    *cp++ = '"';
    *cp = '\0';
    sbuf_cpy(sbuf, sout);
    free(sout);
    return sbuf_len(sbuf);
}


/* Convert a compound value to a string, by using tostring function for
   each member field */
int
nccomp_typ_tostring(const nctype_t *tinfo, safebuf_t *sfbf, const void *valp) {
    size_t nfields = tinfo->nfields;
    size_t fidx;			/* field id */
    safebuf_t* sout2 = sbuf_new();

    sbuf_cpy(sfbf, "{");
    /* put each val in sout2, then append sout2 to sfbf if enough room */
    for (fidx = 0; fidx < nfields; fidx++) {
	size_t offset = tinfo->offsets[fidx];
	nc_type fid = tinfo->fids[fidx];		/* field type id */
	nctype_t *finfo = get_typeinfo(fid);

	if(tinfo->ranks[fidx] == 0) {
	    if(finfo->tid == NC_CHAR) { /* aggregate char rows into strings */
		chars_tostring(sout2, 1, ((char *)valp + offset));
	    } else {
		finfo->typ_tostring(finfo, sout2, ((char *)valp + offset));
	    }
	} else {		/* this field is an array */
	    int i;		/* array element counter when rank > 0 */
	    void *vp = (char *)valp + offset;
	    safebuf_t *sout3 = sbuf_new();
	    sbuf_cpy(sout2, "{");
	    if(finfo->tid == NC_CHAR) { /* aggregate char rows into strings */
		int rank = tinfo->ranks[fidx];
		int j;
		size_t slen = (size_t)tinfo->sides[fidx][rank-1];
		int nstrings = 1;	/* product of all but last array dimension */
		for(j=0; j < rank-1; j++) {
		    nstrings *= tinfo->sides[fidx][j];
		}
		for(i=0; i < nstrings; i++) { /* loop on product of all but
						 last index of array */
		    chars_tostring(sout3, slen, (char *)vp);
		    vp = (char *)vp + slen;
		    if(i < nstrings - 1) {
			sbuf_cat(sout3, ", ");
		    }
		    sbuf_catb(sout2, sout3);
		}
	    } else {
		for(i = 0; i < tinfo->nvals[fidx]; i++) {
		    (void) finfo->typ_tostring(finfo, sout3, vp);
		    vp = (char *)vp + finfo->size;
		    if(i < tinfo->nvals[fidx] - 1) {
			sbuf_cat(sout3, ", ");
		    }
		    sbuf_catb(sout2, sout3);
		}
	    }
	    sbuf_cat(sout2, "}");
	    sbuf_free(sout3);
	}
	sbuf_catb(sfbf, sout2);
	if((fidx + 1) < nfields) {
	    sbuf_cat(sfbf, ", ");
	}
    }
    sbuf_cat(sfbf, "}");
    sbuf_free(sout2);
    return sbuf_len(sfbf);
}
#endif	/* USE_NETCDF4 */

int
ncbyte_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
    char sout[PRIM_LEN];
    int res;
    res = snprintf(sout, PRIM_LEN, varp->fmt, *(signed char *)valp);
    assert(res < PRIM_LEN);
    sbuf_cpy(sfbf, sout);
    return sbuf_len(sfbf);
}

int
ncchar_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
    char sout[PRIM_LEN];
    int res;
    res = snprintf(sout, PRIM_LEN, varp->fmt, *(char *)valp);
    assert(res < PRIM_LEN);
    sbuf_cpy(sfbf, sout);
    return sbuf_len(sfbf);
}

int
ncshort_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
    char sout[PRIM_LEN];
    int res;
    res = snprintf(sout, PRIM_LEN, varp->fmt, *(short *)valp);
    assert(res < PRIM_LEN);
    sbuf_cpy(sfbf, sout);
    return sbuf_len(sfbf);
}

int
ncint_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
    char sout[PRIM_LEN];
    int res;
    res = snprintf(sout, PRIM_LEN, varp->fmt, *(int *)valp);
    assert(res < PRIM_LEN);
    sbuf_cpy(sfbf, sout);
    return sbuf_len(sfbf);
}

int
ncfloat_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
    char sout[PRIM_LEN];
    float vv = *(float *)valp;
    if(isfinite(vv)) {
	int res;
	res = snprintf(sout, PRIM_LEN, varp->fmt, vv);
	assert(res < PRIM_LEN);
    } else {
	float_special_tostring(vv, sout);
    }
    sbuf_cpy(sfbf, sout);
    return sbuf_len(sfbf);
}

int
ncdouble_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
    char sout[PRIM_LEN];
    double vv = *(double *)valp;
    if(isfinite(vv)) {
	int res;
	res = snprintf(sout, PRIM_LEN, varp->fmt, vv);
	assert(res < PRIM_LEN);
    } else {
	double_special_tostring(vv, sout);
    }
    sbuf_cpy(sfbf, sout);
    return sbuf_len(sfbf);
}

/* Convert value of any numeric type to a double.  Beware, this may
 * lose precision for values of type NC_INT64 or NC_UINT64 */
static
double to_double(const ncvar_t *varp, const void *valp) {
    double dd = 0.0;
    switch (varp->type) {
    case NC_BYTE:
	dd = *(signed char *)valp;
	break;
    case NC_SHORT:
	dd = *(short *)valp;
	break;
    case NC_INT:
	dd = *(int *)valp;
	break;
    case NC_FLOAT:
	dd = *(float *)valp;
	break;
    case NC_DOUBLE:
	dd = *(double *)valp;
	break;
    case NC_UBYTE:
	dd = *(unsigned char *)valp;
	break;
    case NC_USHORT:
	dd = *(unsigned short *)valp;
	break;
    case NC_UINT:
	dd = *(unsigned int *)valp;
	break;
    case NC_INT64:
	dd = (double)*(long long *)valp;
	break;
    case NC_UINT64:
	dd = (double)*(unsigned long long *)valp;
	break;
    default:
	error("to_double: type not numeric primitive");
    }
    return dd;
}

int
nctime_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
    char sout[PRIM_LEN];
    double vv = to_double(varp, valp);
    int separator = formatting_specs.iso_separator ? 'T' : ' ';
    if(isfinite(vv)) {
	int oldopts = 0;
	int newopts = 0;
	size_t res;
	sout[0]='"';
	/* Make nctime dump error messages */
	oldopts = cdSetErrOpts(0);
	newopts = oldopts | CU_VERBOSE;
	cdSetErrOpts(newopts);
	cdRel2Iso(varp->timeinfo->calendar, varp->timeinfo->units, separator, vv, &sout[1], sizeof(sout) - 1);
	cdSetErrOpts(oldopts);
	res = strlen(sout);
	sout[res++] = '"';
	sout[res] = '\0';
	assert(res < PRIM_LEN);
    } else {
	double_special_tostring(vv, sout);
    }
    sbuf_cpy(sfbf, sout);
    return sbuf_len(sfbf);
}

int
ncubyte_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
    char sout[PRIM_LEN];
    int res;
    res = snprintf(sout, PRIM_LEN, varp->fmt, *(unsigned char *)valp);
    assert(res < PRIM_LEN);
    sbuf_cpy(sfbf, sout);
    return sbuf_len(sfbf);
}

int
ncushort_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
    char sout[PRIM_LEN];
    int res;
    res = snprintf(sout, PRIM_LEN, varp->fmt, *(unsigned short *)valp);
    assert(res < PRIM_LEN);
    sbuf_cpy(sfbf, sout);
    return sbuf_len(sfbf);
}

int
ncuint_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
    char sout[PRIM_LEN];
    int res;
    res = snprintf(sout, PRIM_LEN, varp->fmt, *(unsigned int *)valp);
    assert(res < PRIM_LEN);
    sbuf_cpy(sfbf, sout);
    return sbuf_len(sfbf);
}

int
ncint64_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
    char sout[PRIM_LEN];
    int res;
    res = snprintf(sout, PRIM_LEN, varp->fmt, *(long long *)valp);
    assert(res < PRIM_LEN);
    sbuf_cpy(sfbf, sout);
    return sbuf_len(sfbf);
}

int
ncuint64_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
    char sout[PRIM_LEN];
    int res;
    res = snprintf(sout, PRIM_LEN, varp->fmt, *(unsigned long long *)valp);
    assert(res < PRIM_LEN);
    sbuf_cpy(sfbf, sout);
    return sbuf_len(sfbf);
}

int
ncstring_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
    return ncstring_typ_tostring(varp->tinfo, sfbf, valp);
}

#ifdef USE_NETCDF4
int
ncenum_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
    return ncenum_typ_tostring(varp->tinfo, sfbf, valp);
}

/* Convert an opaque value to a string, represented as hexadecimal
 * characters */
int
ncopaque_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
    return ncopaque_typ_tostring(varp->tinfo, sfbf, valp);
}

/* Convert a vlen value to a string, by using tostring function for base type */
int
ncvlen_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
    return ncvlen_typ_tostring(varp->tinfo, sfbf, valp);
}

int
nccomp_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
    return nccomp_typ_tostring(varp->tinfo, sfbf, valp);
}
#endif /*USE_NETCDF4*/

static val_equals_func eq_funcs[] = {
	ncbyte_val_equals,
	ncchar_val_equals,
	ncshort_val_equals,
	ncint_val_equals,
	ncfloat_val_equals,
	ncdouble_val_equals,
	ncubyte_val_equals,
	ncushort_val_equals,
	ncuint_val_equals,
	ncint64_val_equals,
	ncuint64_val_equals,
	ncstring_val_equals
    };

static typ_tostring_func ts_funcs[] = {
	ncbyte_typ_tostring,
	ncchar_typ_tostring,
	ncshort_typ_tostring,
	ncint_typ_tostring,
	ncfloat_typ_tostring,
	ncdouble_typ_tostring,
	ncubyte_typ_tostring,
	ncushort_typ_tostring,
	ncuint_typ_tostring,
	ncint64_typ_tostring,
	ncuint64_typ_tostring,
	ncstring_typ_tostring
    };


/* Set function pointer of function to convert a value to a string for
 * the variable pointed to by varp. */
void
set_tostring_func(ncvar_t *varp) {
    val_tostring_func tostring_funcs[] = {
	ncbyte_val_tostring,
	ncchar_val_tostring,
	ncshort_val_tostring,
	ncint_val_tostring,
	ncfloat_val_tostring,
	ncdouble_val_tostring,
	ncubyte_val_tostring,
	ncushort_val_tostring,
	ncuint_val_tostring,
	ncint64_val_tostring,
	ncuint64_val_tostring,
	ncstring_val_tostring
    };
    if(varp->has_timeval && formatting_specs.string_times) {
	varp->val_tostring = (val_tostring_func) nctime_val_tostring;
	return;
    }
    if( !is_user_defined_type(varp->type) ) {
	varp->val_tostring = tostring_funcs[varp->type - 1];
	return;
    }
#ifdef USE_NETCDF4
    switch(varp->tinfo->class) {
    case NC_VLEN:
	varp->val_tostring = (val_tostring_func) ncvlen_val_tostring;
	break;
    case NC_OPAQUE:
	varp->val_tostring = (val_tostring_func) ncopaque_val_tostring;
	break;
    case NC_ENUM:
	varp->val_tostring = (val_tostring_func) ncenum_val_tostring;
	break;
    case NC_COMPOUND:
	varp->val_tostring = (val_tostring_func) nccomp_val_tostring;
	break;
    default:
	error("unrecognized class of user defined type: %d",
	      varp->tinfo->class);
    }
#endif /* USE_NETCDF4 */
}


/* Initialize typelist with primitive types.  For netCDF-3 only need primitive
   types. */
static void
init_prim_types(int ncid) {
    nctype_t *tp;
    int i;
    int types[] =
{
	NC_BYTE,
	NC_CHAR,
	NC_SHORT,
	NC_INT,
	NC_FLOAT,
	NC_DOUBLE,
	NC_UBYTE,
	NC_USHORT,
	NC_UINT,
	NC_INT64,
	NC_UINT64,
	NC_STRING
    };
    size_t sizes[] = {
	sizeof(char),
	sizeof(char),
	sizeof(short),
	sizeof(int),
	sizeof(float),
	sizeof(double),
	sizeof(unsigned char),
	sizeof(unsigned short),
	sizeof(unsigned int),
	sizeof(long long),
	sizeof(unsigned long long),
	sizeof(char **)
    };

#if 0
    for(i=0; i < sizeof(types)/sizeof(int); i++) {
#else
    for(i=0; i < max_atomic_type; i++) {
#endif
	tp = (nctype_t *)emalloc(sizeof(nctype_t));
	tp->ncid = ncid;
	tp->tid = types[i];
	tp->name = strdup(prim_type_name(tp->tid));
	tp->grps = 0;
	tp->class = 0;		/* primitive type */
	tp->size = sizes[i];
	tp->base_tid = NC_NAT;	/* not used for primitive types */
	tp->nfields = 0;	/* not used for primitive types */
	tp->fmt = get_default_fmt(types[i]);
	tp->fids = 0;		/* not used for primitive types */
	tp->offsets = 0;	/* not used for primitive types */
	tp->ranks = 0;		/* not used for primitive types */
	tp->sides = 0;		/* not used for primitive types */
	tp->nvals = 0;		/* not used for primitive types */
	tp->val_equals = (val_equals_func) eq_funcs[i];
	tp->typ_tostring = (typ_tostring_func) ts_funcs[i];
	typeadd(tp);
    }
}

/* Initialize typelist.
 *
 * This must be done over all groups in netCDF-4, because
 * variables in one group may be declared using types in a
 * different group.  For netCDF-3, this is just the info about
 * primitive types.
 */
void
init_types(int ncid) {
#ifdef USE_NETCDF4
    int ntypes;
#endif
    if (max_type == 0) {	/* if called for first time */
	int maxtype = max_typeid(ncid);
	int i;
	nctypes = (nctype_t **) emalloc((size_t)(maxtype + 2) * sizeof(nctype_t *));
	for(i=0; i < maxtype+1; i++)
	    nctypes[i] = NULL;	/* so can later skip over unused type slots */
	init_prim_types(ncid);
    }

#ifdef USE_NETCDF4
   /* Are there any user defined types in this group? */
   NC_CHECK( nc_inq_typeids(ncid, &ntypes, NULL) );
   if (ntypes)
   {
      int t;
      int *typeids = emalloc((size_t)(ntypes + 1) * sizeof(int));
      NC_CHECK( nc_inq_typeids(ncid, NULL, typeids) );
      for (t = 0; t < ntypes; t++) {
	  nctype_t *tinfo;	/* details about the type */
	  char type_name[NC_MAX_NAME + 1];
	  size_t group_name_len;
	  char* group_name;
	  int fidx;		/* for compound type, field index */

	  tinfo = (nctype_t *) emalloc(sizeof(nctype_t));

	  NC_CHECK( nc_inq_user_type(ncid, typeids[t], type_name, &tinfo->size,
		                     &tinfo->base_tid, &tinfo->nfields,
				     &tinfo->class) );
	  tinfo->tid = typeids[t];
	  tinfo->ncid = ncid;
	  tinfo->name = strdup(type_name);
	  tinfo->grps = 0;
	  if(tinfo->class == NC_VLEN) {
	      tinfo->size = sizeof(nc_vlen_t); /* not size of base type */
	  }
	  NC_CHECK( nc_inq_grpname_full(ncid, &group_name_len, NULL) );
	  group_name = (char *) emalloc(group_name_len + 1);
	  NC_CHECK( nc_inq_grpname_full(ncid, &group_name_len, group_name) );

	  tinfo->grps = strdup(group_name);
	  free(group_name);
	  switch(tinfo->class) {
	  case NC_ENUM:
	      tinfo->val_equals = eq_funcs[tinfo->base_tid-1];
	      tinfo->typ_tostring = (typ_tostring_func) ncenum_typ_tostring;
	      break;
	  case NC_COMPOUND:
	      tinfo->val_equals = (val_equals_func) nccomp_val_equals;
	      tinfo->typ_tostring = (typ_tostring_func) nccomp_typ_tostring;
	      tinfo->fids = (nc_type *) emalloc((tinfo->nfields + 1)
						  * sizeof(nc_type));
	      tinfo->offsets = (size_t *) emalloc((tinfo->nfields + 1)
						  * sizeof(size_t));
	      tinfo->ranks = (int *) emalloc((tinfo->nfields + 1)
					     * sizeof(int));
	      tinfo->sides = (int **) emalloc((tinfo->nfields + 1)
						 * sizeof(int *));
	      tinfo->nvals = (int *) emalloc((tinfo->nfields + 1)
					     * sizeof(int));
	      for (fidx = 0; fidx < tinfo->nfields; fidx++) {
		  size_t offset;
		  nc_type ftype;
		  int rank;
		  int *sides;
		  int i;
		  sides = NULL;
		  NC_CHECK( nc_inq_compound_field(ncid, tinfo->tid, fidx, NULL,
						  &offset, &ftype, &rank,
						  sides) );
		  if(rank > 0) sides = (int *) emalloc((size_t)rank * sizeof(int));
		  NC_CHECK( nc_inq_compound_field(ncid, tinfo->tid, fidx, NULL,
						  NULL, NULL, NULL, sides) );
		  tinfo->fids[fidx] = ftype;
		  tinfo->offsets[fidx] = offset;
		  tinfo->ranks[fidx] = rank;
		  if (rank > 0)
		      tinfo->sides[fidx] = (int *) emalloc((size_t)rank * sizeof(int));
		  tinfo->nvals[fidx] = 1;
		  for(i = 0; i < rank; i++) {
		      tinfo->sides[fidx][i] = sides[i];
		      tinfo->nvals[fidx] *= sides[i];
		  }
		  if (rank > 0)
		      free(sides);
	      }
	      break;
	  case NC_VLEN:
	      tinfo->val_equals = (val_equals_func) ncvlen_val_equals;
	      tinfo->typ_tostring = (typ_tostring_func) ncvlen_typ_tostring;
	      break;
	  case NC_OPAQUE:
	      tinfo->val_equals = (val_equals_func) ncopaque_val_equals;
	      tinfo->typ_tostring = (typ_tostring_func) ncopaque_typ_tostring;
	      break;
	  default:
	      error("bad class: %d", tinfo->class);
	      break;
	  }

	  typeadd(tinfo);
      }
      free(typeids);
   }
   /* For netCDF-4, check to see if this group has any subgroups and call
    * recursively on each of them. */
   {
      int g, numgrps, *ncids;

      /* See how many groups there are. */
      NC_CHECK( nc_inq_grps(ncid, &numgrps, NULL) );
      if (numgrps > 0) {
	  ncids = (int *) emalloc((size_t)numgrps * sizeof(int));
	  /* Get the list of group ids. */
	  NC_CHECK( nc_inq_grps(ncid, NULL, ncids) );
	  /* Call this function for each group. */
	  for (g = 0; g < numgrps; g++) {
	      init_types(ncids[g]);
	  }
	  free(ncids);
      }
   }
#endif /* USE_NETCDF4 */
}


/*
 * return 1 if varid identifies a coordinate variable
 * else return 0
 */
int
iscoordvar(int ncid, int varid)
{
    int ndims, ndims1;
    int dimid;
    int* dimids = 0;
    ncdim_t *dims = 0;
#ifdef USE_NETCDF4
    int include_parents = 1;
#endif
    int is_coord = 0;		/* true if variable is a coordinate variable */
    char varname[NC_MAX_NAME];
    int varndims;

    do {	  /* be safe in case someone is currently adding
		   * dimensions */
#ifdef USE_NETCDF4
	NC_CHECK( nc_inq_dimids(ncid, &ndims, NULL, include_parents ) );
#else
	NC_CHECK( nc_inq_ndims(ncid, &ndims) );
#endif
	if (dims)
	    free(dims);
	dims = (ncdim_t *) emalloc((size_t)(ndims + 1) * sizeof(ncdim_t));
	if (dimids)
	    free(dimids);
	dimids = (int *) emalloc((size_t)(ndims + 1) * sizeof(int));
#ifdef USE_NETCDF4
	NC_CHECK( nc_inq_dimids(ncid, &ndims1, dimids, include_parents ) );
#else
	{
	    int i;
	    for(i = 0; i < ndims; i++) {
		dimids[i] = i;	/* for netCDF-3, dimids are 0, 1, ..., ndims-1 */
	    }
	    NC_CHECK( nc_inq_ndims(ncid, &ndims1) );
	}
#endif	/* USE_NETCDF4 */
    } while (ndims != ndims1);

    for (dimid = 0; dimid < ndims; dimid++) {
	NC_CHECK( nc_inq_dimname(ncid, dimids[dimid], dims[dimid].name) );
    }
    NC_CHECK( nc_inq_varname(ncid, varid, varname) );
    NC_CHECK( nc_inq_varndims(ncid, varid, &varndims) );

    for (dimid = 0; dimid < ndims; dimid++) {
	if (strcmp(dims[dimid].name, varname) == 0 && varndims == 1) {
	    is_coord = 1;
	    break;
	}
    }
    if(dims)
	free(dims);
    if(dimids)
	free(dimids);
    return is_coord;
}


/* Return true if user-defined type */
int
is_user_defined_type(nc_type type) {
    nctype_t *typeinfop = get_typeinfo(type);
    return (typeinfop->class > 0);
}


/*
 * Return name of type in user-allocated space, whether built-in
 * primitive type or user-defined type.  Note: name must have enough
 * space allocated to hold type name.
 */
void
get_type_name(int ncid, nc_type type, char *name)
{
#ifdef USE_NETCDF4
    if (is_user_defined_type(type)) {
	NC_CHECK(nc_inq_user_type(ncid, type, name, NULL, NULL, NULL, NULL));
    } else {
	strncpy(name, prim_type_name(type), NC_MAX_NAME + 1);
    }
#else
    strncpy(name, prim_type_name(type), NC_MAX_NAME + 1);
#endif /* USE_NETCDF4 */
}

/*
 * Print type name with CDL escapes for special characters.  locid is
 * the id of the group in which the type is referenced, which is
 * needed to determine whether an absolute type name must be printed.
 * If the type is defined in the referenced group or in some ancestor
 * group, only the simple type name is printed.  If the type is
 * defined in some other non-ancestor group, an absolute path for the
 * typename is printed instead.
 */
void
print_type_name(int locid, int typeid) {
    char *ename;
#ifdef USE_NETCDF4
    char name[NC_MAX_NAME+1];
    int type_inherited = 0;
    int curlocid;		/* group we are searching in */
    int parent_groupid = locid;
    int ntypes;
    int stat;
#endif

    assert(typeid > 0 && typeid <= max_type);
    ename = escaped_name(nctypes[typeid]->name);
#ifdef USE_NETCDF4
    if(is_user_defined_type(typeid)) {
	/* determine if type is inherited, that is if defined in this
	 * group or any ancestor group */
	name[NC_MAX_NAME] = '\0';
	strncpy(name,nctypes[typeid]->name,NC_MAX_NAME);
	do {
	    curlocid = parent_groupid;
	    NC_CHECK( nc_inq_typeids(curlocid, &ntypes, NULL) );
	    if(ntypes > 0) {
		int *typeids = (int *) emalloc((size_t)(ntypes + 1) * sizeof(int));
		int i;
		NC_CHECK( nc_inq_typeids(curlocid, &ntypes, typeids) );
		for(i = 0; i < ntypes; i++) {
		    char curname[NC_MAX_NAME];
		    NC_CHECK( nc_inq_type(curlocid, typeids[i], curname, NULL) );
		    if(strncmp(name, curname, NC_MAX_NAME) == 0) {
			type_inherited = 1;
			break;
		    }
		}
		free(typeids);
		if(type_inherited)
		    break;
	    }
	    stat = nc_inq_grp_parent(curlocid, &parent_groupid);
	} while (stat != NC_ENOGRP && stat != NC_ENOTNC4);

	if (type_inherited == 0) {
	    char *gname = nctypes[typeid]->grps;
	    print_name(gname);
	    fputs("/", stdout);
	}
    }
#endif	/*  USE_NETCDF4 */
    fputs(ename, stdout);
    free(ename);
}

/* Allocate and initialize table of unlimited dimensions for ncid, for
 * use by is_unlim_dim() function.  If ncid is a subgroup of a netCDF
 * dataset, the table will still be initialized for the whole dataset
 * in which the subgroup resides. */
#ifdef USE_NETCDF4
static int
init_is_unlim(int ncid, int **is_unlim_p)
{
    size_t num_grps;	 /* total number of groups */
    int max_dimid = -1;    /* maximum dimid across whole dataset */
    int *grpids = NULL;	 /* temporary list of all grpids */
    int igrp;
    int grpid;

    /* if ncid is not root group, find its ancestor root group id */
    int status = nc_inq_grp_parent(ncid, &grpid);
    while(status == NC_NOERR && grpid != ncid) {
	ncid = grpid;
	status = nc_inq_grp_parent(ncid, &grpid);
    }
    if (status != NC_ENOGRP)
	return NC_EBADGRPID;
    /* Now ncid is root group.  Get total number of groups and their ids */
    NC_CHECK( nc_inq_grps_full(ncid, &num_grps, NULL) );
    grpids = emalloc((num_grps + 1) * sizeof(int));
    NC_CHECK( nc_inq_grps_full(ncid, &num_grps, grpids) );
#define DONT_INCLUDE_PARENTS 0
    /* Get all dimensions in groups and info about which ones are unlimited */
    /* Warning: we cannot assume that the dimension ids are packed */
    /* Find maximum dimension id */
    max_dimid = -1;
    for(igrp = 0; igrp < num_grps; igrp++) {
	int i,ndims;
	int* dimids = NULL;
	grpid = grpids[igrp];
	NC_CHECK( nc_inq_dimids(grpid, &ndims, NULL, DONT_INCLUDE_PARENTS) );
	dimids = (int*)emalloc((size_t)ndims*sizeof(int));
	NC_CHECK( nc_inq_dimids(grpid, &ndims, dimids, DONT_INCLUDE_PARENTS) );
	for(i=0;i<ndims;i++) {if(dimids[i] > max_dimid) max_dimid = dimids[i];}
	free(dimids);
    }
    assert(max_dimid >= 0);
    *is_unlim_p = emalloc((size_t)(max_dimid + 1 + 1) * sizeof(int));
    for(igrp = 0; igrp < num_grps; igrp++) {
	int ndims, idim, *dimids, nundims;
	grpid = grpids[igrp];
	NC_CHECK( nc_inq_dimids(grpid, &ndims, NULL, DONT_INCLUDE_PARENTS) );
	dimids = emalloc((size_t)(ndims + 1) * sizeof(int));
	NC_CHECK( nc_inq_dimids(grpid, &ndims, dimids, DONT_INCLUDE_PARENTS) );
	/* mark all dims in this group as fixed-size */
	for(idim = 0; idim < ndims; idim++) {
	    int* isunlim = *is_unlim_p;
	    int did = dimids[idim];
	    isunlim[did] = 0;
	}
	NC_CHECK( nc_inq_unlimdims(grpid, &nundims, dimids) );
	assert(nundims <= ndims);
	/* mark the subset of dims in this group that are unlimited */
	for(idim = 0; idim < nundims; idim++) {
	    int* isunlim = *is_unlim_p;
	    int did = dimids[idim];
	    isunlim[did] = 1;
	}
	if(dimids)
	    free(dimids);
    }
    free(grpids);
    return NC_NOERR;
}
#endif	/*  USE_NETCDF4 */

/* TODO: make list of these arrays for multiple open datasets, such as
 * the idnode_t lists above.  For now, we just have one of these, for
 * the unique input dataset for this invocation of ncdump. */

#define UNLIM_NOT_INITIALIZED (-1)

/* Is dimid the dimension ID of an unlimited dimension?  */
bool_t
is_unlim_dim(int ncid, int dimid) {
    bool_t result;		/* 0 if fixed, 1 if unlimited size */
    static int for_ncid = UNLIM_NOT_INITIALIZED; /* ensure only ever called for one ncid */
#ifdef USE_NETCDF4
    static int *is_unlim = NULL; /* gets allocated by init_is_unlim() */
    if(for_ncid == UNLIM_NOT_INITIALIZED) {
      NC_CHECK(init_is_unlim(ncid, &is_unlim));
      for_ncid = ncid;
    }
    assert(is_unlim);
    result = is_unlim[dimid];	/* 0 if fixed, 1 if unlimited size */
#else
    static int unlimdimid;
    if(for_ncid == UNLIM_NOT_INITIALIZED) {
	NC_CHECK( nc_inq_unlimdim(ncid, &unlimdimid) );
	for_ncid = ncid;
    }
    result = (dimid == unlimdimid) ;
#endif 	 /* USE_NETCDF4 */
    return result;
}
