/* 
 * tclWinTime.c --
 *
 *	Contains Windows specific versions of Tcl functions that
 *	obtain time values from the operating system.
 *
 * Copyright 1995-1998 by Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * RCS: @(#) $Id: tclWinTime.c,v 1.9 2001/11/21 02:36:21 hobbs Exp $
 */

#include "tclWinInt.h"

#define SECSPERDAY (60L * 60L * 24L)
#define SECSPERYEAR (SECSPERDAY * 365L)
#define SECSPER4YEAR (SECSPERYEAR * 4L + SECSPERDAY)

/*
 * The following arrays contain the day of year for the last day of
 * each month, where index 1 is January.
 */

static int normalDays[] = {
    -1, 30, 58, 89, 119, 150, 180, 211, 242, 272, 303, 333, 364
};

static int leapDays[] = {
    -1, 30, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365
};

typedef struct ThreadSpecificData {
    char tzName[64];		/* Time zone name */
    struct tm tm;		/* time information */
} ThreadSpecificData;
static Tcl_ThreadDataKey dataKey;

/*
 * Calibration interval for the high-resolution timer, in msec
 */

static CONST unsigned long clockCalibrateWakeupInterval = 10000;
				/* FIXME: 10 s -- should be about 10 min! */

/*
 * Data for managing high-resolution timers.
 */

typedef struct TimeInfo {

    CRITICAL_SECTION cs;	/* Mutex guarding this structure */

    int initialized;		/* Flag == 1 if this structure is
				 * initialized. */

    int perfCounterAvailable;	/* Flag == 1 if the hardware has a
				 * performance counter */

    HANDLE calibrationThread;	/* Handle to the thread that keeps the
				 * virtual clock calibrated. */

    HANDLE readyEvent;		/* System event used to
				 * trigger the requesting thread
				 * when the clock calibration procedure
				 * is initialized for the first time */

    /*
     * The following values are used for calculating virtual time.
     * Virtual time is always equal to:
     *    lastFileTime + (current perf counter - lastCounter) 
     *				* 10000000 / curCounterFreq
     * and lastFileTime and lastCounter are updated any time that
     * virtual time is returned to a caller.
     */

    ULARGE_INTEGER lastFileTime;
    LARGE_INTEGER lastCounter;
    LARGE_INTEGER curCounterFreq;

    /* 
     * The next two values are used only in the calibration thread, to track
     * the frequency of the performance counter.
     */

    LONGLONG lastPerfCounter;	/* Performance counter the last time
				 * that UpdateClockEachSecond was called */
    LONGLONG lastSysTime;	/* System clock at the last time
				 * that UpdateClockEachSecond was called */
    LONGLONG estPerfCounterFreq;
				/* Current estimate of the counter frequency
				 * using the system clock as the standard */

} TimeInfo;

static TimeInfo timeInfo = {
    NULL, 0, 0, NULL, NULL, 0, 0, 0, 0, 0
};

CONST static FILETIME posixEpoch = { 0xD53E8000, 0x019DB1DE };
    
/*
 * Declarations for functions defined later in this file.
 */

static struct tm *	ComputeGMT _ANSI_ARGS_((const time_t *tp));

static DWORD WINAPI     CalibrationThread _ANSI_ARGS_(( LPVOID arg ));

static void 		UpdateTimeEachSecond _ANSI_ARGS_(( void ));

/*
 *----------------------------------------------------------------------
 *
 * TclpGetSeconds --
 *
 *	This procedure returns the number of seconds from the epoch.
 *	On most Unix systems the epoch is Midnight Jan 1, 1970 GMT.
 *
 * Results:
 *	Number of seconds from the epoch.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

unsigned long
TclpGetSeconds()
{
    Tcl_Time t;
    Tcl_GetTime( &t );
    return t.sec;
}

/*
 *----------------------------------------------------------------------
 *
 * TclpGetClicks --
 *
 *	This procedure returns a value that represents the highest
 *	resolution clock available on the system.  There are no
 *	guarantees on what the resolution will be.  In Tcl we will
 *	call this value a "click".  The start time is also system
 *	dependant.
 *
 * Results:
 *	Number of clicks from some start time.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

unsigned long
TclpGetClicks()
{
    /*
     * Use the Tcl_GetTime abstraction to get the time in microseconds,
     * as nearly as we can, and return it.
     */

    Tcl_Time now;		/* Current Tcl time */
    unsigned long retval;	/* Value to return */

    Tcl_GetTime( &now );
    retval = ( now.sec * 1000000 ) + now.usec;
    return retval;

}

