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


/* This is in the FDB, but needs to be recompiled on SGI */
#if defined(sgi)
boolean is_host_local(char* host) {
    return false;
}
#endif

extern int unsetvalsfdb(int*);

typedef struct data {
    char* file;
    char* db_name;
    boolean one;
    boolean unique;
    char* classe;
    char* expver;
    char* stream;
    char* type;
} data;

#define ADIM(x) (sizeof(x) / sizeof(x[0]))

struct stream_dbname {
    char stream[10];
    char db_name[10];
};

static struct stream_dbname stream2dbtable[] = {
    /* List only the ones that are not 'fdb' */
    {
        "WASF",
        "seas",
    },
    {
        "SFMM",
        "seas",
    },
    {
        "SWMM",
        "seas",
    },
    {
        "SMMA",
        "seas",
    },
    {
        "MNFC",
        "seas",
    },
    {
        "MNFH",
        "seas",
    },
    {
        "MNFA",
        "seas",
    },
    {
        "MNFW",
        "seas",
    },
    {
        "MFHW",
        "seas",
    },
    {
        "MFAW",
        "seas",
    },
    {
        "MNFM",
        "seas",
    },
    {
        "MFHM",
        "seas",
    },
    {
        "MFAM",
        "seas",
    },
    {
        "MFWM",
        "seas",
    },
    {
        "MHWM",
        "seas",
    },
    {
        "MAWM",
        "seas",
    },
    {
        "MMSF",
        "seas",
    },
    {
        "MSMM",
        "seas",
    },
    {
        "WAMS",
        "seas",
    },
    {
        "MSWM",
        "seas",
    },
    {
        "MMSA",
        "seas",
    },
    {
        "EHMM",
        "seas",
    },
    {
        "ESMM",
        "seas",
    },
    {
        "seas",
        "seas",
    },
    {
        "SEAS",
        "ocean",
    },
    {
        "OCEA",
        "ocean",
    },
};
static data setup;

static option opts[] = {
    /* config, env, opt , default, typ (bool...),offset   */
    {
        NULL,
        NULL,
        "-f",
        NULL,
        t_str,
        sizeof(char*),
        OFFSET(data, file),
    },
    {
        NULL,
        NULL,
        "-d",
        NULL,
        t_str,
        sizeof(char*),
        OFFSET(data, db_name),
    },
    {
        NULL,
        "GRIB2FDB_ONE",
        "-1",
        "1",
        t_boolean,
        sizeof(boolean),
        OFFSET(data, one),
    },
    {
        NULL,
        "GRIB2FDB_EXPVER",
        "-e",
        NULL,
        t_str,
        sizeof(char*),
        OFFSET(data, expver),
    },
    {
        NULL,
        "GRIB2FDB_CLASS",
        "-c",
        NULL,
        t_str,
        sizeof(char*),
        OFFSET(data, classe),
    },
    {
        NULL,
        "GRIB2FDB_STREAM",
        "-s",
        NULL,
        t_str,
        sizeof(char*),
        OFFSET(data, stream),
    },
    {
        NULL,
        "GRIB2FDB_TYPE",
        "-T",
        NULL,
        t_str,
        sizeof(char*),
        OFFSET(data, type),
    },
    {
        NULL,
        "GRIB2FDB_UNIQUE_FDB",
        NULL,
        "1",
        t_boolean,
        sizeof(boolean),
        OFFSET(data, unique),
    },
};

int getDBName(request* r, data setup_options, char* format, char* DBname) {
    int i;
    int found = 0;

    if (setup_options.db_name) {
        strcpy(DBname, setup_options.db_name);
        return 0;
    }

    if (strncmp(format, "BUDG", 4) == 0) {
        strcpy(DBname, "fdb");
        return 0;
    }

    for (i = 0; i < ADIM(stream2dbtable); i++)
        if (strcmp(stream2dbtable[i].stream, get_value(r, "STREAM", 0)) == 0) {
            /* marslog(LOG_INFO,"Using database '%s' for stream %s", stream2dbtable[i].db_name, get_value(r,"STREAM",0)); */
            strcpy(DBname, stream2dbtable[i].db_name);
            return 0;
        }

    /* marslog(LOG_INFO,"Using database 'fdb' for stream %s", get_value(r,"STREAM",0)); */
    strcpy(DBname, "fdb");

    return 0;
}

static int DBid(int* addr, request* r, data setup_options, char* format, char* DBname, char* prevDBname, int makedb_myrank) {
    int err = 0;
    err     = getDBName(r, setup_options, format, DBname);

    if (strcmp(DBname, prevDBname) != 0) {
        if (*addr > -1)
            err = closefdb(addr);

        *addr = -1;

        if ((err = openfdb(DBname, addr, "w")) != 0) {
            marslog(LOG_EROR, "Makedb : DBid : Cannot open database %s", DBname);
            marsexit(1);
        }
        printf("repoened with addr %d,  %s\n", *addr, DBname);
        if (setup_options.unique)
            err = setuniqueidfdb(addr);
        strcpy(prevDBname, DBname);
    }
    return err;
}

