/* Copyright (C) 1990 Transarc Corporation - All rights reserved */

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


/* maximum values for relative dates */

#define	MAX_YEAR_VALUE	0
#define	MAX_MONTH_VALUE 11
#define	MAX_DAY_VALUE	30

/* for parsing relative expiration dates */
static struct parseseqS
{
    afs_int32 ps_field;
    char ps_keychar;
    afs_int32 ps_maxValue;
} parseseq[] = {
    KTIMEDATE_YEAR, 'y', MAX_YEAR_VALUE,	/* no max. value */
    KTIMEDATE_MONTH, 'm', MAX_MONTH_VALUE,	/* months max. 12 */
    KTIMEDATE_DAY, 'd',	MAX_DAY_VALUE,		/* days max. 31 */
    0, 0, 0,
};

/* Encodings to and from relative dates. The caller is responsible for
 * enforcing appropriate use of these routines
 */


/* ktimeRelDate_ToInt32
 *	converts a relative ktime date into an afs_int32.
 * exit:
 *	afs_int32 value of encoded date.
 */

afs_int32
ktimeRelDate_ToInt32(kdptr)
     struct ktime_date *kdptr;
{
    afs_int32 retval;

    retval = (((kdptr->year*(MAX_MONTH_VALUE+1)) +
	       kdptr->month)*(MAX_DAY_VALUE+1)) + kdptr->day;
    return(retval);
}

/* Int32To_ktimeRelDate
 *	Convert a relative date encoded in an afs_int32 - back into a ktime_date
 *	structure
 */

Int32To_ktimeRelDate(int32Date, kdptr)
     afs_int32 int32Date;
     struct ktime_date *kdptr;
{
    bzero(kdptr, sizeof(*kdptr));
    
    kdptr->day = int32Date % (MAX_DAY_VALUE + 1);
    if ( kdptr->day != 0 )
    	kdptr->mask |= KTIMEDATE_DAY;

    int32Date = int32Date/(MAX_DAY_VALUE + 1);
    
    kdptr->month = int32Date % (MAX_MONTH_VALUE + 1);
    if ( kdptr->month != 0 )
    	kdptr->mask |= KTIMEDATE_MONTH;

    int32Date = int32Date/(MAX_MONTH_VALUE + 1);
    
    kdptr->year = int32Date;
    if ( kdptr->year != 0 )
    	kdptr->mask |= KTIMEDATE_YEAR;

    return(0);
}

/* ktimeDate_FromInt32
 *	Converts a time in seconds, to a time (in ktime_date format).
 *	Result is a conventional ktime_date structure.
 *	placed in the supplied structure
 * entry:
 *	timeSecs - time in seconds
 *	ktimePtr - ptr to struct for the return value
 */

ktimeDate_FromInt32(timeSecs, ktimePtr)
     afs_int32 timeSecs;
     struct ktime_date *ktimePtr;
{
    struct tm *timePtr;

    timePtr = localtime((time_t *)&timeSecs);

    /* copy the relevant fields */
    ktimePtr->sec = timePtr->tm_sec;
    ktimePtr->min = timePtr->tm_min;
    ktimePtr->hour = timePtr->tm_hour;
    ktimePtr->day = timePtr->tm_mday;
    ktimePtr->month = timePtr->tm_mon+1;
    ktimePtr->year = timePtr->tm_year;

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

    return(0);
}

#define	RD_DIGIT_LIMIT	4			/* max. no. digits permitted */

/* ParseRelDate
 *	Parses a relative date of the form  <n>y<n>m<n>d representing years
 *	months and days. <n> is limited to RD_DIGIT_LIMIT digits in length
 *	and is further restricted by the maximums specified at the head
 *	of the file.
 * entry:
 *	dateStr - ptr to string to parse. Leading white space ingnored.
 * exit:
 *	returns a ptr to a static ktime_date structure containing
 *		appropriately set fields. The mask field is unused.
 *	0 - error in date specification
 */

afs_int32
ParseRelDate(dateStr, relDatePtr)
     char *dateStr;
     struct ktime_date *relDatePtr;
{
    struct parseseqS *psPtr;
    afs_int32 value, digit_limit;
    afs_int32 type_index;

    bzero(relDatePtr, sizeof(*relDatePtr));
    type_index = 0;

