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

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

/*--------------------------------------------------------------------------*/
/* Table to know which obsgroup the file belongs to							*/
/*--------------------------------------------------------------------------*/
static char subtype_list[] = {
    -1, 0, 0, 0, 0, -1, -1, 0, -1, 0, -1, 0, 0, 0, 0, -1,           /*15*/
    -1, -1, -1, 0, -1, 0, 0, 0, -1, -1, 0, -1, 0, -1, -1, 0,        /*31*/
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*47*/
    -1, 1, -1, 1, -1, 1, 1, 1, -1, 1, -1, 1, -1, 1, 1, 1,           /*63*/
    -1, 1, -1, -1, -1, -1, -1, 1, 1, 1, -1, 1, -1, -1, -1, -1,      /*79*/
    -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, -1, -1, 0,            /*95*/
    0, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, -1, -1, -1, 0, -1,         /*111*/
    -1, -1, -1, -1, -1, -1, -1, -1, -1, 3, 3, 3, 3, 3, 1, 1,        /*127*/
    -1, 5, -1, 0, 0, 0, -1, -1, -1, 4, 4, 1, 0, 0, 0, 0,            /*143*/
    0, 0, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, 1, -1, -1, -1,     /*159*/
    -1, 1, -1, -1, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,    /*175*/
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, -1, -1,  /*191*/
    -1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1,    /*207*/
    1, 1, 12, -1, 3, 3, 3, -1, 3, 1, -1, -1, -1, -1, -1, -1,        /*223*/
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1,  /*239*/
    6, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1,    /*255*/
};

#define NBGROUPS 8
static char groups[NBGROUPS][20] = {
    "conventional",
    "satellite",
    "ssmi",
    "ers1",
    "qscat",
    "trmm",
    "iasi",
    "all",
};

static char obs_types[NBGROUPS][20] = {
    "conventional",
    "satellite",
    "ssmi",
    "ers1",
    "qscat",
    "trmm",
    "iasi",
    "all",
};

typedef struct data {
    char* req_file;
    char* file;
    char* out_file;
    char* info_file;
    char* type;
    request* req;
    long ref_date;

    int obsgroup;
    int retrieve;
    int delta_date;
    boolean esuite_expver;

} data;

static option opts[] = {
    {
        NULL,
        NULL,
        "-obsgroup",
        "-1",
        t_int,
        sizeof(int),
        OFFSET(data, obsgroup),
    },
    {
        NULL,
        NULL,
        "-retrieve",
        "0",
        t_int,
        sizeof(int),
        OFFSET(data, retrieve),
    },
    {
        NULL,
        NULL,
        "-delta",
        "1",
        t_int,
        sizeof(int),
        OFFSET(data, delta_date),
    },
    {
        NULL,
        "ALLOW_ESUITE_EXPVER",
        "-esuite_expver",
        "0",
        t_boolean,
        sizeof(boolean),
        OFFSET(data, esuite_expver),
    },
};

static data setup = {
    NULL, NULL, NULL, NULL, NULL, NULL, 0, 0, 0, 0};

static long file_number = 0;
static double min_time  = 500012312359.0;
static double max_time  = 0;
static long msgcount    = 0;

static void usage() {
    fprintf(stderr, "\n usage: prearcbufr [-obsgroup n] [-delta d] [-retrieve] inputfile\n");
    fprintf(stderr, "                  n = 0: conventional\n");
    fprintf(stderr, "                  n = 1: satellite\n");
    fprintf(stderr, "                  n = 2: ssmi\n");
    fprintf(stderr, "                  n = 3: ers1\n");
    fprintf(stderr, "                  n = 4: qscat\n");
    fprintf(stderr, "                  n = 5: trmm\n");
    fprintf(stderr, "                  n = 6: iasi\n");
    fprintf(stderr, "                  n = 7: all\n");
    exit(1);
}

static void fail_param(const char* name) {
    marslog(LOG_EROR, "parameter %s is missing in request from %s",
            name, setup.req_file);
    marsexit(1);
}

static void wrong_param(const char* name, const char* value) {
    marslog(LOG_EROR, "parameter %s is wrong: %s",
            name, value);
    marsexit(1);
}

#define EQCASE(a, b) (strcasecmp(a, b) == 0)

