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

#ifdef FORTRAN_UPPERCASE
#define bufrex_ BUFREX
#endif

#ifdef FORTRAN_NO_UNDERSCORE
#define bufrex_ bufrex
#endif

#define OUT_FILES 7
#define BUFR 0x42554652
#define CODE_7777 0x37373737

/* this table is used in the server as well. It is a test */
static long kTypes[256] = {
    -1, 1, 1, 1, 1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1,           /* 15 */
    -1, -1, -1, 1, -1, 1, 1, 1, -1, -1, 1, -1, 1, -1, -1, 8,        /* 31	 */
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 47 */
    -1, 2, -1, 2, -1, 2, 2, 2, -1, 2, -1, 2, 2, 2, 2, 2,            /* 63 */
    -1, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* 79 */
    -1, -1, 3, 3, 3, 3, 3, 3, 3, 3, -1, 4, 4, -1, -1, 4,            /* 95 */
    4, -1, -1, -1, -1, 5, 5, 5, -1, -1, 5, -1, -1, 5, 1, 5,         /* 111 */
    5, 5, -1, -1, -1, -1, -1, -1, -1, 12, 12, 12, -1, 6, 12, 12,    /* 127 */
    -1, 2, -1, 6, 6, 6, -1, -1, 12, 12, 12, 12, 1, -1, 7, 7,        /* 143 */
    7, 7, 7, 1, 7, 7, 7, 7, -1, 12, 2, 2, 2, -1, -1, -1,            /* 159 */
    -1, -1, -1, -1, 10, 1, -1, -1, -1, -1, 1, -1, 1, -1, -1, -1,    /* 175 */
    1, -1, 1, -1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 3, 3, -1,        /* 191 */
    -1, -1, -1, -1, -1, -1, -1, -1, -1, 12, 12, 30, 30, -1, 2, 2,   /* 207 */
    2, 2, 12, 2, 3, 12, 12, -1, 2, 12, 12, -1, -1, -1, -1, -1,      /* 223 */
    12, -1, -1, -1, -1, -1, 5, 5, -1, -1, -1, -1, -1, 1, -1, -1,    /* 239 */
    2, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 2, -1, -1, -1, 2,     /* 255 */
};

enum
{
    kWrongKeyLength = 1,
    k7777Missing    = 2,
    kKeyMissing     = 3,
    kIdentJustified = 4,
    kDateWeird      = 5,
    kUnknownSubtype = 6
};

typedef struct Options {

    long date;

    boolean fail_if_bad_messages;
    boolean patch_messages;

    boolean has_bad_message_output;

    char* input;
    char* output;
    char* bad;

} Options;

Options options = {
    -1,
    FALSE,
    TRUE,
    TRUE,
    NULL,
    NULL,
    NULL,
};


static char* suffix[OUT_FILES] = {
    "",
    "length",
    "7777",
    "missing",
    "ident",
    "date",
    "subtype",
};

static long written[OUT_FILES] = {
    0, 0, 0, 0, 0};


/* static long era_start; */
/* static long era_end; */
static char types_done[256];


