/*********************************************************************
 *   Copyright 1993, University Corporation for Atmospheric Research
 *   See netcdf/README file for copying and redistribution conditions.
 *   $Header: /export/home/russ/src/netcdf/ncdump/RCS/ncdump.c,v 1.32 1993/02/17 16:54:07 russ Exp $
 *********************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include <netcdf.h>

#ifdef HAVE_UDUNITS
#include "udunits.h"
#endif

#include "ncdump.h"
#include "dumplib.h"
#include "vardata.h"

#include "util.h"
#include "template.h"

char *progname;

static void
usage()
{
#define USAGE   "\
  file             File name of input netCDF file\n\n\
  The summarizer will write the SOIF attribute value\n\
  pairs required by Harvest to standard out.\n\
  The information that is index is the sames as ncdump -c\n\
  The indivdual entries of the record variable are not indexed.\n"
  

    (void) fprintf(stderr,
		   "%s file\n%s",
		   progname,
		   USAGE);
    exit(EXIT_FAILURE);
}


/* 
 * convert pathname of netcdf file into name for cdl unit, by taking 
 * last component of path and stripping off any extension.
 */
static char *
name_path(path)
     char *path;
{
    char *cp, *new;

#ifdef vms
#define FILE_DELIMITER ']'
#endif    
#ifdef MSDOS
#define FILE_DELIMITER '\\'
#endif    
#ifndef FILE_DELIMITER /* default to unix */
#define FILE_DELIMITER '/'
#endif
    cp = strrchr(path, FILE_DELIMITER);
    if (cp == 0)		/* no delimiter */
      cp = path;
    else			/* skip delimeter */
      cp++;
    new = (char *) malloc((unsigned) (strlen(cp)+1));
    if (new == 0) {
	error("out of memory!");
	exit(EXIT_FAILURE);
    }
    (void) strcpy(new, cp);	/* copy last component of path */
    if ((cp = strrchr(new, '.')) != NULL)
      *cp = '\0';		/* strip off any extension */
    return new;
}


static char *
type_name(type)
     nc_type type;
{
    switch (type) {
      case NC_BYTE:
	return "byte";
      case NC_CHAR:
	return "char";
      case NC_SHORT:
	return "short";
      case NC_LONG:
	return "long";
      case NC_FLOAT:
	return "float";
      case NC_DOUBLE:
	return "double";
      default:
	error("type_name: bad type %d", type);
	return "bogus";
    }
}


/*
 * Remove trailing zeros (after decimal point) but not trailing decimal
 * point from ss, a string representation of a floating-point number that
 * might include an exponent part.
 */
static void
tztrim(ss)
     char *ss;			/* returned string representing dd */
{
    char *cp, *ep;
    
    cp = ss;
    if (*cp == '-')
      cp++;
    while(isdigit((int)*cp) || *cp == '.')
      cp++;
    if (*--cp == '.')
      return;
    ep = cp+1;
    while (*cp == '0')
      cp--;
    cp++;
    if (cp == ep)
      return;
    while (*ep)
      *cp++ = *ep++;
    *cp = '\0';
    return;
}

/*
 * Print list of attribute values.  Attribute values must be printed with
 * explicit type tags, because their types are not declared.
 */
