/********************************************************************

    DrvTime.c	1.3
    Driver Functions to convert times into different formats
    Copyright 1997 Willows Software, Inc. 

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Library General Public License for more details.

You should have received a copy of the GNU Library General Public
License along with this library; see the file COPYING.LIB.  If
not, write to the Free Software Foundation, Inc., 675 Mass Ave,
Cambridge, MA 02139, USA.

The maintainer of the Willows TWIN Libraries may be reached (Email) 
at the address twin@willows.com	

*/



#include "windows.h"
#include <time.h>
#ifdef sun
#include <sys/time.h>
#endif
#include <string.h>

#include "DrvHook.h"

/*
 * Windows defines three different types of times:
 *
 * -- FILETIME is a 64-bit word containing the number of 100-nanosecond
 *        intervals since 12:00 am 1 Jan 1601.
 *
 * -- SYSTEMTIME is a struct similar to the ANSI struct tm containing
 *        a description of a date and time, with resolution down to
 *        milliseconds.
 *
 * -- DOS date and time, a pair of packed short ints.
 *
 * We must convert between these, plus convert between file time and
 * a "local" file time, since file time always uses UCT.
 */

/* Global variables */

/* ltimeOffset: the number of seconds between local time and system time.
 * System time is always in UCT; local time is based on the current time
 * zone.  ltimeOffset = timeLocal - timeSystem */
static time_t ltimeOffset;

/* tzInfo: a TIME_ZONE_INFORMATION structure containing the current
 * time zone information, as passed in SetTimeZoneInformation.  We don't
 * do anything with any of this.  The Bias member should always be calculated
 * from ltimeOffset (timeSystem = timeLocal + bias ==>
 * bias = timeSystem - timeLocal ==> bias = -ltimeOffset / 60). */
static TIME_ZONE_INFORMATION tzInfo;

/* First, some helper functions... */

/* filetime_to_time_t: Convert a FILETIME struct to a time_t
 *
 * Since a FILETIME is the number of 100-nanoseconds since 1 Jan 1601 and a
 * time_t is the number of (whole) seconds since 1 Jan 1970, this shouldn't
 * be _too_ hard...
 */

#define TIMERATIO 10000000
#define TIMEOFFSETH 0x019DB1DE
#define TIMEOFFSETL 0xD53E8000

static time_t filetime_to_time_t(LPFILETIME lpft)
{
  DWORD dw, r, rMod, qMod, nMax;
  FILETIME ftTemp;
  
  /* Firstly: subtract the offset to make this time relative to 1 Jan 1970. */
  ftTemp.dwHighDateTime = lpft->dwHighDateTime - TIMEOFFSETH;
  if (lpft->dwLowDateTime < TIMEOFFSETL)
    ftTemp.dwHighDateTime--;
  ftTemp.dwLowDateTime = lpft->dwLowDateTime - TIMEOFFSETL;
  
  /* How do we divide a 64-bit number by 10^7, using only 32-bit math?
   *
   * -- The high word of the result is the high word of the input, divided
   *        by 10^7.
   */
  ftTemp.dwHighDateTime = lpft->dwHighDateTime / TIMERATIO;
  
  /* -- The low word of the result starts at the low word of the input
   *        divided by 10^7... */
  ftTemp.dwLowDateTime = lpft->dwLowDateTime / TIMERATIO;
  
  /* ...but we also have to account for the remainder from the high word. */
  dw = lpft->dwHighDateTime % TIMERATIO;
  
  /* We also have to track the remainder from the low word. */
  r = lpft->dwLowDateTime % TIMERATIO;
  
  /* To do this properly, we need the quotient and remainder when 2^32 is
   * divided by TIMERATIO. */
  qMod = ((DWORD)(-1)) / TIMERATIO;
  rMod = ((DWORD)(-1)) % TIMERATIO + 1;
  
  /* Each 2^32 ticks (i. e. remainder 1 from dwHighDateTime) will generate
   * qMod in the quotient and rMod in the remainder, but this will overflow
   * r if dw is of any reasonable size (which will happen quite frequently).
   * We therefore need to work in chunks.  But we _can_ deal with qMod all
   * at once, so we do. */
  ftTemp.dwLowDateTime += qMod * dw;
  nMax = ((DWORD)(-1)) / qMod - 1;
  while (dw >= nMax)
    {
      dw -= nMax;
      r += nMax * rMod;
      ftTemp.dwLowDateTime += r / TIMERATIO;
      r %= TIMERATIO;
    }
  r += dw * rMod;
  ftTemp.dwLowDateTime += r / TIMERATIO;
  
  /* These two together contain the number of seconds since 1 Jan 1970,
   * or a time_t.  But a time_t is only 32 bits, so it had better be the
   * low 16 bits...
   */
  
  return (time_t)ftTemp.dwLowDateTime;
}

