/*********************************************************************
 *   Copyright 1993, University Corporation for Atmospheric Research
 *   See netcdf/COPYRIGHT file for copying and redistribution conditions.
 *********************************************************************/

#include <stdio.h>

#include "hdf.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 "emalloc.h"

#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(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(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(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
 */
/* path - name of netcdf file to open */
void
test_ncrecinq(char *path)
{
    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;

    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)
        fprintf(stderr, "FAILED! ***\n");
    else
        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(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(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(int ncid, int 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
 */
/* path - name of writable netcdf file to open */
void
test_ncrecput(char *path)
{
    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];

    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)
        fprintf(stderr, "FAILED! ***\n");
    else
        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
 */
/* path - name of netcdf file to open */
void
test_ncrecget(char *path)
{
    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];

    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)
        fprintf(stderr, "FAILED! ***\n");
    else
        fprintf(stderr, "ok ***\n");
}
