/*********************************************************************
 *   Copyright 1993, University Corporation for Atmospheric Research
 *   See netcdf/COPYRIGHT file for copying and redistribution conditions.
 *   $Id: rec.c 5207 2009-08-05 04:18:22Z bmribler $
 *********************************************************************/

#include <stdio.h>

#include "h4config.h"
#ifdef H4_HAVE_NETCDF
#include "netcdf.h"
#else
#include "hdf4_netcdf.h"
#endif

#include "testcdf.h"		/* defines in-memory test cdf structure */
#include "val.h"
#include "error.h"
#include "tests.h"
#include "alloc.h"
#include "emalloc.h"
#ifdef HDF
#include "hdf.h"
#endif

#define VARS 100

/*
 * Returns number of record variables in an open netCDF file, and an array of
 * the record variable ids, if the array parameter is non-null.  Returns -1 on
 * error.
 */
static int
numrecvars(ncid, recvarids)
     int ncid;
     int *recvarids;     
{
    int ndims, iv, nvars;
    int nrecvars;
    int recdimid;
    int dimids[H4_MAX_VAR_DIMS];

    if (ncinquire(ncid, NULL, &nvars, NULL, &recdimid) == -1)
      return -1;
    if (recdimid == -1)
      return 0;
    nrecvars = 0;
    for (iv = 0; iv < nvars; iv++) {
	if (ncvarinq(ncid, iv, NULL, NULL, &ndims, dimids, NULL) == -1)
	  return -1;
	if (ndims > 0 && dimids[0] == recdimid) {
	    if (recvarids)
	      recvarids[nrecvars] = iv;
	    nrecvars++;
	}
    }
    return nrecvars;
}


/*
 * Returns record size (in bytes) of the record variable with a specified
 * variable id.  Returns 0 if not a record variable.  Returns -1 on error.
 */
static long
ncrecsize(ncid,vid)
     int ncid;
     int vid;
{
    int recdimid;
    nc_type type;
    int ndims;
    int dimids[H4_MAX_VAR_DIMS];
    int id;
    long size;

    if (ncinquire(ncid, NULL, NULL, NULL, &recdimid) == -1)
      return -1;
    if (ncvarinq(ncid, vid, 0, &type, &ndims, dimids, NULL) == -1)
      return -1;
    if (ndims == 0 || dimids[0] != recdimid)
      return 0;
    size = nctypelen(type);
    for (id = 1; id < ndims; id++) {
	long len;
	(void) ncdiminq(ncid, dimids[id], 0, &len);
	size *= len;
    }
    return size;
}


/*
 * Retrieves the number of record variables, the record variable ids, and the
 * record size of each record variable.  If any pointer to info to be returned
 * is null, the associated information is not returned.  Returns -1 on error.
 * This is the same as the ncrecinq() in the library, except that can handle
 * errors better.
 */
static int
recinq(ncid, nrecvars, recvarids, recsizes)
     int ncid;
     int *nrecvars;
     int *recvarids;
     long *recsizes;
{
    int iv;
    int rvarids[VARS];
    int nrvars = numrecvars(ncid, rvarids);

    if (nrvars == -1)
      return -1;

    if (nrecvars)
      *nrecvars = nrvars;
    if (recvarids)
      for (iv = 0; iv < nrvars; iv++)
	recvarids[iv] = rvarids[iv];
    if (recsizes)
      for (iv = 0; iv < nrvars; iv++)
	recsizes[iv] = ncrecsize(ncid, rvarids[iv]);
    return 0;
}


/*
 * Test ncrecinq
 *    try in both data and define modes
 *    check returned values against independently computed values
 *    try with bad netCDF handle, check error
 */
