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

#if HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdlib.h>
#include <string.h>
#include <assert.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include "nc3internal.h"
#include "netcdf_mem.h"
#include "rnd.h"
#include "ncx.h"
#include "ncrc.h"

/* These have to do with version numbers. */
#define MAGIC_NUM_LEN 4
#define VER_CLASSIC 1
#define VER_64BIT_OFFSET 2
#define VER_HDF5 3

#define NC_NUMRECS_OFFSET 4

/* For netcdf classic */
#define NC_NUMRECS_EXTENT3 4
/* For cdf5 */
#define NC_NUMRECS_EXTENT5 8

/* Internal function; breaks ncio abstraction */
extern int memio_extract(ncio* const nciop, size_t* sizep, void** memoryp);

static void
free_NC3INFO(NC3_INFO *nc3)
{
	if(nc3 == NULL)
		return;
	free_NC_dimarrayV(&nc3->dims);
	free_NC_attrarrayV(&nc3->attrs);
	free_NC_vararrayV(&nc3->vars);
	free(nc3);
}

static NC3_INFO *
new_NC3INFO(const size_t *chunkp)
{
	NC3_INFO *ncp;
	ncp = (NC3_INFO*)calloc(1,sizeof(NC3_INFO));
	if(ncp == NULL) return ncp;
        ncp->chunk = chunkp != NULL ? *chunkp : NC_SIZEHINT_DEFAULT;
	/* Note that ncp->xsz is not set yet because we do not know the file format */
	return ncp;
}

static NC3_INFO *
dup_NC3INFO(const NC3_INFO *ref)
{
	NC3_INFO *ncp;
	ncp = (NC3_INFO*)calloc(1,sizeof(NC3_INFO));
	if(ncp == NULL) return ncp;

	if(dup_NC_dimarrayV(&ncp->dims, &ref->dims) != NC_NOERR)
		goto err;
	if(dup_NC_attrarrayV(&ncp->attrs, &ref->attrs) != NC_NOERR)
		goto err;
	if(dup_NC_vararrayV(&ncp->vars, &ref->vars) != NC_NOERR)
		goto err;

	ncp->xsz = ref->xsz;
	ncp->begin_var = ref->begin_var;
	ncp->begin_rec = ref->begin_rec;
	ncp->recsize = ref->recsize;
	NC_set_numrecs(ncp, NC_get_numrecs(ref));
	return ncp;
err:
	free_NC3INFO(ncp);
	return NULL;
}


/*
 *  Verify that this is a user nc_type
 * Formerly NCcktype()
 * Sense of the return is changed.
 */
int
nc3_cktype(int mode, nc_type type)
{
#ifdef ENABLE_CDF5
    if (mode & NC_CDF5) { /* CDF-5 format */
        if (type >= NC_BYTE && type < NC_STRING) return NC_NOERR;
    } else
#endif
      if (mode & NC_64BIT_OFFSET) { /* CDF-2 format */
        if (type >= NC_BYTE && type <= NC_DOUBLE) return NC_NOERR;
    } else if ((mode & NC_64BIT_OFFSET) == 0) { /* CDF-1 format */
        if (type >= NC_BYTE && type <= NC_DOUBLE) return NC_NOERR;
    }
    return(NC_EBADTYPE);
}


/*
 * How many objects of 'type'
 * will fit into xbufsize?
 */
size_t
ncx_howmany(nc_type type, size_t xbufsize)
{
	switch(type){
	case NC_BYTE:
	case NC_CHAR:
		return xbufsize;
	case NC_SHORT:
		return xbufsize/X_SIZEOF_SHORT;
	case NC_INT:
		return xbufsize/X_SIZEOF_INT;
	case NC_FLOAT:
		return xbufsize/X_SIZEOF_FLOAT;
	case NC_DOUBLE:
		return xbufsize/X_SIZEOF_DOUBLE;
	case NC_UBYTE:
 		return xbufsize;
	case NC_USHORT:
		return xbufsize/X_SIZEOF_USHORT;
	case NC_UINT:
		return xbufsize/X_SIZEOF_UINT;
	case NC_INT64:
		return xbufsize/X_SIZEOF_LONGLONG;
	case NC_UINT64:
		return xbufsize/X_SIZEOF_ULONGLONG;
	default:
	        assert("ncx_howmany: Bad type" == 0);
		return(0);
	}
}

#define	D_RNDUP(x, align) _RNDUP(x, (off_t)(align))

/*
 * Compute each variable's 'begin' offset,
 * update 'begin_rec' as well.
 */
