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

#include "ut_includes.h"
#include <stddef.h>

#undef DEBUG

#define OPEN "[{("
#define CLOSE "]})"

#define LPAREN '('
#define RPAREN ')'
#define BLANK ' '

NCbytes* buf = NULL;

static Dimdef* finddim(const char* name, NClist* defs);

#if 0
static void
ranktest(int rank, char c, int count)
{
    if(rank != count) {
	fprintf(stderr,"Option '%c': rank mismatch: rank=%d count=%d\n",
		c,rank,count);
	exit(1);
    }
}
#endif

int
parseslices(const char* s0, int* nslicesp, NCZSlice* slices)
{
    int count,nchars,nslices,i;
    const char* s = NULL;
    unsigned long start,stop,stride;

    /* First, compute number of slices */
    for(s=s0,nslices=0;*s;s++) {
	if(*s == '[') nslices++;
    }
    
    if(nslices > NC_MAX_VAR_DIMS) return THROW(NC_EINVAL); /* too many */
    if(nslicesp) *nslicesp = nslices;

    /* Extract the slices */
    for(i=0,s=s0;*s;s+=nchars,i++) {
        NCZSlice* sl = &slices[i];
	/* Try 3-element slice first */
	stride = 1; /* default */
	nchars = -1;
        count = sscanf(s,"[%lu:%lu]%n",&start,&stop,&nchars);
	if(nchars == -1) {
	    nchars = -1;
            count = sscanf(s,"[%lu:%lu:%lu]%n",&start,&stop,&stride,&nchars);
	    if(count != 3) return THROW(NC_EINVAL);
	}
        sl->start = start;
        sl->stop = stop;
        sl->stride = stride;
    }
    return NC_NOERR;
}

int
parsedimdef(const char* s0, Dimdef** defp)
{
    int nchars;
    const char* s = NULL;
    Dimdef* def = NULL;    
    unsigned l;
    const char* p;
    ptrdiff_t count;
    
    if((def = calloc(1,sizeof(struct Dimdef)))==NULL)
	return THROW(NC_ENOMEM);

    /* Extract */
    s = s0;
    if((p = strchr(s,'=')) == NULL) abort();
    if((count = (p - s)) == 0) return THROW(NC_EINVAL);
    def->name = malloc((size_t)count+1);
    memcpy(def->name,s,(size_t)count);
    def->name[count] = '\0';
    s = p+1;
    sscanf(s,"%u%n",&l,&nchars);
    if(nchars == -1) return NC_EINVAL;
    def->size = (size_t)l;
    if(def->size == 0) def->isunlimited = 1;
    s += nchars;
    if(*s != '\0') return NC_EINVAL;
    if(defp) *defp = def;
    return NC_NOERR;
}

int
parsevardef(const char* s0, NClist* dimdefs, Vardef** varp)
{
    int count;
    const char* s = NULL;
    Vardef* vd = NULL;    
    const char* p;
    ptrdiff_t len;
    char name[NC_MAX_NAME];

    if((vd = calloc(1,sizeof(Vardef)))==NULL)
        return THROW(NC_ENOMEM);

    s=s0;

    /* Scan for the end of type name */
    p = strchr(s,BLANK);
    if(p == NULL) return THROW(NC_EINVAL);
    len = (p - s);  
    if(len == 0) return THROW(NC_EINVAL);
    memcpy(name,s,(size_t)len);
    name[len] = '\0';
    vd->typeid = ut_typeforname(name);
    vd->typesize = ut_typesize(vd->typeid);
    while(*p == BLANK) p++;
    s = p;

    /* Scan for the end of var name */
    p = strchr(s,LPAREN);   
    if(p == NULL) return THROW(NC_EINVAL);
    len = (p - s);  
    if(len == 0) return THROW(NC_EINVAL);
    memcpy(name,s,(size_t)len);
    name[len] = '\0';
    vd->name = strdup(name);     
    /* parse a vector of dimnames and chunksizes and convert */
    s = p;
    if(*s == LPAREN) {
        char** names = NULL;
        char* p;
        s++;
        count = parsestringvector(s,RPAREN,&names);
        if(count >= NC_MAX_VAR_DIMS) return THROW(NC_EINVAL);
        vd->rank = count;
        if(vd->rank > 0) {
            int j;
            for(j=0;j<vd->rank;j++) {
                Dimdef* dimref = NULL;
                /* Split on / to get chunksize */
                p = strchr(names[j],'/');
                if(p) *p++ = '\0';              
                if((dimref = finddim(names[j],dimdefs)) == NULL)
                    return THROW(NC_EINVAL);
                vd->dimrefs[j] = dimref;
		vd->dimsizes[j] = dimref->size;
                if(p == NULL)
                    vd->chunksizes[j] = dimref->size;
                else {
                    unsigned long l;
                    sscanf(p,"%lu",&l);
                    vd->chunksizes[j] = (size_t)l;
                }
            }
            /* Skip past the trailing rparen */
            if((p = strchr(s,RPAREN)) == NULL) abort();
            p++;
        }
        freestringvec(names);
    }
    if(varp) *varp = vd;
    return NC_NOERR;
}

