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