void
test_ncrecinq(path)
     char *path;		/* name of netcdf file to open */
{
    int nerrs = 0;
    static char pname[] = "test_ncrecinq";
    int ncid;
    int nrvars;			/* number of record variables */
    int rvarids[VARS];	/* id of each record variable */
    long rvarsizes[VARS]; /* record size of each record variable */
    int tnrvars;		/* true number of record variables */
    int trvarids[VARS];	/* true id of each record variable */
    long trvarsizes[VARS]; /* true rec size of each record variable */
    int iv;

    (void) fprintf(stderr, "*** Testing %s ...\t", &pname[5]);

    if ((ncid = ncopen(path, NC_WRITE)) == -1) {
	error("%s: ncopen failed", pname);
	return;
    }

    /* First compute independently what ncrecinq should return */
    if (recinq(ncid, &tnrvars, trvarids, trvarsizes) == -1) {
	error("%s: recinq failed", pname);
	ncclose(ncid);
	return;
    }

    /* check that ncrecinq() returns correct information in data mode */
    if (ncrecinq(ncid, &nrvars, rvarids, rvarsizes) == -1) {
	error("%s: ncrecinq failed", pname);
	ncclose(ncid);
	return;
    }

    if (nrvars != tnrvars) {
	error("ncrecinq returned wrong number of rec vars, %d != %d",
	      nrvars, tnrvars);
	nerrs++;
    }

    for (iv = 0; iv < nrvars; iv++) {
	if (rvarids[iv] != trvarids[iv]) {
	    error("ncrecinq returned wrong record id for var %d",
		  trvarids[iv]);
	    nerrs++;
	}
	if (rvarsizes[iv] != trvarsizes[iv]) {
	    error("ncrecinq returned wrong record size for var %d",
		  trvarids[iv]);
	    nerrs++;
	}
    }

    if (ncredef(ncid) == -1) {
	error("%s: ncredef failed", pname);
	ncclose(ncid);
	return;
    }
    /* check that ncrecinq() returns correct information in define mode too */
    if (ncrecinq(ncid, &nrvars, rvarids, rvarsizes) == -1) {
	error("%s: ncrecinq failed in define mode", pname);
	ncclose(ncid);
	return;
    }
    if (nrvars != tnrvars) {
	error("define mode, ncrecinq returned wrong num of rec vars, %d != %d",
	      nrvars, tnrvars);
	nerrs++;
    }
    for (iv = 0; iv < nrvars; iv++) {
	if (rvarids[iv] != trvarids[iv]) {
	    error("define mode, ncrecinq returned wrong record id for var %d",
		  trvarids[iv]);
	    nerrs++;
	}
	if (rvarsizes[iv] != trvarsizes[iv]) {
	    error("define mode, ncrecinq returned wrong rec size for var %d",
		  trvarids[iv]);
	    nerrs++;
	}
    }

    if (ncclose (ncid) == -1) {
	error("%s: ncclose failed", pname);
	return;
    }

    if (ncrecinq(ncid, &nrvars, rvarids, rvarsizes) != -1) {
	error("%s: ncrecinq failed to report bad handle", pname);
	nerrs++;
    }
    
    if (nerrs > 0)
      (void) fprintf(stderr,"FAILED! ***\n");
    else
      (void) fprintf(stderr,"ok ***\n");
}


/*
 * Retrieves the dimension sizes of a variable with a specified variable id in
 * an open netCDF file.  Returns -1 on error.
 */
static int
dimsizes(ncid, varid, sizes)
     int ncid;
     int varid;
     long *sizes;
{
    int ndims;
    int id;
    int dimids[H4_MAX_VAR_DIMS];

    if (ncvarinq(ncid, varid, 0, NULL, &ndims, dimids, NULL) == -1)
      return -1;
    if (ndims == 0 || sizes == 0)
      return 0;
    for (id = 0; id < ndims; id++)
      (void) ncdiminq(ncid, dimids[id], 0, &sizes[id]);
    return 0;
}


/*
 * Write one record's worth of data, except don't write to variables for which
 * the address of the data to be written is NULL.  Return -1 on error.  This is
 * the same as the ncrecput() in the library, except that can handle errors
 * better.
 */
