/*********************************************************************
 *   Copyright 1993, 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.11 2007/02/16 13:39:09 ed 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 <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "netcdf.h"
#include "dumplib.h"
#include "ncdump.h"

static char* has_c_format_att(int ncid, int varid);
static vnode* newvnode(void);

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

/*
 * Print error message to stderr and exit
 */
void
error(const char *fmt, ...)
{
    va_list args ;

    (void) fprintf(stderr,"%s: ", progname);
    va_start(args, fmt) ;
    (void) vfprintf(stderr,fmt,args) ;
    va_end(args) ;

    (void) fprintf(stderr, "\n") ;
    (void) fflush(stderr);	/* to ensure log files are current */
    exit(EXIT_FAILURE);
}

void *
emalloc (			/* check return from malloc */
	size_t size)
{
    void   *p;

    p = (void *) malloc (size);
    if (p == 0) {
	error ("out of memory\n");
    }
    return p;
}

#define LINEPIND	"    "	/* indent of continued lines */

static int linep;
static int max_line_len;

void
set_indent(int in)
{
    linep = in;
}


void
set_max_len(int len)
{
    max_line_len = len-2;
}


void
lput(const char *cp)
{
    size_t nn = strlen(cp);

    if (nn+linep > max_line_len && nn > 2) {
	(void) fputs("\n", stdout);
	(void) fputs(LINEPIND, stdout);
	linep = (int)strlen(LINEPIND);
    }
    (void) fputs(cp,stdout);
    linep += nn;
}

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


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


/*
 * Determine print format to use for each value for this variable.  Use value
 * of attribute C_format if it exists, otherwise a sensible default.
 */
const char *
get_fmt(
     int ncid,			/* netcdf id */
     int varid,			/* variable id */
     nc_type type		/* netCDF data type */
     )
{
    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 && type == NC_FLOAT)
	return float_var_fmt;

    if (double_precision_specified && type == 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;    

    /* Otherwise return sensible default. */
    switch (type) {
      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;
#ifdef USE_NETCDF4
       case NC_UBYTE:
	  return "%d";
       case NC_USHORT:
	  return "%d";
       case NC_UINT:
	  return "%d";
       case NC_INT64:
       case NC_UINT64:
	  return "%lld";
       case NC_STRING:
	  return "%s";
#endif	/* USE_NETCDF4 */
      default:
	error("pr_vals: bad type");
    }

    return 0;
}


static vnode*
newvnode(void)
{
    vnode *newvp = (vnode*) emalloc(sizeof(vnode));
    return newvp;
}


/*
 * Get a new, empty variable list.
 */
vnode*
newvlist(void)
{
    vnode *vp = newvnode();

    vp -> next = 0;
    vp -> id = -1;		/* bad id */

    return vp;
}


void
varadd(vnode* vlist, int varid)
{
    vnode *newvp = newvnode();
    
    newvp -> next = vlist -> next;
    newvp -> id = varid;
    vlist -> next = newvp;
}


/* 
 * return 1 if variable identified by varid is member of variable
 * list vlist points to.
 */
int
varmember(const vnode* vlist, int varid)
{
    vnode *vp = vlist -> next;

    for (; vp ; vp = vp->next)
      if (vp->id == varid)
	return 1;
    return 0;    
}


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

    NC_CHECK( nc_inq_ndims(ncid, &ndims) );
    dims = (ncdim_t *) emalloc((ndims + 1) * sizeof(ncdim_t));
    for (dimid = 0; dimid < ndims; dimid++) {
	NC_CHECK( nc_inq_dimname(ncid, 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;
	}
    }
    free(dims);
    return is_coord;
}


/*
 * return 1 if varid identifies a record variable
 * else return 0
 */
int
isrecvar(int ncid, int varid)
{
    int recdimid;
    int ndims;
    int is_recvar = 0;
    int *dimids;

    NC_CHECK( nc_inq_unlimdim(ncid, &recdimid) );
    NC_CHECK( nc_inq_varndims(ncid, varid, &ndims) );
    if (ndims > 0) {
	dimids = (int *) emalloc((ndims + 1) * sizeof(int));
	NC_CHECK( nc_inq_vardimid(ncid, varid, dimids) );
	/* TODO assumes only one unlimited dimension, but netcdf4 alllows multiple */
	if(dimids[0] == recdimid)
	    is_recvar = 1;
	free(dimids);
    }
    return is_recvar;
}


syntax highlighted by Code2HTML, v. 0.9.1