/*
 * © 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 "mars.h"


typedef int (*sortproc)(int, int);

static int sort_up(int a, int b) {
    return a - b;
}
static int sort_152(int a, int b) {
    if (a == 152 && b != 127)
        a = -1;
    if (b == 152 && a != 127)
        b = -1;
    return a - b;
}
static int sort_down(int a, int b) {
    return b - a;
}

static void sort_params(request* r, parameter* p, sortproc proc) {
}

static void hidden_sort_request(request* r) {
    sortproc proc;
    const char* s;
    parameter* p = r->params;

    while (p) {
        if (count_values(r, p->name) > 1) {
            proc = sort_up;

            if (EQ(p->name, "PARAM"))
                proc = sort_152;

            if ((EQ(p->name, "LEVELIST")) && (s = get_value(r, "LEVTYPE", 0)) && (EQ(s, "PL")))
                proc = sort_down;

            sort_params(r, p, proc);
        }
        p = p->next;
    }
}

static char* names[] = {
    "DATE",
    "TIME",
    "STEP",
    "STREAM",
};


static void unfg(const request* r, int count, char* names[],
                 char* vals[], void* data) {
    request* z         = (request*)data;
    int d              = mars_date_to_julian(atol(vals[0]));
    int t              = atoi(vals[1]) / 100;
    int s              = atoi(vals[2]);
    const char* stream = vals[3];
    int monthly        = (stream[0] == 'M' && stream[1] == 'O' && stream[2] == 0);

    if (s == 0)
        s = 6;

    if (monthly) {
        d = mars_julian_to_date(d, mars.y2k);
        d += 2;
        d = mars_date_to_julian(d);
    }
    marslog(LOG_DBUG, "Un first-guessing:  time %d date %d", t,
            mars_julian_to_date(d, mars.y2k));

    t -= s;

    while (t < 0) {
        t += 24;
        d -= 1;
    }

    marslog(LOG_DBUG, "Un first-guessing:  time %d date %d", t,
            mars_julian_to_date(d, mars.y2k));

    if (monthly) {
        if (mars.mm_firstofmonth)
            add_unique_value(z, "DATE", "%d", (mars_julian_to_date(d, mars.y2k) / 100 * 100) + 1);
        else
            add_unique_value(z, "DATE", "%d", mars_julian_to_date(d, mars.y2k) / 100 * 100);
    }
    else
        add_unique_value(z, "DATE", "%d", mars_julian_to_date(d, mars.y2k));
    add_unique_value(z, "TIME", "%04d", t * 100);
    add_unique_value(z, "STEP", "%d", s);
}

request* un_first_guess(const request* r) {
    if (r) {
        const char* s;
        request* z = clone_one_request(r);

#ifndef FG_IS_REAL_FG
        int m;

        int n = count_values(z, "DATE") * count_values(z, "TIME") * count_values(z, "STEP");

        if ((s = get_value(r, "TYPE", 0)))
            if (EQ(s, "FG")) {
                if ((s = get_value(r, "STREAM", 0)))
                    if (EQ(s, "WAVE"))
                        return z;

                marslog(LOG_DBUG, "Un first-guessing...");
                if (mars.debug)
                    print_one_request(z);

                set_value(z, "TYPE", "FC");
                unset_value(z, "DATE");
                unset_value(z, "TIME");
                unset_value(z, "STEP");

                values_loop(r, NUMBER(names), names, unfg, z);
                if (mars.debug)
                    print_one_request(z);

                m = count_values(z, "DATE") * count_values(z, "TIME") * count_values(z, "STEP");

                if (m != n) {
                    free_all_requests(z);
                    z = clone_one_request(r);
                    marslog(LOG_WARN,
                            "Multi date/time first-guess requests cannot be cached");
                }
            }
#endif

        return z;
    }
    return NULL;
}

request* un_first_guess_all(const request* r) {
    request* first = NULL;
    request* last  = NULL;

    while (r) {
        request* s = un_first_guess(r);
        s->order   = r->order;
        if (first == NULL)
            first = s;
        else
            last->next = s;
        last = s;
        r    = r->next;
    }

    return first;
}

static void check_one(const request* r, const char* p) {
    if (count_values(r, p) != 1) {
        marslog(LOG_WARN, "'VERIFY' needs 1 %s", p);

        if (count_values(r, p) > 1) {
            marslog(LOG_EROR, "Cannot make 1 request with multiple '%s's", p);
            marslog(LOG_EROR, "for the same 'VERIFY'");

            marsexit(1);
        }
    }
}

static void verifydate_to_basedate(request* r) {
    long bjulian      = 0;
    const char* pdate = NULL;
    long second       = 0;
    boolean isjul;
    long bdate = 0;
    long btime = 0;
    int i      = 0;
    int bstep  = 0;

    check_one(r, "DATE");
    check_one(r, "TIME");
    check_one(r, "STEP");

    pdate = get_value(r, "DATE", 0);
    if (is_number(pdate)) {
        bdate   = atoi(pdate);
        bjulian = mars_date_to_julian(bdate);
    }
    else {
        parsedate(pdate, &bjulian, &second, &isjul);
        bdate = mars_julian_to_date(bjulian, mars.y2k);
    }

    btime = atoi(get_value(r, "TIME", 0));
    bstep = atoi(get_value(r, "STEP", 0));
    unset_value(r, "STEP");

    for (i = 0; i < count_values(r, "VERIFY"); i++) {
        const char* p = get_value(r, "VERIFY", i);
        long vjulian  = 0;
        long vdate    = 0;

        if (is_number(p)) {
            vdate   = atoi(p);
            vjulian = mars_date_to_julian(vdate);
        }
        else {
            parsedate(p, &vjulian, &second, &isjul);
            vdate = mars_julian_to_date(vjulian, mars.y2k);
        }

        if (vdate < bdate) {
            marslog(LOG_EROR, "'VERIFY' (%s) cannot preceed 'DATE' (%s)", p, pdate);
            marsexit(1);
        }
        else {
            int n = (vjulian - bjulian) * 24 + bstep; /* - 23; */
            add_value(r, "STEP", "%d", n);

            marslog(LOG_DBUG, "Converting vdate (%ld) to STEP (%d) from DATE (%ld), TIME (%d), STEP (%d)",
                    vdate, n, bdate, btime, bstep);
        }
    }

    unset_value(r, "VERIFY");
}

