/*
 * Copyright 2000, International Business Machines Corporation and others.
 * All Rights Reserved.
 * 
 * This software has been released under the terms of the IBM Public
 * License.  For details, see the LICENSE file in the top-level source
 * directory or online at http://www.openafs.org/dl/license10.html
 */

#include <afs/param.h>
#include <sys/types.h>
#include <stdio.h>
#include <time.h>
#include <ctype.h>
#ifdef AFS_NT40_ENV
#include <malloc.h>
#endif
#if defined(AFS_SUN5_ENV) || defined(AFS_NT40_ENV)
#include <string.h>
#else
#include <strings.h>
#endif
#include "afsutil.h"

#include "ktime.h"

/* some forward reference dcls */
static afs_int32 ktime_ParseDate();
static ParseTime();

/* some date parsing routines */

struct token {
    struct token *next;
    char *key;
};

static char *day[] = {
    "sun",
    "mon",
    "tue",
    "wed",
    "thu",
    "fri",
    "sat"
};

/* free token list returned by parseLine */
static LocalFreeTokens(alist)
    register struct token *alist; {
    register struct token *nlist;
    for(; alist; alist = nlist) {
	nlist = alist->next;
	free(alist->key);
	free(alist);
    }
    return 0;
}

static space(x)
int x; {
    if (x == 0 || x == ' ' || x == '\t' || x== '\n') return 1;
    else return 0;
}

static LocalParseLine(aline, alist)
    char *aline;
    struct token **alist; {
    char tbuffer[256];
    register char *tptr;
    int inToken;
    struct token *first, *last;
    register struct token *ttok;
    register int tc;
    
    inToken = 0;	/* not copying token chars at start */
    first = (struct token *) 0;
    last = (struct token *) 0;
    while (1) {
	tc = *aline++;
	if (tc == 0 || space(tc)) {    /* terminating null gets us in here, too */
	    if (inToken) {
		inToken	= 0;	/* end of this token */
		*tptr++ = 0;
		ttok = (struct token *) malloc(sizeof(struct token));
		ttok->next = (struct token *) 0;
		ttok->key = (char *) malloc(strlen(tbuffer)+1);
		strcpy(ttok->key, tbuffer);
		if (last) {
		    last->next = ttok;
		    last = ttok;
		}
		else last = ttok;
		if (!first) first = ttok;
	    }
	}
	else {
	    /* an alpha character */
	    if (!inToken) {
		tptr = tbuffer;
		inToken = 1;
	    }
	    if (tptr - tbuffer >= sizeof(tbuffer)) return -1;   /* token too long */
	    *tptr++ = tc;
	}
	if (tc == 0) {
	    /* last token flushed 'cause space(0) --> true */
	    if (last) last->next = (struct token *) 0;
	    *alist = first;
	    return 0;
	}
    }
}

/* keyword database for periodic date parsing */
static struct ptemp {
    char *key;
    afs_int32 value;
} ptkeys [] = {
    "sun", 0x10000,
    "mon", 0x10001,
    "tue", 0x10002,
    "wed", 0x10003,
    "thu", 0x10004,
    "fri", 0x10005,
    "sat", 0x10006,
    "sunday", 0x10000,
    "monday", 0x10001,
    "tuesday", 0x10002,
    "wednesday", 0x10003,
    "thursday", 0x10004,
    "thur", 0x10004,
    "friday", 0x10005,
    "saturday", 0x10006,
    "am", 0x20000,
    "pm", 0x20001,
    "a.m.", 0x20000,
    "p.m.", 0x20001,
    0, 0,
};

/* ktime_DateOf
 * entry:
 *	atime - time in seconds (Unix std)
 * exit:
 *	return value - ptr to time in text form. Ptr is to a static string.
 */

char *ktime_DateOf(atime)
afs_int32 atime; {
    static char tbuffer[30];
    register char *tp;
    tp=ctime((time_t *)&atime);
    if (tp) {
	strcpy(tbuffer, tp);
	tbuffer[24] = 0;    /* get rid of new line */
    }
    else
	strcpy(tbuffer, "BAD TIME");
    return tbuffer;
}

afs_int32 ktime_Str2int32(astr) 
register char *astr;
{
struct ktime tk;

bzero(&tk, sizeof(tk));
if ( ParseTime(&tk, astr) )
  return (-1);    /* syntax error */

return ((tk.hour*60 + tk.min)*60 + tk.sec);
}

/* ParseTime
 *	parse 12:33:12 or 12:33 or 12 into ktime structure
 * entry:
 *	astr - ptr to string to be parsed
 *	ak - ptr to struct for return value.
 * exit:
 *	0 - ak holds parsed time.
 *	-1 - error in format
 */

