/* Copyright (c) Robert Walker, support@tunesmithy.co.uk
 * Free source. Do what you wish with it - treat it as you would
 * example code in a book on c programming.
 */
#include "time64.h"
#undef time
#undef localtime
#undef mktime
#undef difftime
#undef gmtime
#undef time_t

#include <time.h>
#include <windows.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>

#define SECS_TO_FT_MULT 10000000

#ifdef _DEBUG
# define DEBUG_TIME_T
#endif

/* From MSVC help:
 * The gmtime, mktime, and localtime functions use the same single,
 * statically allocated structure to hold their results. Each call to
 * one of these functions destroys the result of any previous call.
 * If timer represents a date before midnight, January 1, 1970,
 * gmtime returns NULL. There is no error return.
 *
 * So here is the struct to use for our 64 bit implementation
 *
 * However, it may be useful to be able to make this thread safe
 */

#ifdef USE_THREAD_LOCAL_VARIABLES
# define  TIME64_THREAD_LOCAL _declspec(thread)
#else
# define TIME64_THREAD_LOCAL
#endif

TIME64_THREAD_LOCAL static struct tm today_ret;

static void T64ToFileTime(t64 *pt,FILETIME *pft)
{
   LARGE_INTEGER li;
   li.QuadPart=*pt*SECS_TO_FT_MULT;
   pft->dwLowDateTime=li.LowPart;
   pft->dwHighDateTime=li.HighPart;
}

static void FileTimeToT64(FILETIME *pft,t64 *pt)
{
   LARGE_INTEGER li;
   li.LowPart = pft->dwLowDateTime;
   li.HighPart = pft->dwHighDateTime;
   *pt=li.QuadPart;
   *pt/=SECS_TO_FT_MULT;
}

#define  FindTimeTBase() (t64) 11644473600
 // calculated using
/**
static t64 FindTimeTBase(void)
{
 // Find 1st Jan 1970 as a FILETIME
 SYSTEMTIME st;
 FILETIME ft;
 memset(&st,0,sizeof(st));
 st.wYear=1970;
 st.wMonth=1;
 st.wDay=1;
 SystemTimeToFileTime(&st, &ft);
 FileTimeToT64(&ft,&tbase);
 return tbase;
}
**/

static void SystemTimeToT64(SYSTEMTIME *pst,t64 *pt)
{
   FILETIME ft;
   SystemTimeToFileTime( pst, &ft );
   FileTimeToT64( &ft, pt );
   *pt-=FindTimeTBase();
}

static void T64ToSystemTime(t64 *pt,SYSTEMTIME *pst)
{
   FILETIME ft;
   t64 t=*pt;
   t+=FindTimeTBase();
   T64ToFileTime(&t,&ft);
   FileTimeToSystemTime(&ft,pst);
}


t64 time_64(t64 *pt)
{
   t64 t;
   SYSTEMTIME st;
   GetSystemTime(&st);
   SystemTimeToT64(&st,&t);

#ifdef DEBUG_TIME_T
   {
      time_t t2=time(NULL);
      if ( t2 >= 0 )
         assert(abs(t2-(int)t)<=1);
      // the <=1 here is in case the seconds get incremented
      // betweeen the GetSystemTime(..) call and the time(..) call
   }
#endif
   if ( pt )
      *pt=t;
   return t;
}

double difftime_64(t64 time1, t64 time0)
{
   return (double)(time1-time0);
}

// Converts from a broken down time into calendar time represented as a time_t
// Assumes the incoming time is local and adjusts result to be UTC
t64 mktime_64( struct tm *today )
{
   t64 t;
   SYSTEMTIME st;
   TIME_ZONE_INFORMATION TimeZoneInformation;

   st.wDay=(WORD)today->tm_mday;
   st.wDayOfWeek=(WORD)today->tm_wday;
   st.wHour=(WORD)today->tm_hour;
   st.wMinute=(WORD)today->tm_min;
   st.wMonth=(WORD)(today->tm_mon+1);
   st.wSecond=(WORD)today->tm_sec;
   st.wYear=(WORD)(today->tm_year+1900);
   st.wMilliseconds=0;
   SystemTimeToT64(&st,&t);
   // adjust by timezone
   GetTimeZoneInformation( &TimeZoneInformation );
   t += (60*TimeZoneInformation.Bias);
   return t;
}

#define DAY_IN_SECS (60*60*24)

struct tm *gmtime_64(t64 *t)
{
   SYSTEMTIME st, styear;
   t64 t64Year;

   T64ToSystemTime(t,&st);
   today_ret.tm_wday=st.wDayOfWeek;
   today_ret.tm_min=st.wMinute;
   today_ret.tm_sec=st.wSecond;
   today_ret.tm_mon=st.wMonth-1;
   today_ret.tm_mday=st.wDay;
   today_ret.tm_hour=st.wHour;
   today_ret.tm_year=st.wYear-1900;
   memset(&styear,0,sizeof(styear));
   styear.wYear=st.wYear;
   styear.wMonth=1;
   styear.wDay=1;
   SystemTimeToT64(&styear,&t64Year);
   today_ret.tm_yday=(int)((*t-t64Year)/DAY_IN_SECS);
   today_ret.tm_isdst=0;

#ifdef DEBUG_TIME_T
   {
      struct tm today2;
      time_t t32=(time_t)*t;
      if ( t32 >= 0 )
      {
         today2=*gmtime(&t32);
fprintf(stderr,"yday %d %d wday %d %d min %d %d sec %d %d mon %d %d mday %d %d hour %d %d year %d %d\n",
         today_ret.tm_yday,today2.tm_yday,
         today_ret.tm_wday,today2.tm_wday,
         today_ret.tm_min,today2.tm_min,
         today_ret.tm_sec,today2.tm_sec,
         today_ret.tm_mon,today2.tm_mon,
         today_ret.tm_mday,today2.tm_mday,
         today_ret.tm_hour,today2.tm_hour,
         today_ret.tm_year,today2.tm_year);

         assert(today_ret.tm_yday==today2.tm_yday);
         assert(today_ret.tm_wday==today2.tm_wday);
         assert(today_ret.tm_min==today2.tm_min);
         assert(today_ret.tm_sec==today2.tm_sec);
         assert(today_ret.tm_mon==today2.tm_mon);
         assert(today_ret.tm_mday==today2.tm_mday);
         assert(today_ret.tm_hour==today2.tm_hour);
         assert(today_ret.tm_year==today2.tm_year);
      }
   }
#endif
   return &today_ret;
}

struct tm *localtime_64(t64 *pt)
{
   t64 t=*pt;
   FILETIME ft,ftlocal;
   TIME_ZONE_INFORMATION TimeZoneInformation;
   T64ToFileTime(&t,&ft);
   FileTimeToLocalFileTime(&ft,&ftlocal);
   FileTimeToT64(&ftlocal,&t);
   today_ret=*gmtime_64(&t);
   if ( GetTimeZoneInformation( &TimeZoneInformation ) == TIME_ZONE_ID_DAYLIGHT )
      today_ret.tm_isdst = 1;
   else
      today_ret.tm_isdst = 0;
   return &today_ret;
}

