/*
 * © Copyright 1996-2012 ECMWF.
 * 
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. 
 * In applying this licence, ECMWF does not waive the privileges and immunities 
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */

#ifndef mars_H
#include "mars.h"
#endif

int static eq_pseudo_ok(const char* l,const char* r)
{
	if(mars.pseudogrib && mars.dont_check_pseudogrib) {
		if(!eq_string(l,r))
			marslog(LOG_WARN,"PSEUDO GRIB mismatch ignored: %s %s",l,r);
		return 1;
	}
	return eq_string(l,r);
}


static axis_t axis[] = {

	/* From dhsbase.c 'check_grib' */
	{"CLASS",       eq_pseudo_ok, },
	{"TYPE",        eq_pseudo_ok, },
	{"STREAM",      eq_pseudo_ok, },
	{"LEVTYPE",     eq_string, },

	{"ORIGIN",      eq_string, },
	{"PRODUCT",     eq_string, },
	{"SECTION",     eq_string, },
	{"METHOD",      eq_integer, },
	{"SYSTEM",      eq_integer, },

	/* testing */
	/* {"REPRES",      eq_null, }, */

	/* From field order */
	{"DATE",        eq_date, },
	{"REFDATE",     eq_date, },
	{"HDATE",       eq_date, },
	{"TIME",        eq_time, },
	{"ANOFFSET",   eq_range, },

	{"REFERENCE",   eq_range, },
	{"STEP",        eq_range, },
	{"FCMONTH",     eq_integer, },
	{"FCPERIOD",    eq_range, },

	{"LEADTIME",    eq_range, },
	{"OPTTIME",     eq_range, },

	{"EXPVER",      eq_pseudo_ok, },
	{"DOMAIN",      eq_string, },

	{"DIAGNOSTIC",  eq_integer, },
	{"ITERATION",   eq_integer, },

	{"QUANTILE",    eq_range, },
	{"NUMBER",      eq_integer, },

	{"LEVELIST",    eq_coord, },
	{"LATITUDE",    eq_coord, },
	{"LONGITUDE",   eq_coord, },
	{"RANGE",       eq_range, },

	{"PARAM",       eq_param, },

	{"IDENT",       eq_integer, },
	{"OBSTYPE",     eq_integer, },
	{"INSTRUMENT",  eq_integer, },

	{"FREQUENCY",   eq_integer, },
	{"DIRECTION",   eq_integer, },

	{"CHANNEL",     eq_integer, },
};

static axis_t axisnew[1024]; /* May be enough language attributes for the time being... */
static int init_axis(const request *r)
{
	parameter *p = r->params;
	static int count = 0;

	if(count)
		return count;

	while(p)
	{
		const request *i = p->interface;
		boolean take = true;
		const char *check = NULL;

		if(i && (((check = get_value(i,"check",0)) != NULL) && EQ(check,"false")))
			take = false;

		if(take)
		{
			const char *type = get_value(i,"type",0);
			namecmp cmp = eq_default;

			axisnew[count].name    = p->name;
			if(type)
			{
				if(EQ(type,"string"))
					cmp = eq_string;
				else if(EQ(type,"null"))
					cmp = eq_null;
				else if(EQ(type,"date"))
					cmp = eq_date;
				else if(EQ(type,"time"))
					cmp = eq_time;
				else if(EQ(type,"step"))
					cmp = eq_range;
				else if(EQ(type,"integer"))
					cmp = eq_integer;
				else if(EQ(type,"coord"))
					cmp = eq_coord;
				else if(EQ(type,"real"))
					cmp = eq_real;
				else if(EQ(type,"default"))
					cmp = eq_default;
			}
			axisnew[count].compare = cmp;
		}
		count++;
		p = p->next;
	}
	return count;
}

int axisindex(const char *name)
{
	int i = 0;
	for(i = 0 ; i < NUMBER(axis) ; i++)
	{
		if(EQ(name,axis[i].name))
			return i;
	}
	return -1;
}