static int
recput(ncid, recnum, datap)
     int ncid;
     long recnum;
     void **datap;
{
    int iv;
    int rvids[VARS];
    int nrvars = numrecvars(ncid, rvids);
    long start[H4_MAX_VAR_DIMS];
    long edges[H4_MAX_VAR_DIMS];

    if (nrvars == -1)
      return -1;

    start[0] = recnum;
    for (iv = 1; iv < nrvars; iv++)
	start[iv] = 0;

    for (iv = 0; iv < nrvars; iv++) {
	if (datap[iv] != 0) {
	    (void) dimsizes(ncid, rvids[iv], edges);
	    edges[0] = 1;		/* only 1 record's worth */
	    if (ncvarput(ncid, rvids[iv], start, edges, datap[iv]) == -1)
	      return -1;
	}
    }    
    return 0;
}


/*
 * Read one record's worth of data, except don't read from variables for which
 * the address of the data to be read is null.  Return -1 on error.  This is
 * the same as the ncrecget() in the library, except that can handle errors
 * better.
 */
static int
recget(ncid, recnum, datap)
     int ncid;
     long recnum;
     void **datap;
{
    int iv;
    int rvids[VARS];
    int nrvars = numrecvars(ncid, rvids);
    long start[H4_MAX_VAR_DIMS];
    long edges[H4_MAX_VAR_DIMS];

    if (nrvars == -1)
      return -1;

    start[0] = recnum;
    for (iv = 1; iv < nrvars; iv++)
	start[iv] = 0;

    for (iv = 0; iv < nrvars; iv++) {
	if (datap[iv] != 0) {
	    (void) dimsizes(ncid, rvids[iv], edges);
	    edges[0] = 1;		/* only 1 record's worth */
	    if (ncvarget(ncid, rvids[iv], start, edges, datap[iv]) == -1)
	      return -1;
	}
    }    
    return 0;
}


/*
 * Test ncrecput
 *    check that proper call works putting all recoerd variables
 *    try putting only a proper subset of variables
 *    try putting the empty subset of variables
 *    try in define mode, check error
 *    try with bad netCDF handle, check error
 */