static int
NC_begins(NC3_INFO* ncp,
	size_t h_minfree, size_t v_align,
	size_t v_minfree, size_t r_align)
{
	size_t ii, j;
	int sizeof_off_t;
	off_t index = 0;
	off_t old_ncp_begin_var;
	NC_var **vpp;
	NC_var *last = NULL;
	NC_var *first_var = NULL;       /* first "non-record" var */


	if(v_align == NC_ALIGN_CHUNK)
		v_align = ncp->chunk;
	if(r_align == NC_ALIGN_CHUNK)
		r_align = ncp->chunk;

	if (fIsSet(ncp->flags, NC_64BIT_OFFSET) || fIsSet(ncp->flags, NC_64BIT_DATA)) {
	  sizeof_off_t = 8;
	} else {
	  sizeof_off_t = 4;
	}

	ncp->xsz = ncx_len_NC(ncp,sizeof_off_t);

	if(ncp->vars.nelems == 0)
		return NC_NOERR;

        old_ncp_begin_var = ncp->begin_var;

	/* only (re)calculate begin_var if there is not sufficient space in header
	   or start of non-record variables is not aligned as requested by valign */
	if (ncp->begin_var < ncp->xsz + h_minfree ||
	    ncp->begin_var != D_RNDUP(ncp->begin_var, v_align) )
	{
	  index = (off_t) ncp->xsz;
	  ncp->begin_var = D_RNDUP(index, v_align);
	  if(ncp->begin_var < index + h_minfree)
	  {
	    ncp->begin_var = D_RNDUP(index + (off_t)h_minfree, v_align);
	  }
	}

	if (ncp->old != NULL) {
            /* check whether the new begin_var is smaller */
            if (ncp->begin_var < ncp->old->begin_var)
                ncp->begin_var = ncp->old->begin_var;
	}

	index = ncp->begin_var;

	/* loop thru vars, first pass is for the 'non-record' vars */
	j = 0;
	vpp = ncp->vars.value;
	for(ii = 0; ii < ncp->vars.nelems ; ii++, vpp++)
	{
		if( IS_RECVAR(*vpp) )
		{
			/* skip record variables on this pass */
			continue;
		}
		if (first_var == NULL) first_var = *vpp;

#if 0
fprintf(stderr, "    VAR %d %s: %ld\n", ii, (*vpp)->name->cp, (long)index);
#endif
                if( sizeof_off_t == 4 && (index > X_OFF_MAX || index < 0) )
		{
                    ncp->begin_var = old_ncp_begin_var;
		    return NC_EVARSIZE;
                }
		(*vpp)->begin = index;

		if (ncp->old != NULL) {
          /* move to the next fixed variable */
          for (; j<ncp->old->vars.nelems; j++) {
            if (!IS_RECVAR(ncp->old->vars.value[j]))
              break;
          }

          if (j < ncp->old->vars.nelems) {
            if ((*vpp)->begin < ncp->old->vars.value[j]->begin) {
              /* the first ncp->vars.nelems fixed variables
                 should be the same. If the new begin is smaller,
                 reuse the old begin */
              (*vpp)->begin = ncp->old->vars.value[j]->begin;
              index = (*vpp)->begin;
            }
            j++;
          }
		}

		index += (*vpp)->len;
	}

	if (ncp->old != NULL) {
	    /* check whether the new begin_rec is smaller */
	    if (ncp->begin_rec < ncp->old->begin_rec)
	        ncp->begin_rec = ncp->old->begin_rec;
	}

	/* only (re)calculate begin_rec if there is not sufficient
	   space at end of non-record variables or if start of record
	   variables is not aligned as requested by r_align */
	if (ncp->begin_rec < index + v_minfree ||
	    ncp->begin_rec != D_RNDUP(ncp->begin_rec, r_align) )
	{
	  ncp->begin_rec = D_RNDUP(index, r_align);
	  if(ncp->begin_rec < index + v_minfree)
	  {
	    ncp->begin_rec = D_RNDUP(index + (off_t)v_minfree, r_align);
	  }
	}

	if (first_var != NULL)
	    ncp->begin_var = first_var->begin;
	else
	    ncp->begin_var = ncp->begin_rec;

	index = ncp->begin_rec;

	ncp->recsize = 0;

	/* loop thru vars, second pass is for the 'record' vars */
	j = 0;
	vpp = (NC_var **)ncp->vars.value;
	for(ii = 0; ii < ncp->vars.nelems; ii++, vpp++)
	{
		if( !IS_RECVAR(*vpp) )
		{
			/* skip non-record variables on this pass */
			continue;
		}

#if 0
fprintf(stderr, "    REC %d %s: %ld\n", ii, (*vpp)->name->cp, (long)index);
#endif
                if( sizeof_off_t == 4 && (index > X_OFF_MAX || index < 0) )
		{
                    ncp->begin_var = old_ncp_begin_var;
		    return NC_EVARSIZE;
                }
		(*vpp)->begin = index;

                if (ncp->old != NULL) {
                    /* move to the next record variable */
                    for (; j<ncp->old->vars.nelems; j++)
                        if (IS_RECVAR(ncp->old->vars.value[j]))
                            break;
                    if (j < ncp->old->vars.nelems) {
                        if ((*vpp)->begin < ncp->old->vars.value[j]->begin)
                            /* if the new begin is smaller, use the old begin */
                            (*vpp)->begin = ncp->old->vars.value[j]->begin;
                        j++;
                    }
                }

		index += (*vpp)->len;
		/* check if record size must fit in 32-bits */
#if SIZEOF_OFF_T == SIZEOF_SIZE_T && SIZEOF_SIZE_T == 4
		if( ncp->recsize > X_UINT_MAX - (*vpp)->len )
		{
                    ncp->begin_var = old_ncp_begin_var;
		    return NC_EVARSIZE;
		}
#endif
		ncp->recsize += (*vpp)->len;
		last = (*vpp);
	}

    /*
     * for special case (Check CDF-1 and CDF-2 file format specifications.)
     * "A special case: Where there is exactly one record variable, we drop the
     * requirement that each record be four-byte aligned, so in this case there
     * is no record padding."
     */
    if (last != NULL) {
        if (ncp->recsize == last->len) {
            /* exactly one record variable, pack value */
            ncp->recsize = *last->dsizes * last->xsz;
        }
    }

	if(NC_IsNew(ncp))
		NC_set_numrecs(ncp, 0);
	return NC_NOERR;
}


/*
 * Read just the numrecs member.
 * (A relatively expensive way to do things.)
 */
int
read_numrecs(NC3_INFO *ncp)
{
	int status = NC_NOERR;
	const void *xp = NULL;
	size_t new_nrecs = 0;
	size_t  old_nrecs = NC_get_numrecs(ncp);
	size_t nc_numrecs_extent = NC_NUMRECS_EXTENT3; /* CDF-1 and CDF-2 */

	assert(!NC_indef(ncp));

	if (fIsSet(ncp->flags, NC_64BIT_DATA))
		nc_numrecs_extent = NC_NUMRECS_EXTENT5; /* CDF-5 */

	status = ncio_get(ncp->nciop,
		 NC_NUMRECS_OFFSET, nc_numrecs_extent, 0, (void **)&xp);/* cast away const */
	if(status != NC_NOERR)
		return status;

	if (fIsSet(ncp->flags, NC_64BIT_DATA)) {
	    unsigned long long tmp=0;
	    status = ncx_get_uint64(&xp, &tmp);
	    new_nrecs = (size_t)tmp;
        } else
	    status = ncx_get_size_t(&xp, &new_nrecs);

	(void) ncio_rel(ncp->nciop, NC_NUMRECS_OFFSET, 0);

	if(status == NC_NOERR && old_nrecs != new_nrecs)
	{
		NC_set_numrecs(ncp, new_nrecs);
		fClr(ncp->flags, NC_NDIRTY);
	}

	return status;
}


/*
 * Write out just the numrecs member.
 * (A relatively expensive way to do things.)
 */
int
write_numrecs(NC3_INFO *ncp)
{
	int status = NC_NOERR;
	void *xp = NULL;
	size_t nc_numrecs_extent = NC_NUMRECS_EXTENT3; /* CDF-1 and CDF-2 */

	assert(!NC_readonly(ncp));
	assert(!NC_indef(ncp));

	if (fIsSet(ncp->flags, NC_64BIT_DATA))
	    nc_numrecs_extent = NC_NUMRECS_EXTENT5; /* CDF-5 */

	status = ncio_get(ncp->nciop,
		 NC_NUMRECS_OFFSET, nc_numrecs_extent, RGN_WRITE, &xp);
	if(status != NC_NOERR)
		return status;

	{
		const size_t nrecs = NC_get_numrecs(ncp);
		if (fIsSet(ncp->flags, NC_64BIT_DATA))
		    status = ncx_put_uint64(&xp, (unsigned long long)nrecs);
		else
 		    status = ncx_put_size_t(&xp, &nrecs);
	}

	(void) ncio_rel(ncp->nciop, NC_NUMRECS_OFFSET, RGN_MODIFIED);

	if(status == NC_NOERR)
		fClr(ncp->flags, NC_NDIRTY);

	return status;
}