namecmp comparator(const char *name)
{
	static char *dontcompare = NULL;
	static boolean first = true;
	int i = 0;

	if(first)
	{
		dontcompare = getenv("MARS_DONT_CHECK");
		first = false;
	}

	if(dontcompare != NULL)
	{
		if(EQ(dontcompare,name))
			return eq_null;
	}

	if((i = axisindex(name))!= -1)
		return axis[i].compare;
	marslog(LOG_WARN,"No comparator for %s",name);
	return eq_string;
}

/********************/
/* index accessors  */
/********************/

static int count_index(const hypercube *h)
{
	int i = 0, n = 0;

	for(i = 0; i<h->size; ++i)
		n += h->set[i];
	return n;
}

static int count_holes(const hypercube *h, int cnt)
{
	int i = 0, n = 0;

	for(i = 0; i<cnt;++i)
		n += h->set[i];

	return (n==cnt)?0:(cnt - n);
}

void print_hypercube_index(const hypercube *h)
{
	int i = 0;

	for(i = 0; i<h->size; ++i)
		printf(" %d",h->set[i]);
	printf("\n");
}

static void reset_index(hypercube *h, int v)
{
	memset(h->set,v,h->size);
}


static int pos_to_index(const hypercube *h, int pos, int *from, int *last)
{
	for(; *from < h->size; ++(*from))
	{
		*last += h->set[*from];
		if(*last > pos)
			return (*from)++;
	}
	return -1;
}

static void set_index(hypercube *h, int index, int value)
{
	if(index < 0 || index >= h->count)
	{
		marslog(LOG_EXIT,"Internal error, bad hypercube index %d",index);
	}

	if(index >= h->max)
	{
		int old = h->max;
		while(index>= h->max)
			h->max += 4096;

		h->set = h->set?REALLOC(h->set,h->max):MALLOC(h->max);
		memset(h->set+old,0,h->max - old);
	}

	if(index >= h->size)
		h->size = index+1;

	h->set[index] = value;
}

static int get_index(hypercube *h, int index)
{
	if(index < 0 || index >= h->count)
	{
		marslog(LOG_EXIT,"Internal error, bad hypercube index %d",index);
	}

	if(index >= h->size)
		return 0;

	return h->set[index];
}

static int cmp_index(const hypercube *a, const hypercube *b)
{
	if(sizeof(a->set) == sizeof(b->set))
		return memcmp(a->set,b->set,sizeof(a->set));
	return -1;
}

/**************************/
/* End of index accessors */
/**************************/


/*******************/
/* axis accessors  */
/*******************/

static int count_axis(const hypercube *h)
{
	if(h && h->cube)
		return count_values(h->cube,"AXIS");

	return -1;
}


static const char* get_axis(const hypercube *h, int pos)
{
	const char *axis = NULL;
	if(pos<count_axis(h))
	{
		axis = get_value(h->cube,"AXIS",pos);
	}
	return axis;
}

static void add_axis(hypercube *h, const char *axis)
{
	add_value(h->cube,"AXIS","%s",axis);		
}

static void reset_axis(hypercube *h)
{
	unset_value(h->cube,"AXIS");
}

static void remove_axis(hypercube *h, const char *axis)
{
	unset_param_value(h->cube,"AXIS",axis);
}

static void cube_values(hypercube *h, const char *p)
{
	valcpy(h->cube,h->r,(char *)p,(char *)p);
}

static int count_dimensions(const hypercube *, const char *);

static int set_axis(hypercube *h)
{
	int i     = 0;
	int count = (h && h->r) ? 1 : -1;

	reset_axis(h);
	for(i = (NUMBER(axis) -1) ; i>=0; --i)
	{
		int n = count_dimensions(h,axis[i].name);
		if(n > 1)
		{
			add_axis(h,axis[i].name);
			cube_values(h,axis[i].name);
			count *= n;
		}
	}	

	return count;
}