void
test_ncrecput(path)
     char *path;		/* name of writable netcdf file to open */
{
    int nerrs = 0;
    static char pname[] = "test_ncrecput";
    int nrvars;			/* number of record variables */
    int rvarids[VARS];	/* id of each record variable */
    long rvarsizes[VARS]; /* record size of each record variable */
    int ncid;			/* netcdf id */
    void *datap[VARS];	/* array of address pointers for rec vars */
    void *datar[VARS];	/* pointers for comparison data */
    long recnum = 1;		/* we'll write the second record */
    int iv;
    long recsize[VARS];	/* record size in data elements */
    nc_type vartype[VARS];
    void *zeros[VARS];

    (void) fprintf(stderr, "*** Testing %s ...\t", &pname[5]);

    if ((ncid = ncopen(path, NC_WRITE)) == -1) {
	error("%s: ncopen failed", pname);
	return;
    }

    if (ncrecinq(ncid, &nrvars, rvarids, rvarsizes) == -1) {
	error("%s: ncrecinq failed", pname);
	ncclose(ncid);
	return;
    }

    /* get a block of data of the right type for each record variable */
    for (iv = 0; iv < nrvars; iv++) {
	datap[iv] = emalloc(rvarsizes[iv]);
	datar[iv] = emalloc(rvarsizes[iv]); /* for comparison values */
	if (ncvarinq(ncid, rvarids[iv], 0, &vartype[iv], NULL, NULL, NULL) == -1) {
	    error("%s: ncvarinq failed", pname);
	    ncclose(ncid);
	    return;
	}
	recsize[iv] = rvarsizes[iv]/nctypelen(vartype[iv]);
	/* Fill data blocks with 0,1,2,3,... */
	val_fill(vartype[iv], recsize[iv], datap[iv]);
	/* Zero out comparison data */
	val_fill_zero(vartype[iv], recsize[iv], datar[iv]);
    }

    /* Zero data in recnum record, before trying to put non-zero data */
    if (recput(ncid, recnum, datar) == -1) {
	error("%s: recput failed", pname);
	ncclose(ncid);
	return;
    }

    /* opened in data mode, try putting a complete record */
    if (ncrecput(ncid, recnum, datap) == -1) {
	error("%s: ncrecput failed on complete record", pname);
	nerrs++;
    }

    /* Check that right values were put */
    if (recget(ncid, recnum, datar) == -1) {
	error("%s: recget failed", pname);
	nerrs++;
    }
    for (iv = 0; iv < nrvars; iv++) {
	if (val_cmp(vartype[iv], recsize[iv], datap[iv], datar[iv]) != 0) {
	    error("%s: bad values written by recput", pname);
	    nerrs++;
	}
	val_fill_zero(vartype[iv], recsize[iv], datap[iv]);
	val_fill_zero(vartype[iv], recsize[iv], datar[iv]);
	zeros[iv] = 0;
    }
    
    if (nrvars > 0) {
	void *datap0 = datap[0];

	/* put a partial record, everything but first record variable */
	datap[0] = 0;
	val_fill(vartype[0], recsize[0], datar[0]);
	if (ncrecput(ncid, recnum, datap) == -1) {
	    error("%s: ncrecput failed on partial record", pname);
	    nerrs++;
	}

	/* Check right values were put, first record variable undisturbed */
	datap[0] = datap0;
	if (recget(ncid, recnum, datap) == -1) {
	    error("%s: recget failed after partial record put", pname);
	    nerrs++;
	}
	for (iv = 0; iv < nrvars; iv++) {
	    if (val_cmp(vartype[iv], recsize[iv], datap[iv], datar[iv]) != 0) {
		error("%s: bad values written by partial recput", pname);
		nerrs++;
	    }
	}
    }

    /* Put an empty record, check that values remain undisturbed */
    if (ncrecput(ncid, recnum, zeros) == -1) {
	error("%s: ncrecput failed on empty record", pname);
	nerrs++;
    }
    if (recget(ncid, recnum, datap) == -1) {
	error("%s: recget failed after empty record put", pname);
	nerrs++;
    }
    for (iv = 0; iv < nrvars; iv++) {
	if (val_cmp(vartype[iv], recsize[iv], datap[iv], datar[iv]) != 0) {
	    error("%s: bad values written by empty recput", pname);
	    nerrs++;
	}
    }

    /* try in define mode, check error */
    if (ncredef(ncid) == -1) {
	error("%s: ncredef failed", pname);
 	ncclose(ncid);
	return;
    }

    if (ncrecput(ncid, recnum, datap) != -1) {
	error("%s: ncrecput should fail in define mode", pname);
	nerrs++;
    }

    /* try with bad netCDF handle, check error */
    if (ncclose (ncid) == -1) {
	error("%s: ncclose failed", pname);
	return;
    }
    if (ncrecput(ncid, recnum, datap) != -1) {
	error("%s: ncrecput failed to report bad handle", pname);
	nerrs++;
    }
    for (iv = 0; iv < nrvars; iv++) {
	Free(datap[iv]);
	Free(datar[iv]);
    }

    if (nerrs > 0)
      (void) fprintf(stderr,"FAILED! ***\n");
    else
      (void) fprintf(stderr,"ok ***\n");
}


/*
 * Test ncrecget
 *    check that proper call works getting all record variables
 *    try getting only a proper subset of variables
 *    try getting the empty subset of variables
 *    try with bad netCDF handle, check error
 */
