/*********************************************************************
 *   Copyright 1993, UCAR/Unidata
 *   See netcdf/COPYRIGHT file for copying and redistribution conditions.
 *   $Header: /upc/share/CVS/netcdf-3/ncgen/genbin.c,v 1.1 2010/04/04 20:13:20 dmh Exp $
 *********************************************************************/

#include <ctype.h>	/* for isprint() */
#include "includes.h"
#include "nciter.h"
#include "offsets.h"

#ifdef ENABLE_BINARY

#undef TRACE

extern List* vlenconstants;

/* Forward*/
static void genbin_defineattr(Symbol* asym,Bytebuffer*);
static void genbin_definevardata(Symbol* vsym);
static void genbin_write(Symbol*,Bytebuffer*,Odometer*,int);

#ifdef USE_NETCDF4
static void genbin_deftype(Symbol* tsym);
static void genbin_definespecialattributes(Symbol* var);
#endif

/*
 * Generate C code for creating netCDF from in-memory structure.
 */
void
gen_netcdf(const char *filename)
{
    int stat, ncid;
    int idim, ivar, iatt;
    int ndims, nvars, natts, ngatts, ntyps, ngrps;

#ifdef USE_NETCDF4
    int igrp;
#endif

    Bytebuffer* databuf = bbNew();

    ndims = listlength(dimdefs);
    nvars = listlength(vardefs);
    natts = listlength(attdefs);
    ngatts = listlength(gattdefs);
    ntyps = listlength(typdefs);
    ngrps = listlength(grpdefs);

    /* create netCDF file, uses NC_CLOBBER mode */
    cmode_modifier |= NC_CLOBBER;
#ifdef USE_NETCDF4
    if(!usingclassic)
        cmode_modifier |= NC_NETCDF4;
#endif

    stat = nc_create(filename, cmode_modifier, &ncid);
    check_err(stat,__LINE__,__FILE__);
    
    /* ncid created above is also root group*/
    rootgroup->ncid = ncid;

#ifdef USE_NETCDF4
    /* Define the group structure */
    /* walking grdefs list will do a preorder walk of all defined groups*/
    for(igrp=0;igrp<ngrps;igrp++) {
	Symbol* gsym = (Symbol*)listget(grpdefs,igrp);
	if(gsym == rootgroup) continue; /* ignore root group*/
	stat = nc_def_grp(gsym->container->ncid,gsym->name,&gsym->ncid);
	check_err(stat,__LINE__,__FILE__);
    }
#endif

#ifdef USE_NETCDF4
    /* Define the types*/
    if (ntyps > 0) {
	int ityp;
	for(ityp = 0; ityp < ntyps; ityp++) {
	    Symbol* tsym = (Symbol*)listget(typdefs,ityp);
	    genbin_deftype(tsym);
	}
    }
#endif

    /* define dimensions from info in dims array */
    if (ndims > 0) {
        for(idim = 0; idim < ndims; idim++) {
            Symbol* dsym = (Symbol*)listget(dimdefs,idim);
	    stat = nc_def_dim(dsym->container->ncid,
			      dsym->name,
			      dsym->dim.declsize,
			      &dsym->ncid);
	    check_err(stat,__LINE__,__FILE__);
       }
    }

    /* define variables from info in vars array */
    if (nvars > 0) {
	for(ivar = 0; ivar < nvars; ivar++) {
            Symbol* vsym = (Symbol*)listget(vardefs,ivar);
	    if (vsym->typ.dimset.ndims > 0) {	/* a dimensioned variable */
		/* construct a vector of dimension ids*/
		int dimids[NC_MAX_DIMS];
		for(idim=0;idim<vsym->typ.dimset.ndims;idim++)
		    dimids[idim] = vsym->typ.dimset.dimsyms[idim]->ncid;
		stat = nc_def_var(vsym->container->ncid,
				  vsym->name,
			          vsym->typ.basetype->ncid,
		        	  vsym->typ.dimset.ndims,
				  dimids,
				  &vsym->ncid);
	    } else { /* a scalar */
		stat = nc_def_var(vsym->container->ncid,
				  vsym->name,
			          vsym->typ.basetype->ncid,
		        	  vsym->typ.dimset.ndims,
				  NULL,
				  &vsym->ncid);
	    }
	    check_err(stat,__LINE__,__FILE__);
	}
    }

#ifdef USE_NETCDF4
    /* Collect vlen data*/
    bindata_vlenconstants(vlenconstants);

    /* define special variable properties */
    if(!usingclassic && nvars > 0) {
	for(ivar = 0; ivar < nvars; ivar++) {
            Symbol* var = (Symbol*)listget(vardefs,ivar);
	    genbin_definespecialattributes(var);
	}
    }
#endif /*USE_NETCDF4*/

/* define global attributes */
    if(ngatts > 0) {
	for(iatt = 0; iatt < ngatts; iatt++) {
	    Symbol* gasym = (Symbol*)listget(gattdefs,iatt);
	    bbClear(databuf);
	    genbin_defineattr(gasym,databuf);	    
	}
    }
    
    /* define per-variable attributes */
    if(natts > 0) {
	for(iatt = 0; iatt < natts; iatt++) {
	    Symbol* asym = (Symbol*)listget(attdefs,iatt);
	    bbClear(databuf);
	    genbin_defineattr(asym,databuf);
	}
    }

    if (nofill_flag) {
	stat = nc_set_fill(rootgroup->ncid, NC_NOFILL, 0);
	check_err(stat,__LINE__,__FILE__);
    }

    /* leave define mode */
    stat = nc_enddef(rootgroup->ncid);
    check_err(stat,__LINE__,__FILE__);

    /* Load values into those variables with defined data */
    if(nvars > 0) {
        for(ivar = 0; ivar < nvars; ivar++) {
	    Symbol* vsym = (Symbol*)listget(vardefs,ivar);
	    if(vsym->data != NULL) {
	        bbClear(databuf);
		genbin_definevardata(vsym);
	    }
	}
    }
    bbFree(databuf);
}

