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