/* safe_add: add a 32-bit int to a 64-bit int */
static void safe_add(LPFILETIME lpft, DWORD lb2)
{
  if (-(lpft->dwLowDateTime) >= lb2)
    (lpft->dwHighDateTime)++;
  lpft->dwLowDateTime += lb2;
}

/* time_t_to_filetime: convert a time_t to a FILETIME struct
 *
 * This should also be (relatively) simple, since it's just adding that
 * big number and multiplying.
 */

static void time_t_to_filetime(time_t t, LPFILETIME lpft)
{
  DWORD dwStep;
  DWORD dwOverflow;
  DWORD dwRemainder;
  FILETIME ftTemp;
  
  ftTemp.dwHighDateTime = 0;
  
  /* Since we divided in the last function, we now have to multiply.
   * We know where to start... */
  ftTemp.dwLowDateTime = t * TIMERATIO;
  dwStep = ((DWORD)(-1)) / TIMERATIO + 1;
  dwOverflow = TIMERATIO * dwStep;
  dwRemainder = t;
  while (dwRemainder > dwStep)
    {
      dwRemainder -= dwStep;
      ftTemp.dwHighDateTime++;
      safe_add(&ftTemp, dwOverflow);
    }
  safe_add(&ftTemp, dwRemainder * TIMERATIO);
  
  safe_add(&ftTemp, TIMEOFFSETL);
  ftTemp.dwHighDateTime += TIMEOFFSETH;

  lpft->dwLowDateTime = ftTemp.dwLowDateTime;
  lpft->dwHighDateTime = ftTemp.dwHighDateTime;
}

/* tm_to_systemtime: change an ANSI struct tm into a Windows SYSTEMTIME */
static void tm_to_systemtime(struct tm *ptm, LPSYSTEMTIME lpst)
{
  lpst->wYear = ptm->tm_year + 1900;
  lpst->wMonth = ptm->tm_mon + 1;
  lpst->wDayOfWeek = ptm->tm_wday;
  lpst->wDay = ptm->tm_mday;
  lpst->wHour = ptm->tm_hour;
  lpst->wMinute = ptm->tm_min;
  lpst->wSecond = ptm->tm_sec;
  lpst->wMilliseconds = 0;
}

/* systemtime_to_tm: change a Windows SYSTEMTIME into an ANSI struct tm */
static void systemtime_to_tm(LPSYSTEMTIME lpst, struct tm *ptm)
{
  ptm->tm_year = lpst->wYear - 1900;
  ptm->tm_mon = lpst->wMonth - 1;
  ptm->tm_wday = lpst->wDayOfWeek;
  ptm->tm_mday = lpst->wDay;
  ptm->tm_hour = lpst->wHour;
  ptm->tm_min = lpst->wMinute;
  ptm->tm_sec = lpst->wSecond;
  ptm->tm_isdst = -1;
}

/* FileTimeToSystemTime: Convert a FILETIME to a SYSTEMTIME */
static BOOL DrvFile2System(LPFILETIME lpft, LPSYSTEMTIME lpst)
{
  time_t t;
  struct tm *ptm;

  t = filetime_to_time_t(lpft);
  ptm = gmtime(&t);
  tm_to_systemtime(ptm, lpst);
  return TRUE;
}

/* SystemTimetoFileTime: Convert a SYSTEMTIME to a FILETIME */
static BOOL DrvSystem2File(LPSYSTEMTIME lpst, LPFILETIME lpft)
{
  time_t t;
  struct tm stm;
  
  systemtime_to_tm(lpst, &stm);
  if ((t = mktime(&stm)) == -1)
    return FALSE;
  time_t_to_filetime(t, lpft);
  return TRUE;
}
  
static BOOL DrvFile2Local(LPFILETIME lpft, LPFILETIME lpftLocal)
{
  time_t t;
  t = filetime_to_time_t(lpft);
  t += ltimeOffset;
  time_t_to_filetime(t, lpftLocal);
  return TRUE;
}

