/*********************************************************************
 *   Copyright 2009, UCAR/Unidata
 *   See netcdf/COPYRIGHT file for copying and redistribution conditions.
 *********************************************************************/
/* $Id: cdata.c,v 1.5 2010/05/24 19:59:56 dmh Exp $ */
/* $Header: /upc/share/CVS/netcdf-3/ncgen/cdata.c,v 1.5 2010/05/24 19:59:56 dmh Exp $ */

#include "includes.h"

#ifdef ENABLE_C

#include	<math.h>


/**************************************************/
/* Code for generating C language data lists*/
/**************************************************/
/* For datalist constant rules: see the rules on the man page */

/* Forward*/
static void cdata_primdata(Symbol*, Datasrc*, Bytebuffer*, Datalist*);
static void cdata_fieldarray(Symbol*, Datasrc*, Odometer*, int, Bytebuffer*, Datalist* fillsrc);

/* Specialty wrappers for cdata_data */
void
cdata_attrdata(Symbol* asym, Bytebuffer* codebuf)
{
    Datasrc* src;
    int typecode = asym->typ.basetype->typ.typecode;

    if(asym->data == NULL) return;
    if(typecode == NC_CHAR) {
	gen_charattr(asym,codebuf);
    } else {
        src = datalist2src(asym->data);
        while(srcmore(src)) {
	    bbAppend(codebuf,' ');
            cdata_basetype(asym->typ.basetype,src,codebuf,NULL);
	}
    }
}

void
cdata_array(Symbol* vsym,
		  Bytebuffer* codebuf,
		  Datasrc* src,
		  Odometer* odom,
	          int index,
		  Datalist* fillsrc)
{
    int i;
    int rank = odom->rank;
    int pushed = 0;
    size_t count;
    Symbol* basetype = vsym->typ.basetype;
    int lastdim = (index == (rank - 1)); /* last dimension*/
    int firstdim = (index == 0); /* first dimension*/
    int isunlimited = (odom->declsize[index] == 0);

    ASSERT(index >= 0 && index < rank);

    count = odom->count[index];

    if(!firstdim && isunlimited && issublist(src)) {
	srcpush(src);
	pushed = 1;
    }
   
    if(lastdim) {
        for(i=0;i<count;i++) {
            cdata_basetype(basetype,src,codebuf,fillsrc);
	}
    } else {
        /* now walk count elements and generate recursively */
        for(i=0;i<count;i++) {
	   cdata_array(vsym,codebuf,src,odom,index+1,fillsrc);
	}
    }

    if(isunlimited && pushed) srcpop(src);

    return;
}

/* Generate an instance of the basetype using datasrc */
void
cdata_basetype(Symbol* tsym, Datasrc* datasrc, Bytebuffer* codebuf, Datalist* fillsrc)
{
    int usecmpd;

    switch (tsym->subclass) {

    case NC_ENUM:
    case NC_OPAQUE:
    case NC_PRIM:
	if(issublist(datasrc)) {
	    semerror(srcline(datasrc),"Expected primitive found {..}");
	}
	bbAppend(codebuf,' ');
	cdata_primdata(tsym,datasrc,codebuf,fillsrc);
	break;

    case NC_COMPOUND: {
	int i;
        Constant* con;
	if(!isfillvalue(datasrc) && !issublist(datasrc)) {/* fail on no compound*/
	    semerror(srcline(datasrc),"Compound data must be enclosed in {..}");
        }
        con = srcnext(datasrc);
	if(con == NULL || con->nctype == NC_FILLVALUE) {
	    Datalist* filler = getfiller(tsym,fillsrc);
	    ASSERT(filler->length == 1);
	    con = &filler->data[0];
	    if(con->nctype != NC_COMPOUND) {
	        semerror(con->lineno,"Compound data fill value is not enclosed in {..}");
	    }
	}
        srcpushlist(datasrc,con->value.compoundv); /* enter the sublist*/
        bbAppend(codebuf,'{');
        for(i=0;i<listlength(tsym->subnodes);i++) {
            Symbol* field = (Symbol*)listget(tsym->subnodes,i);
            bbAppend(codebuf,' ');
            cdata_basetype(field,datasrc,codebuf,NULL);
	}
        bbAppend(codebuf,'}');
        srcpop(datasrc);
	} break;

    case NC_VLEN: {
        Constant* con;
	if(!isfillvalue(datasrc) && !issublist(datasrc)) {/* fail on no compound*/
	    semerror(srcline(datasrc),"Vlen data must be enclosed in {..}");
        }
        con = srcnext(datasrc);
	if(con == NULL || con->nctype == NC_FILLVALUE) {
	    Datalist* filler = getfiller(tsym,fillsrc);
	    ASSERT(filler->length == 1);
	    con = &filler->data[0];
	    if(con->nctype != NC_COMPOUND) {
	        semerror(con->lineno,"Vlen data fill value is not enclosed in {..}");
	    }
	}
        /* generate the nc_vlen_t instance*/
        bbprintf0(stmt,"{%u (void*)vlen_%u}",
	         con->value.compoundv->vlen.count,
    		 con->value.compoundv->vlen.uid);
        bbCatbuf(codebuf,stmt);
        } break;

    case NC_FIELD:
	/* enclose in braces if and only if field is an array */
	usecmpd = (issublist(datasrc) && tsym->typ.dimset.ndims > 0);
	if(usecmpd) srcpush(datasrc);
	if(tsym->typ.dimset.ndims > 0) {
	    Odometer* fullodom = newodometer(&tsym->typ.dimset,NULL,NULL);
            cdata_fieldarray(tsym->typ.basetype,datasrc,fullodom,0,codebuf,fillsrc);
	    odometerfree(fullodom);
	} else {
	    cdata_basetype(tsym->typ.basetype,datasrc,codebuf,NULL);
	}
	if(usecmpd) srcpop(datasrc);
	break;

    default: PANIC1("cdata_basetype: unexpected subclass %d",tsym->subclass);
    }
}