/*
 * Read in the header
 * It is expensive.
 */
static int
read_NC(NC3_INFO *ncp)
{
	int status = NC_NOERR;

	free_NC_dimarrayV(&ncp->dims);
	free_NC_attrarrayV(&ncp->attrs);
	free_NC_vararrayV(&ncp->vars);

	status = nc_get_NC(ncp);

	if(status == NC_NOERR)
		fClr(ncp->flags, NC_NDIRTY | NC_HDIRTY);

	return status;
}


/*
 * Write out the header
 */
static int
write_NC(NC3_INFO *ncp)
{
	int status = NC_NOERR;

	assert(!NC_readonly(ncp));

	status = ncx_put_NC(ncp, NULL, 0, 0);

	if(status == NC_NOERR)
		fClr(ncp->flags, NC_NDIRTY | NC_HDIRTY);

	return status;
}


/*
 * Write the header or the numrecs if necessary.
 */
int
NC_sync(NC3_INFO *ncp)
{
	assert(!NC_readonly(ncp));

	if(NC_hdirty(ncp))
	{
		return write_NC(ncp);
	}
	/* else */

	if(NC_ndirty(ncp))
	{
		return write_numrecs(ncp);
	}
	/* else */

	return NC_NOERR;
}


/*
 * Initialize the 'non-record' variables.
 */
static int
fillerup(NC3_INFO *ncp)
{
	int status = NC_NOERR;
	size_t ii;
	NC_var **varpp;

	assert(!NC_readonly(ncp));

	/* loop thru vars */
	varpp = ncp->vars.value;
	for(ii = 0; ii < ncp->vars.nelems; ii++, varpp++)
	{
		if ((*varpp)->no_fill) continue;

		if(IS_RECVAR(*varpp))
		{
			/* skip record variables */
			continue;
		}

		status = fill_NC_var(ncp, *varpp, (*varpp)->len, 0);
		if(status != NC_NOERR)
			break;
	}
	return status;
}

/* Begin endef */

/*
 */
static int
fill_added_recs(NC3_INFO *gnu, NC3_INFO *old)
{
	NC_var ** const gnu_varpp = (NC_var **)gnu->vars.value;

	const int old_nrecs = (int) NC_get_numrecs(old);
	int recno = 0;
	NC_var **vpp = gnu_varpp;
	NC_var *const *const end = &vpp[gnu->vars.nelems];
	int numrecvars = 0;

	/* Determine if there is only one record variable.  If so, we
	   must treat as a special case because there's no record padding */
	for(; vpp < end; vpp++) {
	    if(IS_RECVAR(*vpp)) {
		numrecvars++;
	    }
	}

	for(; recno < old_nrecs; recno++)
	    {
		int varid = (int)old->vars.nelems;
		for(; varid < (int)gnu->vars.nelems; varid++)
		    {
			const NC_var *const gnu_varp = *(gnu_varpp + varid);

			if (gnu_varp->no_fill) continue;

			if(!IS_RECVAR(gnu_varp))
			    {
				/* skip non-record variables */
				continue;
			    }
			/* else */
			{
			    size_t varsize = numrecvars == 1 ? gnu->recsize :  gnu_varp->len;
			    const int status = fill_NC_var(gnu, gnu_varp, varsize, recno);
			    if(status != NC_NOERR)
				return status;
			}
		    }
	    }
	return NC_NOERR;
}

/*
 */
static int
fill_added(NC3_INFO *gnu, NC3_INFO *old)
{
	NC_var ** const gnu_varpp = (NC_var **)gnu->vars.value;
	int varid = (int)old->vars.nelems;

	for(; varid < (int)gnu->vars.nelems; varid++)
	{
		const NC_var *const gnu_varp = *(gnu_varpp + varid);

		if (gnu_varp->no_fill) continue;

		if(IS_RECVAR(gnu_varp))
		{
			/* skip record variables */
			continue;
		}
		/* else */
		{
		const int status = fill_NC_var(gnu, gnu_varp, gnu_varp->len, 0);
		if(status != NC_NOERR)
			return status;
		}
	}

	return NC_NOERR;
}


/*
 * Move the records "out".
 * Fill as needed.
 */
static int
move_recs_r(NC3_INFO *gnu, NC3_INFO *old)
{
	int status;
	int recno;
	int varid;
	NC_var **gnu_varpp = (NC_var **)gnu->vars.value;
	NC_var **old_varpp = (NC_var **)old->vars.value;
	NC_var *gnu_varp;
	NC_var *old_varp;
	off_t gnu_off;
	off_t old_off;
	const size_t old_nrecs = NC_get_numrecs(old);

	/* Don't parallelize this loop */
	for(recno = (int)old_nrecs -1; recno >= 0; recno--)
	{
	/* Don't parallelize this loop */
	for(varid = (int)old->vars.nelems -1; varid >= 0; varid--)
	{
		gnu_varp = *(gnu_varpp + varid);
		if(!IS_RECVAR(gnu_varp))
		{
			/* skip non-record variables on this pass */
			continue;
		}
		/* else */

		/* else, a pre-existing variable */
		old_varp = *(old_varpp + varid);
		gnu_off = gnu_varp->begin + (off_t)(gnu->recsize * recno);
		old_off = old_varp->begin + (off_t)(old->recsize * recno);

		if(gnu_off == old_off)
			continue; 	/* nothing to do */

		assert(gnu_off > old_off);

		status = ncio_move(gnu->nciop, gnu_off, old_off,
			 old_varp->len, 0);

		if(status != NC_NOERR)
			return status;

	}
	}

	NC_set_numrecs(gnu, old_nrecs);

	return NC_NOERR;
}


/*
 * Move the "non record" variables "out".
 * Fill as needed.
 */
static int
move_vars_r(NC3_INFO *gnu, NC3_INFO *old)
{
	int err, status=NC_NOERR;
	int varid;
	NC_var **gnu_varpp = (NC_var **)gnu->vars.value;
	NC_var **old_varpp = (NC_var **)old->vars.value;
	NC_var *gnu_varp;
	NC_var *old_varp;
	off_t gnu_off;
	off_t old_off;

	/* Don't parallelize this loop */
	for(varid = (int)old->vars.nelems -1;
		 varid >= 0; varid--)
	{
		gnu_varp = *(gnu_varpp + varid);
		if(IS_RECVAR(gnu_varp))
		{
			/* skip record variables on this pass */
			continue;
		}
		/* else */

		old_varp = *(old_varpp + varid);
		gnu_off = gnu_varp->begin;
		old_off = old_varp->begin;

		if (gnu_off > old_off) {
		    err = ncio_move(gnu->nciop, gnu_off, old_off,
			               old_varp->len, 0);
		    if (status == NC_NOERR) status = err;
		}
	}
	return status;
}


