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

#include "pproc.h"

extern "C" {
    const char * libemos_version();
}

//----------------------------------------------------------------------------------------------------------------------


extern "C" {

#ifdef FORTRAN_UPPERCASE
#define intout_    INTOUT
#define intin_     INTIN
/* #define intf_      INTF */
/* #define intvect_   INTVECT */
/* #define intuvs_    INTUVS */
/* #define intuvp_    INTUVP */
#define bus012_    BUS012
#define buukey_    BUUKEY
#define iscrsz_    ISCRSZ
#define ibasini_   IBASINI
#define intlogm_   INTLOGM
#define areachk_   AREACHK
#define mbufr_mars_filter_    MBUFR_MARS_FILTER
#define emosnum_ EMOSNUM
#endif

#ifdef FORTRAN_NO_UNDERSCORE
#define intout_    intout
#define intin_     intin
/* #define intf_      intf */
/* #define intvect_   intvect */
/* #define intuvs_    intuvs */
/* #define intuvp_    intuvp */
#define bus012_    bus012
#define buukey_    buukey
#define iscrsz_    iscrsz
#define ibasini_   ibasini
#define intlogm_   intlogm
#define areachk_   areachk
#define mbufr_mars_filter_    mbufr_mars_filter
#define emosnum_ emosnum
#endif

typedef void (*emos_cb_proc)(char *);
extern void intlogs(emos_cb_proc proc);

fortint intout_(char*,fortint*,fortfloat*,const char*,fortint,fortint);
fortint intin_(char*,fortint*,fortfloat*,const char*,fortint,fortint);
/* fortint intf_(char*,fortint*,fortfloat*,char*,fortint*,fortfloat*); */


/* Interpolation merged with grib_api */
extern fortint intf2(char* grib_in, fortint *length_in, char* grib_out, fortint *length_out);
extern fortint intuvs2_(char *vort_grib_in, char *div_grib_in, fortint *length_in, char *vort_grib_out, char *div_grib_out, fortint *length_out);
extern fortint intuvp2_(char *vort_grib_in, char *div_grib_in, fortint * length_in, char *vort_grib_out, char *div_grib_out, fortint *length_out);
extern fortint intvect2_(char *u_grib_in, char *v_grib_in, fortint *length_in, char *u_grib_out, char *v_grib_out, fortint *length_out);


fortint iscrsz_();
fortint ibasini_(fortint*);
void	bus012_(fortint*,char*,fortint*,fortint*,obssec1*,fortint*,fortint*);
void	buukey_(obssec1*,fortint*,obskey*,fortint*,fortint*);
void    intlogm_(fortint (*)(char*,fortint));
fortint areachk_(fortfloat*,fortfloat*,fortfloat*,fortfloat*,fortfloat*,
	fortfloat*);
fortint emosnum_(fortint*);

} // extern C

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

#ifdef OBSOLETE
/* For duplicates .. */
#define KEY_SIZE 32

typedef struct node{
	char key[KEY_SIZE];
	struct node *left;
	struct node *right;
} node;
#endif

static struct {

	int   busy;
	int   quiet;

#ifdef OBSOLETE
	int   no_duplicates;
	node  *top;
	int   dup_count;
#endif

	int   in_count;
	int   out_count;
	int   res_count;
	int   bad_count;

	int   area_cnt;
	int   north;
	int   south;
	int   east;
	int   west;
	int   inter_cnt;

	int   type_cnt;
	int   *types;

	int   block_cnt;
	int   *blocks;

	int   interval_cnt;
	time_interval  *intervals;

	boolean is_bufrfile;
	boolean original_grib;

	int     ident_cnt;
	int     *idents;
	int     instrument_cnt;
	fortint *instruments;

	long   estimate;
	long   edition;
	long   derive_uv;

	char *odb_sql;

} ppdata = {
	0,};

static timer *pptimer = NULL;
static timer *memcpytimer = NULL;

static err no_scalar_postproc(char *buff,long inlen,long *outlen);
static err no_postproc(ppbuffer_t *pp, long *nbuffer);
static err grib_vector_postproc(ppbuffer_t *ppin, long *nbuffer);
static err grib_scalar_postproc(char *buffer,long inlen,long *outlen);
static err grib_postproc(ppbuffer_t *pp, long *nbuffer);
static err track_postproc(ppbuffer_t *pp, long *nbuffer);
static err track_scalar_postproc(char *buffer,long inlen,long *outlen);
static err obs_postproc(ppbuffer_t *pp, long *nbuffer);
static err obs_scalar_postproc(char *buffer,long inlen,long *outlen);
static err odb_postproc(ppbuffer_t *pp, long *nbuffer);
static err odb_scalar_postproc(char *buffer,long inlen,long *outlen);

static void ppinfo()
{
	int i;
	if(ppdata.area_cnt == 4)
		printf("AREA %d-%d-%d-%d\n",
		    ppdata.north,ppdata.west,ppdata.south,ppdata.east);
	if(ppdata.type_cnt) {
		printf("OBSTYPE ");
		for(i=0;i<ppdata.type_cnt;i++)
			printf("%d ",ppdata.types[i]);
		putchar('\n');
	}
	if(ppdata.interval_cnt) {
		printf("INTERVALS ");
		for(i=0;i<ppdata.interval_cnt;i++)
			printf("[%lld,%lld] ",ppdata.intervals[i].begin,
								ppdata.intervals[i].end);
		putchar('\n');
	}
	if(ppdata.block_cnt) {
		printf("BLOCK ");
		for(i=0;i<ppdata.block_cnt;i++)
			printf("%d ",ppdata.blocks[i]);
		putchar('\n');
	}
	if(ppdata.ident_cnt) {
		printf("IDENT ");
		for(i=0;i<ppdata.ident_cnt;i++)
			printf("%d ",ppdata.idents[i]);
		putchar('\n');
	}
	if(ppdata.instrument_cnt) {
		printf("INSTRUMENT ");
		for(i=0;i<ppdata.instrument_cnt;i++)
			printf("%d ",ppdata.instruments[i]);
		putchar('\n');
	}
}

#ifdef OBSOLETE
/* Binary tree to find duplicates */

static int find_key(node *n,char *key,node **which,int *where)
{
	*which = NULL;

	while(n)
	{
		*which = n;
		*where = memcmp(key,n->key,KEY_SIZE);

		if(*where == 0)
		{

			return 1;
		}

		if(*where>0)
			n = n->right;
		else
			n = n->left;
	}
	return 0;
}

static int add_key(node **top,char *k)
{
	node *which;
	node *p;
	int where;

	if(find_key(*top,k,&which,&where))
		return 1;

	p  = NEW(node);

	memcpy(p->key,k,KEY_SIZE);

	p->left = p->right = NULL;
	if(which)
		if(where>0)
			which->right = p;
		else
			which->left = p;
	else *top = p;
	return 0;
}

static void delete_node(node *n)
{
	if(n) {
		delete_node(n->left);
		delete_node(n->right);
		FREE(n);
	}
}

#endif

static err ppout(char* s,fortint n,fortfloat *array, const char *p)
{
	err e ;
	static const char *empty = "";
	if(!p)
		p = empty;
	TRACE(e = intout_(C2FORT(s),&n,array,C2FORT(p),strlen(s),strlen(p)));
	return e;
}

static err ppin(char* s,fortint n,fortfloat *array, const char *p)
{
	err e ;
	static const char *empty = "";
	if(!p)
		p = empty;
	TRACE(e = intin_(C2FORT(s),&n,array,C2FORT(p),strlen(s),strlen(p)));
	return e;
}

