/*----------------------------------------------------------------------------*/
/* Xymon monitor library.                                                     */
/*                                                                            */
/* This is a library module, part of libxymon.                                */
/* It contains routines for timehandling.                                     */
/*                                                                            */
/* Copyright (C) 2002-2011 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 8084 2019-08-30 23:01:18Z jccleaver $";

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

#include "libxymon.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);
        strncpy(timestamp, ctime(&now), 30);
        timestamp[strlen(timestamp)-1] = '\0';

}


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

	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;
		}

		SBUF_MALLOC(wkdays, strlen(daynames[1]) + strlen(daynames[5]) + 2);
		snprintf(wkdays, wkdays_buflen, "%s-%s", daynames[1], daynames[5]);
	}


	p = sCopy = strdup(spec);
	do {
		char *s1, *s2, *s3, *s4, *s5;
		char *days = NULL, *starttime = NULL, *endtime = NULL, *columns = NULL;
		unsigned char *cause = NULL;
		char *oneday, *dtext;
		int daysdone = 0, firstday = 1, causelen;

		/* 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++; }
			days = s1; starttime = s2; endtime = s3;
			columns = "*";
			cause = strdup("Planned downtime");
		}
		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++; }
			days = s2; starttime = s3; endtime = s4;
			columns = s1;
			getescapestring(s5, &cause, &causelen);
		}

		if (!days) return "";

		oneday = days;

		if (STRBUFLEN(result) > 0) addtobuffer(result, "<br>");

		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;
		}

		addtobuffer(result, ":"); addtobuffer(result, starttime);

		addtobuffer(result, ":"); addtobuffer(result, endtime);

		addtobuffer(result, " (status:"); 
		if (strcmp(columns, "*") == 0)
			addtobuffer(result, "All");
		else
			addtobuffer(result, columns); 
		addtobuffer(result, ")");

		if (cause) { 
			addtobuffer(result, " (cause:"); 
			addtobuffer(result, cause); 
			addtobuffer(result, ")");
			xfree(cause);
		}
	} while (*p);

	xfree(sCopy);

	return STRBUF(result);
}

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

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

	result->tv_sec = tend->tv_sec;
	result->tv_nsec = tend->tv_nsec;
	if (result->tv_nsec < tstart->tv_nsec) {
		result->tv_sec--;
		result->tv_nsec += 1000000000;
	}
	result->tv_sec  -= tstart->tv_sec;
	result->tv_nsec -= tstart->tv_nsec;

	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 *holidaykey, 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;
	int newwday;
	char *onesla;

	if (!timespec) return defresult;

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

	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 ((newwday >= 1) && (newwday <=5)) wdaymatch = 1;
				break;

			  case '0': case '1': case '2': case '3': case '4': case '5': case '6':
				if (*wday == (newwday+'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;
}

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;
	SBUF_DEFINE(dayspec);
	SBUF_DEFINE(starttime);
	SBUF_DEFINE(endtime);
	unsigned int istart, iend, inow;
	char *p;

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

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

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

	p++;
	strncpy(starttime, p, starttime_buflen); 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;
	strncpy(endtime, p, endtime_buflen); 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')) strncat(dayspec, "12345", (dayspec_buflen - strlen(dayspec)));
	if (strchr(dayspec, '*')) strncat(dayspec, "0123456", (dayspec_buflen - strlen(dayspec)));
	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_SBUF_DEFINE(result);
	char d1[40],d2[3],d3[40];

	if (result == NULL) SBUF_MALLOC(result, 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));

#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
	#pragma GCC diagnostic push
	#pragma GCC diagnostic ignored "-Wformat-truncation"
#endif  // __GNUC__
	snprintf(result, result_buflen, "%s%s%s", d1, d2, d3);
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
	#pragma GCC diagnostic pop
#endif  // __GNUC__

	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 *startofval;
	char *endpos;
	char savedelim;

	/* Make sure we only process the first token, don't go past whitespace or some other delimiter */
	endpos = dur + strspn(dur, "01234567890mhdw");
	savedelim = *endpos;
	*endpos = '\0';

	startofval = dur;

	while (startofval && (isdigit((int)*startofval))) {
		char *p;
		char modifier;
		int oneval = 0;

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

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

		result += oneval;
		startofval = ((*p) ? p+1 : NULL);
	}

	/* Restore the saved delimiter */
	*endpos = savedelim;

	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 += snprintf(p, (sizeof(result) - (p - result)), "%dw ", n);
		v -= (n * ONE_WEEK);
	}

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

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

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

	if (v > 0) {
		p += snprintf(p, (sizeof(result) - (p - result)), "%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 += snprintf(p, (sizeof(result) - (p - result)), "%ldd", (left / 86400));
		left = (left % 86400);
	}
	if ((left > 3600) || *result) {
		p += snprintf(p, (sizeof(result) - (p - result)), (*result ? "%02ldh" : "%ldh"), (left / 3600));
		left = (left % 3600);
	}
	if ((left > 60) || *result) {
		p += snprintf(p, (sizeof(result) - (p - result)), (*result ? "%02ldm" : "%ldm"), (left / 60));
		left = (left % 60);
	}
	/* Only show seconds if no other info */
	if (*result == '\0') {
		p += snprintf(p, (sizeof(result) - (p - result)), "%02lds", left);
	}

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

time_t timestr2timet(char *s)
{
	/* Convert a string "YYYYMMDDHHMM" to time_t value */
	struct tm tm;

	if (strlen(s) != 12) {
		errprintf("Invalid timestring: '%s'\n", s);
		return -1;
	}

	tm.tm_min = atoi(s+10); *(s+10) = '\0';
	tm.tm_hour = atoi(s+8); *(s+8) = '\0';
	tm.tm_mday = atoi(s+6); *(s+6) = '\0';
	tm.tm_mon = atoi(s+4) - 1; *(s+4) = '\0';
	tm.tm_year = atoi(s) - 1900; *(s+4) = '\0';
	tm.tm_isdst = -1;
	return mktime(&tm);
}


time_t eventreport_time(char *timestamp)
{
	time_t event = 0;
	unsigned int year,month,day,hour,min,sec,count;
	struct tm timeinfo;

	if ((*timestamp) && (*(timestamp + strspn(timestamp, "0123456789")) == '\0'))
		return (time_t) atol(timestamp);

	count = sscanf(timestamp, "%u/%u/%u@%u:%u:%u",
		&year, &month, &day, &hour, &min, &sec);
	if(count != 6) {
		return -1;
	}
	if(year < 1970) {
		return 0;
	}
	else {
		memset(&timeinfo, 0, sizeof(timeinfo));
		timeinfo.tm_year  = year - 1900;
		timeinfo.tm_mon   = month - 1;
		timeinfo.tm_mday  = day;
		timeinfo.tm_hour  = hour;
		timeinfo.tm_min   = min;
		timeinfo.tm_sec   = sec;
		timeinfo.tm_isdst = -1;
		event = mktime(&timeinfo);		
	}

	return event;
}