#ifdef USE_NETCDF4
static void
genbin_definespecialattributes(Symbol* var)
{
    int stat;
    Specialdata* special = &var->var.special;
    if(special->flags & _STORAGE_FLAG) {
        int storage = special->_Storage;
        size_t* chunks = special->_ChunkSizes;
        if(special->nchunks == 0 || chunks == NULL) chunks = NULL;
        stat = nc_def_var_chunking(var->container->ncid,
                                   var->ncid,
                                   (storage == NC_CONTIGUOUS?NC_CONTIGUOUS
                                                            :NC_CHUNKED),
                                   chunks);
        check_err(stat,__LINE__,__FILE__);
    }   
    if(special->flags & _FLETCHER32_FLAG) {
        stat = nc_def_var_fletcher32(var->container->ncid,
                                     var->ncid,
                                     special->_Fletcher32);
        check_err(stat,__LINE__,__FILE__);
    }
    if(special->flags & (_DEFLATE_FLAG | _SHUFFLE_FLAG)) {
        stat = nc_def_var_deflate(var->container->ncid,
                                  var->ncid,
                                  (special->_Shuffle == 1?1:0),
                                  (special->_DeflateLevel >= 0?1:0),
                                  (special->_DeflateLevel >= 0?special->_DeflateLevel
                                                              :0));
        check_err(stat,__LINE__,__FILE__);
    }   
    if(special->flags & _ENDIAN_FLAG) {
        stat = nc_def_var_endian(var->container->ncid,
                                 var->ncid,
                                 (special->_Endianness == NC_ENDIAN_LITTLE?
                                        NC_ENDIAN_LITTLE
                                       :NC_ENDIAN_BIG));
        check_err(stat,__LINE__,__FILE__);
    }   
    if(special->flags & _NOFILL_FLAG) {
        stat = nc_def_var_fill(var->container->ncid,
                                 var->ncid,
		                 (special->_Fill?NC_FILL:NC_NOFILL),
                                 NULL);
        check_err(stat,__LINE__,__FILE__);
    }   
}
#endif /*USE_NETCDF4*/