/*
 * Given a valid ncp, return NC_EVARSIZE if any variable has a bad len
 * (product of non-rec dim sizes too large), else return NC_NOERR.
 */
int
NC_check_vlens(NC3_INFO *ncp)
{
    NC_var **vpp;
    /* maximum permitted variable size (or size of one record's worth
       of a record variable) in bytes.  This is different for format 1
       and format 2. */
    long long vlen_max;
    size_t ii;
    size_t large_vars_count;
    size_t rec_vars_count;
    int last = 0;

    if(ncp->vars.nelems == 0)
	return NC_NOERR;

    if (fIsSet(ncp->flags,NC_64BIT_DATA)) /* CDF-5 */
	vlen_max = X_INT64_MAX - 3; /* "- 3" handles rounded-up size */
    else if (fIsSet(ncp->flags,NC_64BIT_OFFSET) && sizeof(off_t) > 4)
	/* CDF2 format and LFS */
	vlen_max = X_UINT_MAX - 3; /* "- 3" handles rounded-up size */
    else /* CDF1 format */
	vlen_max = X_INT_MAX - 3;

    /* Loop through vars, first pass is for non-record variables.   */
    large_vars_count = 0;
    rec_vars_count = 0;
    vpp = ncp->vars.value;
    for (ii = 0; ii < ncp->vars.nelems; ii++, vpp++) {
	if( !IS_RECVAR(*vpp) ) {
	    last = 0;
	    if( NC_check_vlen(*vpp, vlen_max) == 0 ) {
                if (fIsSet(ncp->flags,NC_64BIT_DATA)) /* too big for CDF-5 */
                    return NC_EVARSIZE;
		large_vars_count++;
		last = 1;
	    }
	} else {
	  rec_vars_count++;
	}
    }
    /* OK if last non-record variable size too large, since not used to
       compute an offset */
    if( large_vars_count > 1) { /* only one "too-large" variable allowed */
      return NC_EVARSIZE;
    }
    /* and it has to be the last one */
    if( large_vars_count == 1 && last == 0) {
      return NC_EVARSIZE;
    }
    if( rec_vars_count > 0 ) {
	/* and if it's the last one, there can't be any record variables */
	if( large_vars_count == 1 && last == 1) {
	    return NC_EVARSIZE;
	}
	/* Loop through vars, second pass is for record variables.   */
	large_vars_count = 0;
	vpp = ncp->vars.value;
	for (ii = 0; ii < ncp->vars.nelems; ii++, vpp++) {
	    if( IS_RECVAR(*vpp) ) {
		last = 0;
		if( NC_check_vlen(*vpp, vlen_max) == 0 ) {
                    if (fIsSet(ncp->flags,NC_64BIT_DATA)) /* too big for CDF-5 */
                        return NC_EVARSIZE;
		    large_vars_count++;
		    last = 1;
		}
	    }
	}
	/* OK if last record variable size too large, since not used to
	   compute an offset */
	if( large_vars_count > 1) { /* only one "too-large" variable allowed */
	    return NC_EVARSIZE;
	}
	/* and it has to be the last one */
	if( large_vars_count == 1 && last == 0) {
	    return NC_EVARSIZE;
	}
    }
    return NC_NOERR;
}

/*----< NC_check_voffs() >---------------------------------------------------*/
/*
 * Given a valid ncp, check whether the file starting offsets (begin) of all
 * variables follows the same increasing order as they were defined.
 */
int
NC_check_voffs(NC3_INFO *ncp)
{
    size_t i;
    off_t prev_off;
    NC_var *varp;

    if (ncp->vars.nelems == 0) return NC_NOERR;

    /* Loop through vars, first pass is for non-record variables */
    prev_off = ncp->begin_var;
    for (i=0; i<ncp->vars.nelems; i++) {
        varp = ncp->vars.value[i];
        if (IS_RECVAR(varp)) continue;

        if (varp->begin < prev_off) {
#if 0
            fprintf(stderr,"Variable \"%s\" begin offset (%lld) is less than previous variable end offset (%lld)\n", varp->name->cp, varp->begin, prev_off);
#endif
            return NC_ENOTNC;
        }
        prev_off = varp->begin + varp->len;
    }

    if (ncp->begin_rec < prev_off) {
#if 0
        fprintf(stderr,"Record variable section begin offset (%lld) is less than fix-sized variable section end offset (%lld)\n", varp->begin, prev_off);
#endif
        return NC_ENOTNC;
    }

    /* Loop through vars, second pass is for record variables */
    prev_off = ncp->begin_rec;
    for (i=0; i<ncp->vars.nelems; i++) {
        varp = ncp->vars.value[i];
        if (!IS_RECVAR(varp)) continue;

        if (varp->begin < prev_off) {
#if 0
            fprintf(stderr,"Variable \"%s\" begin offset (%lld) is less than previous variable end offset (%lld)\n", varp->name->cp, varp->begin, prev_off);
#endif
            return NC_ENOTNC;
        }
        prev_off = varp->begin + varp->len;
    }

    return NC_NOERR;
}

/*
 *  End define mode.
 *  Common code for ncendef, ncclose(endef)
 *  Flushes I/O buffers.
 */
static int
NC_endef(NC3_INFO *ncp,
	size_t h_minfree, size_t v_align,
	size_t v_minfree, size_t r_align)
{
	int status = NC_NOERR;

	assert(!NC_readonly(ncp));
	assert(NC_indef(ncp));

	status = NC_check_vlens(ncp);
	if(status != NC_NOERR)
	    return status;
	status = NC_begins(ncp, h_minfree, v_align, v_minfree, r_align);
	if(status != NC_NOERR)
	    return status;
	status = NC_check_voffs(ncp);
	if(status != NC_NOERR)
	    return status;

	if(ncp->old != NULL)
	{
		/* a plain redef, not a create */
		assert(!NC_IsNew(ncp));
		assert(fIsSet(ncp->flags, NC_INDEF));
		assert(ncp->begin_rec >= ncp->old->begin_rec);
		assert(ncp->begin_var >= ncp->old->begin_var);

		if(ncp->vars.nelems != 0)
		{
		if(ncp->begin_rec > ncp->old->begin_rec)
		{
			status = move_recs_r(ncp, ncp->old);
			if(status != NC_NOERR)
				return status;
			if(ncp->begin_var > ncp->old->begin_var)
			{
				status = move_vars_r(ncp, ncp->old);
				if(status != NC_NOERR)
					return status;
			}
			/* else if (ncp->begin_var == ncp->old->begin_var) { NOOP } */
		}
		else
                {
			/* due to fixed variable alignment, it is possible that header
                           grows but begin_rec did not change */
			if(ncp->begin_var > ncp->old->begin_var)
			{
				status = move_vars_r(ncp, ncp->old);
				if(status != NC_NOERR)
					return status;
			}
		 	/* Even if (ncp->begin_rec == ncp->old->begin_rec)
			   and     (ncp->begin_var == ncp->old->begin_var)
			   might still have added a new record variable */
		        if(ncp->recsize > ncp->old->recsize)
			{
			        status = move_recs_r(ncp, ncp->old);
				if(status != NC_NOERR)
				      return status;
			}
		}
		}
	}

	status = write_NC(ncp);
	if(status != NC_NOERR)
		return status;

	/* fill mode is now per variable */
	{
		if(NC_IsNew(ncp))
		{
			status = fillerup(ncp);
			if(status != NC_NOERR)
				return status;

		}
		else if(ncp->old == NULL ? 0
                                         : (ncp->vars.nelems > ncp->old->vars.nelems))
          {
            status = fill_added(ncp, ncp->old);
            if(status != NC_NOERR)
              return status;
            status = fill_added_recs(ncp, ncp->old);
            if(status != NC_NOERR)
              return status;
          }
	}

	if(ncp->old != NULL)
	{
		free_NC3INFO(ncp->old);
		ncp->old = NULL;
	}

	fClr(ncp->flags, NC_CREAT | NC_INDEF);

	return ncio_sync(ncp->nciop);
}


