/*
 * © 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 <errno.h>
#include <ctype.h>

#define TRACE(a) \
	do { marslog(LOG_DBUG,"-> %s",#a); a ; marslog(LOG_DBUG,"<- %s",#a); } while(0)

/*--------------------------------------------------------------------------*/
/* Table to know which obsgroup the file belongs to							*/
/*--------------------------------------------------------------------------*/
static char subtype_list[] = {
		-1,0,0,0,0,-1,-1,0,-1,0,-1,0,0,0,0,-1,				/*15*/
		-1,-1,-1,0,-1,0,0,0,-1,-1,0,-1,0,-1,-1,0,		/*31*/
		-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,	/*47*/
		-1,1,-1,1,-1,1,1,1,-1,1,-1,1,-1,1,1,1,			/*63*/
		-1,1,-1,-1,-1,-1,-1,1,1,1,-1,1,-1,-1,-1,-1,		/*79*/
		-1,-1,0,0,0,0,0,0,0,0,-1,0,0,-1,-1,0,				/*95*/
		0,-1,-1,-1,-1,0,0,0,0,0,0,-1,-1,-1,0,-1,			/*111*/
		-1,-1,-1,-1,-1,-1,-1,-1,-1,3,3,3,3,3,1,1,			/*127*/
		-1,5,-1,0,0,0,-1,-1,-1,4,4,1,0,0,0,0,			/*143*/
		0,0,-1,-1,-1,-1,-1,-1,-1,-1,1,-1,1,-1,-1,-1,		/*159*/
		-1,1,-1,-1,0, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,		/*175*/
		-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 0,-1,-1,	/*191*/
		-1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,1,1,	    /*207*/
		1,1,12,-1, 3, 3, 3,-1, 3,1,-1,-1,-1,-1,-1,-1,	/*223*/
		-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 1,-1,-1,	/*239*/
		 6,-1,-1,-1,-1,-1,-1,-1,-1,-1,1,-1,-1,-1,-1,1,	/*255*/
	};

#define NBGROUPS 8
static char groups[NBGROUPS][20] = {
								"conventional",
								"satellite",
								"ssmi",
								"ers1",
								"qscat",
								"trmm",
								"iasi",
								"all",
							};

static char obs_types[NBGROUPS][20] = {
								"conventional",
								"satellite",
								"ssmi",
								"ers1",
								"qscat",
								"trmm",
								"iasi",
								"all",
							};

typedef struct data {
	char	*req_file;
	char	*file;
	char	*out_file;
	char	*info_file;
	char	*type;
	request	*req;
	long	ref_date;

	int		obsgroup;
	int		retrieve;
	int		delta_date;
	boolean		esuite_expver;

} data;

static option opts[] = {
	{NULL,NULL,"-obsgroup","-1",t_int,sizeof(int),OFFSET(data,obsgroup),},
	{NULL,NULL,"-retrieve","0",t_int,sizeof(int),OFFSET(data,retrieve),},
	{NULL,NULL,"-delta","1",t_int,sizeof(int),OFFSET(data,delta_date),},
	{NULL,"ALLOW_ESUITE_EXPVER","-esuite_expver","0",t_boolean,sizeof(boolean),OFFSET(data,esuite_expver),},
};

static data setup = {
	NULL,NULL,NULL,NULL,NULL,NULL,0,0,0,0};

static long file_number = 0;
static double	min_time = 500012312359.0;
static double	max_time = 0;
static long	msgcount = 0;

static void usage()
{
	fprintf(stderr,"\n usage: prearcbufr [-obsgroup n] [-delta d] [-retrieve] inputfile\n");
	fprintf(stderr,"                  n = 0: conventional\n");
	fprintf(stderr,"                  n = 1: satellite\n");
	fprintf(stderr,"                  n = 2: ssmi\n");
	fprintf(stderr,"                  n = 3: ers1\n");
	fprintf(stderr,"                  n = 4: qscat\n");
	fprintf(stderr,"                  n = 5: trmm\n");
	fprintf(stderr,"                  n = 6: iasi\n");
	fprintf(stderr,"                  n = 7: all\n");
	exit(1); 
}

static void fail_param(const char *name)
{
	marslog(LOG_EROR,"parameter %s is missing in request from %s",
				name,setup.req_file);
	marsexit(1);
}

static void wrong_param(const char *name,const char *value)
{
	marslog(LOG_EROR,"parameter %s is wrong: %s",
				name,value);
	marsexit(1);
}

#define EQCASE(a,b) (strcasecmp(a,b)==0)