void
cl_netcdf(void)
{
    int stat;
    stat = nc_close(rootgroup->ncid);
    check_err(stat,__LINE__,__FILE__);
}

#ifdef USE_NETCDF4
/*
Generate type definitions
*/
static void
genbin_deftype(Symbol* tsym)
{
    int i,stat;

    ASSERT(tsym->objectclass == NC_TYPE);
    switch (tsym->subclass) {
    case NC_PRIM: break; /* these are already taken care of*/
    case NC_OPAQUE:
	stat = nc_def_opaque(tsym->container->ncid,
			     tsym->typ.size,
			     tsym->name,
			     &tsym->ncid);
        check_err(stat,__LINE__,__FILE__);	
	break;
    case NC_ENUM: {
        Bytebuffer* datum;
        Datalist* ecdl;
        Datasrc* esrc;
	stat = nc_def_enum(tsym->container->ncid,
			   tsym->typ.basetype->typ.typecode,
			   tsym->name,
			   &tsym->ncid);
        check_err(stat,__LINE__,__FILE__);	
	datum = bbNew();
        ecdl = builddatalist(1);
        dlextend(ecdl); /* make room for one constant*/
	ecdl->length = 1;
	esrc = datalist2src(ecdl);
	for(i=0;i<listlength(tsym->subnodes);i++) {
	    Symbol* econst = (Symbol*)listget(tsym->subnodes,i);
	    ASSERT(econst->subclass == NC_ECONST);
	    ecdl->data[0] = econst->typ.econst;		
	    esrc->index = 0;
	    bbClear(datum);
	    bindata_basetype(econst->typ.basetype,esrc,datum,NULL);
	    stat = nc_insert_enum(tsym->container->ncid,
				  tsym->ncid,
				  econst->name,
				  bbContents(datum));
	    check_err(stat,__LINE__,__FILE__);	
	}
	bbFree(datum);
	ecdl->length = 0;
	freedatasrc(esrc);
	} break;
    case NC_VLEN:
	stat = nc_def_vlen(tsym->container->ncid,
			   tsym->name,
			   tsym->typ.basetype->ncid,
			   &tsym->ncid);
        check_err(stat,__LINE__,__FILE__);	
	break;
    case NC_COMPOUND:
	stat = nc_def_compound(tsym->container->ncid,
			       tsym->typ.size,			       
			       tsym->name,
			       &tsym->ncid);			       
        check_err(stat,__LINE__,__FILE__);	
	for(i=0;i<listlength(tsym->subnodes);i++) {
	    Symbol* efield = (Symbol*)listget(tsym->subnodes,i);
	    ASSERT(efield->subclass == NC_FIELD);
	    if(efield->typ.dimset.ndims == 0){ 
	        stat = nc_insert_compound(
				tsym->container->ncid,
				tsym->ncid,
				efield->name,
			        efield->typ.offset,
				efield->typ.basetype->ncid);
	    } else {
		int j;
		Bytebuffer* dimbuf = bbNew();
		/* Generate the field dimension constants*/
		for(j=0;j<efield->typ.dimset.ndims;j++) {
		     unsigned int size = efield->typ.dimset.dimsyms[j]->dim.declsize;
		     bbAppendn(dimbuf,(char*)&size,sizeof(size));
		}
	        stat = nc_insert_array_compound(
				tsym->container->ncid,
				tsym->ncid,
				efield->name,
			        efield->typ.offset,
				efield->typ.basetype->ncid,
				efield->typ.dimset.ndims,
				(int*)bbContents(dimbuf));
		bbFree(dimbuf);
	    }
            check_err(stat,__LINE__,__FILE__);	
	}
	break;
    default: panic("definectype: unexpected type subclass");
    }
}
#endif /*USE_NETCDF4*/