/*************************/
/* End of axis accessors */
/*************************/

/*******************/
/* Cube dimensions */
/*******************/


static int count_dimensions(const hypercube *h, const char *axis)
{
	int dims  = -1;
	if(h && h->r)
		dims = count_values(h->r,axis);
	return dims;
}

/**************************/
/* End of cube dimensions */
/**************************/

/**************************/
/* Auxiliary functions    */
/**************************/


static int count_hypercube(const request *r)
{
	int i = 0, count = 1;
	for(i = 0 ; i< NUMBER(axis); ++i)
	{
		int c = count_values(r,axis[i].name);
		count *= c?c:1;
	}

	return count;
}

int cube_order(const hypercube *h, const request *r)
{
	return _cube_position(h,r,true);
}

static int cube_position(const hypercube *h, const request *r)
{
	return _cube_position(h,r,false);
}

static void reserve_index_cache(hypercube *h, int size)
{
	if(size == 0)
		return;

	if(h->index_cache != 0)
		FREE(h->index_cache);
	marslog(LOG_DBUG,"Allocating hypercube index_cache: %d entries",size);
	h->index_cache = NEW_ARRAY_CLEAR(int,size);
	h->index_cache_size = size;
}

int _cube_position(const hypercube *h, const request *r,boolean remove_holes)
{
	request *cube = h->cube;
	int     c     = count_axis(h);
	int     index = 0;
	int     i     = 0;
	int     n     = 1;
	int     ok    = 0;

	if(h->index_cache == 0 || h->index_cache_size != c)
		reserve_index_cache((hypercube*)h,c);

	for(i=0; i<c;++i)
	{
		const char *axis = get_axis(h,i);
		const char *v    = get_value(r,axis,0);
		const char *w    = NULL;
		int        dims  = count_dimensions(h,axis);
		int        k     = 0;
		int        count = count_values(cube,axis);
		int        last  = h->index_cache[i];

		for(k = 0; k < count; k++)	
		{
			int j = (k+last)%count;
			w = get_value(cube,axis,j);
			if(h->compare ? h->compare[i](w,v) : (w == v))
			{
				index += j*n;
				n     *= dims;
				ok++;
				((hypercube*)h)->index_cache[i]  = j;
				break;
			}
			else
				marslog(LOG_DBUG,"_cube_position, %s, %s != %s [%scompare function available]",axis,w,v,h->compare ? "" : "no ");
		}
	}

	if(remove_holes)
	{
		int holes = 0;
		if(count_index(h) != h->size)
			holes = count_holes(h,index);
		index -= holes;
	}

	return (ok == c) ? index : -1;
}

static void explain_cube_position(const hypercube *h, const request *r)
{
	request *cube = h->cube;
	int     c     = count_axis(h);
	int     i = 0;

	if(h->index_cache == 0 || h->index_cache_size != c)
		reserve_index_cache((hypercube*)h,c);

	for(i=0; i<c;++i)
	{
		const char *axis = get_axis(h,i);
		const char *v    = get_value(r,axis,0);
		const char *w    = NULL;
		int        k     = 0;
		int        count = count_values(cube,axis);
		int        last  = h->index_cache[i];
		int        ok    = 0;


		for(k = 0; k < count; k++)
		{
			int j = (k+last)%count;
			w = get_value(cube,axis,j);
			if(h->compare ? h->compare[i](w,v) : (w == v))
			{
				ok++;
				((hypercube*)h)->index_cache[i]  = j;
				break;
			}
		}

		if(!ok)
			marslog(LOG_EROR,"Could not match %s (%s)",axis,v?v:"(null)");
	}

}