/*
 * Compute the expected size of the file.
 */
int
NC_calcsize(const NC3_INFO *ncp, off_t *calcsizep)
{
	NC_var **vpp = (NC_var **)ncp->vars.value;
	NC_var *const *const end = &vpp[ncp->vars.nelems];
	NC_var *last_fix = NULL;	/* last "non-record" var */
	int numrecvars = 0;	/* number of record variables */

	if(ncp->vars.nelems == 0) { /* no non-record variables and
				       no record variables */
	    *calcsizep = ncp->xsz; /* size of header */
	    return NC_NOERR;
	}

	for( /*NADA*/; vpp < end; vpp++) {
	    if(IS_RECVAR(*vpp)) {
		numrecvars++;
	    } else {
		last_fix = *vpp;
	    }
	}

	if(numrecvars == 0) {
	    off_t varsize;
	    assert(last_fix != NULL);
	    varsize = last_fix->len;
	    if(last_fix->len == X_UINT_MAX) { /* huge last fixed var */
		int i;
		varsize = 1;
  	        for(i = 0; i < last_fix->ndims; i++ ) {
                varsize *= (last_fix->shape ? last_fix->shape[i] : 1);
		    }
	    }
	    *calcsizep = last_fix->begin + varsize;
	    /*last_var = last_fix;*/
	} else {       /* we have at least one record variable */
	    *calcsizep = ncp->begin_rec + ncp->numrecs * ncp->recsize;
	}

	return NC_NOERR;
}

/* Public */

#if 0 /* no longer needed */
int NC3_new_nc(NC3_INFO** ncpp)
{
	NC *nc;
	NC3_INFO* nc3;

	ncp = (NC *) malloc(sizeof(NC));
	if(ncp == NULL)
		return NC_ENOMEM;
	(void) memset(ncp, 0, sizeof(NC));

	ncp->xsz = MIN_NC_XSZ;
	assert(ncp->xsz == ncx_len_NC(ncp,0));

        if(ncpp) *ncpp = ncp;
        return NC_NOERR;

}
#endif

/* WARNING: SIGNATURE CHANGE */
int
NC3_create(const char *path, int ioflags, size_t initialsz, int basepe,
           size_t *chunksizehintp, void *parameters,
           const NC_Dispatch *dispatch, int ncid)
{
	int status = NC_NOERR;
	void *xp = NULL;
	int sizeof_off_t = 0;
        NC *nc;
	NC3_INFO* nc3 = NULL;

        /* Find NC struct for this file. */
        if ((status = NC_check_id(ncid, &nc)))
            return status;

	/* Create our specific NC3_INFO instance */
	nc3 = new_NC3INFO(chunksizehintp);

#if ALWAYS_NC_SHARE /* DEBUG */
	fSet(ioflags, NC_SHARE);
#endif

	/*
	 * Only pe 0 is valid
	 */
	if(basepe != 0) {
            if(nc3) free(nc3);
            return NC_EINVAL;
        }
	assert(nc3->flags == 0);

	/* Now we can set min size */
	if (fIsSet(ioflags, NC_64BIT_DATA))
	    nc3->xsz = MIN_NC5_XSZ; /* CDF-5 has minimum 16 extra bytes */
	else
	    nc3->xsz = MIN_NC3_XSZ;

	if (fIsSet(ioflags, NC_64BIT_OFFSET)) {
	    fSet(nc3->flags, NC_64BIT_OFFSET);
	    sizeof_off_t = 8;
	} else if (fIsSet(ioflags, NC_64BIT_DATA)) {
	    fSet(nc3->flags, NC_64BIT_DATA);
	    sizeof_off_t = 8;
	} else {
	  sizeof_off_t = 4;
	}

	assert(nc3->xsz == ncx_len_NC(nc3,sizeof_off_t));

        status =  ncio_create(path, ioflags, initialsz,
			      0, nc3->xsz, &nc3->chunk, NULL,
			      &nc3->nciop, &xp);
	if(status != NC_NOERR)
	{
		/* translate error status */
		if(status == EEXIST)
			status = NC_EEXIST;
		goto unwind_alloc;
	}

	fSet(nc3->flags, NC_CREAT);

	if(fIsSet(nc3->nciop->ioflags, NC_SHARE))
	{
		/*
		 * NC_SHARE implies sync up the number of records as well.
		 * (File format version one.)
		 * Note that other header changes are not shared
		 * automatically.  Some sort of IPC (external to this package)
		 * would be used to trigger a call to nc_sync().
		 */
		fSet(nc3->flags, NC_NSYNC);
	}

	status = ncx_put_NC(nc3, &xp, sizeof_off_t, nc3->xsz);
	if(status != NC_NOERR)
		goto unwind_ioc;

	if(chunksizehintp != NULL)
		*chunksizehintp = nc3->chunk;

	/* Link nc3 and nc */
        NC3_DATA_SET(nc,nc3);
	nc->int_ncid = nc3->nciop->fd;

	return NC_NOERR;

unwind_ioc:
	if(nc3 != NULL) {
	    (void) ncio_close(nc3->nciop, 1); /* N.B.: unlink */
	    nc3->nciop = NULL;
	}
	/*FALLTHRU*/
unwind_alloc:
	free_NC3INFO(nc3);
	if(nc)
            NC3_DATA_SET(nc,NULL);
	return status;
}

#if 0
/* This function sets a default create flag that will be logically
   or'd to whatever flags are passed into nc_create for all future
   calls to nc_create.
   Valid default create flags are NC_64BIT_OFFSET, NC_CDF5, NC_CLOBBER,
   NC_LOCK, NC_SHARE. */