static void validation_date(request* r) {
    int i;
    int date = 0;
    int time = 0;
    int step = 0;
    int v;

    for (i = 0; i < count_values(r, "STEP"); i++) {
        int n = atoi(get_value(r, "STEP", i));
        if (n > step)
            step = n;
    }

    for (i = 0; i < count_values(r, "DATE"); i++) {
        const char* p = get_value(r, "DATE", i);
        int n;
        if (is_number(p))
            n = atoi(p);
        else {
            long julian = 0, second = 0;
            boolean isjul;
            parsedate(p, &julian, &second, &isjul);
            n = mars_julian_to_date(julian, mars.y2k);
        }
        if (n > date)
            date = n;
    }

    for (i = 0; i < count_values(r, "TIME"); i++) {
        int n = atoi(get_value(r, "TIME", i));
        if (n > time)
            time = n;
    }

    v = mars_date_to_julian(date) + (time / 100 + step + 23) / 24;
    set_value(r, "_VERIF_DATE", "%d", mars_julian_to_date(v, mars.y2k));
    v -= mars_date_to_julian(0);
    set_value(r, "_DELTA_DATE", "%d", v);
    set_value(r, "_CURRENT_DATA", "%s", v >= 1 ? "YES" : "NO");
}

void ensemble_to_number(request* r) {
    static request* done = 0;

    if (done != r) {
        valcpy(r, r, "NUMBER", "ENSEMBLE");
        valcpy(r, r, "NUMBER", "CLUSTER");
        valcpy(r, r, "NUMBER", "PROBABILITY");
        unset_value(r, "ENSEMBLE");
        unset_value(r, "CLUSTER");
        unset_value(r, "PROBABILITY");
        done = r;
    }
}

