/*********************************************************************
 *   Copyright 1993, UCAR/Unidata
 *   See netcdf/COPYRIGHT file for copying and redistribution conditions.
 *   $Header: /upc/share/CVS/netcdf-3/ncdump/vardata.c,v 1.48 2010/05/05 22:15:39 dmh Exp $
 *********************************************************************/

#include <config.h>
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <netcdf.h>
#include "utils.h"
#include "nccomps.h"
#include "dumplib.h"
#include "ncdump.h"
#include "indent.h"
#include "vardata.h"

/* maximum len of string needed for one value of a primitive type */
#define MAX_OUTPUT_LEN 100

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

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

/* Only read this many values at a time, if last dimension is larger
  than this */
#define VALBUFSIZ 8096

static int linep;		/* line position, not counting global indent */
static int max_line_len;	/* max chars per line, not counting global indent */


/* set position in line before lput() calls */
static void
set_indent(int in) {
    linep = in;
}


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


/* 
 * Output a string that should not be split across lines.  If it would
 * make current line too long, first output a newline and current
 * (nested group) indentation, then continuation indentation, then
 * output string.
 */
void
lput(const char *cp) {
    size_t nn = strlen(cp);

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


/*--------------------------------------------------------------------------*/

/* Support function for print_att_times.
 * Output a string that should not be split across lines.
 * Keep track of position on print line.
 * Wrap print lines as needed to keep within requested line length.
 * Start CDL comment on each new line.
 * Handle line indentation.
 *
 * This function is like lput in vardata.c, with variations.
 * Hopefully this function will later be absorbed into a more
 * general lput-type function.
 */

#define CDL_COMMENT_PREFIX "// "
void
lput2(
    const char *cp,		/* string to print */
    boolean first_item,		/* identify first item in list */
    boolean wrap		/* line wrap control: true=enable,
    				 * false=stay on same line  */
    )
{
    static int linep;			/* current line position (number of */
    					/*   chars); saved between calls    */
    int len_prefix = strlen (CDL_COMMENT_PREFIX);
    boolean make_newline;
    
    size_t len1 = strlen(cp);		/* length of input string */

    assert (len1 > 0);

/* (1) Single space or newline/indent sequence, as needed. */
    
    linep = linep + 1 + len1;		/* new line position, without newline */
    					/* add 1 extra for preceeding space   */
    
    make_newline = (wrap && (first_item || linep > max_line_len + 2));
    					/* NEVER new line in no-wrap mode */
    
    if (make_newline) {			/* start new line, if needed */
        printf ("\n");
	indent_out();			/* same exact indentation as pr_att */
	printf ("\t\t");		/* (possible problem here) */
        printf ("  ");			/* add indent for CDL comment */
	linep = 16 + 2 + len1;		/* recompute new line position */
        				/* with newline + indents      */
    } else {
	printf (" ");			/* always one space, if not newline */
    }

/* (2) Add CDL comment prefix, if needed. */

    if (len_prefix > 0) {
	if (first_item || make_newline) {
	    printf (CDL_COMMENT_PREFIX);
            linep = linep + len_prefix;
	}
    }

/* (3) Output caller's string value. */

    printf ("%s", cp);
}


/*
 * Output a value of an attribute.
 */
static void
print_any_att_val (
    struct safebuf_t *sb,	/* string where output goes */
    const ncatt_t *attp,	/* attrbute */
    const void *valp		/* pointer to the value */
	    ) {
    nctype_t *typ = attp->tinfo;
    (*typ->typ_tostring)(typ, sb, valp);
}


/*
 * Output a value of a variable, except if there is a fill value for
 * the variable and the value is the fill value, print the fill-value string
 * instead.  (Floating-point fill values need only be within machine epsilon of
 * defined fill value.)
 */
static void
print_any_val(
    safebuf_t *sb,		/* string where output goes */
    const ncvar_t *varp,	/* variable */
    const void *valp		/* pointer to the value */
	    )
{
    if (varp->has_fillval && 
	(*(varp->tinfo->val_equals))((const nctype_t *)varp->tinfo, 
				     (const void*)varp->fillvalp, valp) ) {
	sbuf_cpy(sb, FILL_STRING);
    } else {
	(*varp->val_tostring)(varp, sb, valp);
    }
}

/*
 * print last delimiter in each line before annotation (, or ;)
 */
static void
lastdelim (boolean more, boolean lastrow)
{
    if (more) {
	printf(", ");
    } else {
	if(lastrow) {
	    printf(";");
	} else {
	    printf(",");
	}
    }
}

/*
 * print last delimiter in each line before annotation (, or ;)
 */
static void
lastdelim2 (boolean more, boolean lastrow)
{
    if (more) {
	lput(", ");
    } else {
	if(lastrow) {
	    lput(" ;");
	    lput("\n");
	} else {
	    lput(",\n");
	    lput("  ");
	}
    }
}


/*
 * Print a number of attribute values
 */
void
pr_any_att_vals(
     const ncatt_t *ap,		/* attribute */
     const void *vals		/* pointer to block of values */
     )
{
    size_t iel;
    size_t len = ap->len;		/* number of values to print */
    const char *valp = (const char *)vals;
    safebuf_t *sb = sbuf_new();

    for (iel = 0; iel < len - 1; iel++) {
	print_any_att_val(sb, ap, (void *)valp);
	valp += ap->tinfo->size; /* next value according to type */
	sbuf_cat(sb, iel == len - 1 ? "" : ", ");
	lput(sbuf_str(sb));
    }
    print_any_att_val(sb, ap, (void *)valp);
    lput(sbuf_str(sb));
    sbuf_free(sb);
}


/*
 * Annotates a value in data section with var name and indices in comment
 */
static void
annotate(
     const ncvar_t *vp,	/* variable */
     const size_t *cor,		/* corner coordinates */
     long iel			/* which element in current row */
     )
{
    int vrank = vp->ndims;
    int id;
    
    /* print indices according to data_lang */
/*     printf("  // %s(", vp->name); */
    printf("  // ");
    print_name(vp->name);
    printf("(");
    switch (formatting_specs.data_lang) {
      case LANG_C:
	/* C variable indices */
	for (id = 0; id < vrank-1; id++)
	  printf("%lu,", (unsigned long) cor[id]);
	printf("%lu", (unsigned long) cor[id] + iel);
	break;
      case LANG_F:
	/* Fortran variable indices */
	printf("%lu", (unsigned long) cor[vrank-1] + iel + 1);
	for (id = vrank-2; id >=0 ; id--) {
	    printf(",%lu", 1 + (unsigned long) cor[id]);
	}
	break;
    }
    printf(")\n    ");
}


/*
 * Print a number of variable values, where the optional comments
 * for each value identify the variable, and each dimension index.
 */
static void
pr_any_vals(
     const ncvar_t *vp,		/* variable */
     size_t len,		/* number of values to print */
     boolean more,		/* true if more data for this row will
				 * follow, so add trailing comma */
     boolean lastrow,		/* true if this is the last row for this
				 * variable, so terminate with ";" instead
				 * of "," */
     const void *vals,		/* pointer to block of values */
     const size_t *cor		/* corner coordinates */
     )
{
    long iel;
    safebuf_t *sb = sbuf_new();
    const char *valp = (const char *)vals;

    for (iel = 0; iel < len-1; iel++) {
	print_any_val(sb, vp, (void *)valp);
	valp += vp->tinfo->size; /* next value according to type */
	if (formatting_specs.full_data_cmnts) {
	    printf("%s, ", sb->buf);
	    annotate (vp, cor, iel);
	} else {
	    sbuf_cat(sb, ", ");
	    lput(sbuf_str(sb));
	}
    }
    print_any_val(sb, vp, (void *)valp);
    if (formatting_specs.full_data_cmnts) {
	printf("%s", sbuf_str(sb));
	lastdelim (more, lastrow);
	annotate (vp, cor, iel);
    } else {
	lput(sbuf_str(sb));
	lastdelim2 (more, lastrow);
    }
    sbuf_free(sb);
}


/*
 * Print a number of char variable values as a text string, where the
 * optional comments for each value identify the variable, and each
 * dimension index.
 */
static void
pr_tvals(
     const ncvar_t *vp,		/* variable */
     size_t len,		/* number of values to print */
     boolean more,		/* true if more data for this row will
				 * follow, so add trailing comma */
     boolean lastrow,		/* true if this is the last row for this
				 * variable, so terminate with ";" instead
				 * of "," */
     const char *vals,		/* pointer to block of values */
     const size_t *cor		/* corner coordinates */
     )
{
    long iel;
    const char *sp;

    printf("\"");
    /* 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 = *vals++ & 0377) {
	case '\b':
	    printf("\\b");
	    break;
	case '\f':
	    printf("\\f");
	    break;
	case '\n':	/* generate linebreaks after new-lines */
	    printf("\\n\",\n    \"");
	    break;
	case '\r':
	    printf("\\r");
	    break;
	case '\t':
	    printf("\\t");
	    break;
	case '\v':
	    printf("\\v");
	    break;
	case '\\':
	    printf("\\\\");
	    break;
	case '\'':
	    printf("\\\'");
	    break;
	case '\"':
	    printf("\\\"");
	    break;
	default:
	    if (isprint(uc))
		printf("%c",uc);
	    else
		printf("\\%.3o",uc);
	    break;
	}
    }
    printf("\"");
    /* if (fsp && formatting_specs.full_data_cmnts) { */
    if (formatting_specs.full_data_cmnts) {
	lastdelim (more, lastrow);
	annotate (vp,  (size_t *)cor, 0L);
    } else {
	lastdelim2 (more, lastrow);
    }
}