/* Used only for structure field arrays*/
static void
cdata_fieldarray(Symbol* basetype, Datasrc* src, Odometer* odom, int index,
		 Bytebuffer* codebuf, Datalist* fillsrc)
{
    int i;
    int rank = odom->rank;
    unsigned int size = odom->declsize[index];
    int lastdim = (index == (rank - 1)); /* last dimension*/
    int chartype = (basetype->typ.typecode == NC_CHAR);

    if(chartype) {
	/* Collect the char field in a separate buffer */
	Bytebuffer* fieldbuf = bbNew();
        gen_charfield(src,odom,fieldbuf);	
	/* Add to the existing data buf as a single constant */
	cquotestring(fieldbuf);
	bbCat(codebuf," ");
        bbCatbuf(codebuf,fieldbuf);
	bbFree(fieldbuf);
    } else {
        ASSERT(size != 0);
        for(i=0;i<size;i++) {
            if(lastdim) {
	        bbAppend(codebuf,' ');
	        cdata_basetype(basetype,src,codebuf,NULL);
            } else { /* !lastdim*/
	        cdata_fieldarray(basetype,src,odom,index+1,codebuf,fillsrc);
	    }
	}
    }
}

static void
cdata_primdata(Symbol* basetype, Datasrc* src, Bytebuffer* codebuf, Datalist* fillsrc)
{
    Constant* prim;
    Constant target;

    prim = srcnext(src);
    if(prim == NULL) prim = &fillconstant;

    ASSERT(prim->nctype != NC_COMPOUND);

    if(prim->nctype == NC_FILLVALUE) {
	Datalist* filler = getfiller(basetype,fillsrc);
	ASSERT(filler->length == 1);
	srcpushlist(src,filler);
	bbAppend(codebuf,' ');
        cdata_primdata(basetype,src,codebuf,NULL);
	srcpop(src);
	goto done;
    }

    target.nctype = basetype->typ.typecode;

    if(target.nctype != NC_ECONST) {
	convert1(prim,&target);
    }

    switch (target.nctype) {
    case NC_ECONST:
        if(basetype->subclass != NC_ENUM) {
	    semerror(prim->lineno,"Conversion to enum not supported (yet)");
	} else {
	    Datalist* econ = builddatalist(1);
	    Symbol* enumv = prim->value.enumv;
	    srcpushlist(src,econ);
	    dlappend(econ,&enumv->typ.econst);
	    cdata_primdata(enumv->typ.basetype,src,codebuf,fillsrc);
	    srcpop(src);
	 } break;
     case NC_OPAQUE: {
	    setprimlength(&target,basetype->typ.size*2);
	} break;
    default: break;
    }
    bbCat(codebuf,cdata_const(&target));
    
done:
    return;
}