static err no_scalar_postproc(char *buff,long inlen,long *outlen)
{
	*outlen = inlen;

	if(ppdata.original_grib)
	{
		fortint len = *outlen;
		err ret = original_grib(buff,&len);
		*outlen = len;
		return ret;
	}

	return NOERR;
}

static err no_postproc(ppbuffer_t *pp, long *nbuffer)
{
	*nbuffer = 1;
	return no_scalar_postproc(pp[0].buffer,pp[0].inlen,&pp[0].outlen);
}

#define PPROC_PRECISION "%.15g"


static int get_parameter(void *buffer, long length, err *ret);

static err ppintf(char *inbuf,long inlen,char *outbuf,long *outlen,boolean copy)
{
	fortint out = *outlen;
	fortint in  = inlen;
	int ret = 0;

	if(*inbuf != 'G')
	{
		marslog(LOG_WARN,"NetCDF/Pseudo GRIB not interpolated");
		*outlen = 0;
	}
	else
	{
		int p = get_parameter(inbuf,inlen,&ret);
		if(mars.use_intuvp && is_wind(p) && (ppdata.derive_uv > 0))
		{
			marslog(LOG_DBUG,"Avoid calling intf2 when intuvp set and U/V");
			*outlen = out=0;
			ret=0;
			ppdata.derive_uv--;
		}
		else
		{
			timer_start(pptimer);
			TRACE(ret = intf2(inbuf,&in,outbuf,&out));
			timer_stop(pptimer,0);
			*outlen = out;
			marslog(LOG_DBUG,"intf2 returns %d",ret);
		}
	}

	if(ret)
	{
		marslog(LOG_EROR,"Interpolation failed (%d)",ret);
		return ret;
	}

	if(ret == 0 && *outlen == 0)
	{
		/* marslog(LOG_INFO,"No interpolation done"); */
		if(copy) {
			timer_start(memcpytimer);
			memcpy(outbuf,inbuf,inlen);
			timer_stop(memcpytimer,inlen);
			*outlen = inlen;
		}
	}

	return ret;
}

/**************************/
/* Vector Post-Processing */
/**************************/

typedef struct pairs_t {
	int param;
	char *buffer;
	int len;
} pairs_t;

#define NPARAMS 257000
pairs_t pair[NPARAMS];

static void set_pair(int p, int q)
{
	pair[p].param  = q;
	pair[p].buffer = NULL;
	pair[p].len    = 0;

	pair[q].param  = p;
	pair[q].buffer = NULL;
	pair[q].len    = 0;
}

static void init_pairs()
{
	int i = 0;
	static boolean first = true;
	if(!first)
		return;
	first = false;

	for(i = 0; i<NPARAMS; ++i)
	{
		pair[i].param  = 0;
		pair[i].buffer = NULL;
		pair[i].len    = 0;
	}
	/* Need to consider table as well: COME BACK HERE */
	set_pair(131,132);
	set_pair(129131,129132);
	set_pair(200131,200132);
	set_pair(165,166);
}


static boolean is_vector_parameter(int p)
{
	init_pairs();

    /* For parameter 3.228, we allow syntax 228003 */
	if(p>257)
		p %= 1000;

	if(pair[p].param)
		return true;

	return false;
}

static boolean vector_parameter_requested(const request *r)
{
	int i = 0;
	const char *param = NULL;

	init_pairs();

	param = r?get_value(r,"PARAM",i++):NULL;
	while(param)
	{
		int p = atoi(param);
		if(is_vector_parameter(p))
		{
			marslog(LOG_DBUG,"Vector parameter %d requested",p);
			return true;
		}
		else
		{
			marslog(LOG_DBUG,"Param %d is not vector",p);
		}
		param = get_value(r,"PARAM",i++);
	}
	return false;
}

static int vector_pair(int p)
{
	if(is_vector_parameter(p))
		return pair[p].param;

	return -1;
}


static int get_parameter(void *buffer, long length, err *ret)
{
	grib_handle *g = grib_handle_new_from_message(0,buffer,length);
	long n;
	*ret = grib_get_long(g,"paramId",&n);
	grib_handle_delete(g);
	return n;
}


err vector_postproc(ppbuffer_t *pp, long *nbuffer)
{
	int ret = 0;
	int p = get_parameter(pp[0].buffer,pp[0].inlen,&ret);
	int q;

	if(ret != 0)
	{
		marslog(LOG_WARN,"Error %d in vector_postproc while getting parameter number",ret);
		marslog(LOG_WARN,"Interpolation not done");
		nbuffer = 0;
		return ret;
	}

	marslog(LOG_DBUG,"vector_postproc called for parameter %d",p);
	if(!is_vector_parameter(p))
	{
		marslog(LOG_DBUG,"Parameter %d is not vector. Calling scalar post-processing",p);
		*nbuffer = 1;
		return grib_scalar_postproc(pp[0].buffer,pp[0].inlen,&pp[0].outlen);
	}


	if((q = vector_pair(p)) == 0)
	{
		marslog(LOG_WARN,"Vector pair for parameter %d not found",p);
		marslog(LOG_WARN,"Perform scalar post-processing");
		marslog(LOG_WARN,"Please, inform MARS analyst");

		*nbuffer = 1;
		return grib_scalar_postproc(pp[0].buffer,pp[0].inlen,&pp[0].outlen);
	}

	/* Copy input buffer */
    pair[p].len    = pp[0].inlen;
    pair[p].buffer = static_cast<char*>(reserve_mem(pair[p].len));
	memcpy(pair[p].buffer, pp[0].buffer, pp[0].inlen);

	if(pair[q].buffer)
	{
		err ret = 0;
		char *pufield = (p<q)?pair[p].buffer:pair[q].buffer;
		char *pvfield = (p<q)?pair[q].buffer:pair[p].buffer;

		size_t u_len = (p<q)?pair[p].len:pair[q].len;
		size_t v_len = (p<q)?pair[q].len:pair[p].len;

		fortint   out = pp[0].buflen;

		marslog(LOG_DBUG,"Got parameters %d and %d. Calling vector post-processing",p,q);

		if(mars.debug)
		{
			request *r = empty_request("WIND");

			marslog(LOG_DBUG,"Buffer for %d at address %x, length %d",p,pair[p].buffer,pair[p].len,pair[p].len);
			grib_to_request(r,pair[p].buffer,pair[p].len);
			print_all_requests(r);
			free_all_requests(r);

			r = empty_request("WIND");
			marslog(LOG_DBUG,"Buffer for %d at address %x, length %d",q,pair[q].buffer,pair[q].len);
			grib_to_request(r,pair[q].buffer,pair[q].len);
			print_all_requests(r);
			free_all_requests(r);

		}

		/* If MARS_USE_INTUVP set and deriving U/V, conversion + interpolation
		has already been done
		*/
		if(mars.use_intuvp && is_wind(p) && (ppdata.derive_uv > 0))
		{
			marslog(LOG_DBUG,"MARS_USE_INTUVP set and parameters are U/V. Avoid calling intvect_");
			memcpy(pp[0].buffer, pufield, u_len);
			pp[0].inlen = u_len;
			memcpy(pp[1].buffer, pvfield, v_len);
			pp[1].inlen = v_len;
			out=0;
			/* ppdata.inter_cnt+=2; */
			ret=0;
			ppdata.derive_uv -= 2;
		}
		else
		{
			/* Call vector interpolation */
			timer_start(pptimer);
			TRACE(ret= intvect2_(pufield,pvfield,&pair[p].len,pp[0].buffer,pp[1].buffer,&out));
			timer_stop(pptimer,0);

		}
		marslog(LOG_DBUG,"intvect2_ returns %d",ret);


		/* If 0, no interpolation as been done.. */

		if((out != 0) && (mars.grib_postproc == 0))
		{
			marslog(LOG_EROR,"Env variable MARS_GRIB_POSTPROC has been set to 0");
			marslog(LOG_EROR,"and some field(s) need interpolation");
			return POSTPROC_ERROR;
		}

		if((out != 0) && (mars.gridded_observations_postproc == 0))
		{
			marslog(LOG_EROR,"Gridded observations need interpolation. In order to force");
			marslog(LOG_EROR,"interpolation, please, set MARS_GRIDDED_OBSERVATIONS_INTERP to 1");
			return POSTPROC_ERROR;
		}

		if(out == 0)
		{
			pp[0].outlen = pp[0].inlen;
			pp[1].outlen = pp[1].inlen;
		}
		else {
			pp[0].outlen = pp[1].outlen = out;
			ppdata.inter_cnt++;
			ppdata.inter_cnt++;
		}

		if(pp[0].outlen > pp[0].buflen)
		{
			marslog(LOG_EROR,"INTVECT2 output is %d bytes",pp[0].outlen);
			marslog(LOG_EXIT,"Buffer is only %d bytes",pp[0].buflen);
			return BUF_TO_SMALL;
		}

		if(ret < 0)
		{
			marslog(LOG_EROR,"Vector interpolation failed (%d)",ret);
			out = 0;
			nbuffer = 0;
			return ret;
		}
		else
		{

			if(ret)
			{
				marslog(LOG_EROR,"Vector interpolation failed (%d)",ret);
				return ret;
			}

			/* Inform 2 buffers are ready */
			*nbuffer = 2;

			/* Release input buffers */
			release_mem(pair[p].buffer);
			pair[p].buffer = NULL;
			pair[p].len    = 0;

			release_mem(pair[q].buffer);
			pair[q].buffer = NULL;
			pair[q].len    = 0;
		}
	}
	else
	{
		/* We don't have q. Keep p and wait for q */
		*nbuffer = 0;
		marslog(LOG_DBUG,"Vector parameter %d kept. Waiting for parameter %d",p,q);
		ret = 0;
	}

	return ret;
}