int verify_key(const char* buffer,
               long length,
               const packed_key* key,
               int reportNo) {
    char s[100];
    long date;
    long jd;
    int count          = 0;
    int group          = 0;
    unsigned long klen = key_length(buffer, key);

    /*---------------------------------------------------------------*/
    /* When we get here, we know that 7777 has been found and is not */
    /* missing.                                                      */
    /*---------------------------------------------------------------*/

    if (!types_done[KEY_SUBTYPE(key)])
        if (kTypes[KEY_SUBTYPE(key)] != KEY_TYPE(key)) {
            marslog(LOG_WARN, "type %d and known type %d don't match for subtype %d",
                    KEY_TYPE(key),
                    kTypes[KEY_SUBTYPE(key)],
                    KEY_SUBTYPE(key));
            types_done[KEY_SUBTYPE(key)] = TRUE;
            if (group == 0)
                group = kUnknownSubtype;
        }

    /*---------------------------------------------------------------*/
    /* We check that the length is the key is equal to the length    */
    /* returned by readbufr.                                          */
    /*---------------------------------------------------------------*/
    if (klen != length) {
        marslog(LOG_WARN, "message %d, wrong key length in bufr message %d %d", reportNo,
                klen, length);
        group = kWrongKeyLength;
        count++;
    }

    /*---------------------------------------------------------------*/
    /* Check the validity of date.                                   */
    /*---------------------------------------------------------------*/
    date = KEY_YEAR(key) * 10000 + KEY_MONTH(key) * 100 + KEY_DAY(key);
    jd   = mars_date_to_julian(date);
    if (mars_julian_to_date(jd, true) != date) {
        marslog(LOG_WARN, "message %d, date is weird %d", reportNo, date);
        if (group == 0)
            group = kDateWeird;
        count++;
    }

    if (KEY_HOUR(key) >= 24) {
        print_key_time(key, s);
        marslog(LOG_WARN, "message %d, time is weird %s", reportNo, s);
        if (group == 0)
            group = kDateWeird;
        count++;
    }

    if (KEY_MINUTE(key) >= 60) {
        print_key_time(key, s);
        marslog(LOG_WARN, "message %d, time is weird %s", reportNo, s);
        if (group == 0)
            group = kDateWeird;
        count++;
    }

    if (KEY_SECOND(key) >= 60) {
        print_key_time(key, s);
        marslog(LOG_WARN, "message %d, time is weird %s", reportNo, s);
        if (group == 0)
            group = kDateWeird;
        count++;
    }

#if 0
	/* if not satelite and not ers1 */
	if ((KEY_TYPE(key) != 2) && (KEY_TYPE(key) != 3) && 
	    (KEY_TYPE(key) != 12) &&
	    (KEY_SUBTYPE(key) < 121 || KEY_SUBTYPE(key) > 127))
		if (KEY_IDENT(key)[0] == ' ')
		{
			marslog(LOG_WARN,"message %d, subtype %d, ident problem",
			    reportNo,KEY_SUBTYPE(key));
			return kIdentJustified;
		}
#endif

    if (count > 1)
        group = kIdentJustified;

    return group;
}

static boolean section_1_valid(char* buffer, packed_key* k) {
    packed_section_1 s;
    long date;
    long jd;

    get_packed_section_1(buffer, &s);
    /*---------------------------------------------------------------*/
    /* Check the validity of date.                                   */
    /*---------------------------------------------------------------*/
    date = (SEC1_YEAR(s) + KEY_YEAR(k) / 100 * 100) * 10000 + SEC1_MONTH(s) * 100 + SEC1_DAY(s);
    jd   = mars_date_to_julian(date);
    if (mars_julian_to_date(jd, true) != date) {
        marslog(LOG_WARN, "section 1 date is weird %d", date);
        return false;
    }

    if (SEC1_HOUR(s) >= 24) {
        marslog(LOG_WARN, "section 1 hour is weird %d", SEC1_HOUR(s));
        return false;
    }

    if (SEC1_MINUTE(s) >= 60) {
        marslog(LOG_WARN, "section 1 minute is weird %d", SEC1_MINUTE(s));
        return false;
    }

    if (SEC1_YEAR(s) != (KEY_YEAR(k) % 100) || SEC1_MONTH(s) != KEY_MONTH(k) || SEC1_DAY(s) != KEY_DAY(k) || SEC1_HOUR(s) != KEY_HOUR(k) || SEC1_MINUTE(s) != KEY_MINUTE(k) || SEC1_SUBTYPE(s) != KEY_SUBTYPE(k))
        return false;

    return true;
}

void print_ident(char* buffer) {
    int i;
    packed_key k;
    packed_key* kp = &k;
    char* p        = KEY_IDENT(kp);
    get_packed_key(buffer, &k);
    putchar('-');
    for (i = 0; i < 8; i++)
        putchar(p[i]);
    putchar('-');
    putchar('\n');
}

#define KVALS 80000

int value_of(const char* k, int kl, int kelem,
             fortfloat values[KVALS],
             char* cnames, boolean fail) {
    int res = -1;
    int i;
    for (i = 0; i < kelem; i++) {
        if (strncmp(k, cnames + 64 * i, kl) == 0)
            res = i;
    }
    if (res != -1)
        return res;

    if (fail) {
        char name[65];
        name[64] = 0;
        for (i = 0; i < kelem; i++) {
            memcpy(name, cnames + 64 * i, 64);
            printf("%s\n", name);
            if (name[0] == ' ')
                break;
            if (name[0] == 0)
                break;
        }
        marslog(LOG_EXIT, "Cannot find %s", k);
    }
    else
        return -1;

    return -1;
}

static fortint ksup[9];
static fortint ksec0[3];
static fortint ksec1[100];
static fortint ksec2[64];
static fortint ksec3[4];
static fortint ksec4[2];

static fortfloat values[KVALS];