/*
This walk of the data lists collects
vlen sublists and constructs separate C constants
for each of them. The "id" of each list is then
recorded in the containing datalist.
*/
void
cdata_vlenconstants(List* vlenconstants, Bytebuffer* codebuf)
{
    int i,nvlen;
    Datasrc* vlensrc;
    Bytebuffer* tmp = bbNew();

    nvlen = listlength(vlenconstants);
    for(i=0;i<nvlen;i++) {
	Constant* cmpd = (Constant*)listget(vlenconstants,i);
	int chartype;
	Symbol* tsym = cmpd->value.compoundv->vlen.schema;
        ASSERT(tsym != NULL);
        chartype = (tsym->typ.basetype->typ.typecode == NC_CHAR);

        bbprintf0(tmp,"static const %s vlen_%u[] = ",
	        ctypename(tsym->typ.basetype),
                cmpd->value.compoundv->vlen.uid);
	bbCatbuf(codebuf,tmp);
	vlensrc = datalist2src(cmpd->value.compoundv);
	bbAppend(codebuf,'{');
	if(chartype) {
   	    /* Collect the char vlen in a separate buffer */
	    Bytebuffer* vlenbuf = bbNew();
            gen_charvlen(vlensrc,vlenbuf);
	    /* Add to the existing data buf as a single constant */
   	    cquotestring(vlenbuf);
            bbCatbuf(codebuf,vlenbuf);
   	    bbFree(vlenbuf);
	} else {
	    size_t count = 0;
  	    while(srcmore(vlensrc)) {
	        if(count > 0) bbCat(codebuf,", ");
                cdata_basetype(tsym->typ.basetype,vlensrc,codebuf,NULL);
		count++;
	    }
	    ASSERT(count == cmpd->value.compoundv->vlen.count);
        }
	bbCat(codebuf,"} ;\n");
    }
    bbFree(tmp);
}

/* Result is a pool string or a constant => do not free*/
char*
cdata_const(Constant* ci)
{
    Bytebuffer* codetmp = bbNew();
    char* result;

    switch (ci->nctype) {
    case NC_CHAR:
	{ 
	    char tmp[64];
	    tmp[0] = '\0';
	    escapifychar(ci->value.charv,tmp,'\'');
	    bbCat(codetmp,"'");
	    bbCat(codetmp,tmp);
	    bbCat(codetmp,"'");
	}
	break;
    case NC_BYTE:
	bbprintf(codetmp,"%hhd",ci->value.int8v);
	break;
    case NC_SHORT:
	bbprintf(codetmp,"%hd",ci->value.int16v);
	break;
    case NC_INT:
	bbprintf(codetmp,"%d",ci->value.int32v);
	break;
    case NC_FLOAT:
	/* Special case for nanf */
	if(isnan(ci->value.floatv))
	    bbprintf(codetmp,"nanf");
	else
	    bbprintf(codetmp,"%f",ci->value.floatv);
	break;
    case NC_DOUBLE:
	/* Special case for nanf */
	if(isnan(ci->value.doublev))
	    bbprintf(codetmp,"nan");
	else
	    bbprintf(codetmp,"%lf",ci->value.doublev);
	break;
    case NC_UBYTE:
	    bbprintf(codetmp,"%hhu",ci->value.uint8v);
	break;
    case NC_USHORT:
	bbprintf(codetmp,"%hu",ci->value.uint16v);
	break;
    case NC_UINT:
	bbprintf(codetmp,"%uU",ci->value.uint32v);
	break;
    case NC_INT64:
	bbprintf(codetmp,"%lldLL",ci->value.int64v);
	break;
    case NC_UINT64:
	bbprintf(codetmp,"%lluLLU",ci->value.uint64v);
	break;
    case NC_ECONST:
	bbprintf(codetmp,"%s",cname(ci->value.enumv));
	break;
    case NC_STRING:
	{ /* handle separately */
	    char* escaped = escapify(ci->value.stringv.stringv,
				 '"',ci->value.stringv.len);
	    result = poolalloc(1+2+strlen(escaped));
	    strcpy(result,"\"");
	    strcat(result,escaped);
	    strcat(result,"\"");
	    goto done;
	}
	break;
    case NC_OPAQUE: {
	char* p;
	int bslen;
	bslen=(4*ci->value.opaquev.len);
	result = poolalloc(bslen+2+1);
	strcpy(result,"\"");
	p = ci->value.opaquev.stringv;
	while(*p) {
	    strcat(result,"\\x");
	    strncat(result,p,2);	    	    
	    p += 2;	
	}
	strcat(result,"\"");
	goto done;
	} break;

    default: PANIC1("ncstype: bad type code: %d",ci->nctype);
    }
    result = pooldup(bbContents(codetmp)); /*except for NC_STRING and NC_OPAQUE*/
    bbFree(codetmp);
done:
    return result;
}

#endif /*ENABLE_C*/