static err grib_vector_postproc(ppbuffer_t *ppin, long *nbuffer)
{
	if(mars.can_do_vector_postproc)
		return vector_postproc(ppin,nbuffer);

	*nbuffer = 1;
	return grib_scalar_postproc(ppin[0].buffer,ppin[0].inlen,&ppin[0].outlen);
}

static err grib_scalar_postproc(char *buffer,long inlen,long *outlen)
{
	long      ret   = 0;
	static    char  *result = NULL;
	static    long   length = 0;
	long      size  = MAX(*outlen,ppestimate());

	if(length < size)
	{
		if(result) release_mem(result);
		length = size;
		result = (char*)reserve_mem(length);
	}

	ret  = ppintf(buffer,inlen,result,&size,false);

	/* If 0, no interpolation as been done.. */

	if((size != 0) && (mars.grib_postproc == 0))
	{
		marslog(LOG_EROR,"Env variable MARS_GRIB_POSTPROC has been set to 0");
		marslog(LOG_EROR,"and some field(s) need interpolation");
		return POSTPROC_ERROR;
	}

	if((size != 0) && (mars.gridded_observations_postproc == 0))
	{
		marslog(LOG_EROR,"Gridded observations need interpolation. In order to force");
		marslog(LOG_EROR,"interpolation, please, set MARS_GRIDDED_OBSERVATIONS_INTERP to 1");
		return POSTPROC_ERROR;
	}

	if(size == 0)
	{
		size = *outlen = inlen;
	}
	else {
		if(ret == 0)
		{
			if(*outlen < size)
			{
				*outlen = size;
				return BUF_TO_SMALL;
			}

			*outlen = size;
			ppdata.inter_cnt++;
			memcpy(buffer,result,size);
		}
	}

	if(ppdata.original_grib)
	{
		fortint len = size;
		ret = original_grib(buffer,&len);
		*outlen = len;
	}

	return ret;
}

static err grib_postproc(ppbuffer_t *pp, long *nbuffer)
{
	*nbuffer = 1;
	return grib_scalar_postproc(pp[0].buffer,pp[0].inlen,&pp[0].outlen);
}

static err odb_postproc(ppbuffer_t *pp, long *nbuffer)
{
	*nbuffer = 1;
	return odb_scalar_postproc(pp[0].buffer,pp[0].inlen,&pp[0].outlen);
}

static err odb_scalar_postproc(char *buffer,long inlen, long *outlen)
{
#if 0
	/* */
	marslog(LOG_DBUG,"in odb_scalar_postproc....inlen: %ld, outlen: %ld",inlen,*outlen);
	marslog(LOG_DBUG,"in odb_scalar_postproc, filter %s",ppdata.odb_sql);
	*outlen = inlen;
	if(ppdata.odb_sql != NULL)
	{
		//marslog(LOG_INFO|LOG_ONCE, "Apply ODB filter '%s'", ppdata.odb_sql);
		marslog(LOG_INFO, "Apply ODB filter '%s'", ppdata.odb_sql);

		size_t filteredLength = 0;
		odb_start();
		marslog(LOG_INFO, "in odb_scalar_postproc, calling filter_in_place: inlen=%ld", inlen);
		int rc = filter_in_place(buffer, inlen, &filteredLength, no_quotes(ppdata.odb_sql));
		if (rc) return rc;
		*outlen = filteredLength;
	}
#endif

	return NOERR;
}

static err track_postproc(ppbuffer_t *pp, long *nbuffer)
{
	*nbuffer = 1;
	return track_scalar_postproc(pp[0].buffer,pp[0].inlen,&pp[0].outlen);
}

static err track_scalar_postproc(char *buffer,long inlen,long *outlen)
{
	packed_key	key;
	packed_key*	keyptr = &key;

	char *p = buffer;
	char *q = buffer;

	unsigned long klength = 0;

	*outlen = 0;


	marslog(LOG_DBUG,"=> Enter track_postproc");
	while(inlen>0)
	{
		int i;
		int ok = 1;

		/* Move to next BUFR */
		while( (p[0]!='B' || p[1]!='U' || p[2]!='F' || p[3]!='R') && inlen>0)
		{
			p++;
			inlen--;
		}

		if(inlen <= 0) break;

		if (!get_packed_key(p,keyptr))
		{
			ok = 0;
			set_key_length(&key,1);
		}

		/* Types */
		if(ok && ppdata.type_cnt>0)
		{
			ok = 0;
			for(i = 0;i<ppdata.type_cnt;i++)
				if(ppdata.types[i] == KEY_SUBTYPE(keyptr))
				{
					ok = 1;
					break;
				}
			marslog(LOG_DBUG,"=> subtype %d %s match",KEY_SUBTYPE(keyptr),(ok?"":"DONT"));
		}

		/* Ident ... */
		if(ok && ppdata.ident_cnt > 0)
		{
			unsigned char *buf = (unsigned char *)KEY_IDENT(keyptr);
			int ident = 0;
			ok = 0;

			for(i=0;i<3;i++)
				ident = ident * 10 + (buf[i] - '0');

			for(i = 0;i<ppdata.ident_cnt;i++)
			{
				marslog(LOG_DBUG,"=> ident %d: %d, (KEY_IDENT %s => %d)", i,ppdata.idents[i],
														  KEY_IDENT(keyptr),ident);
				if(ppdata.idents[i] == ident)
				{
					ok = 1;
					marslog(LOG_DBUG,"      MATCH");
					break;
				}
				else
					marslog(LOG_DBUG," DONT MATCH");

			}
		}

		/* Time */
		if(ok && ppdata.interval_cnt > 0)
		{
			double obs_date;

			ok = 0;
			obs_date = key_2_datetime(keyptr);
			for (i = 0; i < ppdata.interval_cnt; i++)
			{
				if (obs_date >= ppdata.intervals[i].begin &&
					obs_date <= ppdata.intervals[i].end)
				{
					ok = 1;
					break;
				}
			}
			marslog(LOG_DBUG,"=> obs_date_time %lf %s match",obs_date,(ok?"":"DONT"));
		}

		/* Check for area */
		if(ok && ppdata.area_cnt == 4)
		{
			if(KEY_LATITUDE1(keyptr) > ppdata.north ||
				KEY_LATITUDE1(keyptr) < ppdata.south)
				ok = 0;
			if(ppdata.west <  ppdata.east)
			{
				if(KEY_LONGITUDE1(keyptr) < ppdata.west ||
					KEY_LONGITUDE1(keyptr) > ppdata.east)
					ok = 0;
			}
			else
			{
				if(!(KEY_LONGITUDE1(keyptr) > ppdata.west ||
					KEY_LONGITUDE1(keyptr) < ppdata.east))
					ok = 0;

			}
			marslog(LOG_DBUG,"=> lat: %ld, lon: %lf %s match",KEY_LATITUDE1(keyptr),KEY_LONGITUDE1(keyptr),(ok?"":"DONT"));
		}

		klength = key_length(p,keyptr);
		if(ok)
		{
			if(p != q) memcpy(q,p,klength);
			q      += klength;
			(*outlen) += klength;
			ppdata.out_count++;
		}

		p     += klength;
		inlen -= klength;
		ppdata.in_count++;
	}
	return 0;

}