static void
buf_pr_att_vals(cbuffer, type, len, vals, is_udu_time, is_old_time)
     char *cbuffer;
     nc_type type;
     int len;
     void *vals;
     int is_udu_time ;
     int is_old_time ;
{
    int iel;
    union {
	char *cp;
	short *sp;
	long *lp;
	float *fp;
	double *dp;
    } gp;
    char *sp;
    unsigned char uc;
    char gps[30];		/* for ascii of a float or double precision */
    char *f_fmt = "%#.8g";
    char *d_fmt = "%#.16g";
    void prttime(/* double tval, char *tstr */) ;
    void oldprttime(/* double tval, char *tstr */) ;

    switch (type) {
      case NC_BYTE:
	gp.cp = (char *) vals;
	for (iel = 0; iel < len; iel++)
	  if (isprint(uc = *gp.cp++ & 0377))

	    /* Fill the buffer instead. */

	    sprintf(&cbuffer[strlen(cbuffer)],"'%c'%s", uc, iel<len-1 ? ", " : "");
	  else

	    sprintf (&cbuffer[strlen(cbuffer)],"'\\%o'%s", uc, iel<len-1 ? ", " : "");
	break;
      case NC_CHAR:
	gp.cp = (char *) vals;

	/* adjust len so trailing nulls don't get printed */

	sp = gp.cp + len - 1;
	while (*sp-- == '\0' && len > 0)
	    len--;
	for (iel = 0; iel < len; iel++)
	  switch (uc = *gp.cp++ & 0377) {
	    case '\b':
	      sprintf (&cbuffer[strlen(cbuffer)],"\\b");
	      break;
	    case '\f':
	      sprintf (&cbuffer[strlen(cbuffer)], "\\f");
	      break;
	    case '\n':		/* generate linebreaks after new-lines */
	      /* 
		 Don't generate line breaks after new lines.
		 Do add a couple blanks.
	      */
	         sprintf (&cbuffer[strlen(cbuffer)],"  "); 
	      break;
	    case '\r':
	      sprintf (&cbuffer[strlen(cbuffer)], "\\r");
	      break;
	    case '\t':
	      sprintf (&cbuffer[strlen(cbuffer)], "\\t");
	      break;
	    case '\v':
	      sprintf (&cbuffer[strlen(cbuffer)], "\\v");
	      break;
	    case '\\':
	      sprintf (&cbuffer[strlen(cbuffer)], "\\\\");
	      break;
	    case '\'':
	      sprintf (&cbuffer[strlen(cbuffer)], "'");
	      break;
	    case '\"':
	      break;
	    default:
	      if (isprint(uc))
	        sprintf (&cbuffer[strlen(cbuffer)], "%c", uc) ;
	      else
	        sprintf (&cbuffer[strlen(cbuffer)], "\\%o", uc) ;
	      break;
	  }
	break;
      case NC_SHORT:
	gp.sp = (short *) vals;
	for (iel = 0; iel < len; iel++)
	  sprintf (&cbuffer[strlen(cbuffer)], "%ds%s",*gp.sp++,iel<len-1 ? ", " : "");
	break;
      case NC_LONG:
	gp.lp = (long *) vals;
	for (iel = 0; iel < len; iel++)
	  sprintf (&cbuffer[strlen(cbuffer)], "%ld%s",*gp.lp++,iel<len-1 ? ", " : "");
	break;
      case NC_FLOAT:
	gp.fp = (float *) vals;
	for (iel = 0; iel < len; iel++) {
	    int ll;
	    (void) sprintf(gps, f_fmt, * gp.fp++);
	    /* append a trailing "f" for floating-point attributes */
	    ll = strlen(gps);
	    gps[ll + 1] = '\0';
	    gps[ll] = 'f';
	    tztrim(gps);	/* trim trailing 0's after '.' */
	    sprintf (&cbuffer[strlen(cbuffer)], "%s%s", gps, iel<len-1 ? ", " : "");
	}
	break;
      case NC_DOUBLE:
	gp.dp = (double *) vals;
	for (iel = 0; iel < len; iel++) {
#ifdef HAVE_UDUNITS
	    if (is_udu_time) {
	        prttime(*gp.dp++, gps) ;
	    } else if (is_old_time) {
	        oldprttime(*gp.dp++, gps) ;
	    } else {
	        (void) sprintf(gps, d_fmt, *gp.dp++);
	        tztrim(gps);	/* trim trailing 0's after '.' */
	    }
#else
	    (void) sprintf(gps, d_fmt, *gp.dp++);
            tztrim(gps);    /* trim trailing 0's after '.' */
#endif

	    sprintf(&cbuffer[strlen(cbuffer)],"%s%s", 
                    gps, iel<len-1 ? ", " : "") ;
	}
	break;
      default:
	error("buf_pr_att_vals: bad type");
    }
}

#ifdef HAVE_UDUNITS
utUnit unitstruct ;
#endif
char unitstr[1024] ;

static void
do_ncdump(path, specp)
     char *path;
     struct fspec* specp;
{
    int ndims;			/* number of dimensions */
    int nvars;			/* number of variables */
    int ngatts;			/* number of global attributes */
    int xdimid;			/* id of unlimited dimension */
    int dimid;			/* dimension id */
    int varid;			/* variable id */
    struct ncdim dims[MAX_NC_DIMS]; /* dimensions */
    long vdims[MAX_NC_DIMS];	/* dimension sizes for a single variable */
    struct ncvar var;		/* variable */
    struct ncatt att;		/* attribute */
    int id;			/* dimension number per variable */
    int ia;			/* attribute number */
    int iv;			/* variable number */
    int is_coord;		/* true if variable is a coordinate variable */
    int ncid = ncopen(path, NC_NOWRITE); /* netCDF id */
    varnode* vlist = newvlist(); /* list for vars specified with -v option */
    int is_time_var ;   /* is this variable "time" */
    int is_udu_time ;   /* is this a udunits time variable */
    int is_old_time ;   /* Old CDC standard time; units ="YYYYMMDDHHMMSS" */
    int retcode ;       /* return code from udunits calls */
    int time_varid ;    /* save to know about printing values */