static char cnames[20000 * 64];
static char cunits[20000 * 24];
static char cvals[KVALS * 80];

typedef struct cache {
    int inited;
    int year;
    int month;
    int day;
    int hour;
    int minute;
    int second;
} cache;

static cache the_cache[256] = {
    0,
};

boolean patch_date(char* buffer, fortint length) {
    int i;
    char name[65];
    fortint kvals = KVALS;
    fortint kelem = KVALS / subset_count(buffer);

    int year;
    int month;
    int day;
    int hour;
    int minute;
    int second;
    cache* c;

    packed_key k;
    packed_key* pkey;

    fortint kerr = 0;

    bufrex_(&length,
            buffer,
            ksup,
            ksec0, ksec1, ksec2, ksec3, ksec4,

            &kelem,

            cnames,
            cunits,

            &kvals,

            values,
            cvals,
            &kerr,

            64,
            24,
            80);

    if (kerr)
        marslog(LOG_EXIT, "bufrex_ return %d\n", kerr);

    c = &the_cache[ksec1[6]];

    if (!c->inited) {
#define STOP 26
        /* 21rst January 1999
           the theory is that if there are more than one instances
           of date and time in the BUFR message, the correct one
           may either be the first one or the second one. To date
           if we stop searching at index STOP, we are sure to find
           the actual date of the observation for all subtypes.
        */
        int stop  = (STOP < kelem) ? STOP : kelem;
        c->year   = value_of("YEAR", 4, stop, values, cnames, true);
        c->month  = value_of("MONTH", 5, stop, values, cnames, true);
        c->day    = value_of("DAY", 3, stop, values, cnames, true);
        c->hour   = value_of("HOUR", 4, stop, values, cnames, true);
        c->minute = value_of("MINUTE", 6, stop, values, cnames, true);
        c->second = value_of("SECOND", 6, stop, values, cnames, false);
        c->inited = 1;
    }

    year   = values[c->year];
    month  = values[c->month];
    day    = values[c->day];
    hour   = values[c->hour];
    minute = values[c->minute];
    second = c->second >= 0 ? values[c->second] : 0;

    if (get_packed_key(buffer, &k)) {
        long date1;
        long date2;

        long time1 = KEY_HOUR(pkey) * 60 + KEY_MINUTE(pkey);
        long time2 = hour * 60 + minute;
        if (abs(time1 - time2) > 5)
            marslog(LOG_EXIT,
                    "patch_date: time in data too different from time in key", 1);

        date1 = KEY_YEAR(pkey) * 10000 + KEY_MONTH(pkey) * 100 + KEY_DAY(pkey);
        date2 = year * 10000 + month * 100 + day;
        if (date1 != date2)
            marslog(LOG_EXIT,
                    "patch_date: date in data too different from date in key", 1);
    }
    else
        marslog(LOG_EXIT, "patch_date cannot read packed_key", 1);


    /* only reached if everything is fine */
    return patch_key_date(buffer, year, month, day, hour, minute, second);
}

boolean patch_key(char* buffer, int length, int group) {
    boolean done = false;

    switch (group) {
        case kWrongKeyLength:
            done = patch_key_length(buffer, length);
            break;

        case kDateWeird:
            done = patch_date(buffer, length);
            break;
        default:
            marslog(LOG_WARN, "Don't know how to patch %s",
                    suffix[group]);
            break;
    }

    return done;
}

static void createOutputs(FILE** p) {
    char str[1024];
    long i;

    strcpy(str, options.output);
    p[0] = fopen(str, "w");
    if (p[0] == NULL) {
        marslog(LOG_EROR | LOG_PERR, "'%s'", str);
        marsexit(1);
    }

    if (options.has_bad_message_output) {
        for (i = 1; i < OUT_FILES; i++) {
            char s[500];
            sprintf(str, "%s.%s", options.bad, suffix[i]);
            p[i] = fopen(str, "w");
            if (p[i] == NULL) {
                marslog(LOG_EROR | LOG_PERR, "'%s'", str);
                marsexit(1);
            }
        }
    }
    else {
        for (i = 1; i < OUT_FILES; i++) {
            p[i] = fopen("/dev/null", "w");
            if (p[i] == NULL) {
                marslog(LOG_EROR | LOG_PERR, "/dev/null", str);
                marsexit(1);
            }
        }
    }
}