int
parsestringvector(const char* s0, int stopchar, char*** namesp)
{
    int nelems,i;
    const char* s;
    char** names = NULL;

    /* First, compute number of elements */
    for(s=s0,nelems=1;*s;s++) {if(*s == ',') nelems++; if(*s == stopchar) break;}
    if(nelems == 0) return THROW(NC_EINVAL);
    names = calloc((size_t)nelems+1,sizeof(char*));
    for(s=s0,i=0;i<nelems;i++) {
        ptrdiff_t len;
        const char* p = strchr(s,',');
        if(p == NULL) p = strchr(s,stopchar);
        if(p == NULL) p = s + strlen(s);
        if(names[i] == NULL) {
            char* q;
            len = (p - s);
            q = malloc(1+(size_t)len);
            memcpy(q,s,(size_t)len);
            q[len] = '\0';
            names[i] = q;
        }
        if(*p == '\0' || *p == stopchar) s = p; else s = p+1;
    }
    names[nelems] = NULL;
    if(namesp) *namesp = names;
    return nelems;
}

void
freedimdefs(NClist* defs)
{
    size_t i;
    for(i=0;i<nclistlength(defs);i++) {
	Dimdef* dd = nclistget(defs,i);
	nullfree(dd->name);
	nullfree(dd);
    }
}

void
freevardefs(NClist* defs)
{
    size_t i;
    for(i=0;i<nclistlength(defs);i++) {
	Vardef* vd = nclistget(defs,i);
	nullfree(vd->name);
	nullfree(vd);
    }
}

void
freeranges(NCZChunkRange* ranges)
{
}

void
freeslices(NCZSlice* slices)
{
}

void
freestringvec(char** vec)
{
    if(vec != NULL) {
        char** p;
	for(p=vec;*p;p++) free(*p);
    }
    nullfree(vec);
}

void
freeprojvector(int rank, NCZProjection** vec)
{
    if(vec != NULL) {
        int r;
        for(r=0;r<rank;r++) free(vec[r]);
    }
    nullfree(vec);
}

/**************************************************/

#if 0
char*
printvec(int len, size64_t* vec)
{
    char* result = NULL;
    int i;
    char value[128];
    NCbytes* buf = ncbytesnew();

    ncbytescat(buf,"(");
    for(i=0;i<len;i++) {
        if(i > 0) ncbytescat(buf,",");
        snprintf(value,sizeof(value),"%lu",(unsigned long)vec[i]);      
        ncbytescat(buf,value);
    }
    ncbytescat(buf,")");
    result = ncbytesextract(buf);
    ncbytesfree(buf);
    return result;
}
#endif /*0*/

/**************************************************/
size_t
ut_typesize(nc_type t)
{
    switch (t) {
    case NC_BYTE: case NC_UBYTE: return 1;
    case NC_SHORT: case NC_USHORT: return 2;
    case NC_INT: case NC_UINT: return 4;
    case NC_INT64: case NC_UINT64: return 8;
    case NC_FLOAT: return 4;
    case NC_DOUBLE: return 8;
    default: usage(THROW(NC_EINVAL));
    }
    return 0;
}

nc_type
ut_typeforname(const char* tname)
{
    if(strcasecmp("byte",tname)==0) return NC_BYTE;
    if(strcasecmp("ubyte",tname)==0) return NC_UBYTE;
    if(strcasecmp("short",tname)==0) return NC_SHORT;
    if(strcasecmp("ushort",tname)==0) return NC_USHORT;
    if(strcasecmp("int",tname)==0) return NC_INT;
    if(strcasecmp("uint",tname)==0) return NC_UINT;
    if(strcasecmp("int64",tname)==0) return NC_INT64;
    if(strcasecmp("uint64",tname)==0) return NC_UINT64;
    if(strcasecmp("float",tname)==0) return NC_FLOAT;
    if(strcasecmp("double",tname)==0) return NC_DOUBLE;
    usage(THROW(NC_EINVAL));
    return NC_NAT;
}

static Dimdef*
finddim(const char* name, NClist* defs)
{
    size_t i;
    for(i=0;i<nclistlength(defs);i++) {
        Dimdef* dd = nclistget(defs,i);
        if(strcmp(dd->name,name) == 0)
            return dd;
    }    
    return NULL;
}

NCZM_IMPL
kind2impl(const char* kind)
{
    if(strcasecmp("s3",kind)==0) return NCZM_S3;
    else if(strcasecmp("file",kind)==0) return NCZM_FILE;
    else if(strcasecmp("zip",kind)==0) return NCZM_ZIP;
    else return NCZM_UNDEF;
}