/*
 *----------------------------------------------------------------------
 *
 * TclpGetTimeZone --
 *
 *	Determines the current timezone.  The method varies wildly
 *	between different Platform implementations, so its hidden in
 *	this function.
 *
 * Results:
 *	Minutes west of GMT.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
TclpGetTimeZone (currentTime)
    unsigned long  currentTime;
{
    int timeZone;

    tzset();
    timeZone = _timezone / 60;

    return timeZone;
}

/*
 *----------------------------------------------------------------------
 *
 * Tcl_GetTime --
 *
 *	Gets the current system time in seconds and microseconds
 *	since the beginning of the epoch: 00:00 UCT, January 1, 1970.
 *
 * Results:
 *	Returns the current time in timePtr.
 *
 * Side effects:
 *	On the first call, initializes a set of static variables to
 *	keep track of the base value of the performance counter, the
 *	corresponding wall clock (obtained through ftime) and the
 *	frequency of the performance counter.  Also spins a thread
 *	whose function is to wake up periodically and monitor these
 *	values, adjusting them as necessary to correct for drift
 *	in the performance counter's oscillator.
 *
 *----------------------------------------------------------------------
 */

void
Tcl_GetTime(timePtr)
    Tcl_Time *timePtr;		/* Location to store time information. */
{
	
    struct timeb t;

    /* Initialize static storage on the first trip through. */

    /*
     * Note: Outer check for 'initialized' is a performance win
     * since it avoids an extra mutex lock in the common case.
     */

    if ( !timeInfo.initialized ) { 
	TclpInitLock();
	if ( !timeInfo.initialized ) {
	    timeInfo.perfCounterAvailable
		= QueryPerformanceFrequency( &timeInfo.curCounterFreq );

	    /*
	     * Some hardware abstraction layers use the CPU clock
	     * in place of the real-time clock as a performance counter
	     * reference.  This results in:
	     *    - inconsistent results among the processors on
	     *      multi-processor systems.
	     *    - unpredictable changes in performance counter frequency
	     *      on "gearshift" processors such as Transmeta and
	     *      SpeedStep.
	     * There seems to be no way to test whether the performance
	     * counter is reliable, but a useful heuristic is that
	     * if its frequency is 1.193182 MHz or 3.579545 MHz, it's
	     * derived from a colorburst crystal and is therefore
	     * the RTC rather than the TSC.  If it's anything else, we
	     * presume that the performance counter is unreliable.
	     */

	    if ( timeInfo.perfCounterAvailable
		 && timeInfo.curCounterFreq.QuadPart != (LONGLONG) 1193182
		 && timeInfo.curCounterFreq.QuadPart != (LONGLONG) 3579545 ) {
		timeInfo.perfCounterAvailable = FALSE;
	    }

	    /*
	     * If the performance counter is available, start a thread to
	     * calibrate it.
	     */

	    if ( timeInfo.perfCounterAvailable ) {
		DWORD id;
		InitializeCriticalSection( &timeInfo.cs );
		timeInfo.readyEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
		timeInfo.calibrationThread = CreateThread( NULL,
							   8192,
							   CalibrationThread,
							   (LPVOID) NULL,
							   0,
							   &id );
		SetThreadPriority( timeInfo.calibrationThread,
				   THREAD_PRIORITY_HIGHEST );
		WaitForSingleObject( timeInfo.readyEvent, INFINITE );
		CloseHandle( timeInfo.readyEvent );
	    }
	    timeInfo.initialized = TRUE;
	}
	TclpInitUnlock();
    }

    if ( timeInfo.perfCounterAvailable ) {
	
	/*
	 * Query the performance counter and use it to calculate the
	 * current time.
	 */

	LARGE_INTEGER curCounter;
				/* Current performance counter */

	LONGLONG curFileTime;
				/* Current estimated time, expressed
				 * as 100-ns ticks since the Windows epoch */

	static LARGE_INTEGER posixEpoch;
				/* Posix epoch expressed as 100-ns ticks
				 * since the windows epoch */

	LONGLONG usecSincePosixEpoch;
				/* Current microseconds since Posix epoch */

	posixEpoch.LowPart = 0xD53E8000;
	posixEpoch.HighPart = 0x019DB1DE;

	EnterCriticalSection( &timeInfo.cs );

	QueryPerformanceCounter( &curCounter );
	curFileTime = timeInfo.lastFileTime.QuadPart
	    + ( ( curCounter.QuadPart - timeInfo.lastCounter.QuadPart )
		* 10000000 / timeInfo.curCounterFreq.QuadPart );
	timeInfo.lastFileTime.QuadPart = curFileTime;
	timeInfo.lastCounter.QuadPart = curCounter.QuadPart;
	usecSincePosixEpoch = ( curFileTime - posixEpoch.QuadPart ) / 10;
	timePtr->sec = (time_t) ( usecSincePosixEpoch / 1000000 );
	timePtr->usec = (unsigned long ) ( usecSincePosixEpoch % 1000000 );
	
	LeaveCriticalSection( &timeInfo.cs );

	
    } else {
	
	/* High resolution timer is not available.  Just use ftime */
	
	ftime(&t);
	timePtr->sec = t.time;
	timePtr->usec = t.millitm * 1000;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TclpGetTZName --
 *
 *	Gets the current timezone string.
 *
 * Results:
 *	Returns a pointer to a static string, or NULL on failure.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

char *
TclpGetTZName(int dst)
{
    int len;
    char *zone, *p;
    TIME_ZONE_INFORMATION tz;
    Tcl_Encoding encoding;
    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
    char *name = tsdPtr->tzName;

    /*
     * tzset() under Borland doesn't seem to set up tzname[] at all.
     * tzset() under MSVC has the following weird observed behavior:
     *	 First time we call "clock format [clock seconds] -format %Z -gmt 1"
     *	 we get "GMT", but on all subsequent calls we get the current time 
     *	 zone string, even though env(TZ) is GMT and the variable _timezone 
     *   is 0.
     */

    name[0] = '\0';

    zone = getenv("TZ");
    if (zone != NULL) {
	/*
	 * TZ is of form "NST-4:30NDT", where "NST" would be the
	 * name of the standard time zone for this area, "-4:30" is
	 * the offset from GMT in hours, and "NDT is the name of 
	 * the daylight savings time zone in this area.  The offset 
	 * and DST strings are optional.
	 */

	len = strlen(zone);
	if (len > 3) {
	    len = 3;
	}
	if (dst != 0) {
	    /*
	     * Skip the offset string and get the DST string.
	     */

	    p = zone + len;
	    p += strspn(p, "+-:0123456789");
	    if (*p != '\0') {
		zone = p;
		len = strlen(zone);
		if (len > 3) {
		    len = 3;
		}
	    }
	}
	Tcl_ExternalToUtf(NULL, NULL, zone, len, 0, NULL, name,
		sizeof(tsdPtr->tzName), NULL, NULL, NULL);
    }
    if (name[0] == '\0') {
	if (GetTimeZoneInformation(&tz) == TIME_ZONE_ID_UNKNOWN) {
	    /*
	     * MSDN: On NT this is returned if DST is not used in
	     * the current TZ
	     */
	    dst = 0;
	}
	encoding = Tcl_GetEncoding(NULL, "unicode");
	Tcl_ExternalToUtf(NULL, encoding, 
		(char *) ((dst) ? tz.DaylightName : tz.StandardName), -1, 
		0, NULL, name, sizeof(tsdPtr->tzName), NULL, NULL, NULL);
	Tcl_FreeEncoding(encoding);
    } 
    return name;
}

/*
 *----------------------------------------------------------------------
 *
 * TclpGetDate --
 *
 *	This function converts between seconds and struct tm.  If
 *	useGMT is true, then the returned date will be in Greenwich
 *	Mean Time (GMT).  Otherwise, it will be in the local time zone.
 *
 * Results:
 *	Returns a static tm structure.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

struct tm *
TclpGetDate(t, useGMT)
    TclpTime_t t;
    int useGMT;
{
    const time_t *tp = (const time_t *) t;
    struct tm *tmPtr;
    long time;

    if (!useGMT) {
	tzset();

	/*
	 * If we are in the valid range, let the C run-time library
	 * handle it.  Otherwise we need to fake it.  Note that this
	 * algorithm ignores daylight savings time before the epoch.
	 */

	if (*tp >= 0) {
	    return localtime(tp);
	}

	time = *tp - _timezone;
	
	/*
	 * If we aren't near to overflowing the long, just add the bias and
	 * use the normal calculation.  Otherwise we will need to adjust
	 * the result at the end.
	 */

	if (*tp < (LONG_MAX - 2 * SECSPERDAY)
		&& *tp > (LONG_MIN + 2 * SECSPERDAY)) {
	    tmPtr = ComputeGMT(&time);
	} else {
	    tmPtr = ComputeGMT(tp);

	    tzset();

	    /*
	     * Add the bias directly to the tm structure to avoid overflow.
	     * Propagate seconds overflow into minutes, hours and days.
	     */

	    time = tmPtr->tm_sec - _timezone;
	    tmPtr->tm_sec = (int)(time % 60);
	    if (tmPtr->tm_sec < 0) {
		tmPtr->tm_sec += 60;
		time -= 60;
	    }
    
	    time = tmPtr->tm_min + time/60;
	    tmPtr->tm_min = (int)(time % 60);
	    if (tmPtr->tm_min < 0) {
		tmPtr->tm_min += 60;
		time -= 60;
	    }

	    time = tmPtr->tm_hour + time/60;
	    tmPtr->tm_hour = (int)(time % 24);
	    if (tmPtr->tm_hour < 0) {
		tmPtr->tm_hour += 24;
		time -= 24;
	    }

	    time /= 24;
	    tmPtr->tm_mday += time;
	    tmPtr->tm_yday += time;
	    tmPtr->tm_wday = (tmPtr->tm_wday + time) % 7;
	}
    } else {
	tmPtr = ComputeGMT(tp);
    }
    return tmPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * ComputeGMT --
 *
 *	This function computes GMT given the number of seconds since
 *	the epoch (midnight Jan 1 1970).
 *
 * Results:
 *	Returns a (per thread) statically allocated struct tm.
 *
 * Side effects:
 *	Updates the values of the static struct tm.
 *
 *----------------------------------------------------------------------
 */

static struct tm *
ComputeGMT(tp)
    const time_t *tp;
{
    struct tm *tmPtr;
    long tmp, rem;
    int isLeap;
    int *days;
    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);

    tmPtr = &tsdPtr->tm;

    /*
     * Compute the 4 year span containing the specified time.
     */

    tmp = *tp / SECSPER4YEAR;
    rem = *tp % SECSPER4YEAR;

    /*
     * Correct for weird mod semantics so the remainder is always positive.
     */

    if (rem < 0) {
	tmp--;
	rem += SECSPER4YEAR;
    }

    /*
     * Compute the year after 1900 by taking the 4 year span and adjusting
     * for the remainder.  This works because 2000 is a leap year, and
     * 1900/2100 are out of the range.
     */

    tmp = (tmp * 4) + 70;
    isLeap = 0;
    if (rem >= SECSPERYEAR) {			  /* 1971, etc. */
	tmp++;
	rem -= SECSPERYEAR;
	if (rem >= SECSPERYEAR) {		  /* 1972, etc. */
	    tmp++;
	    rem -= SECSPERYEAR;
	    if (rem >= SECSPERYEAR + SECSPERDAY) { /* 1973, etc. */
		tmp++;
		rem -= SECSPERYEAR + SECSPERDAY;
	    } else {
		isLeap = 1;
	    }
	}
    }
    tmPtr->tm_year = tmp;

    /*
     * Compute the day of year and leave the seconds in the current day in
     * the remainder.
     */

    tmPtr->tm_yday = rem / SECSPERDAY;
    rem %= SECSPERDAY;
    
    /*
     * Compute the time of day.
     */

    tmPtr->tm_hour = rem / 3600;
    rem %= 3600;
    tmPtr->tm_min = rem / 60;
    tmPtr->tm_sec = rem % 60;

    /*
     * Compute the month and day of month.
     */

    days = (isLeap) ? leapDays : normalDays;
    for (tmp = 1; days[tmp] < tmPtr->tm_yday; tmp++) {
    }
    tmPtr->tm_mon = --tmp;
    tmPtr->tm_mday = tmPtr->tm_yday - days[tmp];

    /*
     * Compute day of week.  Epoch started on a Thursday.
     */

    tmPtr->tm_wday = (*tp / SECSPERDAY) + 4;
    if ((*tp % SECSPERDAY) < 0) {
	tmPtr->tm_wday--;
    }
    tmPtr->tm_wday %= 7;
    if (tmPtr->tm_wday < 0) {
	tmPtr->tm_wday += 7;
    }

    return tmPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * CalibrationThread --
 *
 *	Thread that manages calibration of the hi-resolution time
 *	derived from the performance counter, to keep it synchronized
 *	with the system clock.
 *
 * Parameters:
 *	arg -- Client data from the CreateThread call.  This parameter
 *             points to the static TimeInfo structure.
 *
 * Return value:
 *	None.  This thread embeds an infinite loop.
 *
 * Side effects:
 *	At an interval of clockCalibrateWakeupInterval ms, this thread
 *	performs virtual time discipline.
 *
 * Note: When this thread is entered, TclpInitLock has been called
 * to safeguard the static storage.  There is therefore no synchronization
 * in the body of this procedure.
 *
 *----------------------------------------------------------------------
 */

static DWORD WINAPI
CalibrationThread( LPVOID arg )
{
    FILETIME curFileTime;

    /* Get initial system time and performance counter */

    GetSystemTimeAsFileTime( &curFileTime );
    QueryPerformanceCounter( &timeInfo.lastCounter );
    QueryPerformanceFrequency( &timeInfo.curCounterFreq );
    timeInfo.lastFileTime.LowPart = curFileTime.dwLowDateTime;
    timeInfo.lastFileTime.HighPart = curFileTime.dwHighDateTime;

    /* Initialize the working storage for the calibration callback */

    timeInfo.lastPerfCounter = timeInfo.lastCounter.QuadPart;
    timeInfo.estPerfCounterFreq = timeInfo.curCounterFreq.QuadPart;

    /*
     * Wake up the calling thread.  When it wakes up, it will release the
     * initialization lock.
     */

    SetEvent( timeInfo.readyEvent );

    /* Run the calibration once a second */

    for ( ; ; ) {
	Sleep( 1000 );
	UpdateTimeEachSecond();
    }

    /* lint */
    return (DWORD) 0;
}

/*
 *----------------------------------------------------------------------
 *
 * UpdateTimeEachSecond --
 *
 *	Callback from the waitable timer in the clock calibration thread
 *	that updates system time.
 *
 * Parameters:
 *	info -- Pointer to the static TimeInfo structure
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Performs virtual time calibration discipline.
 *
 *----------------------------------------------------------------------
 */

static void
UpdateTimeEachSecond()
{

    LARGE_INTEGER curPerfCounter;
				/* Current value returned from
				 * QueryPerformanceCounter */

    LONGLONG perfCounterDiff;	/* Difference between the current value
				 * and the value of 1 second ago */

    FILETIME curSysTime;	/* Current system time */

    LARGE_INTEGER curFileTime;	/* File time at the time this callback
				 * was scheduled. */

    LONGLONG fileTimeDiff;	/* Elapsed time on the system clock
				 * since the last time this procedure
				 * was called */

    LONGLONG instantFreq;	/* Instantaneous estimate of the
				 * performance counter frequency */

    LONGLONG delta;		/* Increment to add to the estimated
				 * performance counter frequency in the
				 * loop filter */

    LONGLONG fuzz;		/* Tolerance for the perf counter frequency */

    LONGLONG lowBound;		/* Lower bound for the frequency assuming
				 * 1000 ppm tolerance */

    LONGLONG hiBound;		/* Upper bound for the frequency */

    /*
     * Get current performance counter and system time.
     */

    QueryPerformanceCounter( &curPerfCounter );
    GetSystemTimeAsFileTime( &curSysTime );
    curFileTime.LowPart = curSysTime.dwLowDateTime;
    curFileTime.HighPart = curSysTime.dwHighDateTime;

    EnterCriticalSection( &timeInfo.cs );

    /*
     * Find out how many ticks of the performance counter and the
     * system clock have elapsed since we got into this procedure.
     * Estimate the current frequency.
     */

    perfCounterDiff = curPerfCounter.QuadPart - timeInfo.lastPerfCounter;
    timeInfo.lastPerfCounter = curPerfCounter.QuadPart;
    fileTimeDiff = curFileTime.QuadPart - timeInfo.lastSysTime;
    timeInfo.lastSysTime = curFileTime.QuadPart;
    instantFreq = ( 10000000 * perfCounterDiff / fileTimeDiff );

    /*
     * Consider this a timing glitch if instant frequency varies
     * significantly from the current estimate.
     */

    fuzz = timeInfo.estPerfCounterFreq >> 10;
    lowBound = timeInfo.estPerfCounterFreq - fuzz;
    hiBound = timeInfo.estPerfCounterFreq + fuzz;
    if ( instantFreq < lowBound || instantFreq > hiBound ) {
	LeaveCriticalSection( &timeInfo.cs );
	return;
    }

    /*
     * Update the current estimate of performance counter frequency.
     * This code is equivalent to the loop filter of a phase locked
     * loop.
     */

    delta = ( instantFreq - timeInfo.estPerfCounterFreq ) >> 6;
    timeInfo.estPerfCounterFreq += delta;

    /*
     * Update the current virtual time.
     */

    timeInfo.lastFileTime.QuadPart
	+= ( ( curPerfCounter.QuadPart - timeInfo.lastCounter.QuadPart )
	     * 10000000 / timeInfo.curCounterFreq.QuadPart );
    timeInfo.lastCounter.QuadPart = curPerfCounter.QuadPart;

    delta = curFileTime.QuadPart - timeInfo.lastFileTime.QuadPart;
    if ( delta > 10000000 || delta < -10000000 ) {

	/*
	 * If the virtual time slip exceeds one second, then adjusting
	 * the counter frequency is hopeless (it'll take over fifteen
	 * minutes to line up with the system clock).  The most likely
	 * cause of this large a slip is a sudden change to the system
	 * clock, perhaps because it was being corrected by wristwatch
	 * and eyeball.  Accept the system time, and set the performance
	 * counter frequency to the current estimate.
	 */

	timeInfo.lastFileTime.QuadPart = curFileTime.QuadPart;
	timeInfo.curCounterFreq.QuadPart = timeInfo.estPerfCounterFreq;

    } else {

	/*
	 * Compute a counter frequency that will cause virtual time to line
	 * up with system time one second from now, assuming that the
	 * performance counter continues to tick at timeInfo.estPerfCounterFreq.
	 */
	
	timeInfo.curCounterFreq.QuadPart
	    = 10000000 * timeInfo.estPerfCounterFreq / ( delta + 10000000 );

	/*
	 * Limit frequency excursions to 1000 ppm from estimate
	 */
	
	if ( timeInfo.curCounterFreq.QuadPart < lowBound ) {
	    timeInfo.curCounterFreq.QuadPart = lowBound;
	} else if ( timeInfo.curCounterFreq.QuadPart > hiBound ) {
	    timeInfo.curCounterFreq.QuadPart = hiBound;
	}
    }

    LeaveCriticalSection( &timeInfo.cs );

}