    int iattr;
    int ilet;

/* Build up attribute value pairs in here before printing . */
    char attr1[MAX_NC_NAME]; 
    char attr2[MAX_NC_NAME]; 
    char attr3[MAX_NC_NAME]; 

    char value1[8192];
    char value2[8192];
    char value3[8192];
    char cbuffer[8192];

#include "soif.h"

    /* SOIF template. */

    Template *t;

    /* Make a SOIF template. */
    t = create_template("netCDF file", "");

    time_varid = -1 ;
    is_udu_time = 0 ;
    is_old_time = 0 ;
    if (ncid == -1) { 
	error("ncopen failed on %s", path);
	return;
    }

    /* if name not specified, derive it from path */

    if (specp->name == (char *)0) {
	specp->name = name_path (path);
    }

     strcpy(attr1,"netCDF-name");
     strcpy(value1,specp->name);

     if (t->list)
	append_AVList(t->list, attr1, value1, strlen(value1));
     else
	t->list = create_AVList(attr1, value1, strlen(value1));

    /*
     * get number of dimensions, number of variables, number of global
     * atts, and dimension id of unlimited dimension, if any
     */
    (void) ncinquire(ncid, &ndims, &nvars, &ngatts, &xdimid);

    /* get dimension info */
    if (ndims > 0)

    strcpy(attr1,"Variable-Dimensions");

    value1[0] = '\0';

    for (dimid = 0; dimid < ndims; dimid++) {
	(void) ncdiminq(ncid, dimid, dims[dimid].name, &dims[dimid].size);

	  sprintf(&value1[strlen(value1)], " %s %d", dims[dimid].name, dims[dimid].size);

    }
     	  if (t->list)
            append_AVList(t->list, attr1, value1, strlen(value1));
          else
             t->list = create_AVList(attr1, value1, strlen(value1));

    /* get variable info, with variable attributes */

    /* Collect various attribute value pairs. */
    strcpy(attr1,"Variable-Full-Names");
    strcpy(attr2,"Variable-Names");
    value1[0] = '\0';
    value2[0] = '\0';
    for (varid = 0; varid < nvars; varid++) {
	
	(void) ncvarinq(ncid, varid, var.name, &var.type, &var.ndims, var.dims,
			&var.natts);

     /* This section determines if the variable in question is "time".
        If so, it treats it differently.                                 */

        if (!strcmp(var.name, "time") || !strcmp(var.name, "TIME")) {
            is_time_var = 1 ;
	    time_varid = varid ;
	    (void) ncattget(ncid, varid, "units", (void *) unitstr) ;
#ifdef HAVE_UDUNITS
	    retcode = utScan(unitstr, &unitstruct) ;
	    if (!retcode) {
                is_udu_time = utIsTime(&unitstruct) ;
            } else {
                is_udu_time = 0 ;
            }
#else
	    is_udu_time = 0;
#endif
            if (!is_udu_time) {
		if (!strcasecmp(unitstr, "YYYYMMDDHHMMSS")) {
		    is_old_time = 1 ;
		} else {
		    is_old_time = 0 ;
		}
            }
        } else {
            is_time_var = 0 ;
        }

	/*
	    rhs	 Start building up the value of attribute/value pair. *?
	*/

	sprintf(&value2[strlen(value2)]," %s",var.name);

	sprintf(&value1[strlen(value1)]," %s:%s", 
                type_name(var.type), var.name);

	if (var.ndims > 0)

	sprintf(&value1[strlen(value1)],"(");

	for (id = 0; id < var.ndims; id++) {

	    sprintf (&value1[strlen(value1)], "%s%s",
		    dims[var.dims[id]].name,
		    id < var.ndims-1 ? ", " : ")");

	}

	/* get variable attributes */

	for (ia = 0; ia < var.natts; ia++) {

	    (void) ncattname(ncid, varid, ia, att.name);
	    
            /* Now build the attribute value pairs in cbuffer based
               on the netCDF attributes. */

	    value3[0] = '\0';
	    attr3[0] = '\0';

	    sprintf(attr3,"Variable-Attribute-%s", att.name);

	    for ( iattr = 0; iattr < NUMATTR; iattr++ )
		if( !strcmp(att.name,nc_attributes[iattr]) )
			sprintf(attr3,"%s", 
                                soif_attributes[iattr]);

	    for( ilet = 0; ilet < (int)strlen(attr3); ilet++)
		if( attr3[ilet] == '_' ) 
		    attr3[ilet] = '-';

            sprintf(value3, "%s ", var.name);

	    (void) ncattinq(ncid, varid, att.name, &att.type, &att.len);

	    att.val = (void *) malloc((unsigned)att.len*nctypelen(att.type));
	    if (!att.val) {
		error("Out of memory!");
		(void) ncclose(ncid);
		return;
	    }

	    (void) ncattget(ncid, varid, att.name, att.val);

	    if (is_time_var) {
	        buf_pr_att_vals(value3, att.type, att.len, att.val, 
				is_udu_time, is_old_time) ;
	    } else {
	        buf_pr_att_vals(value3, att.type, att.len, att.val, 0, 0) ;
	    }

	    free ((char *) att.val);

     	  if (t->list)
            append_AVList(t->list, attr3, value3, strlen(value3));
          else
             t->list = create_AVList(attr3, value3, strlen(value3));

	}

    }