int
nc_set_default_format(int format, int *old_formatp)
{
    /* Return existing format if desired. */
    if (old_formatp)
      *old_formatp = default_create_format;

    /* Make sure only valid format is set. */
#ifdef USE_NETCDF4
    if (format != NC_FORMAT_CLASSIC && format != NC_FORMAT_64BIT_OFFSET &&
	format != NC_FORMAT_NETCDF4 && format != NC_FORMAT_NETCDF4_CLASSIC)
      return NC_EINVAL;
#else
    if (format != NC_FORMAT_CLASSIC && format != NC_FORMAT_64BIT_OFFSET
#ifdef ENABLE_CDF5
        && format != NC_FORMAT_CDF5
#endif
        )
      return NC_EINVAL;
#endif
    default_create_format = format;
    return NC_NOERR;
}
#endif

int
NC3_open(const char *path, int ioflags, int basepe, size_t *chunksizehintp,
         void *parameters, const NC_Dispatch *dispatch, int ncid)
{
	int status;
	NC3_INFO* nc3 = NULL;
        NC *nc;

        /* Find NC struct for this file. */
        if ((status = NC_check_id(ncid, &nc)))
            return status;

	/* Create our specific NC3_INFO instance */
	nc3 = new_NC3INFO(chunksizehintp);

#if ALWAYS_NC_SHARE /* DEBUG */
	fSet(ioflags, NC_SHARE);
#endif

	/*
	 * Only pe 0 is valid.
	 */
	if(basepe != 0) {
            if(nc3) {
                free(nc3);
                nc3 = NULL;
            }
            status = NC_EINVAL;
            goto unwind_alloc;
        }

#ifdef ENABLE_BYTERANGE
    /* If the model specified the use of byte-ranges, then signal by
       a temporary hack using one of the flags in the ioflags.
    */
    if(NC_testmode(path,"bytes"))
        ioflags |= NC_HTTP;
#endif /*ENABLE_BYTERANGE*/

        status = ncio_open(path, ioflags, 0, 0, &nc3->chunk, parameters,
			       &nc3->nciop, NULL);
	if(status)
		goto unwind_alloc;

	assert(nc3->flags == 0);

	if(fIsSet(nc3->nciop->ioflags, NC_SHARE))
	{
		/*
		 * NC_SHARE implies sync up the number of records as well.
		 * (File format version one.)
		 * Note that other header changes are not shared
		 * automatically.  Some sort of IPC (external to this package)
		 * would be used to trigger a call to nc_sync().
		 */
		fSet(nc3->flags, NC_NSYNC);
	}

	status = nc_get_NC(nc3);
	if(status != NC_NOERR)
		goto unwind_ioc;

	if(chunksizehintp != NULL)
		*chunksizehintp = nc3->chunk;

	/* Link nc3 and nc */
        NC3_DATA_SET(nc,nc3);
	nc->int_ncid = nc3->nciop->fd;

	return NC_NOERR;

unwind_ioc:
	if(nc3) {
    	    (void) ncio_close(nc3->nciop, 0);
	    nc3->nciop = NULL;
	}
	/*FALLTHRU*/
unwind_alloc:
	free_NC3INFO(nc3);
	if(nc)
            NC3_DATA_SET(nc,NULL);
	return status;
}

int
NC3__enddef(int ncid,
	size_t h_minfree, size_t v_align,
	size_t v_minfree, size_t r_align)
{
	int status;
	NC *nc;
	NC3_INFO* nc3;

	status = NC_check_id(ncid, &nc);
	if(status != NC_NOERR)
	  return status;
	nc3 = NC3_DATA(nc);

	if(!NC_indef(nc3))
		return(NC_ENOTINDEFINE);

	return (NC_endef(nc3, h_minfree, v_align, v_minfree, r_align));
}

/*
 * In data mode, same as ncclose.
 * In define mode, restore previous definition.
 * In create, remove the file.
 */
int
NC3_abort(int ncid)
{
	int status;
	NC *nc;
	NC3_INFO* nc3;
	int doUnlink = 0;

	status = NC_check_id(ncid, &nc);
	if(status != NC_NOERR)
	    return status;
	nc3 = NC3_DATA(nc);

	doUnlink = NC_IsNew(nc3);

	if(nc3->old != NULL)
	{
		/* a plain redef, not a create */
		assert(!NC_IsNew(nc3));
		assert(fIsSet(nc3->flags, NC_INDEF));
		free_NC3INFO(nc3->old);
		nc3->old = NULL;
		fClr(nc3->flags, NC_INDEF);
	}
	else if(!NC_readonly(nc3))
	{
		status = NC_sync(nc3);
		if(status != NC_NOERR)
			return status;
	}


	(void) ncio_close(nc3->nciop, doUnlink);
	nc3->nciop = NULL;

	free_NC3INFO(nc3);
	if(nc)
            NC3_DATA_SET(nc,NULL);

	return NC_NOERR;
}

int
NC3_close(int ncid, void* params)
{
	int status = NC_NOERR;
	NC *nc;
	NC3_INFO* nc3;

	status = NC_check_id(ncid, &nc);
	if(status != NC_NOERR)
	    return status;
	nc3 = NC3_DATA(nc);

	if(NC_indef(nc3))
	{
		status = NC_endef(nc3, 0, 1, 0, 1); /* TODO: defaults */
		if(status != NC_NOERR )
		{
			(void) NC3_abort(ncid);
			return status;
		}
	}
	else if(!NC_readonly(nc3))
	{
		status = NC_sync(nc3);
		/* flush buffers before any filesize comparisons */
		(void) ncio_sync(nc3->nciop);
	}

	/*
	 * If file opened for writing and filesize is less than
	 * what it should be (due to previous use of NOFILL mode),
	 * pad it to correct size, as reported by NC_calcsize().
	 */
	if (status == NC_NOERR) {
	    off_t filesize; 	/* current size of open file */
	    off_t calcsize;	/* calculated file size, from header */
	    status = ncio_filesize(nc3->nciop, &filesize);
	    if(status != NC_NOERR)
		return status;
	    status = NC_calcsize(nc3, &calcsize);
	    if(status != NC_NOERR)
		return status;
	    if(filesize < calcsize && !NC_readonly(nc3)) {
		status = ncio_pad_length(nc3->nciop, calcsize);
		if(status != NC_NOERR)
		    return status;
	    }
	}

	if(params != NULL && (nc->mode & NC_INMEMORY) != 0) {
	    NC_memio* memio = (NC_memio*)params;
            /* Extract the final memory size &/or contents */
            status = memio_extract(nc3->nciop,&memio->size,&memio->memory);
        }

	(void) ncio_close(nc3->nciop, 0);
	nc3->nciop = NULL;

	free_NC3INFO(nc3);
        NC3_DATA_SET(nc,NULL);

	return status;
}