static ParseTime(ak, astr)
register char *astr;
register struct ktime *ak; {
    int field;
    afs_int32 temp;
    register char *tp;
    register int tc;

    field = 0;	/* 0=hour, 1=min, 2=sec */
    temp = 0;

    ak->mask |= (KTIME_HOUR | KTIME_MIN | KTIME_SEC);
    for(tp=astr;;) {
	tc = *tp++;
	if (tc == 0 || tc == ':') {
	    if (field == 0)
		ak->hour = temp;
	    else if (field == 1)
		ak->min = temp;
	    else if (field == 2)
		ak->sec = temp;
	    temp = 0;
	    field++;
	    if (tc == 0) break;
	    continue;
	}
	else if	(!isdigit(tc)) return -1;   /* syntax error */
	else {
	    /* digit */
	    temp *= 10;
	    temp += tc - '0';
	}
    }
    if (ak->hour >= 24 || ak->min >= 60 || ak->sec >= 60) return -1;
    return 0;
}

/* ktime_ParsePeriodic
 *	Parses periodic date of form
 *		now | never | at [time spec] | every [time spec]
 *	where [time spec] is a ktime string.
 * entry:
 *	adate - string to be parsed
 *	ak - ptr to structure for returned ktime
 * exit:
 *	0 - parsed ktime in ak
 *	-1 - specification error
 */

/* -1 means error, 0 means now, otherwise returns time of next event */
int ktime_ParsePeriodic(adate, ak)
register struct ktime *ak;
char *adate; {
    struct token *tt;
    register afs_int32 code;
    struct ptemp *tp;
    
    bzero(ak, sizeof(*ak));
    code = LocalParseLine(adate, &tt);
    if (code) return -1;
    for(;tt;tt=tt->next) {
	/* look at each token */
	if (strcmp(tt->key, "now") == 0) {
	    ak->mask |= KTIME_NOW;
	    return 0;
	}
	if (strcmp(tt->key, "never") == 0) {
	    ak->mask |= KTIME_NEVER;
	    return 0;
	}
	if (strcmp(tt->key, "at") == 0) continue;
	if (strcmp(tt->key, "every") == 0) continue;
	if (isdigit(tt->key[0])) {
	    /* parse a time */
	    code = ParseTime(ak, tt->key);
	    if (code) return -1;
	    continue;
	}
	/* otherwise use keyword table */
	for(tp = ptkeys;; tp++) {
	    if (tp->key == (char *) 0) {
		return -1;
	    }
	    if (strcmp(tp->key, tt->key) == 0) break;
	}
	/* now look at tp->value to see what we've got */
	if ((tp->value>>16) == 1) {
	    /* a day */
	    ak->mask |= KTIME_DAY;
	    ak->day = tp->value & 0xff;
	}
	if ((tp->value >> 16) == 2) {
	    /* am or pm token */
	    if ((tp->value & 0xff) == 1) {
		/* pm */
		if (!(ak->mask & KTIME_HOUR)) return -1;
		if (ak->hour < 12) ak->hour += 12;
		/* 12 is 12 PM */
		else if (ak->hour != 12) return -1;
	    }
	    else {
		/* am is almost a noop, except that we map 12:01 am to 0:01 */
		if (ak->hour > 12) return -1;
		if (ak->hour == 12) ak->hour = 0;
	    }
	}
    }
    return 0;
}

/* ktime_DisplayString
 *	Display ktime structure as English that could go into the ktime	parser
 * entry:
 *	aparm - ktime to be converted to string
 *	astring - ptr to string, for the result
 * exit:
 *	0 - astring contains ktime string.
 */
ktime_DisplayString(aparm, astring)
register char *astring;
struct ktime *aparm; {
    char tempString[50];

    if (aparm->mask & KTIME_NEVER) {
	strcpy(astring, "never");
	return 0;
    }
    else if (aparm->mask & KTIME_NOW) {
	strcpy(astring, "now");
	return 0;
    }
    else {
	strcpy(astring, "at");
	if (aparm->mask & KTIME_DAY) {
	    strcat(astring, " ");
	    strcat(astring, day[aparm->day]);
	}
	if (aparm->mask & KTIME_HOUR) {
	    if (aparm->hour > 12)
		sprintf(tempString, " %d", aparm->hour-12);
	    else if (aparm->hour == 0)
		strcpy(tempString, " 12");
	    else
		sprintf(tempString, " %d", aparm->hour);
	    strcat(astring, tempString);
	}
	if (aparm->mask & KTIME_MIN) {
	    sprintf(tempString, ":%02d", aparm->min);
	    strcat(astring, tempString);
	}
	if ((aparm->mask & KTIME_SEC) && aparm->sec != 0) {
	    sprintf(tempString, ":%02d", aparm->sec);
	    strcat(astring, tempString);
	}
	if (aparm->mask & KTIME_HOUR) {
	    if (aparm->hour >= 12)
		strcat(astring, " pm");
	    else
		strcat(astring, " am");
	}
    }
    return 0;
}