static const char* get_ignore_case_value(const request* r, const char* parname, int nth) {
    parameter* q = 0;
    if (!parname)
        return 0;
    if (r) {
        parameter* p = r->params;
        while (p && !q) {
            if (EQCASE(p->name, parname))
                q = p;
            p = p->next;
        }
    }
    if (!q)
        return 0;
    else
        return get_value(r, q->name, nth);
}

static void copy_keyword(const char* name, const request* from, request* to) {
    const char* p = get_ignore_case_value(from, name, 0);
    if (p) {
        int i = 0;
        while (p) {
            if (i == 0)
                set_value(to, name, p);
            else
                add_value(to, name, p);
            p = get_ignore_case_value(from, name, ++i);
        }
    }
    else
        fail_param(name);
}

static void copy_database(const request* from, request* to) {
    const char* p = get_ignore_case_value(from, "database", 0);
    if (p) {
        int i = 0;
        while (p) {
            if (i == 0)
                set_value(to, "DATABASE", p);
            else
                add_value(to, "DATABASE", p);
            p = get_ignore_case_value(from, "database", ++i);
        }
    }
    else
        fail_param("database");
}

static void init_parameters(int argc, char** argv) {
    const char* p;
    char* q;

    if (argc < 2)
        usage();

    setup.req_file = strcache(argv[argc - 1]);
    setup.req      = read_request_file(setup.req_file);
    if (!setup.req) {
        marslog(LOG_EROR | LOG_PERR, "error reading request in file %s",
                setup.req_file);
        marsexit(1);
    }

    /*--------------------------------------------------------------*/
    /* copy source file												*/
    /*--------------------------------------------------------------*/
    p = get_ignore_case_value(setup.req, "source", 0);
    if (!p)
        fail_param("source");

    setup.file = NEW_STRING(p);
    q          = setup.file;
    while (*p) {
        if (*p != '\"')
            *q++ = *p;
        p++;
    }
    *q = 0;

    /*--------------------------------------------------------------*/
    /* copy target file												*/
    /*--------------------------------------------------------------*/
    p = get_ignore_case_value(setup.req, "target", 0);
    if (!p)
        fail_param("target");

    setup.out_file = NEW_STRING(p);
    q              = setup.out_file;
    while (*p) {
        if (*p != '\"')
            *q++ = *p;
        p++;
    }
    *q = 0;

    /*--------------------------------------------------------------*/
    /* copy info file for retrieve									*/
    /*--------------------------------------------------------------*/
    setup.info_file = NULL;
    p               = get_ignore_case_value(setup.req, "info", 0);
    if (p) {
        setup.info_file = NEW_STRING(p);
        q               = setup.info_file;
        while (*p) {
            if (*p != '\"')
                *q++ = *p;
            p++;
        }
        *q = 0;
    }

    p = get_ignore_case_value(setup.req, "date", 0);
    if (!p)
        fail_param("date");

    setup.ref_date = mars_date_to_julian(atol(p));

    p = get_ignore_case_value(setup.req, "type", 0);
    if (!p)
        fail_param("type");
    setup.type = NEW_STRING(p);


    p = get_ignore_case_value(setup.req, "expver", 0);
    if (!p)
        fail_param("expver");
    else if (atol(p) != 1 && !setup.esuite_expver)
        wrong_param("expver", p);
}

static unsigned long double_to_minutes(double d) {
    unsigned long date = d / 10000;
    unsigned long time = d - (double)date * 10000.0;

    date = mars_date_to_julian(date) * 60 * 24;
    date += (time / 100) * 60;
    date += time % 100;

    return date;
}

static void set_buffer(FILE* f) {
#ifdef fujitsu
#define BSIZE 1024 * 1024 * 50
    char* p = malloc(BSIZE);
    if (p)
        setvbuf(f, p, _IOFBF, BSIZE);
    else
        marslog(LOG_EROR, "Cannot allocate buffer");
#endif
}

static long double_to_time(double d) {
    long date = d / 10000;
    return d - (double)date * 10000.0;
}

static long double_to_date(double d) {
    return d / 10000;
}

static void fill_zero(char* s) {
    while (*s) {
        if (*s == ' ')
            *s = '0';
        s++;
    }
}