int
NC3_redef(int ncid)
{
	int status;
	NC *nc;
	NC3_INFO* nc3;

	status = NC_check_id(ncid, &nc);
	if(status != NC_NOERR)
		return status;
	nc3 = NC3_DATA(nc);

	if(NC_readonly(nc3))
		return NC_EPERM;

	if(NC_indef(nc3))
		return NC_EINDEFINE;


	if(fIsSet(nc3->nciop->ioflags, NC_SHARE))
	{
		/* read in from disk */
		status = read_NC(nc3);
		if(status != NC_NOERR)
			return status;
	}

	nc3->old = dup_NC3INFO(nc3);
	if(nc3->old == NULL)
		return NC_ENOMEM;

	fSet(nc3->flags, NC_INDEF);

	return NC_NOERR;
}


int
NC3_inq(int ncid,
	int *ndimsp,
	int *nvarsp,
	int *nattsp,
	int *xtendimp)
{
	int status;
	NC *nc;
	NC3_INFO* nc3;

	status = NC_check_id(ncid, &nc);
	if(status != NC_NOERR)
		return status;
	nc3 = NC3_DATA(nc);

	if(ndimsp != NULL)
		*ndimsp = (int) nc3->dims.nelems;
	if(nvarsp != NULL)
		*nvarsp = (int) nc3->vars.nelems;
	if(nattsp != NULL)
		*nattsp = (int) nc3->attrs.nelems;
	if(xtendimp != NULL)
		*xtendimp = find_NC_Udim(&nc3->dims, NULL);

	return NC_NOERR;
}

int
NC3_inq_unlimdim(int ncid, int *xtendimp)
{
	int status;
	NC *nc;
	NC3_INFO* nc3;

	status = NC_check_id(ncid, &nc);
	if(status != NC_NOERR)
		return status;
	nc3 = NC3_DATA(nc);

	if(xtendimp != NULL)
		*xtendimp = find_NC_Udim(&nc3->dims, NULL);

	return NC_NOERR;
}

int
NC3_sync(int ncid)
{
	int status;
	NC *nc;
	NC3_INFO* nc3;

	status = NC_check_id(ncid, &nc);
	if(status != NC_NOERR)
		return status;
	nc3 = NC3_DATA(nc);

	if(NC_indef(nc3))
		return NC_EINDEFINE;

	if(NC_readonly(nc3))
	{
		return read_NC(nc3);
	}
	/* else, read/write */

	status = NC_sync(nc3);
	if(status != NC_NOERR)
		return status;

	status = ncio_sync(nc3->nciop);
	if(status != NC_NOERR)
		return status;

#ifdef USE_FSYNC
	/* may improve concurrent access, but slows performance if
	 * called frequently */
#ifndef _WIN32
	status = fsync(nc3->nciop->fd);
#else
	status = _commit(nc3->nciop->fd);
#endif	/* _WIN32 */
#endif	/* USE_FSYNC */

	return status;
}


int
NC3_set_fill(int ncid,
	int fillmode, int *old_mode_ptr)
{
	int i, status;
	NC *nc;
	NC3_INFO* nc3;
	int oldmode;

	status = NC_check_id(ncid, &nc);
	if(status != NC_NOERR)
		return status;
	nc3 = NC3_DATA(nc);

	if(NC_readonly(nc3))
		return NC_EPERM;

	oldmode = fIsSet(nc3->flags, NC_NOFILL) ? NC_NOFILL : NC_FILL;

	if(fillmode == NC_NOFILL)
	{
		fSet(nc3->flags, NC_NOFILL);
	}
	else if(fillmode == NC_FILL)
	{
		if(fIsSet(nc3->flags, NC_NOFILL))
		{
			/*
			 * We are changing back to fill mode
			 * so do a sync
			 */
			status = NC_sync(nc3);
			if(status != NC_NOERR)
				return status;
		}
		fClr(nc3->flags, NC_NOFILL);
	}
	else
	{
		return NC_EINVAL; /* Invalid fillmode */
	}

	if(old_mode_ptr != NULL)
		*old_mode_ptr = oldmode;

	/* loop thru all variables to set/overwrite its fill mode */
	for (i=0; i<nc3->vars.nelems; i++)
		nc3->vars.value[i]->no_fill = (fillmode == NC_NOFILL);

	/* once the file's fill mode is set, any new variables defined after
	 * this call will check NC_dofill(nc3) and set their no_fill accordingly.
	 * See NC3_def_var() */

	return NC_NOERR;
}

/**
 * Return the file format.
 *
 * \param ncid the ID of the open file.

 * \param formatp a pointer that gets the format. Ignored if NULL.
 *
 * \returns NC_NOERR No error.
 * \returns NC_EBADID Bad ncid.
 * \internal
 * \author Ed Hartnett, Dennis Heimbigner
 */
int
NC3_inq_format(int ncid, int *formatp)
{
   int status;
   NC *nc;
   NC3_INFO* nc3;

   status = NC_check_id(ncid, &nc);
   if(status != NC_NOERR)
      return status;
   nc3 = NC3_DATA(nc);

   /* Why even call this function with no format pointer? */
   if (!formatp)
      return NC_NOERR;

   /* only need to check for netCDF-3 variants, since this is never called for netCDF-4 files */
#ifdef ENABLE_CDF5
   if (fIsSet(nc3->flags, NC_64BIT_DATA))
      *formatp = NC_FORMAT_CDF5;
   else
#endif
      if (fIsSet(nc3->flags, NC_64BIT_OFFSET))
         *formatp = NC_FORMAT_64BIT_OFFSET;
      else
         *formatp = NC_FORMAT_CLASSIC;
   return NC_NOERR;
}

/**
 * Return the extended format (i.e. the dispatch model), plus the mode
 * associated with an open file.
 *
 * \param ncid the ID of the open file.
 * \param formatp a pointer that gets the extended format. Note that
 * this is not the same as the format provided by nc_inq_format(). The
 * extended format indicates the dispatch layer model. Classic, 64-bit
 * offset, and CDF5 files all have an extended format of
 * ::NC_FORMATX_NC3. Ignored if NULL.
 * \param modep a pointer that gets the open/create mode associated with
 * this file. Ignored if NULL.
 *
 * \returns NC_NOERR No error.
 * \returns NC_EBADID Bad ncid.
 * \internal
 * \author Dennis Heimbigner
 */
int
NC3_inq_format_extended(int ncid, int *formatp, int *modep)
{
   int status;
   NC *nc;

   status = NC_check_id(ncid, &nc);
   if(status != NC_NOERR)
      return status;
   if(formatp) *formatp = NC_FORMATX_NC3;
   if(modep) *modep = nc->mode;
   return NC_NOERR;
}