static err fdb_check_grib(data setup, const request* g) {
    if (setup.classe) {
        const char* g_val = get_value(g, "CLASS", 0);
        const char* u_val = upcase(setup.classe);
        if (!EQ(g_val, u_val)) {
            marslog(LOG_EROR, "Mismatch between user class '%s' and GRIB class '%s'", u_val, g_val);
            return -1;
        }
    }

    if (setup.expver) {
        const char* g_val = get_value(g, "EXPVER", 0);
        const char* u_val = upcase(setup.expver);
        if (!EQ(g_val, u_val)) {
            marslog(LOG_EROR, "Mismatch between user expver '%s' and GRIB expver '%s'", u_val, g_val);
            return -1;
        }
    }

    if (setup.stream) {
        const char* g_val = get_value(g, "STREAM", 0);
        const char* u_val = upcase(setup.stream);
        if (!EQ(g_val, u_val)) {
            marslog(LOG_EROR, "Mismatch between user stream '%s' and GRIB stream '%s'", u_val, g_val);
            return -1;
        }
    }

    if (setup.type) {
        const char* g_val = get_value(g, "TYPE", 0);
        const char* u_val = upcase(setup.type);
        if (!EQ(g_val, u_val)) {
            marslog(LOG_EROR, "Mismatch between user type '%s' and GRIB type '%s'", u_val, g_val);
            return -1;
        }
    }

    return NOERR;
}

int main(int argc, char** argv) {
    FILE* fd          = 0;
    int e             = 0;
    int cnt           = 0;
    int dberr         = 0;
    long buflen       = 0;
    long length       = 0;
    char* buffer      = NULL;
    int fdb           = -1;
    int makedb_myrank = 0;
    char buf[80];
    char DBname[80];
    char prevDBname[80];
    char* FdbHost;
    char* FdbConfigMode_standalone = "FDB_CONFIG_MODE=standalone";
    char* FdbConfigMode_client     = "FDB_CONFIG_MODE=client";
    char* FdbRoot;

    DBname[0]     = 0;
    prevDBname[0] = 0;
    marsinit(&argc, argv, &setup, NUMBER(opts), opts);

    if (!setup.file) {
        printf("Usage: %s [ -d database ] -f <file>\n", argv[0]);
        printf("          -f : file to process\n");
        printf("          -d : database to use\n");
        marsexit(1);
    }

    buflen = mars.readany_buffer_size;
    length = buflen;
    buffer = reserve_mem(length);

    start_timer();

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

    putenv(FdbConfigMode_standalone);

    FdbRoot = getenv("FDB_ROOT");
    FdbHost = getenv("FDB_SERVER_HOST");

    /**FDB setup configuration */
    if (FdbRoot == NULL && FdbHost == NULL) {
        marslog(LOG_EROR, "FDB_ROOT or FDB_SERVER_HOST not set");
        marsexit(1);
    }

    if (FdbHost != NULL && !is_host_local(FdbHost))
        putenv(FdbConfigMode_client);
    else {
        putenv(FdbConfigMode_standalone);
        if (FdbRoot == NULL) {
            marslog(LOG_EROR, "FDB_ROOT not set");
            marsexit(1);
        }
    }


    initfdb();

    while ((e = _readany(fd, buffer, &length)) == NOERR) {
        int err    = 0;
        int len    = (int)length;
        request* g = empty_request("GRIB");
        err        = grib_to_request(g, buffer, length);
        if (err != NOERR) {
            marslog(LOG_EROR, "Error %d while decoding field number %d. Grib description:", err, cnt);
            print_all_requests(g);
            marsexit(1);
        }

        dberr = DBid(&fdb, g, setup, "GRIB", DBname, prevDBname, makedb_myrank);
        if (dberr != NOERR) {
            marslog(LOG_EROR, "Error %d while calling DBid()", dberr);
            marsexit(1);
        }

        /* Reset FDB keys for each call. Useful for 2dfd */
        if (setup.one) {
            dberr = unsetvalsfdb(&fdb);
            if (dberr != NOERR) {
                marslog(LOG_EROR, "Error %d while calling unsetvalsfdb()", dberr);
                marsexit(1);
            }
        }


        if (fdb_check_grib(setup, g) != NOERR) {
            marslog(LOG_EROR, "Failed to match GRIB. Exit");
            marsexit(1);
        }


        request_to_fdb(fdb, g, 1);
        if (mars.debug)
            print_all_requests(g);
        marslog(LOG_DBUG, "writefdb length: %ld", length);
        /* writefdb returns:
            0 when OK
            <0 on error
        */
        err = writefdb(&fdb, buffer, &len);
        if (err != NOERR) {
            marslog(LOG_EROR, "Error %d while writing to FDB %d bytes", err, len);
            marsexit(1);
        }
        free_all_requests(g);
        length = buflen;
        cnt++;
    }

    if (fdb > -1)
        closefdb(&fdb);

    switch (e) {
        case EOF:
            e = NOERR;

        case NOERR:
            marslog(LOG_INFO, "Decoded %d GRIB messages", cnt);
            break;

        case BUF_TO_SMALL:
            marslog(LOG_WARN, "Buffer is too small (%d bytes, %d needed.)", buflen, length);
            break;
        case NOT_FOUND_7777:
            marslog(LOG_EROR, "Group 7777 not found at the end of GRIB message");
            break;
        default:
            marslog(LOG_EROR, "readany: got unknown error %d", e);
            break;
    }

    if (buffer)
        release_mem(buffer);

    stop_timer(buf);
    timer_cpu();
    if (*buf)
        marslog(LOG_INFO, "Request time: %s", buf);

    print_memory_usage(NULL);
    marsexit(e);
}