const char*
impl2kind(NCZM_IMPL impl)
{
    switch (impl) {
    case NCZM_S3: return "s3";
    case NCZM_FILE: return "file";
    case NCZM_ZIP: return "zip";
    case NCZM_UNDEF: break;
    }
    return NULL;
}

/* Goal: Given a set of per-dimension indices,
     compute the corresponding linear position.
*/
size64_t
computelinearoffset(int R, const size64_t* indices, const size64_t* max, size64_t* productp)
{
      size64_t offset, product;
      int i;

      offset = 0; product = 1;
      for(i=0;i<R;i++) {
          offset *= max[i];
          offset += indices[i];
	  product *= max[i];
      } 
      if(productp) *productp = product;
      return offset;
}

void
slices2vector(int rank, NCZSlice* slices, size64_t** startp, size64_t** stopp, size64_t** stridep, size64_t** maxp)
{
    static size64_t start[NC_MAX_VAR_DIMS];
    static size64_t stop[NC_MAX_VAR_DIMS];
    static size64_t stride[NC_MAX_VAR_DIMS];
    static size64_t max[NC_MAX_VAR_DIMS];
    int i;
    for(i=0;i<rank;i++) {
	start[i] = slices[i].start;
	stop[i] = slices[i].stop;
	stride[i] = slices[i].stride;
	max[i] = slices[i].len;
    }
    if(startp) *startp = start;
    if(stopp) *stopp = stop;
    if(stridep) *stridep = stride;
    if(maxp) *maxp = max;
}

void
printoptions(struct UTOptions* opts)
{
    char** p;
    size_t i;
    printf("Options:");
#if 0
    printf(" debug=%d",opts->debug);
    printf(" file=|%s|",opts->file);
    printf(" output=|%s|",opts->output);
#endif
    if(opts->kind)
        printf(" kind=%s",opts->kind);
    if(opts->cmds) {
        printf(" cmds=");
        for(i=0,p=opts->cmds;*p;p++,i++)
            printf("%s%s",(i==0?"(":","),*p);
        printf(")");
    }

    for(i=0;i<nclistlength(opts->dimdefs);i++) {
	struct Dimdef* dd = (struct Dimdef*)nclistget(opts->dimdefs,i);
        printf(" -d%s=%llu",dd->name,dd->size);
    }

    for(i=0;i<nclistlength(opts->vardefs);i++) {
	int j;
	struct Vardef* vd = (struct Vardef*)nclistget(opts->vardefs,i);
        printf(" -v '%d %s[",vd->typeid,vd->name);
	for(j=0;j<vd->rank;j++) {
	    Dimdef* vdd = vd->dimrefs[j];
	    if(j > 0) printf(",");
            printf("%s/%llu",vdd->name,vd->chunksizes[j]);
	}
	printf("]'");
    }

    printf(" -s ");
    for(i=0;i<(size_t)opts->nslices;i++) {
	NCZSlice* sl = &opts->slices[i];
	printf("%s",nczprint_slicex(*sl,1));
    }
    printf("\n");
}

int
hasdriveletter(const char* f)
{
    if(f == NULL || *f == '\0' || strlen(f) < 3) return 0;
    if(f[1] != ':') return 0;
    if(f[2] != '/' && f[2] != '\\') return 0;
    if((f[0] < 'z' && f[0] >= 'a') || (f[0] < 'Z' && f[0] >= 'A'))
        return 1;
    return 0;
}

/* bubble sort a list of strings */
void
ut_sortlist(NClist* l)
{
    int switched;
    size_t i;

    if(nclistlength(l) <= 1) return;
    do {
	switched = 0;
        for(i=0;i<nclistlength(l)-1;i++) {
	    char* ith = nclistget(l,i);
	    char* ith1 = nclistget(l,i+1);
	    if(strcmp(ith,ith1) > 0) {
	        nclistset(l,i,ith1);
    	        nclistset(l,i+1,ith);
	        switched = 1;
	    }
	}
    } while(switched);
#ifdef DEBUG
for(i=0;i<nclistlength(l);i++)
fprintf(stderr,"sorted: [%d] %s\n",i,(const char*)nclistget(l,i));
#endif
}

/* Fill in parts of common */
void
fillcommon(struct Common* common, Vardef* var)
{
    memset(common,0,sizeof(struct Common));
    common->typesize = sizeof(int);
    if(var != NULL) {
        common->rank = var->rank;
        memcpy(common->dimlens,var->dimsizes,sizeof(size64_t)*(size_t)common->rank);
        memcpy(common->chunklens,var->chunksizes,sizeof(size64_t)*(size_t)common->rank);
        memcpy(common->memshape,common->dimlens,sizeof(size64_t)*(size_t)common->rank); /* fake it */
    }
}
