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

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",julian_to_date(date,mars.y2k));
			set_value(statistics,"age","%ld",td - date);
		}
		set_value(statistics,"nbdates","%d",count_values(r,"DATE"));
	}
}

void log_statistics(const char *name, 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, 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));
    }

	/* 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);
}
