/*
 * © 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.
 */

#include "mars.h"
#include <ctype.h>


typedef int (*sortproc) (int,int);

static int sort_up  (int a,int b) {
	return a-b;
}
static int sort_152 (int a,int b) {
	if(a==152 && b != 127 ) a = -1;
	if(b==152 && a != 127)  b = -1; 
	return a-b;
}
static int sort_down(int a,int b) {
	return b-a;
}

static void sort_params(request *r,parameter *p,sortproc proc)
{

}

static void hidden_sort_request(request *r)
{
	sortproc proc;
	const char *s;
	parameter *p = r->params;

	while(p)
	{
		if(count_values(r,p->name)>1)
		{
			proc = sort_up;

			if(EQ(p->name,"PARAM"))
				proc = sort_152;

			if((EQ(p->name,"LEVELIST")) &&
					(s = get_value(r,"LEVTYPE",0) )  &&
					(EQ(s,"PL")))
				proc = sort_down;

			sort_params(r,p,proc);
		}
		p = p->next;
	}
}

static char *names[] = {
	"DATE",
	"TIME",
	"STEP",
	"STREAM",
};


static void unfg(const request *r,int count,char *names[],
		char *vals[],void *data)
{
	request *z = (request *)data;
	int d = date_to_julian(atol(vals[0]));
	int t = atoi(vals[1])/100;
	int s = atoi(vals[2]);
	const char *stream = vals[3];
	int monthly = (stream[0]=='M' && stream[1]=='O' && stream[2]==0);

	if(s == 0) s = 6;

	if(monthly)
	{
		d = julian_to_date(d,mars.y2k);
		d += 2;
		d = date_to_julian(d);
	}
	marslog(LOG_DBUG,"Un first-guessing:  time %d date %d",t,
			julian_to_date(d,mars.y2k));

	t -= s;

	while(t <0)
	{
		t += 24;
		d -= 1;
	}

	marslog(LOG_DBUG,"Un first-guessing:  time %d date %d",t,
			julian_to_date(d,mars.y2k));

	if(monthly)
	{
		if(mars.mm_firstofmonth)
			add_unique_value(z,"DATE","%d",(julian_to_date(d,mars.y2k)/100*100)+1);
		else
			add_unique_value(z,"DATE","%d",julian_to_date(d,mars.y2k)/100*100);
	}
	else
		add_unique_value(z,"DATE","%d",julian_to_date(d,mars.y2k));
	add_unique_value(z,"TIME","%04d",t*100);
	add_unique_value(z,"STEP","%d",s);

}

request *un_first_guess(const request *r)
{
	if(r)
	{
		const char *s;
		request *z = clone_one_request(r);

#ifndef FG_IS_REAL_FG
		int m;

		int n = count_values(z,"DATE") * 
			count_values(z,"TIME") * 
			count_values(z,"STEP");

		if( (s = get_value(r,"TYPE",0)) )
			if(EQ(s,"FG"))
			{
				if( (s =  get_value(r,"STREAM",0)) )
					if(EQ(s,"WAVE"))
						return z;

				marslog(LOG_DBUG,"Un first-guessing...");
				if(mars.debug)
					print_one_request(z);

				set_value(z,"TYPE","FC");
				unset_value(z,"DATE");
				unset_value(z,"TIME");
				unset_value(z,"STEP");

				values_loop(r,NUMBER(names),names,unfg,z);
				if(mars.debug)
					print_one_request(z);

				m = count_values(z,"DATE") * 
					count_values(z,"TIME") * 
					count_values(z,"STEP");

				if(m != n)
				{
					free_all_requests(z);
					z = clone_one_request(r);
					marslog(LOG_WARN,
							"Multi date/time first-guess requests cannot be cached");
				}
			}
#endif

		return z;
	}
	return NULL;
}

request *un_first_guess_all(const request *r)
{
	request *first = NULL;
	request *last = NULL;

	while(r)
	{
		request *s = un_first_guess(r);
		s->order = r->order;
		if(first == NULL) 
			first = s;
		else 
			last->next = s;
		last = s;
		r = r->next;
	}

	return first;
}

