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

#include "mars.h"

typedef struct _buffer {
    char* ptr;
    int size;
    int pos;
} _buffer;

static int put_bytes(void* data, void* p, int len) {
    _buffer* b = (_buffer*)data;
    if (b->pos + len > b->size) {
        marslog(LOG_EROR, "free format buffer too small %d %d %d", b->size, b->pos, len);
        return -1;
    }
    memcpy(b->ptr + b->pos, p, len);
    b->pos += len;
    return len;
}

int n = 0;

static int get_bytes(void* data, void* p, int len) {
    _buffer* b = (_buffer*)data;
    if (b->pos + len > b->size) {
        marslog(LOG_EXIT, "free format buffer too small %d %d %d", b->size, b->pos, len);
        return -1;
    }
    memcpy(p, b->ptr + b->pos, len);
    /* printf("read %d %02x\n",len,(int)*(char*)p);  */
    b->pos += len;
    return len;
}

static err put_request(mstream* s, const request* r) {
    int n        = 0;
    parameter* p = r->params;

    /* stream_write_start(s,"Request"); */
    stream_write_string(s, r->name);

    /* count */

    while (p) {
        n++;
        p = p->next;
    }
    stream_write_int(s, n);

    p = r->params;
    while (p) {
        value* v = p->values;

        if (p->values == NULL)
            marslog(LOG_EXIT, "Internal error: missing value for %s", p->name);

        stream_write_string(s, p->name);

        n = 0;
        while (v) {
            n++;
            v = v->next;
        }
        stream_write_int(s, n);

        v = p->values;
        while (v) {
            stream_write_string(s, v->name);
            v = v->next;
        }

        p = p->next;
    }

    /* stream_write_end(s); */

    return s->error;
}

static request* get_request(mstream* s) {
    request* r = empty_request(stream_read_string(s));
    int n      = stream_read_int(s);
    int i;
    /* printf("getrequest %d %s\n",n,r->name); */

    for (i = 0; i < n; i++) {
        const char* p = stream_read_string(s);
        int m         = stream_read_int(s);
        int j;
        char* q = strcache(p);
        /* printf("getrequest %s %d\n",p,m); */

        for (j = 0; j < m; j++)
            add_value(r, q, "%s", stream_read_string(s));

        strfree(q);
    }

    return r;
}

err encode_free_format(void* buffer, long* length,
                       const request* r, const void* blob, long bloblen) {
    mstream s = {
        0,
    };
    _buffer b;

    b.ptr  = (char*)buffer;
    b.size = *length;
    b.pos  = 0;

    s.data  = &b;
    s.write = put_bytes;

    stream_write_int(&s, 1); /* number of requests */
    put_request(&s, r);

    stream_write_blob(&s, blob, bloblen);

    *length = b.pos;

    return s.error;
}

request* decode_free_format_request(void* buffer, long length) {
    mstream s = {
        0,
    };
    _buffer b;

    b.ptr  = (char*)buffer;
    b.size = length;
    b.pos  = 0;

    s.data = &b;
    s.read = get_bytes;

    n = stream_read_int(&s); /* number of requests */
    /* printf("n = %d\n",n); */
    return get_request(&s);
}

long decode_free_format_blob(void* buffer, long length, void* blob, long max) {
    int n;
    mstream s = {
        0,
    };
    _buffer b;
    const void* data;
    long len = 0;

    b.ptr  = (char*)buffer;
    b.size = length;
    b.pos  = 0;

    s.data = &b;
    s.read = get_bytes;

    n = stream_read_int(&s); /* number of requests */
    while (n-- > 0)
        free_all_requests(get_request(&s));

    data = stream_read_blob(&s, &len);
    if (len < 0) {
        marslog(LOG_EROR, "Read blob failed\n");
        return -1;
    }

    if (len > max) {
        marslog(LOG_EROR, "Blob too large: %d > %d", len, max);
        return -1;
    }

    memcpy(blob, data, len);
    return len;
}