static BOOL DrvLocal2File(LPFILETIME lpftLocal, LPFILETIME lpft)
{
  time_t t;
  t = filetime_to_time_t(lpftLocal);
  t -= ltimeOffset;
  time_t_to_filetime(t, lpft);
  return TRUE;
}

static BOOL DrvGetSysTime(LPSYSTEMTIME lpst)
{
  time_t t;
  struct tm *ptm;

  t = time(NULL);
  ptm = gmtime(&t);
  tm_to_systemtime(ptm, lpst);

  return TRUE;
}

static BOOL DrvGetLocTime(LPSYSTEMTIME lpst)
{
  time_t t;
  struct tm *ptm;

  t = time(NULL);
  t += ltimeOffset;
  ptm = gmtime(&t);
  tm_to_systemtime(ptm, lpst);

  return TRUE;
}

static BOOL DrvSetSysTime(LPSYSTEMTIME lpst)
{
  DateTimeRec myDateTimeRec;

  myDateTimeRec.year = lpst->wYear;
  myDateTimeRec.month = lpst->wMonth;
  myDateTimeRec.day = lpst->wDay;
  myDateTimeRec.hour = lpst->wHour;
  myDateTimeRec.minute = lpst->wMinute;
  myDateTimeRec.second = lpst->wSecond;
  myDateTimeRec.dayOfWeek = lpst->wDayOfWeek + 1;
  SetTime(&myDateTimeRec);
  return TRUE;
}

static BOOL DrvSetLocTime(LPSYSTEMTIME lpst)
{
  time_t t, now;
  struct tm stm;

  systemtime_to_tm(lpst, &stm);
  now = time(NULL);
  t = mktime(&stm);
  if (t == -1)
    return FALSE;
  ltimeOffset = t - now;
  return TRUE;
}

/* Time Zone information functions
 * TODO: Anything involving this data -- maybe? */

static BOOL DrvSetTZI(LPTIME_ZONE_INFORMATION lptzi)
{
  tzInfo = *lptzi;
  ltimeOffset = -tzInfo.Bias * 60;
  return TRUE;
}

static BOOL DrvGetTZI(LPTIME_ZONE_INFORMATION lptzi)
{
  *lptzi = tzInfo;
  lptzi->Bias = -ltimeOffset / 60;
  return TRUE;
}

static void time_init()
{
  ltimeOffset = 0;
  memset(&tzInfo, 0, sizeof(tzInfo));
}

/* DOS date and time
 * In 1980, this probably made sense...
 * The date and time for an MS-DOS file are packed into two 16-bit words.
 * The job of these functions is to pack and unpack those values. */

static void tm_to_dos_time(struct tm *ptm, WORD *pwDOSDate, WORD *pwDOSTime)
{
  *pwDOSDate = ((((ptm->tm_year - 80) & 0x7F) << 9) +
		(((ptm->tm_mon + 1) & 0xF) << 5) +
		(((ptm->tm_mday) & 0x1F)));
  *pwDOSTime = ((((ptm->tm_hour) & 0x1F) << 11) +
		(((ptm->tm_min) & 0x3F) << 5) +
		(((ptm->tm_sec / 2) & 0x1F)));
}

static void dos_time_to_tm(WORD wDOSDate, WORD wDOSTime, struct tm *ptm)
{
  ptm->tm_mday = (wDOSDate & 0x1F);
  ptm->tm_mon = ((wDOSDate >> 5) & 0xF) - 1;
  ptm->tm_year = ((wDOSDate >> 9) & 0x7F) + 80;
  ptm->tm_sec = (wDOSTime & 0x1F) * 2;
  ptm->tm_min = ((wDOSTime >> 5) & 0x3F);
  ptm->tm_hour = ((wDOSTime >> 11) & 0x1F);
}

static BOOL DrvDos2File(WORD wDOSDate, WORD wDOSTime, LPFILETIME lpft)
{
  struct tm stm;
  time_t t;

  dos_time_to_tm(wDOSDate, wDOSTime, &stm);
  t = mktime(&stm);
  if (t == -1)
    return FALSE;
  time_t_to_filetime(t, lpft);
  return TRUE;
}

static BOOL DrvFile2Dos(LPFILETIME lpft, LPWORD lpwDOSDate, LPWORD lpwDOSTime)
{
  struct tm *ptm;
  time_t t;

  t = filetime_to_time_t(lpft);
  ptm = gmtime(&t);
  tm_to_dos_time(ptm, lpwDOSDate, lpwDOSTime);
  return TRUE;
}