/*
 * Updates a vector of ints, odometer style.  Returns 0 if odometer
 * overflowed, else 1.
 */
static int
upcorner(
     const size_t *dims,	/* The "odometer" limits for each dimension */
     int ndims,			/* Number of dimensions */
     size_t* odom,		/* The "odometer" vector to be updated */
     const size_t* add		/* A vector to "add" to odom on each update */
     )
{
    int id;
    int ret = 1;

    for (id = ndims-1; id > 0; id--) {
	odom[id] += add[id];
	if(odom[id] >= dims[id]) {
	    odom[id-1]++;
	    odom[id] -= dims[id];
	}
    }
    odom[0] += add[0];
    if (odom[0] >= dims[0])
      ret = 0;
    return ret;
}


/* Output the data for a single variable, in CDL syntax. */
int
vardata(
     const ncvar_t *vp,		/* variable */
     size_t vdims[],		/* variable dimension sizes */
     int ncid,			/* netcdf id */
     int varid			/* variable id */
     )
{
    size_t *cor;	     /* corner coordinates */
    size_t *edg;	     /* edges of hypercube */
    size_t *add;	     /* "odometer" increment to next "row"  */
    size_t gulp;
    void *vals;

    int id;
    int ir;
    size_t nels;
    size_t ncols;
    size_t nrows;
    int vrank = vp->ndims;

    cor = (size_t *) emalloc((1 + vrank) * sizeof(size_t));
    edg = (size_t *) emalloc((1 + vrank) * sizeof(size_t));
    add = (size_t *) emalloc((1 + vrank) * sizeof(size_t));

    nels = 1;
    if(vrank == 0) { /*scalar*/
	cor[0] = 0;
	edg[0] = 1;
    } for (id = 0; id < vrank; id++) {
	cor[id] = 0;
	edg[id] = 1;
	nels *= vdims[id];	/* total number of values for variable */
    }

    printf("\n");
    indent_out();
/* 	printf(" %s = ", vp->name); */
/*          or      */
/* 	printf(" %s =\n  ", vp->name); */
	printf(" ");
	print_name(vp->name);
    if (vrank <= 1) {
	printf(" = ");
	set_indent ((int)strlen(vp->name) + 4 + indent_get());
    } else {
	printf(" =\n  ");
	set_indent (2 + indent_get());
    }

    if (vrank < 1) {
	ncols = 1;
    } else {
	ncols = vdims[vrank-1];	/* size of "row" along last dimension */
	edg[vrank-1] = vdims[vrank-1];
	for (id = 0; id < vrank; id++)
	  add[id] = 0;
	if (vrank > 1)
	  add[vrank-2] = 1;
    }
    nrows = nels/ncols;		/* number of "rows" */
    gulp = ncols < VALBUFSIZ ? ncols : VALBUFSIZ;
    vals = emalloc(gulp * vp->tinfo->size);
    
    for (ir = 0; ir < nrows; ir++) {
	/*
	 * rather than just printing a whole row at once (which might
	 * exceed the capacity of some platforms), we break each row
	 * into smaller chunks, if necessary.
	 */
	size_t corsav = 0;
	int left = (int)ncols;
	boolean lastrow;

	if (vrank > 0) {
	    corsav = cor[vrank-1];
	    if (formatting_specs.brief_data_cmnts != false
		&& vrank > 1
		&& left > 0) {	/* print brief comment with indices range */
/* 		printf("// %s(",vp->name); */
		printf("// ");
		print_name(vp->name);
		printf("(");
		switch (formatting_specs.data_lang) {
		  case LANG_C:
		    /* print brief comment with C variable indices */
		    for (id = 0; id < vrank-1; id++)
		      printf("%lu,", (unsigned long)cor[id]);
		    if (vdims[vrank-1] == 1)
		      printf("0");
		    else
		      printf(" 0-%lu", (unsigned long)vdims[vrank-1]-1);
		    break;
		  case LANG_F:
		    /* print brief comment with Fortran variable indices */
		    if (vdims[vrank-1] == 1)
		      printf("1");
		    else
		      printf("1-%lu ", (unsigned long)vdims[vrank-1]);
		    for (id = vrank-2; id >=0 ; id--) {
			printf(",%lu", (unsigned long)(1 + cor[id]));
		    }
		    break;
		}
		printf(")\n");
		indent_out();
		printf("    ");
		set_indent(4 + indent_get());
	    }
	}
	lastrow = (boolean)(ir == nrows-1);
	while (left > 0) {
	    size_t toget = left < gulp ? left : gulp;
	    if (vrank > 0)
	      edg[vrank-1] = toget;
	    NC_CHECK(nc_get_vara(ncid, varid, cor, edg, vals));
	    /* Test if we should treat array of chars as a string  */
	    if(vp->type == NC_CHAR && 
	       (vp->fmt == 0 || STREQ(vp->fmt,"%s") || STREQ(vp->fmt,""))) {
	        pr_tvals(vp, toget, left > toget, lastrow, (char *) vals, cor);
	    } else {
	        pr_any_vals(vp, toget, left > toget, lastrow, vals, cor);
	    }

	    left -= toget;
	    if (vrank > 0)
	      cor[vrank-1] += toget;
	}
	if (vrank > 0)
	  cor[vrank-1] = corsav;
	if (ir < nrows-1)
	  if (!upcorner(vdims,vp->ndims,cor,add))
	    error("vardata: odometer overflowed!");
	set_indent(2);
    }

    free(vals);
    free(cor);
    free(edg);
    free(add);

    return 0;
}