/* get next time that matches ktime structure after time afrom */
afs_int32 ktime_next(aktime, afrom)
afs_int32 afrom;
struct ktime *aktime; {
    /* try by guessing from now */
    struct tm *tsp;
    time_t start;	/* time to start looking */
    time_t probe;	/* a placeholder to use for advancing day to day */
    time_t time_next;    /* actual UTC time of probe, with time of day set */
    afs_int32 tmask;
    struct ktime_date tdate;

    start = afrom + time(0); /* time to start search */
    tmask = aktime->mask;

    /* handle some special cases */
    if (tmask & KTIME_NEVER) return 0x7fffffff;
    if (tmask & KTIME_NOW) return 0;

    /* Use probe to fill in members of *tsp. Add 23 hours each iteration until 
       time_next is correct. Only add 23 hrs to avoid skipping spring 
       daylight savings time day */
    for (probe=start;;probe += (23*3600)) {
       tsp = localtime(&probe);	/* find out what UTC time "probe" is */

       tdate.year = tsp->tm_year;
       tdate.month = tsp->tm_mon+1;
       tdate.day = tsp->tm_mday;
       tdate.mask = KTIMEDATE_YEAR | KTIMEDATE_MONTH | KTIMEDATE_DAY |
          KTIMEDATE_HOUR | KTIMEDATE_MIN | KTIMEDATE_SEC;
       tdate.hour = aktime->hour;  /* edit in our changes */
       tdate.min = aktime->min;
       tdate.sec = aktime->sec;
       time_next = ktime_InterpretDate(&tdate);  /* Convert back to UTC time */
       if (time_next < start) continue;  /* "probe" time is already past */
       if ((tmask & KTIME_DAY) == 0)   /* don't care about day, we're done */
	  break;
       tsp = localtime(&time_next);
       if (tsp->tm_wday == aktime->day) break;  /* day matches, we're done */
    }
    return time_next;
}


/* compare date in both formats, and return as in strcmp */
static KTimeCmp(aktime, atm)
register struct ktime *aktime;
register struct tm *atm; {
    register afs_int32 tmask;

    /* don't compare day of the week, since we can't tell the
       order in a cyclical set.  Caller must check for equality, if
       she cares */
    tmask = aktime->mask;
    if (tmask & KTIME_HOUR) {
	if (aktime->hour > atm->tm_hour) return 1;
	if (aktime->hour < atm->tm_hour) return -1;
    }
    if (tmask & KTIME_MIN) {
	if (aktime->min > atm->tm_min) return 1;
	if (aktime->min < atm->tm_min) return -1;
    }
    if (tmask & KTIME_SEC) {
	if (aktime->sec > atm->tm_sec) return 1;
	if (aktime->sec < atm->tm_sec) return -1;
    }
    return 0;
}

/* compare date in both formats, and return as in strcmp */
static KDateCmp(akdate, atm)
register struct ktime_date *akdate;
register struct tm *atm; {
    if (akdate->year > atm->tm_year) return 1;
    if (akdate->year < atm->tm_year) return -1;
    if (akdate->month > atm->tm_mon) return 1;
    if (akdate->month < atm->tm_mon) return -1;
    if (akdate->day > atm->tm_mday) return 1;
    if (akdate->day < atm->tm_mday) return -1;
    if (akdate->mask & KTIMEDATE_HOUR) {
	if (akdate->hour > atm->tm_hour) return 1;
	if (akdate->hour < atm->tm_hour) return -1;
    }
    if (akdate->mask & KTIMEDATE_MIN) {
	if (akdate->min > atm->tm_min) return 1;
	if (akdate->min < atm->tm_min) return -1;
    }
    if (akdate->mask & KTIMEDATE_SEC) {
	if (akdate->sec > atm->tm_sec) return 1;
	if (akdate->sec < atm->tm_sec) return -1;
    }
    return 0;
}