static BOOL DrvSys2TZILoc(LPTIME_ZONE_INFORMATION lpTimeZoneInformation,
				     LPSYSTEMTIME lpUCT, LPSYSTEMTIME lpst)
{
  FILETIME ft;
  time_t t;
  DrvSystem2File(lpUCT, &ft);
  t = filetime_to_time_t(&ft);
  t -= lpTimeZoneInformation->Bias * 60;
  time_t_to_filetime(t, &ft);
  DrvFile2System(&ft, lpst);
  return TRUE;
}
DWORD
PrivateTimeHook(WORD wMsg, LPARAM dwParam1,
		LPARAM dwParam2, LPVOID lpStruct)
{
    switch (wMsg) {
	case DSUBSYSTEM_INIT:
	case DSUBSYSTEM_GETCAPS:
	case DSUBSYSTEM_EVENTS:
	    return 1L;

	/* dwParam1 - LPFILETIME */
	/* dwParam2 - LPSYSTEMTIME */
	case PTMH_FT2ST:
	    return (DWORD)DrvFile2System((LPFILETIME)dwParam1,
			(LPSYSTEMTIME)dwParam2);

	/* dwParam1 - LPSYSTEMTIME */
	/* dwParam2 - LPFILETIME */
	case PTMH_ST2FT:
	    return (DWORD)DrvSystem2File((LPSYSTEMTIME)dwParam1,
			(LPFILETIME)dwParam2);

	/* dwParam1 - LPFILETIME */
	/* dwParam2 - LPFILETIME */
	case PTMH_FT2LFT:
	    return (DWORD)DrvFile2Local((LPFILETIME)dwParam1,
			(LPFILETIME)dwParam2);

	/* dwParam1 - LPFILETIME */
	/* dwParam2 - LPFILETIME */
	case PTMH_LFT2FT:
	    return (DWORD)DrvLocal2File((LPFILETIME)dwParam1,
			(LPFILETIME)dwParam2);

	/* dwParam1 - LPSYSTEMTIME */
	case PTMH_GETSYS:
	    return (DWORD)DrvGetSysTime((LPSYSTEMTIME)dwParam1);

	/* dwParam1 - LPSYSTEMTIME */
	case PTMH_GETLOC:
	    return (DWORD)DrvGetLocTime((LPSYSTEMTIME)dwParam1);

	/* dwParam1 - LPSYSTEMTIME */
	case PTMH_SETSYS:
	    return (DWORD)DrvSetSysTime((LPSYSTEMTIME)dwParam1);

	/* dwParam1 - LPSYSTEMTIME */
	case PTMH_SETLOC:
	    return (DWORD)DrvSetLocTime((LPSYSTEMTIME)dwParam1);

	/* dwParam1 - LPTIME_ZONE_INFORMATION */
	case PTMH_SETTZI:
	    return (DWORD)DrvSetTZI((LPTIME_ZONE_INFORMATION)dwParam1);

	/* dwParam1 - LPTIME_ZONE_INFORMATION */
	case PTMH_GETTZI:
	    return (DWORD)DrvGetTZI((LPTIME_ZONE_INFORMATION)dwParam1);

	/* dwParam1 - WORD */
	/* dwParam2 - WORD */
	/* lpStruct - LPFILETIME */
	case PTMH_DOS2FT:
	    return (DWORD)DrvDos2File((WORD)dwParam1,
		(WORD)dwParam2,(LPFILETIME)lpStruct);

	/* dwParam1 - LPFILETIME */
	/* dwParam2 - LPWORD */
	/* lpStruct - LPWORD */
	case PTMH_FT2DOS:
	    return (DWORD)DrvFile2Dos((LPFILETIME)dwParam1,
		(LPWORD)dwParam2,(LPWORD)lpStruct);

	/* dwParam1 - LPTIME_ZONE_INFORMATION */
	/* dwParam2 - LPSYSTEMTIME */
	/* lpStruct - LPSYSTEMTIME */
	case PTMH_SYS2TZILOC:
	    return (DWORD)DrvSys2TZILoc((LPTIME_ZONE_INFORMATION)dwParam1,
		(LPSYSTEMTIME)dwParam2,(LPSYSTEMTIME)lpStruct);

    }

    return (DWORD)FALSE;
}