/*
 * print last delimiter in each line before annotation (, or ;)
 */
static void
lastdelim2x (boolean more, boolean lastrow)
{
    if (more) {
	lput(" ");
    } else {
	if(lastrow) {
	    lput("\n   ");
	} else {
	    lput("\n     ");
	}
    }
}


/*
 * Print a number of char variable values as a text string for NcML
 */
static void
pr_tvalsx(
     const ncvar_t *vp,		/* variable */
     size_t len,		/* number of values to print */
     boolean more,		/* true if more data for this row will
				 * follow, so add trailing comma */
     boolean lastrow,		/* true if this is the last row for this
				 * variable, so terminate with ";" instead
				 * of "," */
     const char *vals		/* pointer to block of values */
     )
{
    long iel;
    const char *sp;

    printf("\"");
    /* 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 = *vals++ & 0377) {
	case '\b':
	    printf("\\b");
	    break;
	case '\f':
	    printf("\\f");
	    break;
	case '\n':	/* generate linebreaks after new-lines */
	    printf("\\n\",\n    \"");
	    break;
	case '\r':
	    printf("\\r");
	    break;
	case '\t':
	    printf("\\t");
	    break;
	case '\v':
	    printf("\\v");
	    break;
	case '\\':
	    printf("\\\\");
	    break;
	case '\'':
	    printf("\\\'");
	    break;
	case '\"':
	    printf("\\\"");
	    break;
	default:
	    if (isprint(uc))
		printf("%c",uc);
	    else
		printf("\\%.3o",uc);
	    break;
	}
    }
    printf("\"");
    lastdelim2x (more, lastrow);
}


