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

#include "mars.h"
#include "math.h"

#define N_VO 0
#define N_D 1
#define N_U 2
#define N_V 3

/* TODO: Read from a file * */


typedef struct wind_family {
    long u;
    long v;
    long vo;
    long d;
} wind_family;

static const wind_family WIND_PARAMS[] = {
    {131, 132, 138, 155},
    {129131, 129132, 129138, 129155},
    {200131, 200132, 200138, 200155},
    {171131, 171132, 171138, 171155},
};

struct wind_data {
    struct wind_data* next;
    request* r;
    grib_handle* grib[4];
    boolean given[4];
    int family;
};

struct _wind {
    struct wind_data* data;
    request* r;
    boolean want[4];
    long64* total;
    boolean active;
};


static struct wind_data* find_wind_data(request* r, struct wind_data* data, err* ret) {
    while (data) {

        if (reqcmp(r, data->r, false) == 0) {
            marslog(LOG_DBUG, "MATCH for find_wind_data");
            return data;
        }

        data = data->next;
    }

    marslog(LOG_DBUG, "NO MATCH for find_wind_data");
    return NULL;
}

static struct wind_data* free_wind_data(struct wind_data* data) {
    int i;
    struct wind_data* next = data->next;
    /* printf("free_wind_data: %x\n",data); */
    for (i = 0; i < 4; i++)
        if (data->grib[i]) {
            /* printf("grib_handle_delete %x\n",data->grib[i]); */
            grib_handle_delete(data->grib[i]);
        }

    free_all_requests(data->r);
    FREE(data);
    return next;
}