/**
 * Determine name and size of netCDF type. This netCDF-4 function
 * proved so popular that a netCDF-classic version is provided. You're
 * welcome.
 *
 * \param ncid The ID of an open file.
 * \param typeid The ID of a netCDF type.
 * \param name Pointer that will get the name of the type. Maximum
 * size will be NC_MAX_NAME. Ignored if NULL.
 * \param size Pointer that will get size of type in bytes. Ignored if
 * null.
 *
 * \returns NC_NOERR No error.
 * \returns NC_EBADID Bad ncid.
 * \returns NC_EBADTYPE Bad typeid.
 * \internal
 * \author Ed Hartnett
 */
int
NC3_inq_type(int ncid, nc_type typeid, char *name, size_t *size)
{
   NC *ncp;
   int stat = NC_check_id(ncid, &ncp);
   if (stat != NC_NOERR)
      return stat;

   if(typeid < NC_BYTE || typeid > NC_STRING)
      return NC_EBADTYPE;

   /* Give the user the values they want. */
   if (name)
      strcpy(name, NC_atomictypename(typeid));
   if (size)
      *size = NC_atomictypelen(typeid);

   return NC_NOERR;
}

/**
 * This is an obsolete form of nc_delete(), supported for backwards
 * compatibility.
 *
 * @param path Filename to delete.
 * @param basepe Must be 0.
 *
 * @return ::NC_NOERR No error.
 * @return ::NC_EIO Couldn't delete file.
 * @return ::NC_EINVAL Invaliod basepe. Must be 0.
 * @author Glenn Davis, Ed Hartnett
 */
int
nc_delete_mp(const char * path, int basepe)
{
	NC *nc;
	int status;
	int ncid;

	status = nc_open(path,NC_NOWRITE,&ncid);
        if(status) return status;

	status = NC_check_id(ncid,&nc);
        if(status) return status;

	/*
	 * Only pe 0 is valid.
	 */
	if(basepe != 0)
		return NC_EINVAL;

	(void) nc_close(ncid);
	if(unlink(path) == -1) {
	    return NC_EIO;	/* No more specific error code is appropriate */
	}
	return NC_NOERR;
}

int
nc_delete(const char * path)
{
        return nc_delete_mp(path, 0);
}

/*----< NC3_inq_default_fill_value() >---------------------------------------*/
/* copy the default fill value to the memory space pointed by fillp */
int
NC3_inq_default_fill_value(int xtype, void *fillp)
{
    if (fillp == NULL) return NC_NOERR;

    switch(xtype) {
        case NC_CHAR   :               *(char*)fillp = NC_FILL_CHAR;   break;
        case NC_BYTE   :        *(signed char*)fillp = NC_FILL_BYTE;   break;
        case NC_SHORT  :              *(short*)fillp = NC_FILL_SHORT;  break;
        case NC_INT    :                *(int*)fillp = NC_FILL_INT;    break;
        case NC_FLOAT  :              *(float*)fillp = NC_FILL_FLOAT;  break;
        case NC_DOUBLE :             *(double*)fillp = NC_FILL_DOUBLE; break;
        case NC_UBYTE  :      *(unsigned char*)fillp = NC_FILL_UBYTE;  break;
        case NC_USHORT :     *(unsigned short*)fillp = NC_FILL_USHORT; break;
        case NC_UINT   :       *(unsigned int*)fillp = NC_FILL_UINT;   break;
        case NC_INT64  :          *(long long*)fillp = NC_FILL_INT64;  break;
        case NC_UINT64 : *(unsigned long long*)fillp = NC_FILL_UINT64; break;
        default : return NC_EBADTYPE;
    }
    return NC_NOERR;
}


/*----< NC3_inq_var_fill() >-------------------------------------------------*/
/* inquire the fill value of a variable */
int
NC3_inq_var_fill(const NC_var *varp, void *fill_value)
{
    NC_attr **attrpp = NULL;

    if (fill_value == NULL) return NC_EINVAL;

    /*
     * find fill value
     */
    attrpp = NC_findattr(&varp->attrs, _FillValue);
    if ( attrpp != NULL ) {
        const void *xp;
        /* User defined fill value */
        if ( (*attrpp)->type != varp->type || (*attrpp)->nelems != 1 )
            return NC_EBADTYPE;

        xp = (*attrpp)->xvalue;
        /* value stored in xvalue is in external representation, may need byte-swap */
        switch(varp->type) {
            case NC_CHAR:   return ncx_getn_text               (&xp, 1,               (char*)fill_value);
            case NC_BYTE:   return ncx_getn_schar_schar        (&xp, 1,        (signed char*)fill_value);
            case NC_UBYTE:  return ncx_getn_uchar_uchar        (&xp, 1,      (unsigned char*)fill_value);
            case NC_SHORT:  return ncx_getn_short_short        (&xp, 1,              (short*)fill_value);
            case NC_USHORT: return ncx_getn_ushort_ushort      (&xp, 1,     (unsigned short*)fill_value);
            case NC_INT:    return ncx_getn_int_int            (&xp, 1,                (int*)fill_value);
            case NC_UINT:   return ncx_getn_uint_uint          (&xp, 1,       (unsigned int*)fill_value);
            case NC_FLOAT:  return ncx_getn_float_float        (&xp, 1,              (float*)fill_value);
            case NC_DOUBLE: return ncx_getn_double_double      (&xp, 1,             (double*)fill_value);
            case NC_INT64:  return ncx_getn_longlong_longlong  (&xp, 1,          (long long*)fill_value);
            case NC_UINT64: return ncx_getn_ulonglong_ulonglong(&xp, 1, (unsigned long long*)fill_value);
            default: return NC_EBADTYPE;
        }
    }
    else {
        /* use the default */
        switch(varp->type){
            case NC_CHAR:                *(char *)fill_value = NC_FILL_CHAR;
                 break;
            case NC_BYTE:          *(signed char *)fill_value = NC_FILL_BYTE;
                 break;
            case NC_SHORT:               *(short *)fill_value = NC_FILL_SHORT;
                 break;
            case NC_INT:                   *(int *)fill_value = NC_FILL_INT;
                 break;
            case NC_UBYTE:       *(unsigned char *)fill_value = NC_FILL_UBYTE;
                 break;
            case NC_USHORT:     *(unsigned short *)fill_value = NC_FILL_USHORT;
                 break;
            case NC_UINT:         *(unsigned int *)fill_value = NC_FILL_UINT;
                 break;
            case NC_INT64:           *(long long *)fill_value = NC_FILL_INT64;
                 break;
            case NC_UINT64: *(unsigned long long *)fill_value = NC_FILL_UINT64;
                 break;
            case NC_FLOAT:               *(float *)fill_value = NC_FILL_FLOAT;
                 break;
            case NC_DOUBLE:             *(double *)fill_value = NC_FILL_DOUBLE;
                 break;
            default:
                 return NC_EINVAL;
        }
    }
    return NC_NOERR;
}