static void
genbin_defineattr(Symbol* asym,Bytebuffer* databuf)
{
    int stat;
    size_t len;
    Datalist* list;
    int varid, grpid, typid;
    Symbol* basetype = asym->typ.basetype;

    bbClear(databuf);

    grpid = asym->container->ncid,
    varid = (asym->att.var == NULL?NC_GLOBAL : asym->att.var->ncid);
    typid = basetype->ncid;

    list = asym->data;
    if(list == NULL) PANIC("empty attribute list");
    len = list->length;
    if(len == 0) PANIC("empty attribute list");

    bindata_attrdata(asym,databuf);

    /* Use the specialized put_att_XX routines if possible*/
    if(isprim(basetype->typ.typecode)) {
	switch (basetype->typ.typecode) {
            case NC_BYTE: {
                signed char* data = (signed char*)bbContents(databuf);
                stat = nc_put_att_schar(grpid,varid,asym->name,typid,len,data);
                check_err(stat,__LINE__,__FILE__);  
            } break;
            case NC_CHAR: {
                char* data = (char*)bbContents(databuf);
		size_t slen = bbLength(databuf);
		/* Revise length if slen == 0 */
		if(slen == 0) {bbAppend(databuf,'\0'); slen++;}
                stat = nc_put_att_text(grpid,varid,asym->name,slen,data);
                check_err(stat,__LINE__,__FILE__);  
            } break;
            case NC_SHORT: {
                short* data = (short*)bbContents(databuf);
                stat = nc_put_att_short(grpid,varid,asym->name,typid,len,data);
                check_err(stat,__LINE__,__FILE__);  
            } break;
            case NC_INT: {
                int* data = (int*)bbContents(databuf);
                stat = nc_put_att_int(grpid,varid,asym->name,typid,len,data);
                check_err(stat,__LINE__,__FILE__);  
            } break;
            case NC_FLOAT: {
                float* data = (float*)bbContents(databuf);
                stat = nc_put_att_float(grpid,varid,asym->name,typid,len,data);
                check_err(stat,__LINE__,__FILE__);  
            } break;
            case NC_DOUBLE: {
                double* data = (double*)bbContents(databuf);
                stat = nc_put_att_double(grpid,varid,asym->name,typid,len,data);
                check_err(stat,__LINE__,__FILE__);  
            } break;
#ifdef USE_NETCDF4
	    case NC_STRING: {
	        const char** data;
	        data = (const char**)bbContents(databuf);
                stat = nc_put_att_string(grpid,varid,asym->name,
				     bbLength(databuf)/sizeof(char*),
				     data);
		} break;
            case NC_UBYTE: {
                unsigned char* data = (unsigned char*)bbContents(databuf);
                stat = nc_put_att_ubyte(grpid,varid,asym->name,typid,len,data);
                check_err(stat,__LINE__,__FILE__);  
            } break;
            case NC_USHORT: {
                unsigned short* data = (unsigned short*)bbContents(databuf);
                stat = nc_put_att_ushort(grpid,varid,asym->name,typid,len,data);
                check_err(stat,__LINE__,__FILE__);  
            } break;
            case NC_UINT: {
                unsigned int* data = (unsigned int*)bbContents(databuf);
                stat = nc_put_att_uint(grpid,varid,asym->name,typid,len,data);
                check_err(stat,__LINE__,__FILE__);  
            } break;
            case NC_INT64: {
                long long* data = (long long*)bbContents(databuf);
                stat = nc_put_att_longlong(grpid,varid,asym->name,typid,len,data);
                check_err(stat,__LINE__,__FILE__);  
            } break;
            case NC_UINT64: {
                unsigned long long* data = (unsigned long long*)bbContents(databuf);
                stat = nc_put_att_ulonglong(grpid,varid,asym->name,typid,len,data);
                check_err(stat,__LINE__,__FILE__);  
            } break;
#endif            
            default: PANIC1("genbin_defineattr: unexpected basetype: %d",basetype->typ.typecode);
	}
    } else { /* use the generic put_attribute for user defined types*/
	const char* data;
	char out[4096];
	data = (const char*)bbContents(databuf);
        stat = nc_put_att(grpid,varid,asym->name,typid,
			        len,(void*)data);
        check_err(stat,__LINE__,__FILE__);
	memset(out,0x77,sizeof(out));
	stat = nc_get_att(grpid,varid,asym->name,&out);
        check_err(stat,__LINE__,__FILE__);
    }
}