long get_name(char* buffer, long length, long* date, double* time) {
    packed_key key;
    packed_key* pkey = &key;
    long this_date;
    long diff;

    if (!get_packed_key(buffer, &key)) {
        marslog(LOG_EROR, "%d: key is missing or invalid, giving up", msgcount);
        marsexit(1);
    }

    if (!verify_bufr_key(buffer, length, &key)) {
        marslog(LOG_EROR, "%d: key is not valid, giving up", msgcount);
        marsexit(1);
    }

    *date = KEY_YEAR(pkey) * 10000 + KEY_MONTH(pkey) * 100 + KEY_DAY(pkey);
    *time = (double)(*date) * 10000.0 + KEY_HOUR(pkey) * 100 + KEY_MINUTE(pkey);

    this_date = mars_date_to_julian(*date);

    diff = this_date - setup.ref_date;
    if (diff < 0)
        diff = -diff;
    if (diff > setup.delta_date) {
        marslog(LOG_EROR, "date %d, found in message %d is more than %d days different", *date, msgcount, setup.delta_date);
        marsexit(1);
    }

    if (setup.obsgroup != -1) {
        int subtype = KEY_SUBTYPE(pkey);
        if (setup.obsgroup != subtype_list[subtype]) {
            marslog(LOG_EROR, "obsgroup mismatch %d %d\n", setup.obsgroup, subtype_list[subtype]);
            marslog(LOG_EROR, "subtype = %d\n", subtype);
            marsexit(1);
        }
    }

    return ((KEY_YEAR(pkey) * 100 + KEY_MONTH(pkey)) << 8) + subtype_list[KEY_SUBTYPE(pkey)];
}

typedef struct node {
    char* file_name;
    long sort_name;
    request* reqarc;
    struct node* left;
    struct node* right;
    FILE* file;
    long count;
    double min_time;
    double max_time;
} node;

void finish(node* n) {
    char tmp[1024];
    unsigned long range;
    int obsgroup;

    if (!n)
        return;
    finish(n->left);
    finish(n->right);

    obsgroup = n->sort_name & 0xff;

    fprintf(stderr, "min: %f\n", n->min_time);
    fprintf(stderr, "max: %f\n", n->max_time);
    fprintf(stderr, "grp: %d\n", obsgroup);


    if (obsgroup < 0 || obsgroup > NBGROUPS - 1) {
        marslog(LOG_EROR | LOG_PERR, "obsgroup is wrong: %d", obsgroup);
        marsexit(1);
    }


    set_value(n->reqarc, "OBSGROUP", groups[obsgroup]);
    set_value(n->reqarc, "OBSTYPE", obs_types[obsgroup]);

    /* update date, time and range */
    range = double_to_minutes(n->max_time) - double_to_minutes(n->min_time);
    sprintf(tmp, "%ld", double_to_date(n->min_time));
    set_value(n->reqarc, "DATE", tmp);

    sprintf(tmp, "%4ld", double_to_time(n->min_time));
    fill_zero(tmp);
    set_value(n->reqarc, "TIME", tmp);
    sprintf(tmp, "%lu", range);
    set_value(n->reqarc, "RANGE", tmp);


    if (fclose(n->file) != 0) {
        marslog(LOG_EROR | LOG_PERR, "write error on %s", n->file_name);
        marsexit(1);
    }

    save_all_requests(stdout, n->reqarc);
}