static void check_one(const request *r,const char *p)
{
	if(count_values(r,p) != 1)
	{
		marslog(LOG_WARN,"'VERIFY' needs 1 %s",p);

		if(count_values(r,p)>1)
		{
			marslog(LOG_EROR,"Cannot make 1 request with multiple '%s's",p);
			marslog(LOG_EROR,"for the same 'VERIFY'");

			marsexit(1);
		}
	}

}

static void verifydate_to_basedate(request *r)
{
	long bjulian = 0;
	const char* pdate = NULL;
	long second = 0; boolean isjul;
	long bdate = 0;
	long btime = 0;
	int i     = 0;
	int bstep = 0;

	check_one(r,"DATE");
	check_one(r,"TIME");
	check_one(r,"STEP");

	pdate = get_value(r,"DATE",0);
	if(is_number(pdate))
	{
		bdate   = atoi(pdate);
		bjulian = date_to_julian(bdate);
	}
	else
	{
		parsedate(pdate,&bjulian,&second,&isjul);
		bdate = julian_to_date(bjulian,mars.y2k);
	}

	btime = atoi(get_value(r,"TIME",0));
	bstep = atoi(get_value(r,"STEP",0));
	unset_value(r,"STEP");

	for(i=0;i<count_values(r,"VERIFY");i++)
	{
		const char* p = get_value(r,"VERIFY",i);
		long vjulian = 0;
		long vdate = 0;

		if(is_number(p))
		{
			vdate   = atoi(p);
			vjulian = date_to_julian(vdate);
		}
		else
		{
			parsedate(p,&vjulian,&second,&isjul);
			vdate = julian_to_date(vjulian,mars.y2k);
		}

		if(vdate<bdate)
		{
			marslog(LOG_EROR,"'VERIFY' (%s) cannot preceed 'DATE' (%s)",p,pdate);
			marsexit(1);
		}
		else
		{
			int n = (vjulian - bjulian) * 24 + bstep; /* - 23; */
			add_value(r,"STEP","%d",n);

			marslog(LOG_DBUG,"Converting vdate (%ld) to STEP (%d) from DATE (%ld), TIME (%d), STEP (%d)",
					vdate,n,bdate,btime,bstep);

		}
	}

	unset_value(r,"VERIFY");
}

static void validation_date(request *r)
{
	int i;
	int date = 0;
	int time = 0;
	int step = 0;
	int v;

	for(i=0;i<count_values(r,"STEP");i++)
	{
		int n = atoi(get_value(r,"STEP",i));
		if(n>step) step = n;
	}

	for(i=0;i<count_values(r,"DATE");i++)
	{
		const char* p = get_value(r,"DATE",i);
		int n;
		if(is_number(p))
			n = atoi(p);
		else
		{
			long julian = 0,second = 0; boolean isjul;
			parsedate(p,&julian,&second,&isjul);
			n = julian_to_date(julian,mars.y2k);
		}
		if(n>date) date = n;
	}

	for(i=0;i<count_values(r,"TIME");i++)
	{
		int n = atoi(get_value(r,"TIME",i));
		if(n>time) time = n;
	}

	v = date_to_julian(date) + (time/100 + step + 23)/24;
	set_value(r,"_VERIF_DATE","%d",julian_to_date(v,mars.y2k));
	v -= date_to_julian(0) ;
	set_value(r,"_DELTA_DATE","%d",v);
	set_value(r,"_CURRENT_DATA","%s",v>=1?"YES":"NO");
}

void ensemble_to_number(request *r)
{
	static request *done = 0;

	if(done != r)
	{
		valcpy(r,r,"NUMBER","ENSEMBLE");    
		valcpy(r,r,"NUMBER","CLUSTER");     
		valcpy(r,r,"NUMBER","PROBABILITY"); 
		unset_value(r,"ENSEMBLE");
		unset_value(r,"CLUSTER");
		unset_value(r,"PROBABILITY");
		done = r;
	}
}