void usage(char* me) {
    int i;
    marslog(LOG_INFO, "usage: %s [-en] inFile outFile [badFile]", me);
    marslog(LOG_INFO, "           -e fail if bad messages");
    marslog(LOG_INFO, "           -n don't patch bad messages");
    marslog(LOG_INFO, "  if -e is on, badFile can be omitted");
    marslog(LOG_INFO, "  if badFile is given, generates: ");
    marslog(LOG_INFO, "   outFile:      correct bufr messages");
    for (i = 1; i < OUT_FILES; i++)
        marslog(LOG_INFO, "   badFile.%s", suffix[i]);
    marsexit(1);
}

void init_options(int argc, char** argv) {
    extern int optind;
    extern char* optarg;
    int left;
    int c;

    while ((c = getopt(argc, argv, "end:")) != -1) {
        switch (c) {
            case 'e':
                options.fail_if_bad_messages = TRUE;
                break;

            case 'n':
                options.patch_messages = FALSE;
                break;

            case 'd':
                options.date = mars_date_to_julian(atol(optarg));
                break;


            default:
                usage(argv[0]);
                break;
        }
    }
    left = argc - optind;
    if (!((left == 2 && options.fail_if_bad_messages) || left == 3))
        usage(argv[0]);

    if (left == 2 && options.fail_if_bad_messages)
        options.has_bad_message_output = FALSE;

    options.input  = NEW_STRING(argv[optind++]);
    options.output = NEW_STRING(argv[optind++]);
    if (options.has_bad_message_output)
        options.bad = NEW_STRING(argv[optind++]);
}

#if 1
static void print_key_sec1(char* buffer, packed_key* k) {
    packed_section_1 s;

    get_packed_section_1(buffer, &s);
    printf("year %d %d\n", KEY_YEAR(k) % 100, SEC1_YEAR(s));
    printf("month %d %d\n", KEY_MONTH(k), SEC1_MONTH(s));
    printf("day %d %d\n", KEY_DAY(k), SEC1_DAY(s));
    printf("hour %d %d\n", KEY_HOUR(k), SEC1_HOUR(s));
    printf("minute %d %d\n", KEY_MINUTE(k), SEC1_MINUTE(s));
    printf("type %d %d\n", KEY_TYPE(k), SEC1_TYPE(s));
    printf("subtype %d %d\n", KEY_SUBTYPE(k), SEC1_SUBTYPE(s));
}
#endif

boolean is_key_reversed(char* buffer, packed_key* k) {
    packed_section_1 s;

    get_packed_section_1(buffer, &s);

    /* print_key_sec1(buffer,k); */

    if (SEC1_SUBTYPE(s) != KEY_SUBTYPE(k)) {

        if (options.date != -1) {
            long date = mars_date_to_julian(KEY_YEAR(k) * 10000 + KEY_MONTH(k) * 100 + KEY_DAY(k));
            long diff = options.date - date;
            if (diff < 0)
                diff = -diff;
            if (diff <= 1) {
                /* marslog(LOG_INFO,"is_key_reversed: bad subtype but date OK"); */
                return false;
            }
        }

        return true;
    }

    return false;
}

boolean reverse_key(char* buffer, packed_key* k) {
    unsigned char* p = (unsigned char*)k;
    unsigned char x;
    unsigned int i;
    packed_section_1 s;

    for (i = 0; i < 48; i += 4) {
        x    = p[0];
        p[0] = p[3];
        p[3] = x;
        x    = p[2];
        p[2] = p[1];
        p[1] = x;

        p += 4;
    }

    /*------------------------------------------------*/
    /* sort of check that the key reversed makes      */
    /* sense                                          */
    /*------------------------------------------------*/
    if (is_key_reversed(buffer, k))
        return false;

    return replace_key(buffer, k);
}

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

    long length;
    char buffer[1024 * 1024];
    err e;
    int i;
    int cnt = 0;
    int bad = 0;
    FILE* f;
    FILE* out[OUT_FILES];
    long pos;
    long reportNo        = 0;
    boolean miss7777     = false;
    boolean after7777    = false;
    long patched         = 0;
    long reversed        = 0;
    boolean patch_failed = false;

    marslogfile(stderr);
    marsinit(&argc, argv, 0, 0, 0);

    /*--------------------------------------------------------*/
    /* initialise date values for verify_key.                 */
    /*--------------------------------------------------------*/
    /* era_start=date_to_julian(19790701); */
    /* era_end=date_to_julian(19981231); */

    memset(types_done, 0, 256);

    init_options(argc, argv);


    f = fopen(options.input, "r");
    if (f == NULL) {
        marslog(LOG_EROR | LOG_PERR, "'%s'", argv[1]);
        marsexit(1);
    }

    createOutputs(out);