static boolean checkuv(wind* w, void* buffer, long length, err* ret) {
    long param = 0;
    struct wind_data* data;
    int which = -1;
    int i;
    int family;

    request* r;
    grib_handle* g = grib_handle_new_from_message_copy(0, buffer, length);
    if (!g) {
        marslog(LOG_EROR, "checkuv: grib_handle_new_from_message_copy() failed");
        *ret = -2;
    }

    *ret = grib_get_long(g, "paramId", &param);

    /* printf("grib_handle_new_from_message_copy %x, param %ld\n",g,param); */

    if (*ret) {
        marslog(LOG_EROR, "grib_get_long(paramId) failed: %s", grib_get_error_message(*ret));
        /* printf("grib_handle_delete %x\n",g); */
        grib_handle_delete(g);
        return false;
    }

    marslog(LOG_DBUG, "WIND got param %ld", param);

    for (i = 0; i < NUMBER(WIND_PARAMS); i++) {

        if (param == WIND_PARAMS[i].vo) /* VO */
        {
            which  = N_VO;
            family = i;
        }

        if (param == WIND_PARAMS[i].d) /* D */
        {
            which  = N_D;
            family = i;
        }

        if (param == WIND_PARAMS[i].u) /* U */
        {
            which  = N_U;
            family = i;
        }

        if (param == WIND_PARAMS[i].v) /* V */
        {
            which  = N_V;
            family = i;
        }
    }

    if (which == -1) {
        /* printf("grib_handle_delete %x\n",g); */
        grib_handle_delete(g);
        return false;
    }

    r    = empty_request(0);
    *ret = handle_to_request(r, g, NULL);
    unset_value(r, "PARAM");
    if (mars.debug)
        print_all_requests(r);

    if (*ret) {
        marslog(LOG_EROR, "handle_to_request failed: %s", grib_get_error_message(*ret));
        /* printf("grib_handle_delete %x\n",g); */
        grib_handle_delete(g);
        return false;
    }


    data = find_wind_data(r, w->data, ret);
    if (!data) {
        struct wind_data* prev = w->data;
        struct wind_data* d    = w->data;
        data                   = NEW_CLEAR(struct wind_data);
        data->r                = r;
        data->family           = family;

        while (d) {
            prev = d;
            d    = d->next;
        }
        if (prev) {
            prev->next = data;
        }
        else {
            w->data = data;
        }
    }
    else {
        free_all_requests(r);
    }

    if (data->family != family) {
        marslog(LOG_EXIT, "wind family mimatch: %d %d", family, data->family);
    }

    if (data->grib[which]) {
        marslog(LOG_EROR, "wind failed: duplicate field");
        *ret = -2;
        print_all_requests(data->r);
        /* printf("grib_handle_delete %x\n",g); */
        grib_handle_delete(g);
        return false;
    }

    data->grib[which] = g;

    /* Assumes that VO/D comes after U/V */
    if (data->grib[N_VO] && data->grib[N_D] && !(data->grib[N_U] || data->grib[N_V])) {
        long len;
        size_t vo_len, d_len;
        const void *vo_buffer, *d_buffer;
        char* u_buffer;
        char* v_buffer;
        long param;
        double estimate_factor;

        grib_get_message(data->grib[N_VO], &vo_buffer, &vo_len);
        grib_get_message(data->grib[N_D], &d_buffer, &d_len);
        if (mars.debug) {
            marslog(LOG_EROR, "checkuv: VO D");
            r = empty_request(0);
            grib_to_request(r, (char*)vo_buffer, vo_len);
            print_all_requests(r);
            free_all_requests(r);

            r = empty_request(0);
            grib_to_request(r, (char*)d_buffer, d_len);
            print_all_requests(r);
            free_all_requests(r);
        }

        /* estimate buffer size from VO, factoring in an increase in accuracy */
        len = MAX(vo_len + 4096, ppestimate());
        if (mars.accuracy > 16) {
            estimate_factor = (double)mars.accuracy / 16.;
            len             = (long)ceil((double)len * estimate_factor);
        }

        u_buffer = reserve_mem(len);
        v_buffer = reserve_mem(len);

        if (mars.debug) {
            marslog(LOG_DBUG, "makeuv for:");
            print_all_requests(data->r);
        }

        /* clear flag */
        mars.wind_requested_by_server = false;

        *ret = makeuv(
            (char*)vo_buffer,
            (char*)d_buffer,
            d_len,
            u_buffer,
            v_buffer,
            &len);


        if (*ret == 0) {
            grib_handle* u = grib_handle_new_from_message_copy(0, u_buffer, len);
            grib_handle* v = grib_handle_new_from_message_copy(0, v_buffer, len);

            if (!u) {
                marslog(LOG_EXIT, "checkuv: grib_handle_new_from_message_copy() failed");
                *ret = -2;
            }

            param = 0;
            grib_get_long(u, "paramId", &param);
            if (param != WIND_PARAMS[data->family].u) {
                marslog(LOG_WARN | LOG_ONCE, "makeuv returns invalid paramId for U %ld (should be %ld)", param, WIND_PARAMS[data->family].u);
                *ret = grib_set_long(u, "paramId", WIND_PARAMS[data->family].u);
            }

            if (!v) {
                marslog(LOG_EXIT, "checkuv: grib_handle_new_from_message_copy() failed");
                *ret = -2;
            }

            param = 0;
            grib_get_long(v, "paramId", &param);
            if (param != WIND_PARAMS[data->family].v) {
                marslog(LOG_WARN | LOG_ONCE, "makeuv returns invalid paramId for V %ld (should be %ld)", param, WIND_PARAMS[data->family].v);
                *ret = grib_set_long(v, "paramId", WIND_PARAMS[data->family].v);
            }


            /* printf("grib_handle_new_from_message_copy %x, param %ld\n",g,param); */


            if (mars.debug) {
                marslog(LOG_EROR, "checkuv: U V");
                r = empty_request(0);
                grib_to_request(r, u_buffer, len);
                print_all_requests(r);
                free_all_requests(r);
                r = empty_request(0);
                grib_to_request(r, v_buffer, len);
                print_all_requests(r);
                free_all_requests(r);
            }

            if (data->grib[N_U]) {
                marslog(LOG_EROR, "wind failed: duplicate U field");
                *ret = -2;
                print_all_requests(data->r);
                /* printf("grib_handle_delete %x\n",u); */
                grib_handle_delete(u);
            }
            else {
                data->grib[N_U] = u;
                marslog(LOG_DBUG, "U ready");
            }

            if (data->grib[N_V]) {
                marslog(LOG_EROR, "wind failed: duplicate V field");
                *ret = -2;
                print_all_requests(data->r);
                /* printf("grib_handle_delete %x\n",v); */
                grib_handle_delete(v);
            }
            else {
                data->grib[N_V] = v;
                marslog(LOG_DBUG, "V ready");
            }

            release_mem(u_buffer);
            release_mem(v_buffer);


            /* Try to recover some memory */
            for (i = 0; i < 4; i++) {
                if (!w->want[i] && data->grib[i]) {
                    /* printf("grib_handle_delete %x\n",data->grib[i]); */
                    grib_handle_delete(data->grib[i]);
                    data->grib[i] = NULL;
                }
            }
        }
    }

    return *ret ? false : true;
}