boolean cube_contains(const hypercube *h, const request *r)
{
	request *cube = h->cube;
	int     c     = count_axis(h);
	int     i = 0;
	int     j = 0;

	if(h->index_cache == 0 || h->index_cache_size != c)
		reserve_index_cache((hypercube*)h,c);

	for(i=0; i<c;++i)
	{
		const char *axis = get_axis(h,i);

		const char *w    = NULL;
		int        k     = 0;
		int        count = count_values(cube,axis);
		int        last  = h->index_cache[i];

		int        j     = 0;
		const char *v    = get_value(r,axis,j++);
		int        ok    = (v == NULL);

		while(v && !ok)
		{

			for(k = 0; k < count && !ok; k++)
			{
				int j = (k+last)%count;
				w = get_value(cube,axis,j);
				/* printf("w: %s, axis: %s, j: %d\n",w,axis,j); */
				if(h->compare ? h->compare[i](w,v) : (w == v))
				{
					ok++;
					((hypercube*)h)->index_cache[i]  = j;
					break;
				}
			}
			v = get_value(r,axis,j++);
		}

		if(!ok)
		{
			marslog(LOG_DBUG,"cube_contais: Could not match %s (%s)",axis,v?v:"(null)");
			return false;
		}
	}
	return true;

}
void cube_indexes(const hypercube *h, request *r, int *indexes, int size)
{
	request *cube = h->cube;
	int     c     = count_axis(h);
	int     i     = 0;
	int     index = 0;
	int     n     = 1;
	int     ok    = 0;

	if(size < c)
	{
		marslog(LOG_WARN,"MARS internal error in cube_indexes. size=%d < axis=%d",size,c);
	}

	if(h->index_cache == 0 || h->index_cache_size != c)
		reserve_index_cache((hypercube*)h,c);

	for(i=0; i<c;++i)
	{
		const char *axis = get_axis(h,i);
		const char *v    = get_value(r,axis,0);
		const char *w    = NULL;
		int        dims  = count_dimensions(h,axis);
		int        j     = 0;
		int        k     = 0;
		int        count = count_values(cube,axis);
		int        last  = h->index_cache[i];


		for(k = 0; k < count; k++)
		{
			j = (k+last)%count;
			w = get_value(cube,axis,j);
			if(h->compare ? h->compare[i](w,v) : (w == v))
			{
				index += j*n;
				n     *= dims;
				ok++;
				((hypercube*)h)->index_cache[i]  = j;
				break;

			}
		}
		indexes[i] = j;
	}

}

/*********************************/
/* End of Auxiliary functions    */
/*********************************/


hypercube *new_hypercube(const request *r)
{
	hypercube *h = NEW_CLEAR(hypercube);
	int total = 0, count = 0;
	int n = 0;
	const char *val = 0;

	h->r    = clone_one_request(r);
	h->cube = empty_request("CUBE");

	h->count = total = count_hypercube(r);
	count = set_axis(h);

	h->compare = 0;

	if((total != count) || (count == 0))
	{
		marslog(LOG_EROR,"Internal error while computing hypercube fields");
		marslog(LOG_EROR,"Number of fields in request %d",total);
		marslog(LOG_EROR,"Number of fields in hypercube %d",count);
	}
	set_index(h,count-1,1);
	memset(h->set,1,count);

	/* This is expensive, but makes the iterator with only
	   those parameters found as axis */
	h->iterator = empty_request(0);
	for(n = 0; n < NUMBER(axis); ++n)
		if( (val = get_value(h->r,axis[n].name,0)) )
			set_value(h->iterator,axis[n].name,val);

	return h;	
}

int add_field_to_hypercube(hypercube *h,request* r)
{
	return -1;
}