    while ( 1 )
    { /*w*/

	while ( isspace(*dateStr) )                /* skip leading whitespace */
                dateStr++;

	if ( isdigit(*dateStr) == 0 )
    		goto error;

	digit_limit = RD_DIGIT_LIMIT;
	value = 0;
	while ( isdigit(*dateStr) )
	{
	    value = value*10 + *dateStr - '0';
	    dateStr++;
	    if ( digit_limit-- == 0 )
	    	goto error;
	}

	psPtr = &parseseq[type_index];
	/* determine the units. Search for a matching type character */
	while ( (psPtr->ps_keychar != *dateStr)
	&&  	(psPtr->ps_keychar != 0)
	      )
	{
	    type_index++;
	    psPtr = &parseseq[type_index];
	}

	/* no matching type found */
	if ( psPtr->ps_keychar == 0 )
 		goto error;

	/* check the bounds on the maximum value. Can't be negative
	 * and if a maximum value is specified, check against it
	 */
	if ( (value < 0)
	||   ((psPtr->ps_maxValue > 0) && (value > psPtr->ps_maxValue))
	   )
	    	goto error;

	/* save computed value in the relevant type field */
	switch ( psPtr->ps_field )
	{
	  case KTIMEDATE_YEAR:
	    relDatePtr->year = value;
	    relDatePtr->mask |= KTIMEDATE_YEAR;
	    break;

	  case KTIMEDATE_MONTH:
	    if ( value > MAX_MONTH_VALUE )
	    	goto error;
	    relDatePtr->month = value;
	    relDatePtr->mask |= KTIMEDATE_MONTH;
	    break;

	  case KTIMEDATE_DAY:
	    if ( value > MAX_DAY_VALUE )
	    	goto error;

	    relDatePtr->mask |= KTIMEDATE_DAY;
	    relDatePtr->day = value;
	    break;

	  default:
	    goto error;
	}
	dateStr++;				/* next digit */

	if ( *dateStr == 0 )
	{
	    /* no more chars to process, return the result */
	    return(0);
	}
    } /*w*/

error:
    return(1);
}

/* RelDatetoString
 *	returns a static string representing the relative date. This is in
 *	a format acceptable to the relative date parser.
 * entry:
 *	datePtr - relative date to be converted.
 * exit:
 *	ptr to static string
 */

char *
RelDatetoString(datePtr)
     struct ktime_date *datePtr;
{
    static char dateString[64];
    char tempstring[64], *sptr;

    dateString[0] = 0;
    sptr = &dateString[0];

    if ( datePtr->mask & KTIMEDATE_YEAR )
    {
	sprintf(tempstring, "%-dy", datePtr->year);
	strcat(sptr, tempstring);
    }

    if ( datePtr->mask & KTIMEDATE_MONTH )
    {
	strcat(sptr, " ");
	sprintf(tempstring, "%-dm", datePtr->month);
	strcat(sptr, tempstring);
    }

    if ( datePtr->mask & KTIMEDATE_DAY )
    {
	strcat(sptr, " ");
	sprintf(tempstring, "%-dd", datePtr->day);
	strcat(sptr, tempstring);
    }
    return(sptr);
}

/* Add_RelDate_to_Time
 *	Returns current time with a relative time added. Note that the 
 *	computation adds in most significant fields first, i.e. year, month
 *	day etc. Addition of least significant fields would produce different
 *	results (depending on the data).
 * entry:
 *	relDatePtr - a ktime_date containing a relative time specification
 * exit:
 *	returns specified time with relative time added.
 */

afs_int32
Add_RelDate_to_Time(relDatePtr, atime)
     struct ktime_date *relDatePtr;
     afs_int32 atime;
{
    afs_int32 moreYears;
    static struct ktime_date absDate;
    
    ktimeDate_FromInt32(atime, &absDate);	/* convert to ktime */

    /* add in years */
    if ( relDatePtr->mask & KTIMEDATE_YEAR )
	    absDate.year += relDatePtr->year;

    /* add in months */
    if ( relDatePtr->mask & KTIMEDATE_MONTH )
    	absDate.month += relDatePtr->month;

    if ( absDate.month > 12 )
    {
	moreYears = absDate.month / 12;
	absDate.month = absDate.month % 12;
	absDate.year += moreYears;
    }
    
    /* day computations depend upon month size, so do these in seconds */
    atime = ktime_InterpretDate(&absDate);

    if ( relDatePtr->mask & KTIMEDATE_DAY )
	    atime = atime + relDatePtr->day*24*60*60;

    if ( relDatePtr->mask & KTIMEDATE_HOUR )
	    atime = atime + relDatePtr->hour*60*60;

    if ( relDatePtr->mask & KTIMEDATE_MIN )
	    atime = atime + relDatePtr->min*60;

    if ( relDatePtr->mask & KTIMEDATE_SEC )
	    atime = atime + relDatePtr->sec;

    return(atime);
}	
