/*----------------------------------------------------------------------------*/
/* Hobbit monitor library.                                                    */
/*                                                                            */
/* This is a library module, part of libbbgen.                                */
/* It contains routines for timehandling.                                     */
/*                                                                            */
/* Copyright (C) 2002-2006 Henrik Storner <henrik@storner.dk>                 */
/*                                                                            */
/* This program is released under the GNU General Public License (GPL),       */
/* version 2. See the file "COPYING" for details.                             */
/*                                                                            */
/*----------------------------------------------------------------------------*/

static char rcsid[] = "$Id: timefunc.c,v 1.31 2006/07/20 16:06:41 henrik Rel $";

#include <time.h>
#include <sys/time.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>

#include "libbbgen.h"

#ifdef time
#undef time
#endif

time_t fakestarttime = 0;

time_t getcurrenttime(time_t *retparm)
{
	static time_t firsttime = 0;

	if (fakestarttime != 0) {
		time_t result;

		if (firsttime == 0) firsttime = time(NULL);
		result = fakestarttime + (time(NULL) - firsttime);
		if (retparm) *retparm = result;
		return result;
	}
	else
		return time(retparm);
}


char *timestamp = NULL;
void init_timestamp(void)
{
	time_t	now;

	if (timestamp == NULL) timestamp = (char *)malloc(30);

        now = getcurrenttime(NULL);
        strcpy(timestamp, ctime(&now));
        timestamp[strlen(timestamp)-1] = '\0';

}


char *timespec_text(char *spec)
{
	static char *daynames[7] = { NULL, };
	static char *wkdays = NULL;
	static strbuffer_t *result = NULL;
	char *sCopy;
	char *sItem;

	if (result == NULL) result = newstrbuffer(0);
	clearstrbuffer(result);

	if (!daynames[0]) {
		/* Use strftime to get the locale-specific weekday names */
		time_t now;
		int i;

		now = time(NULL);
		for (i=0; (i<7); i++) {
			char dtext[10];
			struct tm *tm = localtime(&now);
			strftime(dtext, sizeof(dtext), "%a", tm);
			daynames[tm->tm_wday] = strdup(dtext);
			now -= 86400;
		}

		wkdays = (char *)malloc(strlen(daynames[1]) + strlen(daynames[5]) + 2);
		sprintf(wkdays, "%s-%s", daynames[1], daynames[5]);
	}

	sCopy = strdup(spec);
	sItem = strtok(sCopy, ",");
	while (sItem) {
		char *oneday, *dtext;
		int daysdone = 0, firstday = 1;
		oneday = sItem;

		while (!daysdone) {
			switch (*oneday) {
			  case '*': dtext = "All days"; break;
			  case 'W': dtext = wkdays; break;
			  case '0': dtext = daynames[0]; break;
			  case '1': dtext = daynames[1]; break;
			  case '2': dtext = daynames[2]; break;
			  case '3': dtext = daynames[3]; break;
			  case '4': dtext = daynames[4]; break;
			  case '5': dtext = daynames[5]; break;
			  case '6': dtext = daynames[6]; break;
			  default : dtext = oneday; daysdone = firstday = 1; break;
			}

			if (!firstday) addtobuffer(result, "/");
			addtobuffer(result, dtext);
			oneday++;
			firstday = 0;
		}

		sItem = strtok(NULL, ",");
		if (sItem) addtobuffer(result, ", ");
	}
	xfree(sCopy);

	return STRBUF(result);
}

struct timeval *tvdiff(struct timeval *tstart, struct timeval *tend, struct timeval *result)
{
	static struct timeval resbuf;

	if (result == NULL) result = &resbuf;

	result->tv_sec = tend->tv_sec;
	result->tv_usec = tend->tv_usec;
	if (result->tv_usec < tstart->tv_usec) {
		result->tv_sec--;
		result->tv_usec += 1000000;
	}
	result->tv_sec  -= tstart->tv_sec;
	result->tv_usec -= tstart->tv_usec;

	return result;
}