     	if (t->list)
           append_AVList(t->list, attr1, value1, strlen(value1));
        else
           t->list = create_AVList(attr1, value1, strlen(value1));

     	if (t->list)
           append_AVList(t->list, attr2, value2, strlen(value2));
        else
           t->list = create_AVList(attr2, value2, strlen(value2));

    /* get global attributes */

    if (ngatts > 0)

    for (ia = 0; ia < ngatts; ia++) {
	(void) ncattname(ncid, NC_GLOBAL, ia, att.name);

	attr1[0] = '\0';
	value1[0] = '\0';
	
        sprintf(attr1,"Global-%s", att.name);
        for ( iattr = 0; iattr < NUMATTR; iattr++ )
              if( !strcmp(att.name,nc_attributes[iattr]) )
                      sprintf(attr1,"%s",
                              soif_attributes[iattr]);


	(void) ncattinq(ncid, NC_GLOBAL, att.name, &att.type, &att.len);
	att.val = (void *) malloc((unsigned) (att.len*nctypelen(att.type)));
	if (!att.val) {
	    error("Out of memory!");
	    (void) ncclose(ncid);
	    return;
	}
	(void) ncattget(ncid, NC_GLOBAL, att.name, att.val);

	buf_pr_att_vals(value1, att.type, att.len, att.val, 0);

	        for( ilet = 0; ilet < (int)strlen(attr1); ilet++)
		  if( attr1[ilet] == '_' ) 
		      attr1[ilet] = '-';

     	if (t->list)
           append_AVList(t->list, attr1, value1, strlen(value1));
        else
           t->list = create_AVList(attr1, value1, strlen(value1));
    }

	free ((char *) att.val);
    
	if (nvars > 0)

	/* output variable data */

	/* Collect for indexing the coordinate variables, except
           the record variable. */
 
	for (varid = 0; varid < nvars; varid++) {
	    /* if var list specified, test for membership */
	    if (specp->nlvars > 0 && ! varmember(vlist, varid))
	      continue;
	    (void) ncvarinq(ncid, varid, var.name, &var.type, &var.ndims,
			    var.dims, &var.natts);
	    if (specp->coord_vals) {
		/* Find out if this is a coordinate variable */
		is_coord = 0;
		for (dimid = 0; dimid < ndims; dimid++) {
		    if (strcmp(dims[dimid].name, var.name) == 0 &&
			var.ndims == 1) {
			is_coord = 1;
			break;
		    }
		}
		if (! is_coord)	/* don't get data for non-coordinate vars */
		  continue;
	    }
	    /*
	     * Only get data for variable if it is not a record variable,
	     * or if it is a record variable and at least one record has
	     * been written.
	     */
	    if (var.ndims == 0
		|| var.dims[0] != xdimid
		|| dims[xdimid].size != 0) {
		/* Collect variable's dim sizes */
		for (id = 0; id < var.ndims; id++)
		  vdims[id] = dims[var.dims[id]].size;

	        value1[0] = '\0';
		if (varid == time_varid) {
	  	    /*
                       If it's time don't do anything...

		    retcode = vardata(&var, vdims, ncid, varid, is_udu_time,
		                      is_old_time, specp) ;
		    */
		} else {
		    retcode = vardata(value1, &var, vdims, ncid, varid, 0, 0, specp) ;
		}

		sprintf(attr1,"Coordinate-Variable-%s", var.name);


	        for( ilet = 0; ilet < (int)strlen(attr1); ilet++)
		  if( attr1[ilet] == '_' ) 
		      attr1[ilet] = '-';

     		if (t->list)
           	   append_AVList(t->list, attr1, value1, strlen(value1));
                else
		   t->list = create_AVList(attr1, value1, strlen(value1));

		if (retcode == -1) {
		    error("can't output data for variable %s", var.name);
		    (void) ncclose(ncid); return;
		}
	    }
	}