err encode_free_format_grib(
    unsigned char* bin, unsigned char* bout,
    fortint* length, fortint maxlen,
    request* r,
    int marsclass, int type, int stream, char* expver) {
    unsigned char* in  = bin;
    unsigned char* out = bout;
    long len;
    fortint outlen;
    unsigned char local[10240];
    unsigned char newlocal[10240];
    unsigned char ff[10240];
    int locallen;
    int newlocallen;
    int n;
    int i            = 0;
    boolean localdef = false;
    int fixedlensec1 = 40;

    unsigned char subcenter;

    if (in[7] != 1) {
        marslog(LOG_EROR, "encode_free_format_grib only supported for grib edition 1 (edition is %ld)", (long)in[7]);
        return -2;
    }

    /* copy sec0 */
    memcpy(out, in, 8);
    marslog(LOG_DBUG, "memcpy in -> out 8 bytes");
    out += 8;
    marslog(LOG_DBUG, "advance 'out' of 8 bytes");
    in += 8;
    marslog(LOG_DBUG, "advance 'in' of 8 bytes");

    /* sec 1 */
    len          = (in[0] << 16) | (in[1] << 8) | (in[2] << 0);
    subcenter    = in[25];
    localdef     = (len > 40);
    fixedlensec1 = localdef ? 40 : len;

    marslog(LOG_DBUG, "sec1 len: %d, fixedlensec1 %d", len, fixedlensec1);

    memcpy(out, in, fixedlensec1);
    out[25] = 98; /* sub-center ecmwf */
    if (localdef) {
        locallen = len - 40;
        if (locallen > sizeof(local)) {
            marslog(LOG_EROR, "Original local definition too large %d, sec1 len: %d", locallen, len);
            return -2;
        }

        marslog(LOG_DBUG, "Save original local extension");

        /* save original local extension */
        memcpy(local, in + 40, locallen);
    }
    else {
        marslog(LOG_DBUG, "GRIB doesn't have local extension");
        locallen = 0;
    }
    in += locallen + fixedlensec1;
    marslog(LOG_DBUG, "advance 'in' of %d bytes (%d + %d)", locallen + fixedlensec1, locallen, fixedlensec1);

    /* put ours */
    memset(newlocal, 0, sizeof(newlocal));
    n = 0;

    newlocal[n++] = 191;
    newlocal[n++] = marsclass; /* class */
    newlocal[n++] = type;      /* type */

    newlocal[n++] = (stream >> 8) & 0xff; /* stream */
    newlocal[n++] = (stream >> 0) & 0xff; /* stream */

    newlocal[n++] = expver[0]; /* expver */
    newlocal[n++] = expver[1]; /* expver */
    newlocal[n++] = expver[2]; /* expver */
    newlocal[n++] = expver[3]; /* expver */

    newlocal[n++] = 0; /* spare */
    newlocal[n++] = 0; /* spare */

    newlocal[n++] = 1; /* version */
    newlocal[n++] = 0; /* sub-version */

    newlocal[n++] = subcenter; /* origial sub-center */
    newlocal[n++] = 0;         /* origial local def */

    newlocal[n++] = 0; /* spare */
    newlocal[n++] = 0; /* spare */
    newlocal[n++] = 0; /* spare */


    /* encode original local extension */
    len = sizeof(newlocal) - n - 80;
    if (encode_free_format(ff, &len, r, local, locallen))
        marslog(LOG_EXIT, "Encode error");

    newlocal[n++] = (len >> 8) & 0xff; /* len */
    newlocal[n++] = (len >> 0) & 0xff; /* len */

    marslog(LOG_DBUG, "copy original local def encoded, len = %d, pos = %d", len, out - bout + 40 + n);

    i = 0;
    while (len-- > 0)
        newlocal[n++] = ff[i++];

    /* add padding */

    /* The section is padded with zeroes to make the overall */
    /* length of the section = 60 + 80*M for some M > 0      */
    while (((40 + n) < 60 + 80) || (((n + 40) - 60) % 80))
        newlocal[n++] = 0;
    marslog(LOG_DBUG, "pad newlocal extension with %d bytes", n - 40);

    if (n > sizeof(newlocal))
        marslog(LOG_EXIT, "Local extension buffer too small");

    newlocallen = n;
    marslog(LOG_DBUG, "newlocal extension length %d bytes", newlocallen);

    memcpy(out + 40, newlocal, newlocallen);
    marslog(LOG_DBUG, "copy newlocal extension into 'out+40', %d bytes", newlocallen);

    /* set length */
    len    = 40 + newlocallen;
    out[0] = (len >> 16) & 0xff;
    out[1] = (len >> 8) & 0xff;
    out[2] = (len)&0xff;
    marslog(LOG_DBUG, "set grib section 1 length to %d bytes", len);

    out += 40 + newlocallen;
    marslog(LOG_DBUG, "advance 'out' of %d bytes", 40 + newlocallen);

    /* copy rest */

    outlen = (*length) - locallen + newlocallen + (40 - fixedlensec1);
    ;
    if (outlen > maxlen)
        marslog(LOG_EXIT, "Output buffer too small %d > %d", outlen, maxlen);
    marslog(LOG_DBUG, "copy rest, outlen %d, input length %d", outlen, *length);

    marslog(LOG_DBUG, "memcpy in -> out, %d bytes", *length - (in - bin));
    memcpy(out, in, *length - (in - bin));
    *length = outlen;

    /* update length */
    bout[4] = ((*length) >> 16) & 0xff;
    bout[5] = ((*length) >> 8) & 0xff;
    bout[6] = ((*length) >> 0) & 0xff;
    marslog(LOG_DBUG, "update full GRIB length to %d bytes", *length);

    return 0;
}

