/*********************************************************************
 *   Copyright 2018, 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 "netcdf.h"
#include "nccomps.h"
#include "dumplib.h"
#include "ncdump.h"
#include "indent.h"
#include "vardata.h"
#include "netcdf_aux.h"

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

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

#define LBRACE "{"
#define RBRACE "}"

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 10000

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.  If string ends with a newline to force short line,
 * reset indentation after output.
 */
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);
    if (nn > 0 && cp[nn - 1] == '\n') {
	linep = indent_get();
    } else
	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 */
    bool_t first_item,		/* identify first item in list */
    bool_t 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);
    bool_t 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 preceding 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 (bool_t more, bool_t lastrow)
{
    if (more) {
	printf(", ");
    } else {
	if(lastrow) {
	    printf(";");
	} else {
	    printf(",");
	}
    }
}

/*
 * print last delimiter in each line before annotation (, or ;)
 */
static void
lastdelim2 (bool_t more, bool_t 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);
}

/*
 * Prints brief annotation for a row of data values
 */
static void
annotate_brief(
    const ncvar_t *vp,		/* variable */
    const size_t *cor,		/* corner coordinates */
    size_t vdims[]		/* variable dimension sizes */
    )
{
    int vrank = vp->ndims;
    int id;
    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());
}

/*
 * 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 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 */
     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 (formatting_specs.full_data_cmnts) { */
    /* 	lastdelim (0, lastrow); */
    /* 	annotate (vp,  (size_t *)cor, 0L); */
    /* }  */
}


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

/*  Print data values for variable varid.
 *
 * Recursive to handle possibility of variables with multiple
 * unlimited dimensions, for which the CDL syntax requires use of "{"
 * and "}" in data section to disambiguate the size of nested records
 * in a simple linear list of values.
 */
static int
print_rows(
    int level,          /* 0 at top-level, incremented for each recursive level */
    int ncid,		/* netcdf id */
    int varid,		/* variable id */
    const ncvar_t *vp,	/* variable */
    size_t vdims[],    	/* variable dimension sizes */
    size_t cor[],      	/* corner coordinates */
    size_t edg[],      	/* edges of hypercube */
    void *vals,   	/* allocated buffer for ncols values in a row */
    int marks_pending	/* number of pending closing "}" record markers */
    )
{
    int rank = vp->ndims;
    size_t ncols = rank > 0 ? vdims[rank - 1] : 1; /* number of values in a row */
    int d0 = 0;
    size_t inc = 1;
    int i;
    bool_t mark_record = (level > 0 && is_unlim_dim(ncid, vp->dims[level]));
    safebuf_t *sb = sbuf_new();
    if (rank > 0)
	d0 = vdims[level];
    for(i = level + 1; i < rank; i++) {
	inc *= vdims[i];
    }
    if(mark_record) { /* the whole point of this recursion is printing these "{}" */
	lput(LBRACE);
	marks_pending++;	/* matching "}"s to emit after last "row" */
    }
    if(rank - level > 1) {     	/* this level is just d0 next levels */
	size_t *local_cor = emalloc((rank + 1) * sizeof(size_t));
	size_t *local_edg = emalloc((rank + 1) * sizeof(size_t));
	for(i = 0; i < rank; i++) {
	    local_cor[i] = cor[i];
	    local_edg[i] = edg[i];
	}
	local_cor[level] = 0;
	local_edg[level] = 1;
	for(i = 0; i < d0 - 1; i++) {
	    print_rows(level + 1, ncid, varid, vp, vdims,
		       local_cor, local_edg, vals, 0);
	    local_cor[level] += 1;
	}
	print_rows(level + 1, ncid, varid, vp, vdims,
		   local_cor, local_edg, vals, marks_pending);
	free(local_edg);
	free(local_cor);
    } else {			/* bottom out of recursion */
	char *valp = vals;
	bool_t lastrow;
	int j;
	if(formatting_specs.brief_data_cmnts && rank > 1 && ncols > 0) {
	    annotate_brief(vp, cor, vdims);
	}
	NC_CHECK(nc_get_vara(ncid, varid, cor, edg, (void *)valp));

	/* Test if we should treat array of chars as strings along last dimension  */
	if(vp->type == NC_CHAR && (vp->fmt == 0 || NCSTREQ(vp->fmt,"%s") || NCSTREQ(vp->fmt,""))) {
	    pr_tvals(vp, ncols, vals, cor);
	} else {			/* for non-text variables */
	    for(i=0; i < d0 - 1; i++) {
		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, i);
		} else {
		    sbuf_cat(sb, ", ");
		    lput(sbuf_str(sb));
		}
	    }
	    print_any_val(sb, vp, (void *)valp);
	}
        /* In case vals has memory hanging off e.g. vlen or string, make sure to reclaim it */
        (void)ncaux_reclaim_data(ncid,vp->type,vals,ncols);

	/* determine if this is the last row */
	lastrow = true;
	for(j = 0; j < rank - 1; j++) {
            if (cor[j] != vdims[j] - 1) {
		lastrow = false;
		break;
            }
	}
	if (formatting_specs.full_data_cmnts) {
      for (j = 0; j < marks_pending; j++) {
		sbuf_cat(sb, RBRACE);
      }
      printf("%s", sbuf_str(sb));
      lastdelim (0, lastrow);
      annotate (vp, cor, (d0 > 0 ? d0-1 : d0));
	} else {
            for (j = 0; j < marks_pending; j++) {
		sbuf_cat(sb, RBRACE);
            }
      	    lput(sbuf_str(sb));
            lastdelim2 (0, lastrow);
	}
    }
    sbuf_free(sb);
    return NC_NOERR;
}

/* 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"  */
    void *vals;

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

    int level = 0;
    int marks_pending = 0;

    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;
    } else {
	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(" ");
    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 == 0) {
	ncols = 1;
    } else {
	ncols = vdims[vrank-1];	/* size of "row" along last dimension */
	edg[vrank-1] = ncols;
	for (id = 0; id < vrank; id++)
	  add[id] = 0;
	if (vrank > 1)
	  add[vrank-2] = 1;
    }
    vals = emalloc(ncols * vp->tinfo->size);

    NC_CHECK(print_rows(level, ncid, varid, vp, vdims, cor, edg, vals, marks_pending));
    free(vals);
    free(cor);
    free(edg);
    free(add);

    return 0;
}


/*
 * print last delimiter in each line before annotation (, or ;)
 */
static void
lastdelim2x (bool_t more, bool_t 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 */
     bool_t more,		/* true if more data for this row will
				 * follow, so add trailing comma */
     bool_t 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 */
     bool_t more,		/* true if more data for this row will
				 * follow, so add trailing comma */
     bool_t 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"  */
    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" */
    vals = emalloc(ncols * vp->tinfo->size);

    for (ir = 0; ir < nrows; ir++) {
	size_t corsav = 0;
	bool_t lastrow;

	if (vrank > 0) {
	    corsav = cor[vrank-1];
	}
	lastrow = (bool_t)(ir == nrows-1);

	if (vrank > 0)
	    edg[vrank-1] = ncols;
	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 || NCSTREQ(vp->fmt,"%s") || NCSTREQ(vp->fmt,""))) {
	    pr_tvalsx(vp, ncols, 0, lastrow, (char *) vals);
	} else {
	    pr_any_valsx(vp, ncols, 0, lastrow, vals);
	}

	if (vrank > 0)
	    cor[vrank-1] += ncols;

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