static err obs_postproc(ppbuffer_t *pp, long *nbuffer)
{
	*nbuffer = 1;
	return obs_scalar_postproc(pp[0].buffer,pp[0].inlen,&pp[0].outlen);
}

static err obs_scalar_postproc(char *buffer,long inlen,long *outlen)
{
	int    		issat;
	packed_key	key;
	packed_key*	keyptr = &key;

	char *p = buffer;
	char *q = buffer;

	unsigned long klength = 0;

	*outlen = 0;


	while(inlen>0)
	{
		int i;
		int ok = 1;
		unsigned int keylength = 0;
		int version;

		/* Move to next BUFR */
		while( (p[0]!='B' || p[1]!='U' || p[2]!='F' || p[3]!='R') && inlen>0)
		{
			p++;
			inlen--;
		}

		if(inlen <= 0) break;

		version = p[7];

		if (!get_packed_key(p,keyptr))
		{
			ok = 0;
			set_key_length(&key,1);
		}

		issat = (KEY_TYPE(keyptr) == 2) ||
				(KEY_TYPE(keyptr) == 3) ||
				(KEY_TYPE(keyptr) == 12);

		/* WMO block ... */
		if(ok && ppdata.block_cnt>0)
		{
			int wmo   = (KEY_IDENT(keyptr)[0] - '0')*10
						+ (KEY_IDENT(keyptr)[1] - '0');
			ok = 0;
			if(!issat)
			{
				for(i = 0;i<ppdata.block_cnt;i++)
					if(ppdata.blocks[i] == wmo)
					{
						ok = 1;
						break;
					}
			}
		}

		/* Types */
		if(ok && ppdata.type_cnt>0)
		{
			ok = 0;
			for(i = 0;i<ppdata.type_cnt;i++)
				if(ppdata.types[i] == KEY_SUBTYPE(keyptr))
				{
					ok = 1;
					break;
				}
		}

		/* Ident ... */
		if(ok && ppdata.ident_cnt > 0)
		{
			ok = 0;
			if(issat)
			{
				int ident = bufr_sat_id(p,keyptr);

				for(i = 0;i<ppdata.ident_cnt;i++)
					if(ppdata.idents[i] == ident)
					{
						ok = 1;
						break;
					}
			}
			else
			{
				int ident = 0;
				for(i=0;i<5;i++)
					ident = 10*ident + (KEY_IDENT(keyptr)[i]-'0');

				for(i = 0;i<ppdata.ident_cnt;i++)
					if(ppdata.idents[i] == ident)
					{
						ok = 1;
						break;
					}
			}
		}

		/* time . Note that time is irrelevant for SSMI (126) data */
		if(ok && ppdata.interval_cnt > 0 &&
		   KEY_SUBTYPE(keyptr)!= 126 &&
		   !ppdata.is_bufrfile)
		{
			double obs_date;

			ok = 0;
			obs_date = key_2_datetime(keyptr);
			for (i = 0; i < ppdata.interval_cnt; i++)
			{
				if (obs_date >= ppdata.intervals[i].begin &&
					obs_date <= ppdata.intervals[i].end)
				{
					ok = 1;
					break;
				}
			}
		}

		/* Check for area */
		if(ok && ppdata.area_cnt == 4)
		{
			if(issat)
			{
				if(KEY_LATITUDE1(keyptr) > ppdata.north ||
				    KEY_LATITUDE2(keyptr) < ppdata.south)
					ok = 0;
				if((KEY_LONGITUDE1(keyptr) < ppdata.west ||
				    KEY_LONGITUDE2(keyptr) > ppdata.east) &&
					!(ppdata.west == ppdata.east))
					ok = 0;
			}
			else {
				if(KEY_LATITUDE1(keyptr) > ppdata.north ||
				    KEY_LATITUDE1(keyptr) < ppdata.south)
					ok = 0;
				if(ppdata.west <  ppdata.east)
				{
					if(KEY_LONGITUDE1(keyptr) < ppdata.west ||
						KEY_LONGITUDE1(keyptr) > ppdata.east)
						ok = 0;
				}
				else
				{
					if(!(KEY_LONGITUDE1(keyptr) > ppdata.west ||
						KEY_LONGITUDE1(keyptr) < ppdata.east))
						ok = 0;

				}
#if 0
					if(ok)
					{
					marslog(LOG_INFO,"longitude: %d, west: %d, east: %d ",
							KEY_LONGITUDE1(keyptr),
							ppdata.west,
							ppdata.east);
					}
#endif
			}
		}

		if(ok && issat && (ppdata.instrument_cnt > 0))
		{
#if defined(BUFR_FILTER)
			fortint n_instr = ppdata.instrument_cnt;
			fortint found   = 0;
			fortint e       = 0;
			timer *filter_instruments = get_timer("Filter instruments",NULL,true);
			fortint orig_len  = inlen;
			char    *orig_buf = p;

			timer_start(filter_instruments);
			mbufr_mars_filter_(&n_instr,ppdata.instruments,&orig_len,orig_buf,&found,&e);
			timer_stop(filter_instruments,0);
			if(e != 0)
				marslog(LOG_WARN,"Error %d while filtering instruments on message %d",e,ppdata.in_count);
			else
				ok = found;
#else
			marslog(LOG_WARN,"Filter by instrument not active");
#endif
		}

#ifdef OBSOLETE
		if(ok && ppdata.no_duplicates)
		{
			if(add_key(&ppdata.top,(char*)&key))
			{
				ppdata.dup_count++;
				ok = 0;
			}
		}
#endif

#ifdef ECMWF
		if(ok && mars.restriction)
		{
			if(restricted(keyptr))
			{
				ok = 0;
				ppdata.res_count++;
			}
		}
#endif

		klength = key_length(p,keyptr);

		if(klength <= 4 || klength > inlen ||
			p[klength-4] != '7' ||
			p[klength-3] != '7' ||
			p[klength-2] != '7' ||
			p[klength-1] != '7'
		)
		{
			/* Some obs are wrong... june in 1990 */
			unsigned char *s = (unsigned char*)(p + 4); /* skip BUFR */
			int len = 8;
			int nsec = 4;


			/* skip section 0 , check for BUFR version */
			char* sec1_start = p;
			if((unsigned char)sec1_start[7]>1) sec1_start += 8;
			else sec1_start += 4;
			if(SEC1_FLAGS(sec1_start, version)== 0)
			{
				marslog(LOG_WARN,"Report %d has no key but was included",
					ppdata.in_count+1);
				ok = 1;
				nsec = 3;
			}

			for(i=0;i< nsec && len <= inlen ;i++)
			{
				int sec = (s[0] << 16) + (s[1] << 8) + s[2];
				len += sec;
				s   += sec;
			}
			ppdata.bad_count++;

			set_key_length(&key,len);

			klength = key_length(p,keyptr);
			if (SEC1_FLAGS(p, version)!= 0)
			{
				if(len <= 4 || len > inlen ||
				p[klength-4] != '7' ||
				p[klength-3] != '7' ||
				p[klength-2] != '7' ||
				p[klength-1] != '7')
				{
					ok = 0;
					set_key_length(&key,sizeof(long));
					marslog(LOG_EROR,"Bad report found at position %d, skipped",
						ppdata.in_count+1);
				}
			}

		}

		keylength  = key_length(p,keyptr);

		set_key_length(&key,((keylength + sizeof(long)-1)/sizeof(long))*sizeof(long));

		if( ok && keylength > inlen)
		{
			ok = 0;
			set_key_length(&key,sizeof(long));
			marslog(LOG_EROR,"Report to large found at position %d, skipped",
				ppdata.in_count+1);
		}

		klength = key_length(p,keyptr);
		if(ok)
		{
			if(p != q) memcpy(q,p,klength);
			q      += klength;
			(*outlen) += klength;
			ppdata.out_count++;
		}

		p     += klength;
		inlen -= klength;
		ppdata.in_count++;
	}
	return 0;
}