static err request_to_gridname(request* r) {
    const char* grid = get_value(r, "GRID", 0);

    const char* oldgauss = getenv("MARS_REGULAR_GRID");

    if (grid && strlen(grid)) {
        if (isalpha(grid[0])) {

            set_value(r, "_GRIDNAME", "%s", grid);
            set_value(r, "_GAUSSIAN", "%s", grid + 1);
        }
        else if (isdigit(grid[0])) { /* first is digit so we need to look at GAUSSIAN */

            if (grid[0] == '0') {
                marslog(LOG_EROR, "GRID cannot start with leading digit 0");
                return -2;
            }

            const char* gauss = get_value(r, "GAUSSIAN", 0);

            const char* k = "F"; /* default is regular gg */

            if (gauss && strcmp(gauss, "REDUCED") == 0) {
                k = "N";
            }

            if (oldgauss && atoi(oldgauss)) {
                k = "F";
                marslog(LOG_WARN, "The use of MARS_REGULAR_GRID is discontinued, please contact Service Desk");
            }

            set_value(r, "_GRIDNAME", "%s%s", k, grid);
            set_value(r, "_GAUSSIAN", "%s", grid);
        }
        else {
            return -2;
        }

        set_value(r, "GRID", "%s", get_value(r, "_GRIDNAME", 0));
        unset_value(r, "GAUSSIAN");
    }
    else {
        return -2;
    }

    return 0;
}

err add_hidden_parameters(request* r) {
    const char* s;
    int i, n;
    int j = 0;

    while ((s = no_quotes(get_value(r, "EXPVER", j++)))) {
        if (is_number(s)) {
            n = atoi(s);
            add_value(r, "TMPEXPVER", "%04d", n);
        }
        else if (strlen(s) == 4) {
            add_value(r, "TMPEXPVER", "%s", lowcase(s));
            n = 0;
            while (*s)
                n = (n << 8) + (unsigned char)*s++;
        }
        else
            n = -1;
        add_value(r, "_EXPVER", "%d", n);
    }

    valcpy(r, r, "EXPVER", "TMPEXPVER");
    unset_value(r, "TMPEXPVER");

    if ((s = get_value(r, "RESOL", 0))) {
        if (!EQ(s, "AV") && !EQ(s, "AUTO") && s[0] != 'N' && s[0] != 'O')
            set_value(r, "_TRUNCATION", s);
    }

    /* Changes in accuracy:                   mars.accuracy
AV : output same as input             -1
N  : output same as input             -1
nn : output with nn bits per value    nn
L or R : output 8 bits per value       8
<nothing> :                            0

interpolation    in compute
=============    ==========
mars.accuracy == 0  => same as input    24
mars.accuracy == n  => n                n
mars.accuracy == -1 => same as input    same as input

     */
    if ((s = get_value(r, "ACCURACY", 0))) {
        if (isdigit(*s)) {
            int n = atoi(s);
            if (n)
                mars.accuracy = n;
            marslog(LOG_DBUG, "hidden: Using %d bits for accuracy", mars.accuracy);
        }
        else {
            if ((strcmp(s, "N") == 0) || (strcmp(s, "AV") == 0)) {
                mars.accuracy = -1;
                marslog(LOG_INFO, "Accuracy %s selected. Using input field accuracy", s);
                marslog(LOG_DBUG, "hidden: Same bits as input for accuracy (%d)", mars.accuracy);
            }
            else
                mars.accuracy = 0;
        }
    }
    else
        mars.accuracy = 0;

    if ((i = count_values(r, "AREA"))) {
        int n, s;

        if (i != 4) {
            marslog(LOG_EROR, "AREA must have 4 values");
            return -2;
        }

        n = 0;
        s = 2;

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

            /* Don't check if ocean */
            if (!ocean) {
                n = 2;
                s = 0;
                marslog(LOG_WARN, "SOUTH and NORTH parts of AREA were swapped");
            }
        }

        set_value(r, "_AREA_N", get_value(r, "AREA", n));
        set_value(r, "_AREA_W", get_value(r, "AREA", 1));
        set_value(r, "_AREA_S", get_value(r, "AREA", s));
        set_value(r, "_AREA_E", get_value(r, "AREA", 3));

        if (s == 0) {
            set_value(r, "AREA", "%s", get_value(r, "_AREA_N", 0));
            add_value(r, "AREA", "%s", get_value(r, "_AREA_W", 0));
            add_value(r, "AREA", "%s", get_value(r, "_AREA_S", 0));
            add_value(r, "AREA", "%s", get_value(r, "_AREA_E", 0));
        }
    }

    set_value(r, "_MARS_VERSION", "%ld", marsversion());

    ensemble_to_number(r);

    char* gridname;

    switch (count_values(r, "GRID")) {
        case 0:
            break;

        case 1: /* It's Gaussian !! */

            if (request_to_gridname(r) != NOERR) {
                marslog(LOG_EROR, "Cannot establish a valid gridname");
                return -2;
            }

            break;

        case 2:

            /* It's lat/lon */

            set_value(r, "_GRID_EW", get_value(r, "GRID", 0));
            set_value(r, "_GRID_NS", get_value(r, "GRID", 1));

            break;
    }


    switch (count_values(r, "ROTATION")) {
        case 0:
            break;

        case 1:
            marslog(LOG_EROR, "ROTATION must have 2 values");
            return -2;

        case 2:
            set_value(r, "_ROTATION_LAT", get_value(r, "ROTATION", 0));
            set_value(r, "_ROTATION_LON", get_value(r, "ROTATION", 1));
            break;
    }