int remove_field_from_hypercube(hypercube *h,request* r,int n)
{
	parameter *p;
	int o = cube_position(h,r);

	if(o < 0)
	{
		marslog(LOG_EROR,"Field %d is unknown",n);
		explain_cube_position(h,r);
		print_one_request(r);
		return HYPERCUBE_ERROR;
	}

	if(get_index(h,o) == 0)
	{
		marslog(LOG_EROR,"Field %d is duplicated or not described in request",n);
		print_one_request(r);
		return HYPERCUBE_ERROR;
	}

	p = r->params;
	while(p)
	{
		if(*p->name != '_' && !count_values(h->cube,p->name))
		{
			if(axisindex(p->name) != -1)
			{
				marslog(LOG_EROR,"Field %d has %s = %s, but %s is not in request",n,p->name,get_value(r,p->name,0),p->name);
				print_one_request(r);
				return HYPERCUBE_ERROR;
			}
		}
		p = p->next;
	}

	set_index(h,o,0);

	return NOERR;

}

void remove_name_from_hypercube(hypercube *h,const char *ignore)
{
	unset_value(h->r,ignore);
	unset_value(h->cube,ignore);
	unset_param_value(h->cube,"AXIS",ignore);
}

void print_hypercube(const hypercube *h)
{
	print_all_requests(h->r);
	print_all_requests(h->cube);
	marslog(LOG_INFO,"%d active out of %d fields described\n",count_index(h),h->size);
}

void free_hypercube(hypercube *h)
{
	free_all_requests(h->r);
	free_all_requests(h->cube);
	free_all_requests(h->iterator);
	FREE(h->index_cache);
	FREE(h->compare);
	FREE(h->set);
	FREE(h);
}

request *_get_cubelet(hypercube *h, int index)
{
	int i = 0;
	int c = count_axis(h);

	for(i=0; i<c;++i)
	{
		const char *axis = get_axis(h,i);
		int        dims  = count_dimensions(h,axis);
		int        coord = index % dims;
		const char *val  = get_value(h->r,axis,coord);

		if(!val)
		{
			marslog(LOG_EROR,"MARS internal error handling field %d",index);
			marslog(LOG_EROR,"No value for '%s'[%d] from request",axis,coord);
			marslog(LOG_EROR,"Contact the MARS group");
			marsexit(1);
		}
		set_value(h->iterator,axis,"%s",val);
		index    /= dims;
	}
	return h->iterator; 
}

request *get_cubelet(hypercube *h, int index)
{
	int from = 0, last = 0;
	int pos = pos_to_index(h,index, &from, &last);

	return (pos>=0) ? _get_cubelet(h,pos) : NULL;
}

request *next_cubelet(hypercube *h, int *from, int *last)
{
	int pos = pos_to_index(h,*last, from, last);
	return (pos>=0) ? _get_cubelet(h,pos) : NULL;
}

static void copy_cube(hypercube *a , const hypercube *b, int v)
{
	request *r = NULL;
	int from = 0, last = 0;

	while( (r = next_cubelet((hypercube *)b,&from,&last)) )
	{
		int     new_index = cube_position(a,r);
		set_index(a,new_index,v);
	}
}


hypercube *merge_cube(const hypercube *a, const hypercube *b, int init, int va, int vb)
{
	hypercube *n = NULL;
	request *r = clone_one_request(a->r);

	reqmerge(r,b->r);

	n = new_hypercube(r);
	free_all_requests(r);

	reset_index(n,init);

	copy_cube(n,a,va);
	copy_cube(n,b,vb);

	return n;

}

struct stuff_1 {
	hypercube *c;
	request   *r;
};

static void reqcb_1(const request *r,int count,char *names[],
		char *vals[],void *data)
{
	struct stuff_1 *s = (struct stuff_1*)data;
	int i;

	for(i=0;i<count;i++)
		if(vals[i])
			set_value(s->r,names[i],vals[i]);

	set_index(s->c,cube_position(s->c,s->r),1);
}