static const char *get_ignore_case_value(const request *r,const char *parname,int nth)
{
	parameter *q = 0;
	if(!parname) return 0;
	if(r)
	{
		parameter *p = r->params;
		while(p && !q)
		{
			if(EQCASE(p->name,parname))
				q = p;
			p = p->next;
		}
	}
	if(!q)
		return 0;
	else
		return get_value(r,q->name,nth);
}

static void copy_keyword(const char *name, const request *from, request *to)
{
	const char *p = get_ignore_case_value(from,name,0);
	if(p)
	{
		int i = 0;
		while(p)
		{
			if(i == 0)
				set_value(to,name,p);
			else
				add_value(to,name,p);
			p = get_ignore_case_value(from,name,++i);
		}
	}
	else
		fail_param(name);


}

static void copy_database(const request *from, request *to)
{
	const char *p = get_ignore_case_value(from,"database",0);
	if(p)
	{
		int i = 0;
		while(p)
		{
			if(i == 0)
				set_value(to,"DATABASE",p);
			else
				add_value(to,"DATABASE",p);
			p = get_ignore_case_value(from,"database",++i);
		}
	}
	else
		fail_param("database");

}

static void init_parameters(int argc, char ** argv)
{
	const char* p;
	char* q;

	if(argc < 2)
		usage();

	setup.req_file=strcache(argv[argc-1]);
	setup.req = read_request_file(setup.req_file);
	if (!setup.req)
	{
		marslog(LOG_EROR|LOG_PERR,"error reading request in file %s",
				setup.req_file);
		marsexit(1);
	}

	/*--------------------------------------------------------------*/
	/* copy source file												*/
	/*--------------------------------------------------------------*/
	p = get_ignore_case_value(setup.req,"source",0);
	if (!p)
		fail_param("source");

	setup.file=NEW_STRING(p);
	q = setup.file;
	while (*p)
	{
		if (*p!='\"')
			*q++=*p;
		p++;
	}
	*q = 0;

	/*--------------------------------------------------------------*/
	/* copy target file												*/
	/*--------------------------------------------------------------*/
	p = get_ignore_case_value(setup.req,"target",0);
	if (!p)
		fail_param("target");

	setup.out_file=NEW_STRING(p);
	q = setup.out_file;
	while (*p)
	{
		if (*p!='\"')
			*q++=*p;
		p++;
	}
	*q = 0;

	/*--------------------------------------------------------------*/
	/* copy info file for retrieve									*/
	/*--------------------------------------------------------------*/
	setup.info_file=NULL;
	p = get_ignore_case_value(setup.req,"info",0);
	if (p)
	{
		setup.info_file=NEW_STRING(p);
		q = setup.info_file;
		while (*p)
		{
			if (*p!='\"')
				*q++=*p;
			p++;
		}
		*q = 0;
	}

	p=get_ignore_case_value(setup.req,"date",0);
	if (!p)
		fail_param("date");

	setup.ref_date = mars_date_to_julian(atol(p));	

	p=get_ignore_case_value(setup.req,"type",0);
	if (!p) fail_param("type");
	setup.type = NEW_STRING(p);


	p=get_ignore_case_value(setup.req,"expver",0);
	if (!p)
		fail_param("expver");
	else
		if (atol(p) != 1 && !setup.esuite_expver)
			wrong_param("expver",p);
}

static unsigned long double_to_minutes(double d)
{
	unsigned long date=d/10000;
	unsigned long time=d - (double) date * 10000.0;

	date = mars_date_to_julian(date) * 60 * 24;
	date += (time/100) * 60;
	date += time%100;

	return date;
}

static void set_buffer(FILE* f)
{
#ifdef fujitsu
#define BSIZE 1024*1024*50
	char *p = malloc(BSIZE);
	if(p)
		setvbuf(f,p,_IOFBF,BSIZE);
	else
		marslog(LOG_EROR,"Cannot allocate buffer");
#endif
}

static long double_to_time(double d)
{
	long date=d/10000;
	return d - (double) date * 10000.0;
}

static long double_to_date(double d)
{
	return d/10000;
}

static void fill_zero(char* s)
{
	while (*s)
	{
		if (*s == ' ')
			*s = '0';
		s++;
	}
}