/* ktime_DateToInt32
 *	Converts a ktime date string into an afs_int32
 * entry:
 *	adate - ktime date string
 *	aint32 - ptr to afs_int32
 * exit:
 *	0 - aint32 contains converted date.
 */

afs_int32 ktime_DateToInt32(adate, aint32)
afs_int32 *aint32;
char *adate; {
    struct ktime_date tdate;
    register afs_int32 code;

    /* parse the date into a ktime_date structure */
    code = ktime_ParseDate(adate, &tdate);
    if (code) return code;	/* failed to parse */

    code = ktime_InterpretDate(&tdate);	/* interpret date as seconds since 1970 */
    *aint32 = code;	/* return it */
    return 0;		/* and declare no errors */
}

/* ktime_ParseDate
 * 	parse date string into ktime_date structure
 * entry:
 *	adate - string to be parsed
 *	akdate - ptr to ktime_date for result
 * exit:
 *	0 - akdate contains converted date
 *	-1 - parsing failure
 */

static afs_int32 ktime_ParseDate(adate, akdate)
char *adate;
struct ktime_date *akdate; {
    int code;
    afs_int32 month, day, year, hour, min, sec;
    char never[7];
    char c;

    lcstring (never, adate, sizeof(never));
    if (strcmp (never, "never") == 0) akdate->mask = KTIMEDATE_NEVER;
    else if (strcmp (never, "now") == 0) akdate->mask = KTIMEDATE_NOW;
    else akdate->mask = 0;
    if (akdate->mask) return 0;


    code = sscanf(adate, "%d / %d / %d %d : %d : %d%1s",
		  &month, &day, &year, &hour, &min, &sec, &c);
    if (code != 6) {
       sec = 0;
       code = sscanf(adate, "%d / %d / %d %d : %d%1s",
		     &month, &day, &year, &hour, &min, &c);
       if (code != 5) {
	  hour = min = 0;
	  code = sscanf(adate, "%d / %d / %d%1s", &month, &day, &year, &c);
	  if (code != 3) {
	     return -1;
	  }
       }
    }

    if ((year  < 0) || 
	(month < 1) || (month > 12) ||
        (day   < 1) || (day   > 31) ||     /* more or less */
	(hour  < 0) || (hour  > 23) || 
	(min   < 0) || (min   > 59) ||
	(sec   < 0) || (sec   > 59))
      return -2;

    if      (year < 69)    year += 100;	   /* make 1/1/1 => Jan 1, 2001 */
    else if (year >= 1900) year -= 1900;   /* allow 1/1/2001 to work */
    else if (year > 99)    return -2;	   /* only allow 2 or 4 digit years */

    akdate->mask = KTIMEDATE_YEAR | KTIMEDATE_MONTH | KTIMEDATE_DAY |
                   KTIMEDATE_HOUR | KTIMEDATE_MIN   | KTIMEDATE_SEC;

    akdate->year  = year;
    akdate->month = month;
    akdate->day   = day;
    akdate->hour  = hour;
    akdate->min   = min;
    akdate->sec   = sec;

    /* done successfully */
    return 0;
}

/* get useful error message to print about date input format */
char *ktime_GetDateUsage() {
    return "date format is 'mm/dd/yy [hh:mm]', using a 24 hour clock";
}


/* ktime_InterpretDate
 *	Converts ktime_date to an afs_int32
 * entry:
 *	akdate - date to be converted/interpreted
 * exit:
 *	returns KTIMEDATE_NEVERDATE - if never flag was set, or
 *	date converted to afs_int32.
 */

afs_int32 ktime_InterpretDate(struct ktime_date *akdate)
{
    register afs_uint32 tresult;
    register afs_uint32 tbit;
    time_t temp;
    register struct tm *tsp;

    if (akdate->mask & KTIMEDATE_NOW) return time(0);
    if (akdate->mask & KTIMEDATE_NEVER) return KTIMEDATE_NEVERDATE;

    tbit = 1<<30;		/* start off at max signed result */
    tresult = 0;		/* result to return */
    while(tbit > 0) {
	temp = tresult + tbit;	/* see if adding this bit keeps us < akdate */
	tsp = localtime(&temp);
	tsp->tm_mon++;
#ifdef notdef
	if (tsp->tm_mon == 0) {
	    tsp->tm_mon = 12;
	    tsp->tm_year--;
	}
#endif
	if (KDateCmp(akdate, tsp) >= 0) {
	    /* if temp still represents earlier than date than we're searching
             * for, add in bit as increment, otherwise use old value and look
             * for smaller increment */
	    tresult = temp;
	}
	tbit = tbit >> 1;	/* try next bit */
    }

    return tresult;
}