hypercube *new_hypercube_from_mars_request(const request *r)
{
	int i;
	int n;

	struct stuff_1 s;

#if 0
	const request *lang = mars_language_from_request(r);
	int count = 0;

	count = init_axis(lang);
	marslog(LOG_DBUG,"cube %s",r->kind);
	/* print_all_requests(mars_language_from_request(r)); */
	marslog(LOG_INFO,"NUMBER(axis): %d, number axisnew: %d",NUMBER(axis),count);
#endif

	s.c = new_hypercube(r);
	s.r = clone_one_request(r);

	reset_index(s.c,0);
	names_loop(r,reqcb_1,&s);

	free_one_request(s.r);

	/* add single paramters */	

	for(i = 0; i < NUMBER(axis) ; i++)
	{
		int m = count_values(r,axis[i].name);
		if(m == 1)
		{
			add_value(s.c->cube,"AXIS",axis[i].name);
			set_value(s.c->cube,axis[i].name,get_value(r,axis[i].name,0));
		}
	}

	n =  count_values(s.c->cube,"AXIS");
	if(n)
		s.c->compare = NEW_ARRAY(namecmp,n);

	for(i = 0; i < n; i++)
		s.c->compare[i] = comparator(get_value(s.c->cube,"AXIS",i));

	return s.c;
}

/* This one doesn't have single parameters in CUBE */
hypercube *new_simple_hypercube_from_mars_request(const request *r)
{
	int i;
	int n;

	struct stuff_1 s;
	s.c = new_hypercube(r);
	s.r = clone_one_request(r);

	reset_index(s.c,0);
	names_loop(r,reqcb_1,&s);

	free_one_request(s.r);
	n =  count_values(s.c->cube,"AXIS");
	if(n)
		s.c->compare = NEW_ARRAY(namecmp,n);

	for(i = 0; i < n; i++)
		s.c->compare[i] = comparator(get_value(s.c->cube,"AXIS",i));

	return s.c;
}

hypercube *new_hypercube_from_fieldset_cb(fieldset *fs, void (*callback)(request*,void*), void *data)
{
	int i;
	request* r = empty_request(0);
	hypercube *c;

	if(!fs)
		return 0;

	for(i = 0; i < fs->count; i++)
	{
		request *s = field_to_request(fs->fields[i]);
		reqmerge(r,s);
	}

	callback(r,data);

	c = new_hypercube(r);
	reset_index(c,0);
	for(i = 0; i < fs->count; i++)
	{
		request *s = field_to_request(fs->fields[i]);
		set_index(c,cube_position(c,s),1);
	}

	if(fs->count != count_index(c))
	{
		marslog(LOG_EROR,"Duplicate fields found in fieldset");
		marslog(LOG_EROR,"Number of fields in fieldset %d",fs->count);
		marslog(LOG_EROR,"Number of fields in hypercube %d",count_index(c));
		free_hypercube(c);
		return 0;
	}

	return c;
}

static void empty_callback(request *r, void *data) 
{ 
	/* Empty */
}

hypercube *new_hypercube_from_fieldset(fieldset *fs)
{
	return new_hypercube_from_fieldset_cb(fs,empty_callback,NULL);
}


hypercube *new_hypercube_from_file(const char* path)
{
	fieldset *fs = read_fieldset(path,0);
	hypercube *c = new_hypercube_from_fieldset(fs);
	free_fieldset(fs);

	return c;
}

hypercube * add_cube(const hypercube *a, const hypercube *b)
{
	return merge_cube(a,b,0,1,1);
}

hypercube *remove_cube(const hypercube *a, const hypercube *b)
{
	return merge_cube(a,b,0,1,0);
}

int hypercube_field_count(const hypercube *a)
{
	return count_index(a);
}

int hypercube_cube_size(const hypercube *a)
{
	return count_hypercube(a->r);
}

int hypercube_compare(const hypercube *a, const hypercube *b)
{
	if(hypercube_field_count(a) != hypercube_field_count(b))
	{
		return 1;
	}

	if(cmp_index(a,b) != 0)
	{
		return 1;
	}

	return 0;
}