long get_name(char* buffer,long length,long* date,double* time)
{
	packed_key key;	
	packed_key *pkey = &key;	
	long this_date;
	long diff;

	if(!get_packed_key(buffer,&key))
	{
		marslog(LOG_EROR,"%d: key is missing or invalid, giving up",msgcount);
		marsexit(1);
	}

	if (!verify_bufr_key(buffer,length,&key))
	{
		marslog(LOG_EROR,"%d: key is not valid, giving up",msgcount);
		marsexit(1);
	}

	*date = KEY_YEAR(pkey) * 10000 + KEY_MONTH(pkey) * 100 + KEY_DAY(pkey);
	*time = (double) (*date)*10000.0 + KEY_HOUR(pkey) * 100 + KEY_MINUTE(pkey);

	this_date = mars_date_to_julian(*date);

	diff = this_date - setup.ref_date;
	if(diff <0) diff = -diff;
	if (diff > setup.delta_date)
	{
		marslog(LOG_EROR,"date %d, found in message %d is more than %d days different",*date,msgcount,setup.delta_date);
		marsexit(1);
	}

	if (setup.obsgroup != -1)
	{
		int subtype	= KEY_SUBTYPE(pkey);
		if(setup.obsgroup != subtype_list[subtype])
		{
			marslog(LOG_EROR,"obsgroup mismatch %d %d\n",setup.obsgroup ,subtype_list[subtype]);
			marslog(LOG_EROR,"subtype = %d\n",subtype);
			marsexit(1);
		}
	}

	return ( (KEY_YEAR(pkey) * 100 + KEY_MONTH(pkey)) << 8) + subtype_list[KEY_SUBTYPE(pkey)];

}
	
typedef struct node{
	char*       file_name;
	long		sort_name;
	request     *reqarc;
	struct node *left;
	struct node *right;
	FILE        *file;
	long         count;
	double		min_time;
	double		max_time;
} node;

void finish(node *n)
{
	char tmp[1024];
	unsigned long range;
	int obsgroup;

	if(!n) return;
	finish(n->left);
	finish(n->right);

	obsgroup = n->sort_name & 0xff;

	fprintf(stderr,"min: %f\n",n->min_time);
	fprintf(stderr,"max: %f\n",n->max_time);
	fprintf(stderr,"grp: %d\n",obsgroup);


	if (obsgroup < 0 || obsgroup > NBGROUPS-1)
	{
		marslog(LOG_EROR|LOG_PERR,"obsgroup is wrong: %d",obsgroup);
		marsexit(1);
	}

	

	set_value(n->reqarc,"OBSGROUP",groups[obsgroup]);
	set_value(n->reqarc,"OBSTYPE",obs_types[obsgroup]);

	/* update date, time and range */
	range=double_to_minutes(n->max_time)-double_to_minutes(n->min_time);
	sprintf(tmp,"%d",double_to_date(n->min_time));
	set_value(n->reqarc,"DATE",tmp);

	sprintf(tmp,"%4d",double_to_time(n->min_time));
	fill_zero(tmp);
	set_value(n->reqarc,"TIME",tmp);
	sprintf(tmp,"%lu",range);
	set_value(n->reqarc,"RANGE",tmp);


	if(fclose(n->file) != 0)
	{
		marslog(LOG_EROR|LOG_PERR,"write error on %s",n->file_name);
		marsexit(1);
	}

	save_all_requests(stdout,n->reqarc);
}