int main(int argc, char** argv) {

    node* head = 0;
    long length;
    char buffer[1024 * 1024];
    char* base = getenv("MARS_BASE_FOR_SPLIT");
    err e;
    node *n, *p;
    int left;
    off_t pos   = 0;
    int cnt     = 0;
    int bad     = 0;
    double time = 0;


    FILE* f;

    marslogfile(stderr);
    marsinit(&argc, argv, &setup, NUMBER(opts), opts);

    init_parameters(argc, argv);


    f = fopen(setup.file, "r");
    if (f == NULL) {
        marslog(LOG_EROR | LOG_PERR, "'%s'", setup.file);
        marsexit(1);
    }
    set_buffer(f);

again:
    length = sizeof(buffer);
    pos    = ftell(f);
    while ((e = _readany(f, buffer, &length)) == NOERR || e == -5) {
        long date      = 0;
        long sort_name = get_name(buffer, length, &date, &time);
        pos            = ftell(f);
        cnt++;

        /* Look in tree */

        n = head;
        p = NULL;

        while (n) {
            int cmp = sort_name - n->sort_name;
            if (cmp == 0)
                break;
            p = n;

            if (cmp < 0) {
                left = 1;
                n    = n->left;
            }
            else {
                left = 0;
                n    = n->right;
            }
        }

        if (n == NULL) {
            char tmp[2048];

            n = NEW(node);
            if (p == NULL)
                head = n;
            else if (left)
                p->left = n;
            else
                p->right = n;

            sprintf(tmp, "%s.%2d", setup.out_file, file_number);
            fill_zero(tmp);

            n->left = n->right = NULL;
            n->file_name       = strcache(tmp);
            n->sort_name       = sort_name;
            n->count           = 0;
            n->min_time        = time;
            n->max_time        = time;
            if (min_time > time)
                min_time = time;
            if (max_time < time)
                max_time = time;

            n->file = fopen(tmp, "w");
            if (!n->file) {
                marslog(LOG_EROR | LOG_PERR, "'%s'", tmp);
                marsexit(1);
            }
            set_buffer(n->file);
            /* creating archive request */
            n->reqarc = empty_request("ARCHIVE");
            set_value(n->reqarc, "SOURCE", "\"%s\"", tmp);
            set_value(n->reqarc, "TYPE", setup.type);
            /* copy_database(setup.req,n->reqarc); */
            copy_keyword("database", setup.req, n->reqarc);
            /* This one should be activated, but it may cause dcda to be archived as da */
            /* copy_keyword("stream",setup.req,n->reqarc); */
            if (setup.esuite_expver)
                set_value(n->reqarc, "EXPVER", "%s", get_ignore_case_value(setup.req, "expver", 0));

            if (setup.obsgroup != 2)
                set_value(n->reqarc, "DUPLICATES", "REMOVE");
            else
                set_value(n->reqarc, "DUPLICATES", "KEEP");

            file_number++;
        }

        if (n->count) {

            /*-------------------------------------------------*/
            /* Handle Time									   */
            /*-------------------------------------------------*/
            if (n->min_time > time)
                n->min_time = time;
            if (min_time > time)
                min_time = time;
            if (n->max_time < time)
                n->max_time = time;
            if (max_time < time)
                max_time = time;
        }

        n->count++;
        msgcount++;

        if (fwrite(buffer, 1, length, n->file) != length) {
            marslog(LOG_EROR | LOG_PERR, "write error on %s", n->file_name);
            marsexit(1);
        }

        length = sizeof(buffer);
    }

    if (e == -5) {
        /*
        marslog(LOG_EROR,"error on message %d %d %s, skipping",cnt,pos,
            get_name(buffer,length,&date,&time,sort_name));
        */
        fseek(f, pos + 8, SEEK_SET);
        bad++;
        goto again;
    }

    if (e == -1)
        e = 0;
    if (e)
        marslog(LOG_EROR, "readany returns %d", e);

    {
        long date;
        long last;
        request* req = empty_request("STAGE");
        copy_database(setup.req, req);

        set_value(req, "TYPE", setup.type);

        date = double_to_date(min_time);
        date = (date / 100) * 100 + 1;
        last = mars_julian_to_date(mars_date_to_julian(date) + 32, true);
        last = (last / 100) * 100 + 1;
        set_value(req, "DATE", "%d", date);

        set_value(req, "TIME", "0000");
        set_value(req, "RANGE", "%d", (mars_date_to_julian(last) - mars_date_to_julian(date)) * 60 * 24 - 1);


        if (double_to_date(min_time) == date)
            print_all_requests(req);
        free_all_requests(req);
    }

    finish(head);

    /* finish the retrieve request */
    /* creating retrieve request */
    if (setup.info_file) {
        unsigned long range;
        char tmp[1024];
        FILE* f;
        request* req;

        fprintf(stderr, "global min: %f\n", min_time);
        fprintf(stderr, "global max: %f\n", max_time);
        fprintf(stderr, "Messages: %ld\n", msgcount);

        req = empty_request("RETRIEVE");

        set_value(req, "OBSTYPE", obs_types[setup.obsgroup]);

        range = double_to_minutes(max_time) - double_to_minutes(min_time);
        sprintf(tmp, "%ld", double_to_date(min_time));
        set_value(req, "DATE", tmp);

        sprintf(tmp, "%4ld", double_to_time(min_time));
        fill_zero(tmp);
        set_value(req, "TIME", tmp);
        sprintf(tmp, "%lu", range);
        set_value(req, "RANGE", tmp);

        f = fopen(setup.info_file, "w");
        if (f == NULL) {
            marslog(LOG_EROR | LOG_PERR, "'%s'", setup.info_file);
            marsexit(1);
        }
        save_all_requests(f, req);
        fclose(f);
    }

    marsexit(e);
}