static int minutes(char *p)
{
	/* Converts string HHMM to number indicating minutes since midnight (0-1440) */
	if (isdigit((int)*(p+0)) && isdigit((int)*(p+1)) && isdigit((int)*(p+2)) && isdigit((int)*(p+3))) {
		return (10*(*(p+0)-'0')+(*(p+1)-'0'))*60 + (10*(*(p+2)-'0')+(*(p+3)-'0'));
	}
	else {
		errprintf("Invalid timespec - expected 4 digits, got: '%s'\n", p);
		return 0;
	}
}

int within_sla(char *timespec, int defresult)
{
	/*
	 *    timespec is of the form W:HHMM:HHMM[,W:HHMM:HHMM]*
	 *    "W" = weekday : '*' = all, 'W' = Monday-Friday, '0'..'6' = Sunday ..Saturday
	 */

	int found = 0;
	time_t tnow;
	struct tm *now;
	int curtime;
	char *onesla;

	if (!timespec) return defresult;

	tnow = getcurrenttime(NULL);
	now = localtime(&tnow);
	curtime = now->tm_hour*60+now->tm_min;

	onesla = timespec;
	while (!found && onesla) {

		char *wday;
		int validday, wdaymatch = 0;
		char *endsla, *starttimep, *endtimep;
		int starttime, endtime;

		endsla = strchr(onesla, ','); if (endsla) *endsla = '\0';

		for (wday = onesla, validday=1; (validday && !wdaymatch); wday++) {
			switch (*wday) {
			  case '*':
				wdaymatch = 1;
				break;

			  case 'W':
			  case 'w':
				if ((now->tm_wday >= 1) && (now->tm_wday <=5)) wdaymatch = 1;
				break;

			  case '0': case '1': case '2': case '3': case '4': case '5': case '6':
				if (*wday == (now->tm_wday+'0')) wdaymatch = 1;
				break;

			  case ':':
				/* End of weekday spec. is OK */
				validday = 0;
				break;

			  default:
				errprintf("Bad timespec (missing colon or wrong weekdays): %s\n", onesla);
				validday = 0;
				break;
			}
		}

		if (wdaymatch) {
			/* Weekday matches */
			starttimep = strchr(onesla, ':');
			if (starttimep) {
				starttime = minutes(starttimep+1);
				endtimep = strchr(starttimep+1, ':');
				if (endtimep) {
					endtime = minutes(endtimep+1);
					if (endtime > starttime) {
						/* *:0200:0400 */
						found = ((curtime >= starttime) && (curtime <= endtime));
					}
					else {
						/* The period crosses over midnight: *:2330:0400 */
						found = ((curtime >= starttime) || (curtime <= endtime));
					}
					dbgprintf("\tstart,end,current time = %d, %d, %d - found=%d\n", 
						starttime,endtime,curtime,found);
				}
				else errprintf("Bad timespec (missing colon or no endtime): %s\n", onesla);
			}
			else errprintf("Bad timespec (missing colon or no starttime): %s\n", onesla);
		}
		else {
			dbgprintf("\tWeekday does not match\n");
		}

		/* Go to next SLA spec. */
		if (endsla) *endsla = ',';
		onesla = (endsla ? (endsla + 1) : NULL);
	}

	return found;
}