static err request_to_gridname(request *r)
{
    const char* grid = get_value(r,"GRID",0);

    const char *oldgauss = getenv("MARS_REGULAR_GRID");

    if( grid && strlen(grid) )
    {
        if (isalpha(grid[0])) {

            set_value(r,"_GRIDNAME","%s",grid);
            set_value(r,"_GAUSSIAN","%s",grid+1);

        } else if (isdigit(grid[0])) { /* first is digit so we need to look at GAUSSIAN */

            if(grid[0] == '0') {
                marslog(LOG_EROR,"GRID cannot start with leading digit 0");
                return -2;
            }

            const char* gauss = get_value(r,"GAUSSIAN",0);

            const char* k = "F"; /* default is regular gg */

            if(gauss && strcmp(gauss,"REDUCED") == 0) {
                k = "N";
            }

            if(oldgauss && atoi(oldgauss)) {
                k = "F";
                marslog(LOG_WARN,"The use of MARS_REGULAR_GRID is discontinued, please contact User Support");
            }

            set_value(r,"_GRIDNAME","%s%s",k,grid);
            set_value(r,"_GAUSSIAN","%s",grid);
        }
        else
        {
            return -2;
        }

        set_value(r,"GRID","%s",get_value(r,"_GRIDNAME",0));
        unset_value(r,"GAUSSIAN");

    }
    else
    {
        return -2;
    }

    return 0;
}

err add_hidden_parameters(request *r)
{
	const char *s;
    int  i,n;
	int j = 0;

	while( (s = no_quotes(get_value(r,"EXPVER",j++))) )
	{
		if(is_number(s))
		{
			n = atoi(s);
			add_value(r,"TMPEXPVER","%04d",n);
		}
		else if(strlen(s) == 4)
		{
			add_value(r,"TMPEXPVER","%s",lowcase(s));
			n = 0;
			while(*s) 
				n = (n <<8) + (unsigned char)*s++; 
		}
		else n = -1;
		add_value(r,"_EXPVER","%d",n);
	}

	valcpy(r,r,"EXPVER","TMPEXPVER"); 
	unset_value(r,"TMPEXPVER");

	if( (s = get_value(r,"RESOL",0)) )
	{
        if(!EQ(s,"AV") && !EQ(s,"AUTO") && s[0] != 'N' && s[0] != 'O')
			set_value(r,"_TRUNCATION",s);
	}

	/* Changes in accuracy:                   mars.accuracy
AV : output same as input             -1 
N  : output same as input             -1
nn : output with nn bits per value    nn
L or R : output 8 bits per value       8
<nothing> :                            0

interpolation    in compute
=============    ==========
mars.accuracy == 0  => same as input    24
mars.accuracy == n  => n                n
mars.accuracy == -1 => same as input    same as input

	 */
	if((s = get_value(r,"ACCURACY",0)))
	{
		if(isdigit(*s))
		{
			int n = atoi(s);
			if(n)
				mars.accuracy = n;
			marslog(LOG_DBUG,"hidden: Using %d bits for accuracy",mars.accuracy);
		}
		else
		{
			if((strcmp(s,"N")==0) || (strcmp(s,"AV")==0))
			{
				mars.accuracy = -1;
				marslog(LOG_INFO,"Accuracy %s selected. Using input field accuracy",s);
				marslog(LOG_DBUG,"hidden: Same bits as input for accuracy (%d)",mars.accuracy);
			}
			else
				mars.accuracy = 0;
		}
	}
	else
		mars.accuracy = 0;

	if( (i = count_values(r,"AREA")) )
	{
		int n,s;

		if(i != 4)
		{
			marslog(LOG_EROR,"AREA must have 4 values");
			return -2;
		}

		n = 0;
		s = 2;

		if(atof(get_value(r,"AREA",2)) > atof(get_value(r,"AREA",0)))
		{
			const char *level = get_value(r,"LEVTYPE",0);
			boolean     ocean = level && (strcmp(level,"DP") == 0);            /* Level depth means ocean data */

			/* Don't check if ocean */
			if(!ocean)
			{
				n = 2;
				s = 0;
				marslog(LOG_WARN,"SOUTH and NORTH parts of AREA were swapped");
			}
		}

		set_value(r,"_AREA_N",get_value(r,"AREA",n));
		set_value(r,"_AREA_W",get_value(r,"AREA",1));
		set_value(r,"_AREA_S",get_value(r,"AREA",s));
		set_value(r,"_AREA_E",get_value(r,"AREA",3));

		if(s == 0)
		{
			set_value(r,"AREA","%s",get_value(r,"_AREA_N",0));
			add_value(r,"AREA","%s",get_value(r,"_AREA_W",0));
			add_value(r,"AREA","%s",get_value(r,"_AREA_S",0));
			add_value(r,"AREA","%s",get_value(r,"_AREA_E",0));
		}
	}

	set_value(r,"_MARS_VERSION","%ld",marsversion());

	ensemble_to_number(r);

    char* gridname;

	switch(count_values(r,"GRID"))
	{
		case 0:
			break;

        case 1: /* It's Gaussian !! */

            if( request_to_gridname(r) != NOERR )
            {
                marslog(LOG_EROR,"Cannot establish a valid gridname");
                return -2;
            }

			break;

		case 2:

			/* It's lat/lon */

			set_value(r,"_GRID_EW",get_value(r,"GRID",0));
			set_value(r,"_GRID_NS",get_value(r,"GRID",1));

			break;
	}


	switch(count_values(r,"ROTATION"))
	{
		case 0:
			break;

		case 1:
			marslog(LOG_EROR,"ROTATION must have 2 values");
			return -2;

		case 2:
			set_value(r,"_ROTATION_LAT",get_value(r,"ROTATION",0));	
			set_value(r,"_ROTATION_LON",get_value(r,"ROTATION",1));	
			break;
	}

#if 0
	if(count_values(r,"VERIFY"))
		verifydate_to_basedate(r);
#endif

	validation_date(r);

	pprotation(r);

	ppstyle(r);

	pparea(r);

	patch_ranges(r);

	patch_steprange(r);

	return NOERR;
}

