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

#include <errno.h>
#include <fcntl.h>

/********************************************************/
/* General Purpose locking funcions on File Descriptors */
/********************************************************/
/* Do not use standard I/O library because of internal buffering performed */
/*                                              */
/* DO NOT USE IT ON FILES IN AN MVFS FILESYSTEM */
/*                                              */
#include <setjmp.h>
#include <signal.h>

static jmp_buf env;

/* Lock errors (must be negative) */
#define FCNTL_ERROR -1
#define LOCK_TIMEOUT -2

static void catch_alarm(int sig) {
    longjmp(env, 1);
}


static int lock_fd(int fd) {
    int ret = 0;
    struct flock lock;

    lock.l_type   = F_WRLCK;
    lock.l_whence = SEEK_SET;
    lock.l_start  = 0;
    lock.l_len    = 0; /* write lock entire file */

    if (setjmp(env) != 0) {
        marslog(LOG_WARN, "Timeout while waiting for lock");
        return LOCK_TIMEOUT;
    }
    signal(SIGALRM, catch_alarm);
    alarm(60);

    ret = fcntl(fd, F_SETLK, &lock);
    alarm(0);

    return ret;
}

static int unlock_fd(int fd) {
    struct flock lock;

    lock.l_type   = F_UNLCK;
    lock.l_whence = SEEK_SET;
    lock.l_start  = 0;
    lock.l_len    = 0; /* write lock entire file */

    return fcntl(fd, F_SETLK, &lock);
}

/********************************************************/

#define MAX_LOCK_RETRY 40
err locked_write(const char* fname, char* str, long len) {
    boolean locked    = false;
    int retry         = 0;
    int fd            = 0;
    struct flock lock = {.l_type = F_WRLCK, .l_whence = SEEK_SET, .l_start = 0, .l_len = 0}; /* Write lock entire file */
    err ret           = NOERR;
    long pid          = getpid();

    while (!locked && retry < MAX_LOCK_RETRY) {
        if ((fd = open(fname, O_WRONLY | O_APPEND | O_CREAT, 0777)) >= 0) {
            if ((ret = fcntl(fd, F_SETLK, &lock)) == -1) {
                // printf("ret %d\n", ret);

                int e = errno;
                if (++retry == MAX_LOCK_RETRY) {
                    FILE* f = NULL;

                    marslog(LOG_WARN | LOG_PERR, "Error locking '%s'", fname);

                    f = mail_open(mars.dhsmail, "Error while locking statistics");
                    mail_msg(f, "Statistics file: %s", fname);
                    if (e > 0)
                        mail_msg(f, "(%s)", strerror(e));
                    mail_close(f);
                }
                close(fd);
                /* If you reduce from 100 msec., the sleep() is less effective == useless */
                usleep(pid * retry % (100000 * retry));
            }
            else {
                locked = true;
            }
        }
        else {
            int e   = errno;
            FILE* f = NULL;

            marslog(LOG_WARN | LOG_PERR, "Error opening '%s'", fname);

            f = mail_open(mars.dhsmail, "Error while opening statistics");
            mail_msg(f, "Statistics file: %s", fname);
            if (e > 0)
                mail_msg(f, "(%s)", strerror(e));
            mail_close(f);
            retry = MAX_LOCK_RETRY;
        }
    }

    if (locked) {
        if (len > 0) {
            fchmod(fd, S_IRWXU | S_IRWXG | S_IRWXO);
            write(fd, str, len);
        }

        lock.l_type = F_UNLCK;
        if ((ret = fcntl(fd, F_SETLK, &lock)) == -1) {
            marslog(LOG_WARN | LOG_PERR, "Error unlocking '%s'", fname);
        }
        close(fd);
    }

    return ret;
}

/********************************************************/

static request* statistics = 0;

/********************************************************/

static void add_env_variable(const char* var, const char* prefix) {
    if (*var) {
        const char* value = getenv(var);
        if (value) {
            char name[10260];
            sprintf(name, "%s%s", prefix ? prefix : "env_", lowcase(var));
            set_value(statistics, name, "%s", value);
        }
    }
}