wind* wind_new(request* r, long64* total, boolean active) {
    wind* w = NEW_CLEAR(wind);

    int i, n = count_values(r, "PARAM"), j;

    w->total  = total;
    w->active = active;

    if (active) {

        for (i = 0; i < n; i++) {
            const char* p = get_value(r, "PARAM", i);
            long param = 0, table = 0;
            paramtable(p, &param, &table, false);

            param += (table == 128 ? 0 : table) * 1000;

            marslog(LOG_DBUG, "wind param.... %d", param);

            for (j = 0; j < NUMBER(WIND_PARAMS); j++) {

                if (param == WIND_PARAMS[j].u) {
                    if (w->want[N_U])
                        marslog(LOG_WARN | LOG_ONCE, "Wind convertion: more than one U-like parameter requested.");
                    w->want[N_U] = true;
                }
                if (param == WIND_PARAMS[j].v) {
                    if (w->want[N_V])
                        marslog(LOG_WARN | LOG_ONCE, "Wind convertion: more than one V-like parameter requested.");
                    w->want[N_V] = true;
                }
                if (param == WIND_PARAMS[j].vo) {
                    if (w->want[N_VO])
                        marslog(LOG_WARN | LOG_ONCE, "Wind convertion: more than one VO-like parameter requested.");
                    w->want[N_VO] = true;
                }
                if (param == WIND_PARAMS[j].d) {
                    if (w->want[N_D])
                        marslog(LOG_WARN | LOG_ONCE, "Wind convertion: more than one D-like parameter requested.");
                    w->want[N_D] = true;
                }
            }
        }
    }

    /* printf("wind_new: %x\n",w); */
    return w;
}

void wind_free(wind* w) {
    int i;

    /* printf("wind_free: %x\n",w); */
    if (w) {
        struct wind_data* data = w->data;
        while (data) {
            data = free_wind_data(data);
        }
        FREE(w);
    }
}

static boolean wind_advance(wind* w, char* buffer, long* length, err* ret) {
    struct wind_data* prev = NULL;
    struct wind_data* data = w->data;
    boolean done;
    while (data) {
        int i, j = -1;
        for (i = 0; i < 4; i++)
            if (w->want[i] && data->grib[i] && !data->given[i]) {
                j = i;
                break;
            }

        if (j >= 0) {
            size_t len;
            const void* msg;
            grib_get_message(data->grib[j], &msg, &len);

            if (*length < len) {
                *length = len;
                *ret    = -3;
                return true;
            }

            *length = len;
            memcpy(buffer, msg, len);

            data->given[j] = true;

            done = true;
            for (i = 0; i < 4; i++)
                if (w->want[i] && !data->given[i])
                    done = false;

            if (done) {
                struct wind_data* next;

                if (mars.debug) {
                    marslog(LOG_DBUG, "WIND free");
                    print_all_requests(data->r);
                }

                next = free_wind_data(data);
                if (prev == NULL) {
                    w->data = next;
                }
                else {
                    prev->next = next;
                }
            }

            return true;
        }
        prev = data;
        data = data->next;
    }


    return false;
}

boolean is_wind(long param) {
    boolean found = false;
    int i         = 0;
    for (i = 0; i < NUMBER(WIND_PARAMS); i++) {

        if (param == WIND_PARAMS[i].u) /* U */
        {
            found = true;
        }

        if (param == WIND_PARAMS[i].v) /* V */
        {
            found = true;
        }
        if (found)
            break;
    }
    return found;
}

err wind_next(wind* w, FILE* f, char* buffer, long* length, timer* t) {
    err ret    = 0;
    long inlen = *length;

    if (w->want[N_U] || w->want[N_V]) {
        for (;;) {
            *length = inlen;

            /* Are they ready made fields ? */

            if (wind_advance(w, buffer, length, &ret))
                return ret;

            /* ret = _readany(f,buffer,length); */
            ret = timed_readany(f, buffer, length, t);
            if (ret == 0) {
                if (w->total)
                    (*w->total) += *length;
            }
            else
                return ret;

            if (!checkuv(w, buffer, *length, &ret)) {
                /* other fields */
                return ret;
            }
        }
    }
    else {
        /* ret = _readany(f,buffer,length); */
        ret = timed_readany(f, buffer, length, t);
        if (ret == NOT_FOUND_7777 && mars.ignore_7777) {
            marslog(LOG_WARN, "Group 7777 not found, but message included");
            ret = 0;
        }
        if (ret == 0) {
            if (w->total)
                (*w->total) += *length;
        }
    }

    return ret;
}