/* Following is patterned after the walk functions in semantics.c */
static void
genbin_definevardata(Symbol* vsym)
{
    int varid, grpid;
    int rank;
    Bytebuffer* memory;
    Datasrc* src;
    int chartype = (vsym->typ.basetype->typ.typecode == NC_CHAR);
    nciter_t iter;
    Odometer* odom = NULL;
    size_t nelems;
    Datalist* fillsrc = vsym->var.special._Fillvalue;

    grpid = vsym->container->ncid,
    varid = vsym->ncid;
    rank = vsym->typ.dimset.ndims;

    memory = bbNew();

    /* give the buffer a running start to be large enough*/
    bbSetalloc(memory, nciterbuffersize);

    if(vsym->data == NULL) return;
    src = datalist2src(vsym->data);
    if(vsym->typ.dimset.ndims == 0) { /*scalar */
	bindata_basetype(vsym->typ.basetype,src,memory,fillsrc); /*scalar*/
        if(bbLength(memory) > 0)
	    genbin_write(vsym,memory,odom,1);
    } else { /* This is the heavy lifing */
        /* Create an iterator to generate blocks of data */
        nc_get_iter(vsym,nciterbuffersize,&iter);
        /* Fill in the local odometer instance */
        odom = newodometer(&vsym->typ.dimset,NULL,NULL);
        for(;;) {
	    nelems=nc_next_iter(&iter,odom->start,odom->count);
	    if(nelems == 0) break;
if(debug > 0) {/*dump the iteration info*/
int i;
fprintf(stderr,"iter: %s->start[",vsym->name);
for(i=0;i<odom->rank;i++)
fprintf(stderr,"%s%lu",(i==0?"":","),(unsigned long)odom->start[i]);
fprintf(stderr,"]\n      %s->count[",vsym->name);
for(i=0;i<odom->rank;i++)
fprintf(stderr,"%s%lu",(i==0?"":","),(unsigned long)odom->count[i]);
fprintf(stderr,"]\n");
}
            if(chartype) {/* Handle character case separately */
                gen_chararray(vsym,memory,src,odom,0);
    	    } else
                bindata_array(vsym,memory,src,odom,/*index=*/0,fillsrc);
   	    /* Dump this chunk of (non-scalar) memory */
	    genbin_write(vsym,memory,odom,0);
	}
        /* Write any residual data */
        if(bbLength(memory) > 0)
	    genbin_write(vsym,memory,odom,0);
    }
    odometerfree(odom);
    bbFree(memory);
}

static void
genbin_write(Symbol* vsym, Bytebuffer* memory, Odometer* odom, int scalar)
{
    int stat = NC_NOERR;
if(!scalar && debug > 0) {
    int i;
    fprintf(stderr,"startset = [");
    for(i=0;i<odom->rank;i++)
	fprintf(stderr,"%s%lu",(i>0?", ":""),(unsigned long)odom->start[i]);
    fprintf(stderr,"] ");
    fprintf(stderr,"countset = [");
    for(i=0;i<odom->rank;i++)
	fprintf(stderr,"%s%lu",(i>0?", ":""),(unsigned long)odom->count[i]);
    fprintf(stderr,"]\n");
    fflush(stderr);
}
	
    if(scalar) {
	size_t count[1] = {1};
        stat = nc_put_var1(vsym->container->ncid, vsym->ncid, count,
                           bbContents(memory));
    } else {
        stat = nc_put_vara(vsym->container->ncid, vsym->ncid,
                       odom->start, odom->count,
                       bbContents(memory));
    }
    check_err(stat,__LINE__,__FILE__);
    bbClear(memory);
}

#endif /*ENABLE_BINARY*/