static void statistics_extra() {
    const char* mars_statistics_extra_variables = getenv("MARS_STATISTICS_EXTRA_VARIABLES");
    const char* mars_statistics_extra_prefix    = getenv("MARS_STATISTICS_EXTRA_PREFIX");

    if (mars_statistics_extra_variables) {

        char buffer[10240];
        int i = 0;

        const char* p = mars_statistics_extra_variables;

        while (*p) {

            if (*p == ',') {
                buffer[i] = 0;
                add_env_variable(buffer, mars_statistics_extra_prefix);
                i = 0;
            }
            else {
                if (i < sizeof(buffer) - 1) {
                    buffer[i++] = *p;
                }
            }

            p++;
        }

        buffer[i] = 0;
        add_env_variable(buffer, mars_statistics_extra_prefix);
    }
}

static void statistics_for_bond() {
    int bonddataset                   = 0;
    const char* mars_for_bond_dataset = getenv("MARS_FOR_BOND_DATASET");
    if (mars_for_bond_dataset) {
        bonddataset = atoi(mars_for_bond_dataset);
    }
    set_value(statistics, "bonddataset", "%d", bonddataset);
}

/********************************************************/

void init_statistics(const request* r, const request* e) {
    char sdate[24];
    char stime[24];
    time_t now;
    const char* verb = r && r->name ? r->name : 0;

    if (statistics) {
        /* Free statistics */
        free_all_requests(statistics);
        statistics = 0;
    }

    /* Create a new statistics request */
    statistics = empty_request("STAT");

    /* Get the time stamp */
    time(&now);
    strftime(sdate, sizeof(sdate), "%Y%m%d", gmtime(&now));
    strftime(stime, sizeof(stime), "%H:%M:%S", gmtime(&now));
    set_value(statistics, "startdate", "%s", sdate);
    set_value(statistics, "starttime", "%s", stime);

    if (verb)
        set_value(statistics, "verb", verb);
    set_value(statistics, "version", "%ld", marsversion());
    if (mars.appl)
        set_value(statistics, "application", "%s", mars.appl);

    if (r) {
        long date          = get_julian_from_request(r, 0);
        long td            = today();
        const char* type   = get_value(r, "TYPE", 0);
        const char* class  = get_value(r, "CLASS", 0);
        const char* stream = get_value(r, "STREAM", 0);
        const char* expver = get_value(r, "EXPVER", 0);
        if (class)
            set_value(statistics, "class", "%s", class);
        if (type)
            set_value(statistics, "type", "%s", type);
        if (stream)
            set_value(statistics, "stream", "%s", stream);
        if (expver)
            set_value(statistics, "expver", "%s", expver);
        if (type && !EQ(type, "CL")) {
            set_value(statistics, "retdate", "%ld", mars_julian_to_date(date, mars.y2k));
            set_value(statistics, "age", "%ld", td - date);
        }
        set_value(statistics, "nbdates", "%d", count_values(r, "DATE"));

        statistics_for_bond();
        statistics_extra();
    }
}

void log_statistics(const char* name, const char* fmt, ...) {
    char buf[1024000];
    va_list list;

    if (!mars.statfile)
        return;

    if (!statistics)
        return;

    va_start(list, fmt);
    vsprintf(buf, fmt, list);
    va_end(list);

    add_value(statistics, name, "%s", buf);
}

void log_statistics_unique(const char* name, const char* fmt, ...) {
    char buf[1024000];
    va_list list;

    if (!mars.statfile)
        return;

    if (!statistics)
        return;

    va_start(list, fmt);
    vsprintf(buf, fmt, list);
    va_end(list);

    set_value(statistics, name, "%s", buf);
}