void
test_ncrecget(path)
     char *path;		/* name of netcdf file to open */
{
    int nerrs = 0;
    static char pname[] = "test_ncrecget";
    int nrvars;			/* number of record variables */
    int rvarids[VARS];	/* id of each record variable */
    long rvarsizes[VARS]; /* record size of each record variable */
    int ncid;			/* netcdf id */
    void *datap[VARS];	/* array of address pointers for rec vars */
    void *datar[VARS];	/* pointers for comparison data */
    long recnum = 1;		/* we'll write the second record */
    int iv;
    long recsize[VARS];	/* record size in data elements */
    nc_type vartype[VARS];
    void *zeros[VARS];

    (void) fprintf(stderr, "*** Testing %s ...\t", &pname[5]);

    if ((ncid = ncopen(path, NC_WRITE)) == -1) {
	error("%s: ncopen failed", pname);
	return;
    }

    if (ncrecinq(ncid, &nrvars, rvarids, rvarsizes) == -1) {
	error("%s: ncrecinq failed", pname);
	ncclose(ncid);
	return;
    }

    /* get a block of data of the right type for each record variable */
    for (iv = 0; iv < nrvars; iv++) {
	datap[iv] = emalloc(rvarsizes[iv]);
	datar[iv] = emalloc(rvarsizes[iv]); /* for comparison values */
	if (ncvarinq(ncid, rvarids[iv], 0, &vartype[iv], NULL, NULL, NULL) == -1) {
	    error("%s: ncvarinq failed", pname);
	    ncclose(ncid);
	    return;
	}
	recsize[iv] = rvarsizes[iv]/nctypelen(vartype[iv]);
	/* Fill data blocks with 0,1,2,3,... */
	val_fill(vartype[iv], recsize[iv], datap[iv]);
	/* Zero out comparison data */
	val_fill_zero(vartype[iv], recsize[iv], datar[iv]);
    }

    if (recput(ncid, recnum, datap) == -1) {
	error("%s: recput failed", pname);
	ncclose(ncid);
	return;
    }

    /* opened in data mode, try getting a complete record */
    if (recget(ncid, recnum, datap) == -1) {
	error("%s: recget failed on complete record", pname);
	nerrs++;
    }
    if (ncrecget(ncid, recnum, datar) == -1) {
	error("%s: ncrecget failed on complete record", pname);
	nerrs++;
    }

    for (iv = 0; iv < nrvars; iv++) {
	if (val_cmp(vartype[iv], recsize[iv], datap[iv], datar[iv]) != 0) {
	    error("%s: bad values written by recget", pname);
	    nerrs++;
	}
	val_fill_zero(vartype[iv], recsize[iv], datap[iv]);
	val_fill_zero(vartype[iv], recsize[iv], datar[iv]);
	zeros[iv] = 0;
    }
    
    if (nrvars > 0) {
	void *datap0 = datap[0];
	void *datar0 = datar[0];

	/* get a partial record, everything but first record variable */
	datap[0] = 0;
	if (ncrecget(ncid, recnum, datap) == -1) {
	    error("%s: ncrecget failed on partial record", pname);
	    nerrs++;
	}
	datar[0] = 0;
	if (recget(ncid, recnum, datar) == -1) {
	    error("%s: recget failed on partial record", pname);
	    nerrs++;
	}
	/* Check right values were got, first record variable undisturbed */
	datap[0] = datap0;
	datar[0] = datar0;
	for (iv = 0; iv < nrvars; iv++) {
	    if (val_cmp(vartype[iv], recsize[iv], datap[iv], datar[iv]) != 0) {
		error("%s: bad values read by partial recget", pname);
		nerrs++;
	    }
	}
    }

    /* Get an empty record */
    if (ncrecget(ncid, recnum, zeros) == -1) {
	error("%s: ncrecget failed on empty record", pname);
	nerrs++;
    }

    /* try with bad netCDF handle, check error */
    if (ncclose (ncid) == -1) {
	error("%s: ncclose failed", pname);
	return;
    }
    if (ncrecget(ncid, recnum, datap) != -1) {
	error("%s: ncrecget failed to report bad handle", pname);
	nerrs++;
    }
    for (iv = 0; iv < nrvars; iv++) {
	Free(datap[iv]);
	Free(datar[iv]);
    }

    if (nerrs > 0)
      (void) fprintf(stderr,"FAILED! ***\n");
    else
      (void) fprintf(stderr,"ok ***\n");
}