char *check_downtime(char *hostname, char *testname)
{
	namelist_t *hinfo = hostinfo(hostname);
	char *dtag;

	if (hinfo == NULL) return NULL;

	dtag = bbh_item(hinfo, BBH_DOWNTIME);
	if (dtag && *dtag) {
		static char *downtag = NULL;
		static unsigned char *cause = NULL;
		static int causelen = 0;
		char *s1, *s2, *s3, *s4, *s5, *p;
		char timetxt[30];

		if (downtag) xfree(downtag);
		if (cause) xfree(cause);

		p = downtag = strdup(dtag);
		do {
			/* Its either DAYS:START:END or SERVICE:DAYS:START:END:CAUSE */

			s1 = p; p += strcspn(p, ":"); if (*p != '\0') { *p = '\0'; p++; }
			s2 = p; p += strcspn(p, ":"); if (*p != '\0') { *p = '\0'; p++; }
			s3 = p; p += strcspn(p, ":;,"); 
			if ((*p == ',') || (*p == ';') || (*p == '\0')) { 
				if (*p != '\0') { *p = '\0'; p++; }
				snprintf(timetxt, sizeof(timetxt), "%s:%s:%s", s1, s2, s3);
				cause = strdup("Planned downtime");
				s1 = "*";
			}
			else if (*p == ':') {
				*p = '\0'; p++; 
				s4 = p; p += strcspn(p, ":"); if (*p != '\0') { *p = '\0'; p++; }
				s5 = p; p += strcspn(p, ",;"); if (*p != '\0') { *p = '\0'; p++; }
				snprintf(timetxt, sizeof(timetxt), "%s:%s:%s", s2, s3, s4);
				getescapestring(s5, &cause, &causelen);
			}

			if (within_sla(timetxt, 0)) {
				char *onesvc, *buf;

				if (strcmp(s1, "*") == 0) return cause;

				onesvc = strtok_r(s1, ",", &buf);
				while (onesvc) {
					if (strcmp(onesvc, testname) == 0) return cause;
					onesvc = strtok_r(NULL, ",", &buf);
				}
			}
		} while (*p);
	}

	return NULL;
}

int periodcoversnow(char *tag)
{
	/*
	 * Tag format: "-DAY-HHMM-HHMM:"
	 * DAY = 0-6 (Sun .. Mon), or W (1..5)
	 */

	time_t tnow;
	struct tm *now;

        int result = 1;
        char *dayspec, *starttime, *endtime;
	unsigned int istart, iend, inow;
	char *p;

        if ((tag == NULL) || (*tag != '-')) return 1;

	dayspec = (char *) malloc(strlen(tag)+1+12); /* Leave room for expanding 'W' and '*' */
	starttime = (char *) malloc(strlen(tag)+1); 
	endtime = (char *) malloc(strlen(tag)+1); 

	strcpy(dayspec, (tag+1));
	for (p=dayspec; ((*p == 'W') || (*p == '*') || ((*p >= '0') && (*p <= '6'))); p++) ;
	if (*p != '-') {
		xfree(endtime); xfree(starttime); xfree(dayspec); return 1;
	}
	*p = '\0';

	p++;
	strcpy(starttime, p); p = starttime;
	if ( (strlen(starttime) < 4) || 
	     !isdigit((int) *p)            || 
	     !isdigit((int) *(p+1))        ||
	     !isdigit((int) *(p+2))        ||
	     !isdigit((int) *(p+3))        ||
	     !(*(p+4) == '-') )          goto out;
	else *(starttime+4) = '\0';

	p+=5;
	strcpy(endtime, p); p = endtime;
	if ( (strlen(endtime) < 4) || 
	     !isdigit((int) *p)          || 
	     !isdigit((int) *(p+1))      ||
	     !isdigit((int) *(p+2))      ||
	     !isdigit((int) *(p+3))      ||
	     !(*(p+4) == ':') )          goto out;
	else *(endtime+4) = '\0';

	tnow = getcurrenttime(NULL);
	now = localtime(&tnow);


	/* We have a timespec. So default to "not included" */
	result = 0;

	/* Check day-spec */
	if (strchr(dayspec, 'W')) strcat(dayspec, "12345");
	if (strchr(dayspec, '*')) strcat(dayspec, "0123456");
	if (strchr(dayspec, ('0' + now->tm_wday)) == NULL) goto out;

	/* Calculate minutes since midnight for start, end and now */
	istart = (600 * (starttime[0]-'0'))   +
		 (60  * (starttime[1]-'0'))   +
		 (10  * (starttime[2]-'0'))   +
		 (1   * (starttime[3]-'0'));
	iend   = (600 * (endtime[0]-'0'))     +
		 (60  * (endtime[1]-'0'))     +
		 (10  * (endtime[2]-'0'))     +
		 (1   * (endtime[3]-'0'));
	inow   = 60*now->tm_hour + now->tm_min;

	if ((inow < istart) || (inow > iend)) goto out;

	result = 1;
out:
	xfree(endtime); xfree(starttime); xfree(dayspec); 
	return result;
}