int main(int argc,char **argv)
{

	node *head = 0;
	long length;
	char buffer[1024*1024];
	char *base = getenv("MARS_BASE_FOR_SPLIT");
	err e;
	node *n,*p;
	int left;
	off_t pos = 0;
	int cnt = 0;
	int bad = 0;
	double time = 0;


	FILE *f; 

	marslogfile(stderr);
	marsinit(&argc,argv,&setup,NUMBER(opts),opts);

	init_parameters(argc,argv);


	f = fopen(setup.file,"r");
	if(f == NULL)
	{
		marslog(LOG_EROR|LOG_PERR,"'%s'",setup.file);
		marsexit(1);
	}
	set_buffer(f);

again:
	length = sizeof(buffer);
	pos = ftell(f);
	while((e = _readany(f,buffer,&length)) == NOERR || e == -5)
	{
		long date = 0;
		long sort_name = get_name(buffer,length,&date,&time);
		pos = ftell(f);
		cnt++;

		 /* Look in tree */

		n = head;
		p = NULL;

		while(n)
		{
			int cmp = sort_name - n->sort_name;
			if(cmp == 0)
				break;
			p   = n;

			if(cmp < 0)
			{
				left = 1;
				n = n->left;
			}
			else
			{
				left = 0;
				n = n->right;
			}
		}
		
		if(n == NULL)
		{
			char tmp[2048];

			n = NEW(node);
			if(p == NULL) 
				head = n;
			else if(left) p->left = n; else p->right = n;

			sprintf(tmp,"%s.%2d",setup.out_file,file_number);
			fill_zero(tmp);

			n->left = n->right = NULL;
			n->file_name = strcache(tmp);
			n->sort_name = sort_name;
			n->count = 0;
			n->min_time = time;
			n->max_time = time;
			if (min_time > time)
				min_time = time;
			if (max_time < time)
				max_time = time;

			n->file = fopen(tmp,"w");
			if(!n->file)
			{
				marslog(LOG_EROR|LOG_PERR,"'%s'",tmp);
				marsexit(1);
			}
			set_buffer(n->file);
			/* creating archive request */
			n->reqarc  = empty_request("ARCHIVE");
			set_value(n->reqarc,"SOURCE","\"%s\"",tmp);
			set_value(n->reqarc,"TYPE",setup.type);
			/* copy_database(setup.req,n->reqarc); */
			copy_keyword("database",setup.req,n->reqarc);
			/* This one should be activated, but it may cause dcda to be archived as da */
			/* copy_keyword("stream",setup.req,n->reqarc); */
			if(setup.esuite_expver)
				set_value(n->reqarc,"EXPVER","%s",get_ignore_case_value(setup.req,"expver",0));

			if (setup.obsgroup != 2)
				set_value(n->reqarc,"DUPLICATES","REMOVE");
			else
				set_value(n->reqarc,"DUPLICATES","KEEP");

			file_number++;
		}

		if (n->count)
		{

			/*-------------------------------------------------*/
			/* Handle Time									   */
			/*-------------------------------------------------*/
			if (n->min_time > time)
				n->min_time = time;
			if (min_time > time)
				min_time = time;
			if (n->max_time < time)
				n->max_time = time;
			if (max_time < time)
				max_time = time;
		}

		n->count++;
		msgcount++;

		if( fwrite(buffer,1,length,n->file) != length)
		{
			marslog(LOG_EROR|LOG_PERR,"write error on %s",n->file_name);
			marsexit(1);
		}

		length = sizeof(buffer);
	}

	if(e == -5)
	{
		/*
		marslog(LOG_EROR,"error on message %d %d %s, skipping",cnt,pos,
			get_name(buffer,length,&date,&time,sort_name));
		*/
		fseek(f,pos + 8,SEEK_SET);
		bad++;
		goto again;
	}

	if(e == -1) e = 0;
	if(e) marslog(LOG_EROR,"readany returns %d",e);

	{
		long date;
		long last;
		request* req = empty_request("STAGE");
		copy_database(setup.req,req);

		set_value(req,"TYPE",setup.type);

		date=double_to_date(min_time);
		date = (date / 100) * 100 + 1;
		last = mars_julian_to_date(mars_date_to_julian(date) + 32,true);
		last = (last / 100) * 100 + 1;
		set_value(req,"DATE","%d",date);

		set_value(req,"TIME","0000");
		set_value(req,"RANGE","%d",(mars_date_to_julian(last)-mars_date_to_julian(date))*60*24 - 1);


		if (double_to_date(min_time) == date)
			print_all_requests(req);
		free_all_requests(req);

	}

	finish(head);

	/* finish the retrieve request */
	/* creating retrieve request */
	if (setup.info_file)
	{
		unsigned long range;
		char	 tmp[1024];
		FILE	 *f;
		request	 *req;

		fprintf(stderr,"global min: %f\n",min_time);
		fprintf(stderr,"global max: %f\n",max_time);
		fprintf(stderr,"Messages: %ld\n",msgcount);

		req = empty_request("RETRIEVE");

		set_value(req,"OBSTYPE",obs_types[setup.obsgroup]);

		range=double_to_minutes(max_time)-double_to_minutes(min_time);
		sprintf(tmp,"%d",double_to_date(min_time));
		set_value(req,"DATE",tmp);

		sprintf(tmp,"%4d",double_to_time(min_time));
		fill_zero(tmp);
		set_value(req,"TIME",tmp);
		sprintf(tmp,"%lu",range);
		set_value(req,"RANGE",tmp);

		f = fopen(setup.info_file,"w");
		if(f == NULL)
		{
			marslog(LOG_EROR|LOG_PERR,"'%s'",setup.info_file);
			marsexit(1);
		}
		save_all_requests(f,req);
		fclose(f);
	}

	marsexit(e);
}
