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

static FILE* marslogf = NULL;

static char* titles[] = {
    "DEBUG ",
    "INFO  ",
    "WARN  ",
    "ERROR ",
    "FATAL ",
};

typedef struct exitrec {
    struct exitrec* next;
    exitproc proc;
    void* data;
    long pid;
} exitrec;

static exitrec* exits = NULL;

#define MAXKEEP 5

static int keepptr = 0;

static char* keep[MAXKEEP] = {
    0,
};

typedef struct logmessage {
    struct logmessage* next;
    char* msg;
} logmessage;

static boolean already_logged(char* msg) {
    static logmessage* messages = NULL;
    logmessage* l               = messages;
    logmessage* n               = NULL;

    char* m = strcache(msg);

    while (l) {
        if (l->msg == m)
            return true;
        l = l->next;
    }

    n        = NEW_CLEAR(logmessage);
    n->msg   = m;
    n->next  = messages;
    messages = n;

    return false;
}

void marsdebug(boolean on) {
    mars.debug = on;
}

FILE* marslogfile(FILE* f) {
    FILE* old = marslogf;
    marslogf  = f;
    return old;
}

void mars_grib_api_log(const grib_context* c, int level, const char* msg) {
    int lvl           = LOG_EROR;
    int debug_setting = mars.debug;
    switch (level) {
        case GRIB_LOG_INFO:
            lvl = LOG_INFO;
            break;
        case GRIB_LOG_WARNING:
            lvl = LOG_WARN;
            break;
        case GRIB_LOG_ERROR:
            lvl = LOG_EROR;
            break;
        case GRIB_LOG_FATAL:
            lvl = LOG_EXIT;
            break;
        case GRIB_LOG_DEBUG:
            lvl = LOG_DBUG;
            break;
    }

    /* If GRIB_LOG_DEBUG, we need to set mars.debug in order for MARS to print it */
    if (level == GRIB_LOG_DEBUG)
        mars.debug = true;
    marslog(lvl, "%s [%s]", msg, grib_get_package_name());
    if (level == GRIB_LOG_DEBUG)
        mars.debug = debug_setting;
}


