/*
 * © 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 <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 <signal.h>
#include <setjmp.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 = {F_WRLCK, SEEK_SET, 0, 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)
			{
				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);
}