char *histlogtime(time_t histtime)
{
	static char *result = NULL;
	char d1[40],d2[3],d3[40];

	if (result == NULL) result = (char *)malloc(30);
	MEMDEFINE(d1); MEMDEFINE(d2); MEMDEFINE(d3);

	/*
	 * Historical logs use a filename like "Fri_Nov_7_16:01:08_2002 
	 * But apparently there is no simple way to generate a day-of-month 
	 * with no leading 0.
	 */

        strftime(d1, sizeof(d1), "%a_%b_", localtime(&histtime));
        strftime(d2, sizeof(d2), "%d", localtime(&histtime));
	if (d2[0] == '0') { d2[0] = d2[1]; d2[1] = '\0'; }
        strftime(d3, sizeof(d3), "_%H:%M:%S_%Y", localtime(&histtime));

	snprintf(result, 29, "%s%s%s", d1, d2, d3);

	MEMUNDEFINE(d1); MEMUNDEFINE(d2); MEMUNDEFINE(d3);

	return result;
}


int durationvalue(char *dur)
{
	/* 
	 * Calculate a duration, taking special modifiers into consideration.
	 * Return the duration as number of minutes.
	 */

	int result = 0;
	char *p;
	char modifier;

	p = dur + strspn(dur, "0123456789");
	modifier = *p;
	*p = '\0';
	result = atoi(dur);
	*p = modifier;

	switch (modifier) {
	  case 'm': break;			/* minutes */
	  case 'h': result *= 60; break;	/* hours */
	  case 'd': result *= 1440; break;	/* days */
	  case 'w': result *= 10080; break;	/* weeks */
	}

	return result;
}

char *durationstring(time_t secs)
{
#define ONE_WEEK   (7*24*60*60)
#define ONE_DAY    (24*60*60)
#define ONE_HOUR   (60*60)
#define ONE_MINUTE (60)

	static char result[50];
	char *p = result;
	time_t v = secs;
	int n;

	if (secs == 0) return "-";

	*result = '\0';

	if (v >= ONE_WEEK) {
		n = (int) (v / ONE_WEEK);
		p += sprintf(p, "%dw ", n);
		v -= (n * ONE_WEEK);
	}

	if (v >= ONE_DAY) {
		n = (int) (v / ONE_DAY);
		p += sprintf(p, "%dd ", n);
		v -= (n * ONE_DAY);
	}

	if (v >= ONE_HOUR) {
		n = (int) (v / ONE_HOUR);
		p += sprintf(p, "%dh ", n);
		v -= (n * ONE_HOUR);
	}

	if (v >= ONE_MINUTE) {
		n = (int) (v / ONE_MINUTE);
		p += sprintf(p, "%dm ", n);
		v -= (n * ONE_MINUTE);
	}

	if (v > 0) {
		p += sprintf(p, "%ds ", (int)v);
	}

	return result;
}

char *agestring(time_t secs)
{
	static char result[128];
	char *p;
	time_t left = secs;

	*result = '\0';
	p = result;

	if (left > 86400) {
		p += sprintf(p, "%ldd", (left / 86400));
		left = (left % 86400);
	}
	if ((left > 3600) || *result) {
		p += sprintf(p, (*result ? "%02ldh" : "%ldh"), (left / 3600));
		left = (left % 3600);
	}
	if ((left > 60) || *result) {
		p += sprintf(p, (*result ? "%02ldm" : "%ldm"), (left / 60));
		left = (left % 60);
	}
	/* Only show seconds if no other info */
	if (*result == '\0') {
		p += sprintf(p, "%02lds", left);
	}

	*p = '\0';
	return result;
}