void patch_ranges(request* r)
{
	if (observation(r))
	{
		if (count_values(r,"TIME") == 3 && EQ(get_value(r,"TIME",1),"TO"))
		{
			long from = atol(get_value(r,"TIME",0));
			long to = atol(get_value(r,"TIME",2));

			long start = from / 100 * 60 + from % 100;	
			long end = to / 100 * 60 + to % 100;	
			long range = end - start;
			while (range < 0)
				range += 24 * 60;
			marslog(LOG_WARN,"Changing time %04d/to/%04d to range %d",
					from,to,range);

			copy_to_ibm_values(r,"TIME");

			set_value(r,"RANGE","%d",range);
			set_value(r,"TIME","%04d",from);

		}
		else if (count_values(r,"TIME") == 1 && count_values(r,"RANGE") != 0)
		{
			request *clone = clone_one_request(r);
			char	tmp[32];
			char	*p;

			long from=atol(get_value(r,"TIME",0));
			long range=atol(get_value(r,"RANGE",0));
			long to;

			to=from / 100 * 60 + from % 100+range;
			to = to / 60 * 100 + to % 60;

			/* printf("%4ld %4ld\n",from,to); */

			sprintf(tmp,"%4ld",from);
			p = tmp;
			while (*p) { if (*p == ' ') *p = '0'; p++; }
			set_value(clone,"TIME",tmp);
			add_value(clone,"TIME","TO");
			sprintf(tmp,"%4ld",to);
			p = tmp;
			while (*p) { if (*p == ' ') *p = '0'; p++; }
			add_value(clone,"TIME",tmp);

			move_to_ibm_values(clone,r,"TIME");
			free_all_requests(clone);
		}
	}
	else 
	{
		/* Fields */
		const char *levtype = get_value(r,"LEVTYPE",0);
		boolean ocean = levtype && EQ(levtype,"DP");

		if(!ocean)
			unset_value(r,"RANGE");
	}
}