#if 0
	if(count_values(r,"VERIFY"))
		verifydate_to_basedate(r);
#endif

    validation_date(r);

    pprotation(r);

    ppstyle(r);

    pparea(r);

    patch_ranges(r);

    patch_steprange(r);

    return NOERR;
}

void patch_ranges(request* r) {
    if (observation(r)) {
        if (count_values(r, "TIME") == 3 && EQ(get_value(r, "TIME", 1), "TO")) {
            long from = atol(get_value(r, "TIME", 0));
            long to   = atol(get_value(r, "TIME", 2));

            long start = from / 100 * 60 + from % 100;
            long end   = to / 100 * 60 + to % 100;
            long range = end - start;
            while (range < 0)
                range += 24 * 60;
            marslog(LOG_WARN, "Changing time %04d/to/%04d to range %d",
                    from, to, range);

            copy_to_ibm_values(r, "TIME");

            set_value(r, "RANGE", "%d", range);
            set_value(r, "TIME", "%04d", from);
        }
        else if (count_values(r, "TIME") == 1 && count_values(r, "RANGE") != 0) {
            request* clone = clone_one_request(r);
            char tmp[32];
            char* p;

            long from  = atol(get_value(r, "TIME", 0));
            long range = atol(get_value(r, "RANGE", 0));
            long to;

            to = from / 100 * 60 + from % 100 + range;
            to = to / 60 * 100 + to % 60;

            /* printf("%4ld %4ld\n",from,to); */

            sprintf(tmp, "%4ld", from);
            p = tmp;
            while (*p) {
                if (*p == ' ')
                    *p = '0';
                p++;
            }
            set_value(clone, "TIME", tmp);
            add_value(clone, "TIME", "TO");
            sprintf(tmp, "%4ld", to);
            p = tmp;
            while (*p) {
                if (*p == ' ')
                    *p = '0';
                p++;
            }
            add_value(clone, "TIME", tmp);

            move_to_ibm_values(clone, r, "TIME");
            free_all_requests(clone);
        }
    }
    else {
        /* Fields */
        const char* levtype = get_value(r, "LEVTYPE", 0);
        boolean ocean       = levtype && EQ(levtype, "DP");

        if (!ocean)
            unset_value(r, "RANGE");
    }
}
