/*********************************************************************
 *   Copyright 1993, UCAR/Unidata
 *   See netcdf/COPYRIGHT file for copying and redistribution conditions.
 *********************************************************************/

#include <stdio.h>
#include <string.h>
#include <math.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 "add.h"     /* functions to update in-memory netcdf */
#include "error.h"
#include "tests.h"
#include "emalloc.h"

#define LEN_OF(array) ((sizeof array) / (sizeof array[0]))
#define EPS64         ((float64)1.0E-14)
#define EPS32         ((float32)1.0E-7)

/*
 * Test ncvardef
 *    check that proper define worked with ncvarinq
 *    check that returned id is one more than previous id
 *    try redefining an existing variable, check error
 *    try adding scalar variable (no dimensions)
 *    try with bad datatype, check error
 *    try with bad number of dimensions, check error
 *    try with bad dimension ids, check error
 *    try in data mode, check error
 */
/* path - name of writable netcdf file to open */
void
test_ncvardef(char *path)
{
    int                  nerrs = 0;
    int                  cdfid; /* netcdf id */
    static char          pname[] = "test_ncvardef";
    int                  id, iv;
    static struct cdfvar va[] = {/* variables of all shapes and sizes */
                                 {"bytev", NC_BYTE, 6, ___, 0},    {"charv", NC_CHAR, 5, ___, 0},
                                 {"shortv", NC_SHORT, 4, ___, 0},  {"longv", NC_LONG, 3, ___, 0},
                                 {"floatv", NC_FLOAT, 2, ___, 0},  {"doublev", NC_DOUBLE, 1, ___, 0},
                                 {"scalarv", NC_DOUBLE, 0, ___, 0}};
    int                  nv   = LEN_OF(va); /* number of variables to define */
    int                  va_id[LEN_OF(va)]; /* variable ids */
    static struct cdfvar tmp =              /* variable for testing bad types, etc. */
        {"tmpv", NC_DOUBLE, 1, ___, 0};
    /* if d5 >= 91 in following, problem on machines with 16-bit ints ??? */
    static struct cdfdim di[] = {/* a bunch of dimensions */
                                 {"d0", 2}, {"d1", 3}, {"d2", 5}, {"d3", 6}, {"d4", 4}, {"d5", 31}};
    int                  nd   = LEN_OF(di); /* number of dimensions */
    int                  di_id[LEN_OF(di)]; /* dimension ids */

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

    if ((cdfid = ncopen(path, NC_WRITE)) == -1) {
        error("%s: ncopen failed", pname);
        return;
    }
    /* opened, defining a variable should fail in data mode */
    if (ncvardef(cdfid, va[0].name, va[0].type, va[0].ndims, va[0].dims) != -1) {
        error("%s: ncvardef should have failed in data mode", pname);
        ncclose(cdfid);
        return;
    }
    /* enter define mode */
    if (ncredef(cdfid) == -1) {
        error("%s: cdredef failed", pname);
        ncclose(cdfid);
        return;
    }

    /* Add nd more dimensions */
    for (id = 0; id < nd; id++) {
        if ((di_id[id] = ncdimdef(cdfid, di[id].name, di[id].size)) == -1) {
            error("%s: ncdimdef failed for %s, size %d", pname, di[id].name, di[id].size);
            ncclose(cdfid);
            return;
        }
        add_dim(test_g, &di[id]); /* keep in-memory netcdf in sync */
    }

    tmp.dims = (int *)emalloc(sizeof(int) * H4_MAX_VAR_DIMS);
    tmp.name = (char *)emalloc(H4_MAX_NC_NAME);

    /* in define mode, add variables of each type with various shapes */
    for (iv = 0; iv < nv; iv++) {
        /* set shape to use subset of dimensions previously defined */
        va[iv].dims = (int *)emalloc(sizeof(int) * va[iv].ndims);
        for (id = 0; id < va[iv].ndims; id++)
            va[iv].dims[id] = di_id[id];
        if ((va_id[iv] = ncvardef(cdfid, va[iv].name, va[iv].type, va[iv].ndims, va[iv].dims)) == -1) {
            error("%s: ncvardef failed", pname);
            errvar(test_g, &va[iv]); /* prints details about variable */
            ncclose(cdfid);
            return;
        }
        add_var(test_g, &va[iv]); /* keep in-memory netcdf in sync */
        /* check that var id returned is one more than previous var id */
        if (va_id[iv] != test_g->nvars - 1) {
            error("%s: ncvardef returned %d for var id, expected %d", pname, va_id[iv], test_g->nvars - 1);
            ncclose(cdfid);
            return;
        }
        /* use ncvarinq to get values just set and compare values */
        if (ncvarinq(cdfid, va_id[iv], tmp.name, &tmp.type, &tmp.ndims, tmp.dims, &tmp.natts) == -1) {
            error("%s: ncvarinq failed", pname);
            errvar(test_g, &va[iv]); /* prints details about variable */
            ncclose(cdfid);
            return;
        }
        if (strcmp(tmp.name, va[iv].name) != 0 || tmp.type != va[iv].type || tmp.ndims != va[iv].ndims ||
            tmp.natts != va[iv].natts) {
            error("%s: ncvardef and ncvarinq don't agree for %s", pname, va[iv].name);
            nerrs++;
            errvar(test_g, &va[iv]);
            errvar(test_g, &tmp);
        }
        for (id = 0; id < va[iv].ndims; id++) {
            if (tmp.dims[id] != va[iv].dims[id]) {
                error("%s: ncvardef and ncvarinq don't agree on shape of %s", pname, va[iv].name);
                nerrs++;
                errvar(test_g, &va[iv]);
                errvar(test_g, &tmp);
            }
        }
    }
    /* try adding same variable again, this should fail */
    if (ncvardef(cdfid, va[0].name, va[0].type, va[0].ndims, va[0].dims) != -1) {
        error("%s: ncvardef should not allow redefinition", pname);
        ncclose(cdfid);
        return;
    }
    /* try bad type, should fail */
    if (ncvardef(cdfid, "badtype", BAD_TYPE, va[0].ndims, va[0].dims) != -1) {
        error("%s: ncvardef should have failed on bad type", pname);
        ncclose(cdfid);
        return;
    }
    /* try bad ndims, should fail */
    if (ncvardef(cdfid, "badndims", va[0].type, -1, va[0].dims) != -1) {
        error("%s: ncvardef should have failed on bad ndims", pname);
        ncclose(cdfid);
        return;
    }
    /* try bad ids in dims vector, should fail */
    id                          = va[0].dims[0];
    va[0].dims[va[0].ndims - 1] = -1;
    if (ncvardef(cdfid, "baddims", va[0].type, va[0].ndims, va[0].dims) != -1) {
        error("%s: ncvardef should have failed on negative dim id in dims", pname);
        ncclose(cdfid);
        return;
    }
    if (ncendef(cdfid) == -1) {
        error("%s: ncendef failed", pname);
        ncclose(cdfid);
        return;
    }

    /* try reading a value of each type, should get appropriate fill value */
    for (iv = 0; iv < nv; iv++) {
        static long where[] = {0, 0, 0, 0, 0, 0};

        switch (va[iv].type) {
            case NC_BYTE: {
                char val, fillval = FILL_BYTE;
                if (ncvarget1(cdfid, va_id[iv], where, (void *)&val) != -1) {
                    if (val != fillval) {
                        error("%s: unwritten byte not FILL_BYTE", pname);
                        nerrs++;
                    }
                }
                else {
                    error("%s: ncvarget1 failure for byte", pname);
                    nerrs++;
                }
            } break;
            case NC_CHAR: {
                char val, fillval = FILL_CHAR;
                if (ncvarget1(cdfid, va_id[iv], where, (void *)&val) != -1) {
                    if (val != fillval) {
                        error("%s: unwritten char not FILL_CHAR", pname);
                        nerrs++;
                    }
                }
                else {
                    error("%s: ncvarget1 failure for char", pname);
                    nerrs++;
                }
            } break;
            case NC_SHORT: {
                short val, fillval = FILL_SHORT;
                if (ncvarget1(cdfid, va_id[iv], where, (void *)&val) != -1) {
                    if (val != fillval) {
                        error("%s: unwritten short not FILL_SHORT", pname);
                        nerrs++;
                    }
                }
                else {
                    error("%s: ncvarget1 failure for short", pname);
                    nerrs++;
                }
            } break;
            case NC_LONG: {
                nclong val, fillval;
                val     = 0;
                fillval = 0;
                fillval = FILL_LONG;
                if (ncvarget1(cdfid, va_id[iv], where, (void *)&val) != -1) {
                    if ((nclong)val != (nclong)fillval) {
                        nclong a, b;

                        printf("\n\n Was expecting %d instead got a %d\n", (int)fillval, (int)val);
                        a = (nclong)val;
                        b = (nclong)fillval;
                        printf("\n\n After cast %d %d\n", (int)a, (int)b);

                        error("%s: unwritten long not FILL_LONG", pname);
                        nerrs++;
                    }
                }
                else {
                    error("%s: ncvarget1 failure for long", pname);
                    nerrs++;
                }
            } break;
            case NC_FLOAT: {
                float val, fillval = FILL_FLOAT;
                if (ncvarget1(cdfid, va_id[iv], where, (void *)&val) != -1) {
                    if (fabs((double)(val - fillval)) > fabs((double)(fillval * EPS32))) {
                        error("%s: unwritten float not FILL_FLOAT", pname);
                        nerrs++;
                    }
                }
                else {
                    error("%s: ncvarget1 failure for float", pname);
                    nerrs++;
                }
            } break;
            case NC_DOUBLE: {
                double val, fillval = FILL_DOUBLE;
                if (ncvarget1(cdfid, va_id[iv], where, (void *)&val) != -1) {
                    if (fabs((double)(val - fillval)) > fabs((double)(fillval * EPS64))) {
                        error("%s: unwritten double not FILL_DOUBLE", pname);
                        nerrs++;
                    }
                }
                else {
                    error("%s: ncvarget1 failure for double", pname);
                    nerrs++;
                }
            } break;
            default:
                break;
        }
    }

    if (ncclose(cdfid) == -1) {
        error("%s: ncclose failed", pname);
        return;
    }
    free(tmp.dims);
    free(tmp.name);
    for (iv = 0; iv < nv; iv++)
        free(va[iv].dims);
    if (nerrs > 0)
        fprintf(stderr, "FAILED! ***\n");
    else
        fprintf(stderr, "ok ***\n");
}