        /* Print out the template */
        (void) init_print_template(stdout);
        print_template_body(t);
        finish_print_template();
        free_template(t);


    (void) ncclose(ncid);
}


static void
make_lvars(optarg, fspecp)
     char *optarg;
     struct fspec* fspecp;
{
    char *cp = optarg;
    int nvars = 1;
    char ** cpp;

    /* compute number of variable names in comma-delimited list */
    fspecp->nlvars = 1;
    while (*cp++)
      if (*cp == ',')
 	nvars++;

    fspecp->lvars = (char **) malloc(nvars * sizeof(char*));
    if (!fspecp->lvars) {
	error("out of memory");
	exit(EXIT_FAILURE);
    }

    cpp = fspecp->lvars;
    /* copy variable names into list */
    for (cp = strtok(optarg, ",");
	 cp != NULL;
	 cp = strtok((char *) NULL, ",")) {
	
	*cpp = (char *) malloc(strlen(cp) + 1);
	if (!*cpp) {
	    error("out of memory");
	    exit(EXIT_FAILURE);
	}
	strcpy(*cpp, cp);
	cpp++;
    }
    fspecp->nlvars = nvars;
}


/*
 * Extract the significant-digits specifiers from the -d argument on the
 * command-line and update the default data formats appropriately.
 */
static void
set_sigdigs(optarg)
     char *optarg;
{
    char *ptr = optarg;
    char *ptr2 = 0;
    long flt_digits = 7;	/* default floating-point digits */
    long dbl_digits = 15;	/* default double-precision digits */
    char flt_fmt[6];
    char dbl_fmt[6];

    if (optarg != 0 && ((int) strlen(optarg)) > 0 && optarg[0] != ',')
        flt_digits=strtol(optarg, &ptr, 10);

    if (flt_digits < 1 || flt_digits > 10) {
	error("unreasonable value for float significant digits: %d",
	      flt_digits);
	exit(EXIT_FAILURE);
    }
    if (*ptr == ',')
      dbl_digits = strtol(ptr+1, &ptr2, 10);
    if (ptr2 == ptr+1 || dbl_digits < 1 || dbl_digits > 20) {
	error("unreasonable value for double significant digits: %d",
	      dbl_digits);
	exit(EXIT_FAILURE);
    }
    (void) sprintf(flt_fmt, "%%.%dg", flt_digits);
    (void) sprintf(dbl_fmt, "%%.%dg", dbl_digits);
    set_formats(flt_fmt, dbl_fmt);
}


int
main(argc, argv)
int argc;
char *argv[];
{
    extern int optind;
    extern int opterr;
    extern char *optarg;
    static struct fspec fspec =	/* defaults, overridden on command line */
      {
	  0,			/* construct netcdf name from file name */
	  false,		/* print header info only, no data? */
	  false,		/* just print coord vars? */
	  false,		/* brief  comments in data section? */
	  false,		/* full annotations in data section?  */
	  LANG_NONE,		/* language conventions for indices */
	  0,			/* if -v specified, number of variables */
	  0			/* if -v specified, list of variable names */
	  };
    int c;
    int i;
    int max_len = 80;		/* default maximum line length */
    int retcode ;

    opterr = 1;
    progname = argv[0];

#ifdef HAVE_UDUNITS
    if (retcode = utInit(UDUNITSDB)) {
        fprintf(stderr, "utInit failed, returned %d.\n", retcode) ;
	exit(97) ;
    }
#endif

    set_max_len(max_len);
    
    i = 1;
    fspec.coord_vals = true;

    if (argc != 2 )
    {
	usage();
	exit(99);
    }


	  do_ncdump(argv[1], &fspec);
    return EXIT_SUCCESS;
}