/*
 * Print a number of variable values for NcML
 */
static void
pr_any_valsx(
     const ncvar_t *vp,		/* variable */
     size_t len,		/* number of values to print */
     boolean more,		/* true if more data for this row will
				 * follow, so add trailing comma */
     boolean lastrow,		/* true if this is the last row for this
				 * variable, so terminate with ";" instead
				 * of "," */
     const void *vals		/* pointer to block of values */
     )
{
    long iel;
    safebuf_t *sb = sbuf_new();
    const char *valp = (const char *)vals;

    for (iel = 0; iel < len-1; iel++) {
	print_any_val(sb, vp, (void *)valp);
	valp += vp->tinfo->size; /* next value according to type */
	sbuf_cat(sb, " ");
	lput(sbuf_str(sb));
    }
    print_any_val(sb, vp, (void *)valp);
    lput(sbuf_str(sb));
    lastdelim2x (more, lastrow);
    sbuf_free(sb);
}


/* Output the data for a single variable, in NcML syntax.
 *  TODO: currently not called, need option for NcML with values ... */
int
vardatax(
     const ncvar_t *vp,		/* variable */
     size_t vdims[],		/* variable dimension sizes */
     int ncid,			/* netcdf id */
     int varid			/* variable id */
     )
{
    size_t *cor;	     /* corner coordinates */
    size_t *edg;	     /* edges of hypercube */
    size_t *add;	     /* "odometer" increment to next "row"  */
    size_t gulp;
    void *vals;

    int id;
    int ir;
    size_t nels;
    size_t ncols;
    size_t nrows;
    int vrank = vp->ndims;

    cor = (size_t *) emalloc((vrank + 1) * sizeof(size_t));
    edg = (size_t *) emalloc((vrank + 1) * sizeof(size_t));
    add = (size_t *) emalloc((vrank + 1) * sizeof(size_t));

    nels = 1;
    for (id = 0; id < vrank; id++) {
	cor[id] = 0;
	edg[id] = 1;
	nels *= vdims[id];	/* total number of values for variable */
    }

    printf("    <values>\n     ");
    set_indent (7);

    if (vrank < 1) {
	ncols = 1;
    } else {
	ncols = vdims[vrank-1];	/* size of "row" along last dimension */
	edg[vrank-1] = vdims[vrank-1];
	for (id = 0; id < vrank; id++)
	  add[id] = 0;
	if (vrank > 1)
	  add[vrank-2] = 1;
    }
    nrows = nels/ncols;		/* number of "rows" */
    gulp = ncols < VALBUFSIZ ? VALBUFSIZ : ncols;
    vals = emalloc(gulp * vp->tinfo->size);
    
    for (ir = 0; ir < nrows; ir++) {
	/*
	 * rather than just printing a whole row at once (which might
	 * exceed the capacity of some platforms), we break each row
	 * into smaller chunks, if necessary.
	 */
	size_t corsav;
	int left = (int)ncols;
	boolean lastrow;

	if (vrank > 0) {
	    corsav = cor[vrank-1];
	}
	lastrow = (boolean)(ir == nrows-1);
	while (left > 0) {
	    size_t toget = left < gulp ? left : gulp;
	    if (vrank > 0)
	      edg[vrank-1] = toget;
	    NC_CHECK(nc_get_vara(ncid, varid, cor, edg, vals) );

	    /* Test if we should treat array of chars as a string  */
	    if(vp->type == NC_CHAR && 
	       (vp->fmt == 0 || STREQ(vp->fmt,"%s") || STREQ(vp->fmt,""))) {
	        pr_tvalsx(vp, toget, left > toget, lastrow, (char *) vals);
	    } else {
	        pr_any_valsx(vp, toget, left > toget, lastrow, vals);
	    }

	    left -= toget;
	    if (vrank > 0)
	      cor[vrank-1] += toget;
	}
	if (vrank > 0)
	  cor[vrank-1] = corsav;
	if (ir < nrows-1)
	  if (!upcorner(vdims,vp->ndims,cor,add))
	    error("vardata: odometer overflowed!");
	set_indent(2);
    }
    printf(" </values>\n");
    free(vals);
    free(cor);
    free(edg);
    free(add);
    return 0;
}