again:
    length = sizeof(buffer);
    pos    = ftell(f);
    while ((e = _readbufr(f, buffer, &length)) == NOERR || e == -5) {
        int werr;
        long group = 0;
        packed_key k;
        cnt++;

        /*------------------------------------------------------------*/
        /* Try to get the key and check its validity.                 */
        /*------------------------------------------------------------*/
        if (e == -5) {
            group = k7777Missing;
            marslog(LOG_WARN, "message %d, offset %d, length %ld, readbufr returns -5", reportNo, pos, length);
        }
        else if (!get_packed_key(buffer, &k)) {
            group = kKeyMissing;
            marslog(LOG_WARN, "message %d, key missing", reportNo);
        }
        else {
            /*--------------------------------------------------------*/
            /* if the key is reversed, we reverse it                  */
            /*--------------------------------------------------------*/
            if (is_key_reversed(buffer, &k)) {
                marslog(LOG_INFO, "reversing key of report %d", reportNo);
                if (!reverse_key(buffer, &k)) {
                    marslog(LOG_EROR, "failed to reverse the key");
                    group = kIdentJustified;
                }
                reversed++;
            }
            /*--------------------------------------------------------*/
            /* now check the validity of the key                      */
            /*--------------------------------------------------------*/
            group = verify_key(buffer, length, &k, reportNo);
        }

        /*------------------------------------------------------------*/
        /* if the key is not missing but is wrong, we try to patch it */
        /* if 7777 is missing we can't do much about it since we      */
        /* can't really trust the key length.                         */
        /*------------------------------------------------------------*/
        if (group > 0 && group != kIdentJustified && group != kKeyMissing && group != k7777Missing) {
            boolean run  = options.patch_messages;
            patch_failed = true;
            while (run) {
                patch_failed = true;
                if (patch_key(buffer, length, group)) {
                    packed_key other;
                    int old_group = group;

                    if (!get_packed_key(buffer, &other)) {
                        marslog(LOG_EROR, "Impossible to re-read a patched key");
                        exit(1);
                    }
                    group = verify_key(buffer, length, &other, reportNo);
                    if (group != 0) {
                        if (group == old_group) {
                            marslog(LOG_WARN, "Key patching failed ");
                            run = FALSE;
                        }
                        run = FALSE;
                    }
                    else {
                        patched++;
                        run          = FALSE;
                        patch_failed = false;
                    }
                }
                else {
                    marslog(LOG_WARN, "Could not find key addr in patch n.%d",
                            reportNo);
                    run = FALSE;
                }
            }
        }
#if 0
		if (!section_1_valid(buffer,&k))
			group = kIdentJustified;
#endif

        werr = fwrite(buffer, 1, length, out[group]);
        if (werr != length) {
            marslog(LOG_EROR | LOG_PERR, "error writing");
            marsexit(1);
        }
        written[group]++;
        length = sizeof(buffer);
        reportNo++;

        if (group == k7777Missing)
            miss7777 = TRUE;
        else if (miss7777)
            after7777 = TRUE;

        if (group == k7777Missing) {
            fseek(f, pos + 4, SEEK_SET);
            goto again;
        }
        pos = ftell(f);
    }

    fclose(f);
    for (i = 0; i < OUT_FILES; i++)
        fclose(out[i]);

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

    if (miss7777 && !after7777) {
        marslog(LOG_INFO, "All %d missing 7777 at end of file", written[k7777Missing]);
        written[k7777Missing] = 0;
    }

    printf("\n");
    marslog(LOG_INFO, "%6d messages found", reportNo);
    marslog(LOG_INFO, "%6d correct messages", written[0]);
    marslog(LOG_INFO, "%6d messages patched\n", patched);
    marslog(LOG_INFO, "%6d messages reversed\n", reversed);
    for (i = 1; i < OUT_FILES; i++) {
        marslog(LOG_INFO, "%6d %s error(s)", written[i], suffix[i]);
        if (written[i] == 0 && options.has_bad_message_output) {
            sprintf(buffer, "%s.%s", options.bad, suffix[i]);
            unlink(buffer);
        }
        else
            bad += written[i];
    }

    if (bad > 0 && options.fail_if_bad_messages) {
        marslog(LOG_EROR, "Bad messages found and -e given, failing");
        marsexit(1);
    }

    marsexit(e);
}