err decode_free_format_grib(unsigned char* bin, unsigned char* bout, fortint* length, fortint maxlen, request* r) {
    unsigned char* in  = bin;
    unsigned char* out = bout;
    long len           = 0;
    fortint outlen;
    unsigned char local[10240];
    int sec1len;

    if (in[7] != 1) {
        marslog(LOG_EROR, "decode_free_format_grib only supported for grib edition 1 (edition is %ld)", (long)in[7]);
        return -2;
    }

    /* copy sec0 */
    memcpy(out, in, 8);
    out += 8;
    in += 8;

    /* sec 1 */
    sec1len = (in[0] << 16) | (in[1] << 8) | (in[2] << 0);

    if (in[25] != 98 && in[40] != 191)
        marslog(LOG_EXIT, "Grib not local");

    memcpy(out, in, 40);

    /* put sub-center back */
    out[25] = in[53];

    len = decode_free_format_blob(in + 60, sec1len - 60, local, sizeof(local));
    if (len < 0)
        marslog(LOG_EXIT, "Cannot get local extension");

    memcpy(out + 40, local, len);

    /* set length */
    len    = 40 + len;
    out[0] = (len >> 16) & 0xff;
    out[1] = (len >> 8) & 0xff;
    out[2] = (len)&0xff;

    out += len;
    in += sec1len;

    /* copy rest */

    outlen = (*length) - sec1len + len;
    if (outlen > maxlen)
        marslog(LOG_EXIT, "Output buffer too small %d > %d", outlen, maxlen);

    memcpy(out, in, *length - (in - bin));
    *length = outlen;

    /* update length */
    bout[4] = ((*length) >> 16) & 0xff;
    bout[5] = ((*length) >> 8) & 0xff;
    bout[6] = ((*length) >> 0) & 0xff;

    return 0;
}

err original_grib(char* bin, fortint* length) {
    unsigned char* in = (unsigned char*)bin;
    static char* out  = 0;
    static int outlen = 0;
    err e             = 0;

    in += 8;

    if (in[25] != 98 && in[40] != 191)
        return 0;

    if (out == 0 || outlen < *length) {
        if (out)
            release_mem(out);
        out    = reserve_mem(*length);
        outlen = *length;
    }


    e = decode_free_format_grib(
        (unsigned char*)bin,
        (unsigned char*)out,
        length, outlen, 0);

    if (e == 0)
        memcpy(bin, out, *length);

    return e;
}