/* Initialise post-processing .... */

static void pperror(char *msg)
{
	marslog(LOG_WARN|LOG_ONCE,"%s",msg);
}

namespace marsclient {

    class PProcEMOS : public PProc {
    public: // methods

        PProcEMOS(const char* name) : name_(name) {}

        virtual ~PProcEMOS() {}

       	virtual const std::string& name() const { return name_; }

        virtual err initialise(int argc, char** argv) {
            marslog(LOG_DBUG,"Post processing backend is %s", name().c_str());
            return 0;
        }

        virtual void print_version() {
            marslog(LOG_INFO,"EMOSLIB version: %s", libemos_version());
        }

        virtual err ppinit(const request *r, postproc *proc);
        virtual err ppdone(void);
        virtual err ppcount(int *in,int *out);
        virtual err pparea(request *r);
        virtual fieldset* pp_fieldset(const char *file,request *filter);
        virtual err ppstyle(const request *r);
        virtual err pprotation(const request *r);
        virtual long ppestimate();
        virtual err makeuv(char *vo,char *d,long inlen,char *u,char *v,long *outlen);

    private: // members

        std::string name_;

    };

err PProcEMOS::ppstyle(const request *r)
{
	err       ret     = NOERR;
	fortfloat array[4];
	fortint   n       = 0;

	if(get_value(r,"STYLE",0))
	{
		const char *style = no_quotes(get_value(r,"STYLE",0));
		marslog(LOG_DBUG,"Setting post-processing style to: '%s'",style);
		ret  = ppout("style",n,array,lowcase(style));
		if(ret) return ret;
		if(EQ(style,"DISSEMINATION"))
		{
			marslog(LOG_WARN,"With style=dissemination, avoid intermediate packing");
			mars.use_intuvp = 1;
		}
	}
	return NOERR;
}

err PProcEMOS::pprotation(const request *r)
{
	err       ret     = NOERR;
	fortfloat array[4];
	fortint   n       = 0;

	/* rotation */
	if(get_value(r,"_ROTATION_LAT",0) || get_value(r,"_ROTATION_LON",0))
	{
		array[0] = atof(get_value(r,"_ROTATION_LAT",0));
		array[1] = atof(get_value(r,"_ROTATION_LON",0));
		ret      = ppout("rotation",n,array,0);
		if(ret) return ret;
	}
	return NOERR;
}

err PProcEMOS::pparea(request *r)
{
	const char *p;
	fortfloat ew = 0,ns = 0,n = 0,s = 0,e = 0,w = 0;
	fortfloat ew_ = 0, ns_ = 0,n_ = 0,s_ = 0,e_ = 0,w_ = 0;

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

	if( p = get_value(r,"_AREA_N",0)) n_  = n = atof(p);
	if( p = get_value(r,"_AREA_S",0)) s_  = s = atof(p);
	if( p = get_value(r,"_AREA_E",0)) e_  = e = atof(p);
	if( p = get_value(r,"_AREA_W",0)) w_  =  w  = atof(p);
	if( p = get_value(r,"_GRID_EW",0))ew_ =  ew = atof(p);
	if( p = get_value(r,"_GRID_NS",0))ns_ =  ns = atof(p);
    if( p = get_value(r,"_GAUSSIAN",0)) ew_ =  ew = atof(p);

	if(ew == 0 && ns == 0)
		return 0;

	if(ocean)
		marslog(LOG_INFO,"Ocean field. Don't check AREA boundaries");
	else
	{
		if(areachk_(&ew,&ns,&n,&w,&s,&e))
			marslog(LOG_WARN,"AREACHK returns an error");
	}


	if( ((ew_ != 0) && (ew_ != ew)) || (ns_ != ns) )
	{
		if(ns == 0)
		{
            marslog(LOG_WARN,"Grid not supported, changed from " PPROC_PRECISION " to " PPROC_PRECISION,
                ew_,ew);
			set_value(r,"_GAUSSIAN",PPROC_PRECISION,ew);
			set_value(r,"GRID","%g",ew);
		}
		else
		{
            marslog(LOG_WARN,"Grid not supported, changed from " PPROC_PRECISION "/" PPROC_PRECISION " to " PPROC_PRECISION "/" PPROC_PRECISION,
                ew_,ns_,ew,ns);
			set_value(r,"_GRID_EW",PPROC_PRECISION,ew);
			set_value(r,"_GRID_NS",PPROC_PRECISION,ns);
			set_value(r,"GRID",PPROC_PRECISION,ew);
			add_value(r,"GRID",PPROC_PRECISION,ns);
		}
	}

	if(n_ != 0 || s_ != 0 || e_ != 0 || w_ != 0)
	if(n_ != n || s_ != s || e_ != e || w_ != w)
	{
		if((e_ != e && e_ != (e - 360)) ||
		   (w_ != w  && w_ != (w - 360)) ||
		   (n_ != n)                     ||
		   (s_ != s)
		   )
		{
			marslog(LOG_WARN,"Area not compatible with grid");
            marslog(LOG_WARN,"Area changed from " PPROC_PRECISION "/" PPROC_PRECISION "/" PPROC_PRECISION "/" PPROC_PRECISION " to " PPROC_PRECISION "/" PPROC_PRECISION "/" PPROC_PRECISION "/" PPROC_PRECISION,
				n_,w_,s_,e_,
				n,w,s,e);
		}

		set_value(r,"_GRID_N",PPROC_PRECISION,n);
		set_value(r,"_GRID_W",PPROC_PRECISION,w);
		set_value(r,"_GRID_S",PPROC_PRECISION,s);
		set_value(r,"_GRID_E",PPROC_PRECISION,e);

		set_value(r,"AREA",PPROC_PRECISION,n);
		add_value(r,"AREA",PPROC_PRECISION,w);
		add_value(r,"AREA",PPROC_PRECISION,s);
		add_value(r,"AREA",PPROC_PRECISION,e);

		set_value(r,"_AREA_N",PPROC_PRECISION,n);
		set_value(r,"_AREA_S",PPROC_PRECISION,s);
		set_value(r,"_AREA_E",PPROC_PRECISION,e);
		set_value(r,"_AREA_W",PPROC_PRECISION,w);
	}

	return 0;
}


err PProcEMOS::makeuv(char *vo,char *d,long inlen,char *u,char *v,long *outlen)
{
	err e;
	fortint out = *outlen;
	fortint in  = inlen;

	if(mars.grib_postproc == 0)
	{
		marslog(LOG_EROR,"Env variable MARS_GRIB_POSTPROC has been set to 0");
		marslog(LOG_EROR,"and conversion to U/V requested");
		return POSTPROC_ERROR;
	}

	if(!ppdata.quiet)
	{
		marslog(LOG_INFO,"Deriving U and V from vorticity and divergence");
		ppdata.quiet = 1;
	}

	marslog(LOG_DBUG,"-> INTUV%s in=%d out=%d",mars.use_intuvp?"P":"S",*outlen,out);
	timer_start(pptimer);
	if(mars.use_intuvp)
	{
		e = intuvp2_(vo,d,&in,u,v,&out);
		if(in != out)
			ppdata.inter_cnt+=2;
		marslog(LOG_DBUG,"MARS_USE_INTUVP set and parameters are U/V. Avoid calling intf2");
		ppdata.derive_uv += 2;
	}
	else
		e = intuvs2_(vo,d,&in,u,v,&out);
	timer_stop(pptimer,0);

	marslog(LOG_DBUG,"<- INTUV%s in=%d out=%d",mars.use_intuvp?"P":"S",*outlen,out);

	if(out >  *outlen)
	{
		marslog(LOG_EROR,"INTUV%s output is %d bytes",mars.use_intuvp?"P":"S",out);
		marslog(LOG_EXIT,"Buffer is only %d bytes",*outlen);
	}

	*outlen = out;

	/* Check if rounding to a multiple of 4 changes */
	/* the actual size.                             */

	if( *outlen > (inlen+sizeof(fortint)))
	{
		marslog(LOG_DBUG,"INTUV%s returns bigger field %d > %d",mars.use_intuvp?"P":"S",*outlen ,inlen);
		/* return -3; */
	}

	return e;
}

long PProcEMOS::ppestimate()
{
	return ppdata.estimate;
}

err PProcEMOS::ppinit(const request *r, postproc *proc)
{
	fortfloat array[4];
	fortint   n = 0;
	err       ret;
	int      i;
	const char *p;
	const char *mars_postproc = get_value(r,"_POSTPROCESSING",0);

	*proc = no_postproc;

	if(ppdata.busy)
	{
        marslog(LOG_EROR,"Post-processing package already open");
		return -2;
	}

	intlogs(pperror);

	/* Reset */
	n = 1;
	TRACE(ibasini_(&n));
	n = 0;

	ppdata.derive_uv = 0;
	ppdata.busy = 1;
	ppdata.inter_cnt =
	ppdata.in_count = ppdata.out_count = ppdata.bad_count =
	    ppdata.res_count = 0;

	pptimer = get_timer("Post-processing","postprocessing",false);
	memcpytimer = get_timer("Memory copy",NULL,false);

#ifdef OBSOLETE
	ppdata.dup_count = 0;
#endif

	if( (p = get_value(r,"GRIB",0)) )
	{
		ppdata.original_grib = EQ(p,"ORIGINAL");
	}

	if(mars_postproc && !(atol(mars_postproc)))
	{
		*proc = no_postproc;
	}
	else if(fetch(r))
	{
		*proc = no_postproc;
	}
	else if (feedback(r) || bias(r))
	{
		*proc = no_postproc;
	}
    else if (is_ocean(r))
    {
        *proc = no_postproc; // don't post-process ocean (netcdf) data
    }
    else if (is_odb(r))
	{
#ifdef ODB_SUPPORT
		/* Filter */
		const char *filter = get_value(r, "FILTER", 0);
		ppdata.odb_sql = NULL;
		if(filter)
			ppdata.odb_sql = strcache(filter);

		marslog(LOG_DBUG,"filter is: %s",ppdata.odb_sql);
		*proc = odb_postproc;
#else
		marslog(LOG_EROR,"This MARS client doesn't support ODB");
		marslog(LOG_EROR,"Please, contact the MARS team");
		*proc = no_postproc;
#endif

	}
	else if (track(r))
	{
		long time_count;
		long date_count;

		/* Ident */

		if(ppdata.idents) FREE(ppdata.idents);

		if( (ppdata.ident_cnt = count_values(r,"IDENT")) )
			ppdata.idents = NEW_ARRAY(int,ppdata.ident_cnt);

		for(i = 0;i<ppdata.ident_cnt;i++)
		{
			const char *p = no_quotes(get_value(r,"IDENT",i));
			int ident;
			int n = 0;

			if(p) n = strlen(p);

			/* Tropical cyclones have a alph num ident */
			char buf[8];
			int j;

			ident = 0;
			sprintf(buf,"%s",p);
			for(j=0;j<n;j++)
			{
				if(islower(buf[j])) buf[j] = toupper(buf[j]);
				ident = ident * 10 + (buf[j] - '0');
			}

			ppdata.idents[i] = ident;
			marslog(LOG_DBUG,"Requested ident %d: %s [%s]. Internal representation: %d",i,p,buf,ident);
		}

		/* Types */

		if(ppdata.types) FREE(ppdata.types);

		if( (ppdata.type_cnt = count_values(r,"OBSTYPE")) )
		{
			int zero = 0;
			ppdata.types = NEW_ARRAY(int,ppdata.type_cnt);
			for(i = 0;i<ppdata.type_cnt;i++)
			{
				ppdata.types[i] = atoi(get_value(r,"OBSTYPE",i));
				if(ppdata.types[i] == 0) zero++;
			}

			if(zero > 0 && ppdata.type_cnt != zero)
			{
				marslog(LOG_WARN,
				    "Cannot mix types and subtypes when retrieving tracks");
				marslog(LOG_WARN, "Only corresponding types will be returned");
			}

			if(zero) ppdata.type_cnt = 0;
		}

		/* Time  */

		if(ppdata.intervals)
			FREE(ppdata.intervals);

		time_count =  count_values(r,"TIME");
		date_count =  count_values(r,"DATE");
		ppdata.interval_cnt = time_count * date_count;

		ppdata.intervals = NEW_ARRAY(time_interval, ppdata.interval_cnt);
		for (i = 0; i < date_count; i++)
		{
			int j;
			long date = get_julian_from_request(r,i);
			for (j = 0; j < time_count; j++)
			{
				long time = atol(get_value(r,"TIME",j));
				double dt = date_time_2_datetime(date,
											time / 100,
											time % 100,
											0);
				ppdata.intervals[i*time_count+j].begin = dt;
				ppdata.intervals[i*time_count+j].end   = dt;
			}
		}

		/* Area ... */
		ppdata.area_cnt  = count_values(r,"AREA");
		if(ppdata.area_cnt == 4)
		{
			ppdata.north = atof(get_value(r,"_AREA_N",0))*100000.0+9000000.0;
			ppdata.south = atof(get_value(r,"_AREA_S",0))*100000.0+9000000.0;
			ppdata.east  = atof(get_value(r,"_AREA_E",0))*100000.0+18000000.0;
			ppdata.west  = atof(get_value(r,"_AREA_W",0))*100000.0+18000000.0;
			ppdata.east %= 36000000;
			ppdata.west %= 36000000;
		}

		*proc = track_postproc;
	}
	else if(observation(r))
	{
		long time_count;
		long date_count;
		const char *p;

		ppdata.is_bufrfile = feedback(r) || bias(r);

		/* Ident */

		if(ppdata.idents) FREE(ppdata.idents);

		if( (ppdata.ident_cnt = count_values(r,"IDENT")) )
			ppdata.idents = NEW_ARRAY(int,ppdata.ident_cnt);

		for(i = 0;i<ppdata.ident_cnt;i++)
		{
			const char *p = get_value(r,"IDENT",i);
			int ident;

			if(is_number(p))
				ident = atoi(p);
			else {
				/* Ships have a alph num ident */
				char buf[8];
				int j;

				ident = 0;
				sprintf(buf,"%-5s",p);
				for(j=0;j<5;j++)
				{
					if(islower(buf[j])) buf[j] = toupper(buf[j]);
					ident = ident * 10 + (buf[j] - '0');
				}

			}

			ppdata.idents[i] = ident;
		}

		/* Instruments */
		if(ppdata.instruments) FREE(ppdata.instruments);

		if( (ppdata.instrument_cnt = count_values(r,"INSTRUMENT")) )
			ppdata.instruments = NEW_ARRAY(fortint,ppdata.instrument_cnt);

		for(i = 0;i<ppdata.instrument_cnt;i++)
			ppdata.instruments[i] = atoi(get_value(r,"INSTRUMENT",i));

		/* Types */

		if(ppdata.types) FREE(ppdata.types);

		if( (ppdata.type_cnt = count_values(r,"OBSTYPE")) )
		{
			int zero = 0;
			ppdata.types = NEW_ARRAY(int,ppdata.type_cnt);
			for(i = 0;i<ppdata.type_cnt;i++)
			{
				ppdata.types[i] = atoi(get_value(r,"OBSTYPE",i));
				if(ppdata.types[i] == 0) zero++;
			}

			if(zero > 0 && ppdata.type_cnt != zero)
			{
				marslog(LOG_WARN,
				    "Cannot mix types and subtypes when retrieving observations");
				marslog(LOG_WARN, "Only corresponding types will be returned");
			}

			if(zero) ppdata.type_cnt = 0;
		}

		/* WMO block */

		if(ppdata.blocks) FREE(ppdata.blocks);

		if( (ppdata.block_cnt = count_values(r,"BLOCK")) )
			ppdata.blocks = NEW_ARRAY(int,ppdata.block_cnt);

		for(i = 0;i<ppdata.block_cnt;i++)
			ppdata.blocks[i] = atoi(get_value(r,"BLOCK",i));


		/* Time  */

		if(ppdata.intervals)
			FREE(ppdata.intervals);

		time_count =  count_values(r,"TIME");
		date_count =  count_values(r,"DATE");
		ppdata.interval_cnt = time_count * date_count;

		/* if the time/to/time is not expanded in this version */
		if (time_count == 3 && (strcmp(get_value(r,"TIME",1),"TO") == 0))
		{
			long time1 = atoi(get_value(r,"TIME",0));
			long time2 = atoi(get_value(r,"TIME",2));
			ppdata.intervals = NEW_ARRAY(time_interval,
									date_count);
			for (i = 0; i < date_count; i++)
			{
				long date = get_julian_from_request(r,i);
				ppdata.intervals[i].begin =
						date_time_2_datetime(date,time1 / 100,
											time1 % 100,
											0);
				ppdata.intervals[i].end =
						date_time_2_datetime(date,time2 / 100,
											time2 % 100,
											59);
			}
			ppdata.interval_cnt = date_count;
		}
		else
		{
			long range = 0;
			if (count_values(r,"RANGE") != 0)
				range = atol(get_value(r,"RANGE",0));
			ppdata.intervals = NEW_ARRAY(time_interval,
							ppdata.interval_cnt);
			for (i = 0; i < date_count; i++)
			{
				int j;
				long date = get_julian_from_request(r,i);
				for (j = 0; j < time_count; j++)
				{
					long time = atol(get_value(r,"TIME",j));
					double dt = date_time_2_datetime(date,
											time / 100,
											time % 100,
											0);
					init_time_interval(&ppdata.intervals[i*time_count+j],
										dt,range * 60 + 59);
				}
			}
		}

		/* Area ... */
		ppdata.area_cnt  = count_values(r,"AREA");
		if(ppdata.area_cnt == 4)
		{
			ppdata.north = atof(get_value(r,"_AREA_N",0))*100000.0+9000000.0;
			ppdata.south = atof(get_value(r,"_AREA_S",0))*100000.0+9000000.0;
			ppdata.east  = atof(get_value(r,"_AREA_E",0))*100000.0+18000000.0;
			ppdata.west  = atof(get_value(r,"_AREA_W",0))*100000.0+18000000.0;
			ppdata.east %= 36000000;
			ppdata.west %= 36000000;
		}

#ifdef OBSOLETE
		p = get_value(r,"DUPLICATES",0);
		ppdata.no_duplicates = p && (*p == 'R');
#endif

		/* ppinfo(); */

		*proc = obs_postproc;
	}
	else if(image(r) || simulated_image(r))
		*proc = no_postproc;
	else if(gridded_observations(r) && !mars.gridded_observations_postproc)
		*proc = no_postproc;
	else {
		/* Check for vector parameters */
		boolean vector = vector_parameter_requested(r);
		*proc = grib_postproc;
		ppdata.estimate = 0;

		if(!gridded_observations(r))
			mars.gridded_observations_postproc = 1;

		/* By default, copy edition from intput to output: edition=0*/
		ppdata.edition = 0;
		if( (p = get_value(r,"FORMAT",0)) )
		{
			if(EQ(p,"GRIB1")) ppdata.edition = 1;
			if(EQ(p,"GRIB2")) ppdata.edition = 2;
			marslog(LOG_WARN,"Format conversion not enabled");

#if 0
			if(ppdata.edition > 0)
			{
				marslog(LOG_DBUG,"ppout: setting edition to %d",ppdata.edition);
				ret  = ppout("edition",ppdata.edition,array,0);
				if(ret) return ret;
			}
#endif

		}

		/* gridname */
		const char *gridname = get_value(r,"_GRIDNAME",0);
		if(gridname)
		{
			ret = ppout("gridname",n,array,gridname);
			if(ret) return ret;

			n = atoi(get_value(r,"_GAUSSIAN",0));
			long guess;
			guess = (2*n) * (4*n+20); /* nb lats * nb points at equator for octahedral grid */
			if( guess > ppdata.estimate ) ppdata.estimate = guess;
		}

		/* set LatLon grid */
		if(get_value(r,"_GRID_EW",0))
		{
			array[0] = atof(get_value(r,"_GRID_EW",0));
			array[1] = atof(get_value(r,"_GRID_NS",0));
			ret      = ppout("grid",n,array,0);
			if(ret) return ret;
			ppdata.estimate = (360.0/array[0] + 1) * (180.0 / array[1] + 1);
		}

		/* set area */
		if(get_value(r,"_AREA_N",0))
		{
			array[0] = atof(get_value(r,"_AREA_N",0));
			array[1] = atof(get_value(r,"_AREA_W",0));
			array[2] = atof(get_value(r,"_AREA_S",0));
			array[3] = atof(get_value(r,"_AREA_E",0));

			/* If AREA not specified (0/0/0/0), don't call intout to avoid
			   interpolation sw. to be called when no post-processing is
			   required */
			if(!(array[0] == 0 && array[1] == 0 && array[2] == 0 && array[3] == 0))
			{
				ret = ppout("area",n,array,0);
				if(ret) return ret;
			}
		}

		/* frame */
		if(get_value(r,"FRAME",0))
		{
			n = atoi(get_value(r,"FRAME",0));
			ret  = ppout("frame",n,array,0);
			if(ret) return ret;
		}

		/* bitmap */
		if(get_value(r,"BITMAP",0))
		{
			const char *bitmap = no_quotes(get_value(r,"BITMAP",0));
			marslog(LOG_DBUG,"bitmap file is: '%s'",bitmap);
			ret  = ppout("bitmap",n,array,bitmap);
			if(ret) return ret;
		}

		/* interpolation */
		if(get_value(r,"INTERPOLATION",0))
		{
			const char *method  = get_value(r,"INTERPOLATION",0);

			if(strcmp(method,"NEAREST NEIGHBOUR") == 0)
				method = "nearest neighbour";
			else if(strcmp(method,"NEAREST LSM") == 0)
				method = "nearest lsm";
			else if(strcmp(method,"BILINEAR") == 0)
				method = "bilinear";
			else
			{
				marslog(LOG_WARN,"Ignoring unknown interpolation method '%s'",method);
				method = NULL;
			}

			if(method)
			{
				ret = ppout("interpolation",n,array,method);
				if(ret) return ret;
			}
		}

		/* Auto Resolution */
		if(get_value(r,"RESOL",0))
		{
			const char *trunc = get_value(r,"RESOL",0);
			if(trunc)
			{
				if(EQ(trunc,"AUTO"))
				{
					if(mars.autoresol)
					{
						marslog(LOG_DBUG,"Using Auto-Resolution");
						ret = ppout("autoresol",1,array,0);
						if(ret) marslog(LOG_EROR,"Auto Resolution error %d",ret);
					}
				}
				if(trunc[0] == 'N' || trunc[0] == 'O')
				{
					n    = atoi(trunc+1);
					marslog(LOG_DBUG,"Using GG double interpolation to %s", trunc);
					ret = ppout("intermediate_gaussian",n,array,trunc);
					if(ret) marslog(LOG_EROR,"Double interpolation error %d",ret);
				}
			}
		}

		/* truncation */
		if(get_value(r,"_TRUNCATION",0))
		{
			long guess;
			const char *trunc = get_value(r,"_TRUNCATION",0);

			n  = atoi(trunc);
			ret     = ppout("truncation",n,array,0);
			ret     = ppin("truncation",n,array,0);

			marslog(LOG_DBUG,"Setting truncation to %d",n);
			guess = (n+1)*(n+4);
			if( guess > ppdata.estimate ) ppdata.estimate = guess;
		}

		/* style */
		if(get_value(r,"STYLE",0))
		{
			ret = ppstyle(r);
			if(ret) return ret;
		}

		/* rotation */
		if(get_value(r,"_ROTATION_LAT",0) || get_value(r,"_ROTATION_LON",0))
		{
			ret = pprotation(r);
			if(ret) return ret;
			if(vector)
				*proc = grib_vector_postproc;
		}

		/* accuracy */
		if(mars.accuracy > 0)
		{
			marslog(LOG_DBUG,"ppout: Using %d bit packing",mars.accuracy);
			ret  = ppout("accuracy",mars.accuracy,array,0);
			if(ret) return ret;
		}

		/* packing */
		if(get_value(r,"PACKING",0))
		{
			const char *packing = no_quotes(get_value(r,"PACKING",0));
			marslog(LOG_DBUG,"Packing set to '%s'",packing);
			ret  = ppout("packing",n,array,lowcase(packing));
			if(ret) return ret;
		}

		/* specification */
		if(get_value(r,"SPECIFICATION",0))
		{
			const char *spec = get_value(r,"SPECIFICATION",0);
			marslog(LOG_DBUG,"Specification set to '%s'",spec);
			n = atoi(spec);
			ret  = ppout("specification",n,array,0);
			if(ret) return ret;
		}

		/* warnings for unsupported keywords */
		{
			static const char *warnings[] = {
                "TRUNCATION",
                "INTGRID",
                "LSM",
				0
			};

			for (i = 0; warnings[i]; i++) {
				if(get_value(r, warnings[i], 0)) {
					marslog(LOG_WARN,"Ignoring unsupported keyword '%s'", warnings[i]);
				}
			}
		}

		/* ppdata.estimate contains the number of point, we need
		the header and sizeof(fortint) bytes per value */
		if(ppdata.estimate)
			ppdata.estimate = ppdata.estimate * sizeof(fortint) + 4096;

	}
	return 0;
}

err PProcEMOS::ppdone(void)
{
	if(!ppdata.busy) marslog(LOG_EROR,"Post-processing package already closed");
	ppdata.busy  = 0;
	ppdata.quiet = 0;
#ifdef OBSOLETE
	delete_node(ppdata.top);
	ppdata.top   = 0;
	if(ppdata.dup_count)
		marslog(LOG_INFO,"%d duplicates reports",ppdata.dup_count);
#endif
	if(ppdata.res_count)
		marslog(LOG_DBUG,"%d restricted reports found",ppdata.res_count);

	if(ppdata.inter_cnt)
	{
		char host[80];
		char s[1024] = "";
		gethostname(host,sizeof(host));
		if(mars.show_hosts)
			sprintf(s,"on '%s'",host);
		marslog(LOG_INFO,"%d field%s ha%s been interpolated %s",
			ppdata.inter_cnt,
				ppdata.inter_cnt==1?"":"s",
				ppdata.inter_cnt==1?"s":"ve",
				s);
	}
	log_statistics("interpolated","%d",ppdata.inter_cnt);

	return 0;
}

err PProcEMOS::ppcount(int *in,int *out)
{
	if(ppdata.bad_count)
	{
		marslog(LOG_WARN,"%d report(s) have a wrong length in their key.",
			ppdata.bad_count);
		marslog(LOG_WARN,"Please inform Mars group");
	}
	*in  = ppdata.in_count;
	*out = ppdata.out_count;
	return 0;
}



fieldset* PProcEMOS::pp_fieldset(const char *file,request *filter)
{
	fieldset *v = read_fieldset(file,filter);
	fieldset *w = NULL;
	err e = 0;
	int i;
	postproc proc;

	if(!v) return NULL;
	if( (e = ppinit(filter,&proc)) ) {
		marslog(LOG_EROR,"Interpolation initialisation failed (%d)",e);
		ppdone();
		return NULL;
	}


	w = new_fieldset(v->count);

	for(i=0;i<v->count && e == 0;i++)
	{
		field *g   = get_field(v,i,packed_mem);
		/* On Rotation, output header can be bigger than
		   the input header. Give a bit of space */
		long len   = MAX(g->length+5*sizeof(fortint),ppestimate());
		const void *ibuff = NULL;
		size_t ibuff_len;
		char *obuff = (char*)reserve_mem(len);
		size_t ilen = len;

		grib_get_message(g->handle,&ibuff,&ibuff_len);
		e =  ppintf((char *)ibuff,g->length,obuff,&len,true);

		release_field(g);

		if(e == 0)
		{
			g = w->fields[i] = mars_new_field();
			g->handle = grib_handle_new_from_message_copy(0,obuff, len);
			g->shape  = packed_mem;
			g->length = len;
			g->refcnt++;
			save_fieldset(w);
		}

		release_mem(obuff);
	}

	ppdone();

	return e == 0?w:NULL;
}


static PProcBuilderT<PProcEMOS> emosBuilder("EMOS");


}  // namespace marsclient