void marslog(int level, const char* fmt, ...) {
    char buf[10240];
    va_list list;
    time_t now;
    int lvl      = level & (~(LOG_PERR | LOG_NOCR | LOG_ONCE));
    boolean per  = (boolean)((level & LOG_PERR) == LOG_PERR);
    boolean nocr = (boolean)((level & LOG_NOCR) == LOG_NOCR);
    boolean once = (boolean)((level & LOG_ONCE) == LOG_ONCE);
    boolean out  = (boolean)((mars.outproc == NULL || lvl == LOG_DBUG || lvl == LOG_EXIT));

    char msg[20480];

    if (marslogf == NULL)
        marslogf = stdout;

    if (mars.quiet) {
        if (lvl == LOG_EXIT)
            marsexit(1);
        return;
    }

    if (lvl == LOG_DBUG && !mars.debug)
        return;

    if (lvl == LOG_WARN && !mars.warning)
        return;

    if (lvl == LOG_INFO && !mars.info)
        return;

    /* if(lvl == LOG_SMS) */
    if ((mars.sms_label || mars.ecflow_label) && (lvl != LOG_DBUG)) {
        char buf[10240];
        char cmd[10240];

        if (!mars.sms_label && !mars.ecflow_label)
            return;

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

        if (mars.sms_label && !mars.ecflow_label) {
            sprintf(cmd, "env SMSDENIED=1 smslabel %s '%d out of %d' '%s' 2>&1 >/dev/null &",
                    mars.sms_label,
                    mars.current_request,
                    mars.number_of_requests,
                    buf);
        }

        if (mars.ecflow_label) {
            sprintf(cmd, "env ECF_DENIED=1 ecflow_client --label %s '%d out of %d' '%s' 2>&1 >/dev/null &",
                    mars.ecflow_label,
                    mars.current_request,
                    mars.number_of_requests,
                    buf);
        }
        system(cmd);

        /* return; */
    }

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

    if (once && already_logged(msg) && (lvl != LOG_DBUG))
        return;

    if (lvl != LOG_NONE) {
        time(&now);
        strftime(buf, sizeof(buf), "%Y%m%d.%H%M%S", gmtime(&now));
        if (!mars.show_pid)
            fprintf(marslogf, "%s - %s - %s - ", progname(), titles[lvl], buf);
        else
            fprintf(marslogf, "%s [%6d] - %s - %s - ", progname(), mars.show_pid, titles[lvl], buf);
    }
    else {
        char name[256];
        size_t l = strlen(progname());
        memset(name, ' ', sizeof(name));
        name[l] = 0;

        memset(buf, ' ', strlen(buf));

        if (!mars.show_pid)
            fprintf(marslogf, "%s   %s   %s   ", name, "      ", buf);
        else
            fprintf(marslogf, "%s            %s   %s   ", name, "      ", buf);
    }
    fprintf(marslogf, "%s", msg);

    if (per && errno)
        fprintf(marslogf, " (%s)", strerror(errno));

    if (!nocr) {
        fputc('\n', marslogf);
        fflush(marslogf);
    }

    if (!out) {
        char buf[10240];
        va_start(list, fmt);
        vsprintf(buf, fmt, list);
        va_end(list);


        level &= ~LOG_NOCR;
        level &= ~LOG_PERR;

        if (level == LOG_EXIT)
            level = LOG_EROR;

        if (per && errno) {
            strcat(buf, " (");
            strcat(buf, strerror(errno));
            strcat(buf, " )");
        }

        mars.outproc(level, buf);
    }

    level &= ~LOG_NOCR;
    level &= ~LOG_PERR;
    if (level == LOG_EXIT || level == LOG_EROR) {
        char buf[10240];
        va_start(list, fmt);
        vsprintf(buf, fmt, list);
        va_end(list);

        if (per && errno) {
            strcat(buf, " (");
            strcat(buf, strerror(errno));
            strcat(buf, " )");
        }

        strfree(keep[keepptr]);
        keep[keepptr++] = strcache(buf);
        keepptr %= MAXKEEP;
    }

    if (lvl == LOG_EXIT)
        marsexit(1);
}

void log_errors() {
    int i;
    for (i = 0; i < MAXKEEP; i++) {
        char* p = keep[(keepptr + i) % MAXKEEP];
        if (p)
            log_statistics("reason", "%s", p);
    }
}

void install_exit_proc(exitproc p, void* data) {
    exitrec* e = NEW_CLEAR(exitrec);

    e->pid  = getpid();
    e->proc = p;
    e->data = data;
    e->next = exits;
    exits   = e;
}

void remove_exit_proc(exitproc p, void* data) {
    exitrec* e = exits;
    exitrec* f = NULL;

    while (e) {
        exitrec* g = e->next;
        if (e->proc == p && e->data == data) {
            if (f)
                f->next = e->next;
            else
                exits = e->next;
            FREE(e);
        }
        else
            f = e;
        e = g;
    }
}


static void cleanup(int code) {
    static boolean done = false;
    exitrec* e          = exits;
    long pid            = getpid();

    if (done)
        return;

    done = true;
    while (e) {
        if (e->pid == pid)
            e->proc(code, e->data);
        e = e->next;
    }
}

void _marsexit(void) {
    cleanup(0);
}

void marsexit(int code) {
    code = code ? 1 : 0;
    cleanup(code);
    exit(code);
}

#if 0
void dumpmem(void)
{
#ifndef VMS
	static long last = 0;
	struct mallinfo minfo = mallinfo();
	marslog(LOG_INFO,"memory : %d (%d)",minfo.uordblks,minfo.uordblks - last);
	last = minfo.uordblks;
#endif
}
#endif
