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

#include "includes.h"
#include "nc_iter.h"

#ifdef ENABLE_BINARY

static void alignto(int alignment, Bytebuffer* buf, int base);

static int bin_uid = 0;

static int
bin_charconstant(Generator* generator, Symbol* sym, Bytebuffer* buf, ...)
{
    /* Just transfer charbuf to codebuf */
    Bytebuffer* charbuf;
    va_list ap;
    vastart(ap,buf);
    charbuf = va_arg(ap, Bytebuffer*);
    va_end(ap);
    bbNull(charbuf);
    bbCatbuf(buf,charbuf);
    return 1;
}

static int
bin_constant(Generator* generator, Symbol* sym, NCConstant* con, Bytebuffer* buf,...)
{
    if(con->nctype != NC_ECONST) {
        alignbuffer(con,buf);
    }
    switch (con->nctype) {
    case NC_OPAQUE: {
        unsigned char* bytes = NULL;
        size_t len;
	/* Assume the opaque string has been normalized */
        bytes=makebytestring(con->value.opaquev.stringv,&len);
        bbAppendn(buf,(void*)bytes,len);
	free(bytes);
    } break;
    case NC_CHAR:
        bbAppendn(buf,&con->value.charv,sizeof(con->value.charv));
        break;
    case NC_BYTE:
        bbAppendn(buf,(void*)&con->value.int8v,sizeof(con->value.int8v));
        break;
    case NC_SHORT:
        bbAppendn(buf,(void*)&con->value.int16v,sizeof(con->value.int16v));
        break;
    case NC_INT:
        bbAppendn(buf,(void*)&con->value.int32v,sizeof(con->value.int32v));
        break;
    case NC_FLOAT:
        bbAppendn(buf,(void*)&con->value.floatv,sizeof(con->value.floatv));
        break;
    case NC_DOUBLE:
        bbAppendn(buf,(void*)&con->value.doublev,sizeof(con->value.doublev));
        break;
    case NC_UBYTE:
        bbAppendn(buf,(void*)&con->value.uint8v,sizeof(con->value.uint8v));
        break;
    case NC_USHORT:
        bbAppendn(buf,(void*)&con->value.uint16v,sizeof(con->value.uint16v));
        break;
    case NC_UINT:
        bbAppendn(buf,(void*)&con->value.uint32v,sizeof(con->value.uint32v));
        break;
    case NC_INT64: {
        union SI64 { char ch[8]; long long i64;} si64;
        si64.i64 = con->value.int64v;
        bbAppendn(buf,(void*)si64.ch,sizeof(si64.ch));
        } break;
    case NC_UINT64: {
        union SU64 { char ch[8]; unsigned long long i64;} su64;
        su64.i64 = con->value.uint64v;
        bbAppendn(buf,(void*)su64.ch,sizeof(su64.ch));
        } break;
    case NC_NIL:
    case NC_STRING: {
        char* ptr;
        int len = (size_t)con->value.stringv.len;
	if(len == 0 && con->value.stringv.stringv == NULL) {
	    char* nil = NULL;
            bbAppendn(buf,(void*)&nil,sizeof(nil));
	} else {
	    ptr = (char*)malloc(len+1);
	    memcpy(ptr,con->value.stringv.stringv,len);
	    ptr[len] = '\0';
            bbAppendn(buf,(void*)&ptr,sizeof(ptr));
        }
	} break;

    default: PANIC1("bin_constant: unexpected type: %d",con->nctype);
    }
    return 1;
}

static int
bin_listbegin(Generator* generator, Symbol* tsym, void* liststate, ListClass lc, size_t size, Bytebuffer* buf, int* uidp, ...)
{
    if(uidp) *uidp = ++bin_uid;
    if(lc == LISTCOMPOUND)
        *((int*)liststate) = bbLength(buf);
    return 1;
}

static int
bin_list(Generator* generator, Symbol* tsym, void* liststate, ListClass lc, int uid, size_t count, Bytebuffer* buf, ...)
{
    if(lc == LISTCOMPOUND) {
        int offsetbase = *((int*)liststate);
        /* Pad for the alignment */
	alignto(tsym->typ.alignment,buf,offsetbase);		
    }
    return 1;
}

static int
bin_listend(Generator* generator, Symbol* tsym, void* liststate, ListClass lc, int uid, size_t count, Bytebuffer* buf, ...)
{
    if(lc == LISTCOMPOUND) {
        int offsetbase = *((int*)liststate);
        /* Pad out the whole instance */
	alignto(tsym->typ.cmpdalign,buf,offsetbase);		
    }
    return 1;
}


static int
bin_vlendecl(Generator* generator, Symbol* tsym, Bytebuffer* buf, int uid, size_t count,...)
{
    va_list ap;
    Bytebuffer* vlenmem;
    nc_vlen_t ptr;
    vastart(ap,count);
    vlenmem = va_arg(ap, Bytebuffer*);
    va_end(ap);
    ptr.len = count;
    ptr.p = bbDup(vlenmem);
    bbAppendn(buf,(char*)&ptr,sizeof(ptr));
    return 1;
}

static int
bin_vlenstring(Generator* generator, Symbol* sym, Bytebuffer* codebuf, int* uidp, size_t* sizep,...)
{
    Bytebuffer* vlenmem;
    nc_vlen_t ptr;
    va_list ap;
    if(uidp) *uidp = ++bin_uid;
    vastart(ap,sizep);
    vlenmem = va_arg(ap, Bytebuffer*);
    va_end(ap);
    ptr.len = bbLength(vlenmem);
    ptr.p = bbDup(vlenmem);
    bbAppendn(codebuf,(char*)&ptr,sizeof(ptr));
    return 1;
}

static const char zeros[] =
    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";

static void
alignto(int alignment, Bytebuffer* buf, int base)
{
    int pad = 0;
    int offset = bbLength(buf);
    offset -= base; /* Need to actually align wrt to the base */
    pad = getpadding(offset,alignment);
    if(pad > 0) {
	bbAppendn(buf,(void*)zeros,pad);
    }
}

/* Define the single static bin data generator  */
static Generator bin_generator_singleton = {
    NULL,
    bin_charconstant,
    bin_constant,
    bin_listbegin,
    bin_list,
    bin_listend,
    bin_vlendecl,
    bin_vlenstring
};
Generator* bin_generator = &bin_generator_singleton;

#endif /*ENABLE_BINARY*/