static char* flush_request(char* buf, const request* r, const char* prefix, size_t l) {
    char* p        = buf;
    parameter* par = r->params;

    while (par) {
        int n     = count_values(r, par->name);
        value* v  = par->values;
        int count = 0;

        if (*(par->name) != '_') {
            p += sprintf(p, "$%s%s=", prefix, lowcase(par->name));
            switch (n) {
                case 0:
                    p += sprintf(p, "\"\"");
                    break;

                case 1:
                    if (v)
                        p += sprintf(p, "\"%s\"", lowcase(no_quotes(v->name)));
                    break;

                default:
                    p += sprintf(p, "\"");
                    while (v) {
                        /* Do not print more than 400 values */
                        if (count >= 400) {
                            int remain       = n - count - 1;
                            const char* last = get_value(r, par->name, n - 1);
                            p += sprintf(p, "(%d)|%s\"", remain, lowcase(no_quotes(last)));
                            break;
                        }

                        p += sprintf(p, "%s", lowcase(no_quotes(v->name)));
                        v = v->next;
                        if (v)
                            p += sprintf(p, "|"); /* more values */
                        else
                            p += sprintf(p, "\""); /* last value */
                        count++;
                    }
                    break;
            }

            p += sprintf(p, ";");

            if (p - buf > l / 2) {
                p += sprintf(p, "# incomplete line");
                break;
            }
        }
        par = par->next;
    }

    return p;
}

void flush_statistics(const request* r, const request* env) {
    char sdate[24];
    char stime[24];
    char fname[256];
    char buf[1024000];
    time_t now = 0;
    char* p    = buf;
    size_t l   = sizeof(buf);

    if (!mars.statfile)
        return;

    if (!statistics) {
        FILE* f = mail_open(mars.dhsmail, "MARS internal Error with statistics");
        mail_msg(f, "Trying to flush uninitialesed statistics");
        mail_close(f);
        return;
    }

    /* Get time stamp */
    time(&now);
    strftime(sdate, sizeof(sdate), "%Y%m%d", gmtime(&now));
    strftime(stime, sizeof(stime), "%H:%M:%S", gmtime(&now));
    set_value(statistics, "stopdate", "%s", sdate);
    set_value(statistics, "stoptime", "%s", stime);

    /* Create string for output */
    p = buf;
    l = sizeof(buf);
    p = flush_request(p, statistics, "", l - (p - buf));
    p = flush_request(p, env, "", l - (p - buf));
    p = flush_request(p, r, "r_", l - (p - buf));
    sprintf(p, "\n");

    /* Choose statistics file */
    sprintf(fname, "%s.%s.%ld", mars.statfile, sdate, marsversion());

    /* Perform unbuffered output locking the file */
    locked_write(fname, buf, strlen(buf));

    /* Write statistics to a user provided file */
    if (get_value(r, "LOGSTATS", 0)) {
        const char* extfile = no_quotes(get_value(r, "LOGSTATS", 0));
        locked_write(extfile, buf, strlen(buf));
    }

#if mars_client_HAVE_UDP_STATS
    send_udp_statistics(statistics, env, r);
#endif

    /* Free statistics */
    if (statistics)
        free_all_requests(statistics);
    statistics = 0;
}


void test_statistics() {
    char sdate[24];
    char stime[24];
    char fname[256];
    char buf[1024000];
    time_t now = 0;
    char* p    = buf;
    size_t l   = sizeof(buf);

    request* statistics = empty_request("statistics");
    request* env        = get_environ();


    const char* path = "/home/ma/mar/marslog/statistics/test";

    marslog(LOG_INFO, "test_statistics()");
    /* Get time stamp */
    time(&now);
    strftime(sdate, sizeof(sdate), "%Y%m%d", gmtime(&now));
    strftime(stime, sizeof(stime), "%H:%M:%S", gmtime(&now));

    set_value(statistics, "stopdate", "%s", sdate);
    set_value(statistics, "stoptime", "%s", stime);

    /* Create string for output */
    p = buf;
    l = sizeof(buf);
    p = flush_request(p, statistics, "", l - (p - buf));
    p = flush_request(p, env, "", l - (p - buf));
    sprintf(p, "\n");

    print_all_requests(statistics);

    /* Choose statistics file */
    sprintf(fname, "%s.%s.%ld", path, sdate, marsversion());

    /* Perform unbuffered output locking the file */
    locked_write(fname, buf, strlen(buf));

    free_all_requests(statistics);
}
