#include "timestamp.h"
#include <cstring>
#include <ctime>

#if defined(WIN32)

static void UnixTimeToSystemTime(time_t t, LPSYSTEMTIME pst);
static time_t SystemTimeToUnixTime(const SYSTEMTIME* pst);

#endif

Timestamp::Timestamp()
{
#if defined(WIN32)
  m_value.wYear = 1970;
  m_value.wMonth = 1;
  m_value.wDayOfWeek = 0;
  m_value.wDay = 1;
  m_value.wHour = 0;
  m_value.wMinute = 0;
  m_value.wSecond = 0;
  m_value.wMilliseconds = 0;
#else
  m_value.tv_sec = 0;
  m_value.tv_usec = 0;
#endif
}

Timestamp::Timestamp(const Timestamp& copy)
{
#if defined(WIN32)
  std::memcpy(&m_value, &copy.m_value, sizeof(m_value));
#else
  std::memcpy(&m_value, &copy.m_value, sizeof(m_value));
#endif
}

double Timestamp::DifferenceInSeconds(Timestamp& other) const
{
#if defined(WIN32)
  FILETIME lft, rft;
  SystemTimeToFileTime(&m_value, &lft);
  SystemTimeToFileTime(&other.m_value, &rft);

  u64 lval = ((u64)lft.dwHighDateTime) << 32 | (u64)lft.dwLowDateTime;
  u64 rval = ((u64)rft.dwHighDateTime) << 32 | (u64)rft.dwLowDateTime;
  s64 diff = ((s64)lval - (s64)rval);
  return double(diff / 10000000ULL) + (double(diff % 10000000ULL) / 10000000.0);

#else
  return (double)(m_value.tv_sec - other.m_value.tv_sec) +
         (((double)(m_value.tv_usec - other.m_value.tv_usec)) / 1000000.0);
#endif
}

s64 Timestamp::DifferenceInSecondsInt(Timestamp& other) const
{
#if defined(WIN32)
  FILETIME lft, rft;
  SystemTimeToFileTime(&m_value, &lft);
  SystemTimeToFileTime(&other.m_value, &rft);

  u64 lval = ((u64)lft.dwHighDateTime) << 32 | (u64)lft.dwLowDateTime;
  u64 rval = ((u64)rft.dwHighDateTime) << 32 | (u64)rft.dwLowDateTime;
  s64 diff = ((s64)lval - (s64)rval);
  return diff / 10000000ULL;

#else
  return static_cast<s64>(m_value.tv_sec - other.m_value.tv_sec);
#endif
}

Timestamp::UnixTimestampValue Timestamp::AsUnixTimestamp() const
{
#if defined(WIN32)
  return (UnixTimestampValue)SystemTimeToUnixTime(&m_value);
#else
  return (UnixTimestampValue)m_value.tv_sec;
#endif
}

Timestamp::ExpandedTime Timestamp::AsExpandedTime() const
{
  ExpandedTime et;

#if defined(WIN32)
  et.Year = m_value.wYear;
  et.Month = m_value.wMonth;
  et.DayOfMonth = m_value.wDay;
  et.DayOfWeek = m_value.wDayOfWeek;
  et.Hour = m_value.wHour;
  et.Minute = m_value.wMinute;
  et.Second = m_value.wSecond;
  et.Milliseconds = m_value.wMilliseconds;
#else
  struct tm t;
  time_t unixTime = (time_t)m_value.tv_sec;
  gmtime_r(&unixTime, &t);
  et.Year = t.tm_year + 1900;
  et.Month = t.tm_mon + 1;
  et.DayOfMonth = t.tm_mday;
  et.DayOfWeek = t.tm_wday;
  et.Hour = t.tm_hour;
  et.Minute = t.tm_min;
  et.Second = t.tm_sec;
  et.Milliseconds = m_value.tv_usec / 1000;
#endif

  return et;
}

void Timestamp::SetNow()
{
#if defined(WIN32)
  GetSystemTime(&m_value);
#else
  gettimeofday(&m_value, NULL);
#endif
}

void Timestamp::SetUnixTimestamp(UnixTimestampValue value)
{
#if defined(WIN32)
  UnixTimeToSystemTime((time_t)value, &m_value);
#else
  m_value.tv_sec = (time_t)value;
  m_value.tv_usec = 0;
#endif
}

void Timestamp::SetExpandedTime(const ExpandedTime& value)
{
#if defined(WIN32)
  // bit of a hacky way to fill in the missing fields
  SYSTEMTIME st;
  st.wYear = (WORD)value.Year;
  st.wMonth = (WORD)value.Month;
  st.wDay = (WORD)value.DayOfMonth;
  st.wDayOfWeek = (WORD)0;
  st.wHour = (WORD)value.Hour;
  st.wMinute = (WORD)value.Minute;
  st.wSecond = (WORD)value.Second;
  st.wMilliseconds = (WORD)value.Milliseconds;
  FILETIME ft;
  SystemTimeToFileTime(&st, &ft);
  FileTimeToSystemTime(&ft, &m_value);
#else
  struct tm t;
  std::memset(&t, 0, sizeof(t));
  t.tm_sec = value.Second;
  t.tm_min = value.Minute;
  t.tm_hour = value.Hour;
  t.tm_mday = value.DayOfMonth;
  t.tm_mon = value.Month - 1;
  t.tm_year = value.Year - 1900;
  time_t unixTime = mktime(&t);
  SetUnixTimestamp((UnixTimestampValue)unixTime);
#endif
}

String Timestamp::ToString(const char* format) const
{
  SmallString destination;
  ToString(destination, format);
  return String(destination);
}

void Timestamp::ToString(String& destination, const char* format) const
{
  time_t unixTime = (time_t)AsUnixTimestamp();
  tm localTime;

#if defined(WIN32)
  localtime_s(&localTime, &unixTime);
#else
  localtime_r(&unixTime, &localTime);
#endif

  char buffer[256];
  strftime(buffer, countof(buffer) - 1, format, &localTime);
  buffer[countof(buffer) - 1] = 0;

  destination.Clear();
  destination.AppendString(buffer);
}

Timestamp Timestamp::Now()
{
  Timestamp t;
  t.SetNow();
  return t;
}

Timestamp Timestamp::FromUnixTimestamp(UnixTimestampValue value)
{
  Timestamp t;
  t.SetUnixTimestamp(value);
  return t;
}

Timestamp Timestamp::FromExpandedTime(const ExpandedTime& value)
{
  Timestamp t;
  t.SetExpandedTime(value);
  return t;
}

bool Timestamp::operator==(const Timestamp& other) const
{
#if defined(WIN32)
  return std::memcmp(&m_value, &other.m_value, sizeof(m_value)) == 0;
#else
  return std::memcmp(&m_value, &other.m_value, sizeof(m_value)) == 0;
#endif
}

bool Timestamp::operator!=(const Timestamp& other) const
{
  return !operator==(other);
}

bool Timestamp::operator<(const Timestamp& other) const
{
#if defined(WIN32)

  if (m_value.wYear > other.m_value.wYear)
    return false;
  else if (m_value.wYear < other.m_value.wYear)
    return true;

  if (m_value.wMonth > other.m_value.wMonth)
    return false;
  else if (m_value.wMonth < other.m_value.wMonth)
    return true;

  if (m_value.wDay > other.m_value.wDay)
    return false;
  else if (m_value.wDay < other.m_value.wDay)
    return true;

  if (m_value.wHour > other.m_value.wHour)
    return false;
  else if (m_value.wHour < other.m_value.wHour)
    return true;

  if (m_value.wMinute > other.m_value.wMinute)
    return false;
  else if (m_value.wMinute < other.m_value.wMinute)
    return true;

  if (m_value.wSecond > other.m_value.wSecond)
    return false;
  else if (m_value.wSecond < other.m_value.wSecond)
    return true;

  if (m_value.wMilliseconds > other.m_value.wMilliseconds)
    return false;
  else if (m_value.wMilliseconds < other.m_value.wMilliseconds)
    return true;

  return false;

#else

  if (m_value.tv_sec > other.m_value.tv_sec)
    return false;
  else if (m_value.tv_sec < other.m_value.tv_sec)
    return true;

  if (m_value.tv_usec > other.m_value.tv_usec)
    return false;
  else if (m_value.tv_usec < other.m_value.tv_usec)
    return true;

  return false;

#endif
}

bool Timestamp::operator<=(const Timestamp& other) const
{
#if defined(WIN32)

  if (m_value.wYear > other.m_value.wYear)
    return false;
  else if (m_value.wYear < other.m_value.wYear)
    return true;

  if (m_value.wMonth > other.m_value.wMonth)
    return false;
  else if (m_value.wMonth < other.m_value.wMonth)
    return true;

  if (m_value.wDay > other.m_value.wDay)
    return false;
  else if (m_value.wDay < other.m_value.wDay)
    return true;

  if (m_value.wHour > other.m_value.wHour)
    return false;
  else if (m_value.wHour < other.m_value.wHour)
    return true;

  if (m_value.wMinute > other.m_value.wMinute)
    return false;
  else if (m_value.wMinute < other.m_value.wMinute)
    return true;

  if (m_value.wSecond > other.m_value.wSecond)
    return false;
  else if (m_value.wSecond <= other.m_value.wSecond)
    return true;

  if (m_value.wMilliseconds > other.m_value.wMilliseconds)
    return false;
  else if (m_value.wMilliseconds < other.m_value.wMilliseconds)
    return true;

  return false;

#else

  if (m_value.tv_sec > other.m_value.tv_sec)
    return false;
  else if (m_value.tv_sec < other.m_value.tv_sec)
    return true;

  if (m_value.tv_usec > other.m_value.tv_usec)
    return false;
  else if (m_value.tv_usec <= other.m_value.tv_usec)
    return true;

  return false;

#endif
}

bool Timestamp::operator>(const Timestamp& other) const
{
#if defined(WIN32)

  if (m_value.wYear < other.m_value.wYear)
    return false;
  else if (m_value.wYear > other.m_value.wYear)
    return true;

  if (m_value.wMonth < other.m_value.wMonth)
    return false;
  else if (m_value.wMonth > other.m_value.wMonth)
    return true;

  if (m_value.wDay < other.m_value.wDay)
    return false;
  else if (m_value.wDay > other.m_value.wDay)
    return true;

  if (m_value.wHour < other.m_value.wHour)
    return false;
  else if (m_value.wHour > other.m_value.wHour)
    return true;

  if (m_value.wMinute < other.m_value.wMinute)
    return false;
  else if (m_value.wMinute > other.m_value.wMinute)
    return true;

  if (m_value.wSecond < other.m_value.wSecond)
    return false;
  else if (m_value.wSecond > other.m_value.wSecond)
    return true;

  if (m_value.wMilliseconds < other.m_value.wMilliseconds)
    return false;
  else if (m_value.wMilliseconds > other.m_value.wMilliseconds)
    return true;

  return false;

#else

  if (m_value.tv_sec < other.m_value.tv_sec)
    return false;
  else if (m_value.tv_sec > other.m_value.tv_sec)
    return true;

  if (m_value.tv_usec < other.m_value.tv_usec)
    return false;
  else if (m_value.tv_usec > other.m_value.tv_usec)
    return true;

  return false;

#endif
}

bool Timestamp::operator>=(const Timestamp& other) const
{
#if defined(WIN32)

  if (m_value.wYear < other.m_value.wYear)
    return false;
  else if (m_value.wYear > other.m_value.wYear)
    return true;

  if (m_value.wMonth < other.m_value.wMonth)
    return false;
  else if (m_value.wMonth > other.m_value.wMonth)
    return true;

  if (m_value.wDay < other.m_value.wDay)
    return false;
  else if (m_value.wDay > other.m_value.wDay)
    return true;

  if (m_value.wHour < other.m_value.wHour)
    return false;
  else if (m_value.wHour > other.m_value.wHour)
    return true;

  if (m_value.wMinute < other.m_value.wMinute)
    return false;
  else if (m_value.wMinute > other.m_value.wMinute)
    return true;

  if (m_value.wSecond < other.m_value.wSecond)
    return false;
  else if (m_value.wSecond >= other.m_value.wSecond)
    return true;

  if (m_value.wMilliseconds < other.m_value.wMilliseconds)
    return false;
  else if (m_value.wMilliseconds > other.m_value.wMilliseconds)
    return true;

  return false;

#else

  if (m_value.tv_sec < other.m_value.tv_sec)
    return false;
  else if (m_value.tv_sec > other.m_value.tv_sec)
    return true;

  if (m_value.tv_usec < other.m_value.tv_usec)
    return false;
  else if (m_value.tv_usec >= other.m_value.tv_usec)
    return true;

  return false;

#endif
}

Timestamp& Timestamp::operator=(const Timestamp& other)
{
#if defined(WIN32)
  std::memcpy(&m_value, &other.m_value, sizeof(m_value));
#else
  std::memcpy(&m_value, &other.m_value, sizeof(m_value));
#endif

  return *this;
}

#if defined(WIN32)

// http://support.microsoft.com/kb/167296
static void UnixTimeToFileTime(time_t t, LPFILETIME pft)
{
  LONGLONG ll;
  ll = Int32x32To64(t, 10000000ULL) + 116444736000000000ULL;
  pft->dwLowDateTime = (DWORD)ll;
  pft->dwHighDateTime = ll >> 32;
}
static void UnixTimeToSystemTime(time_t t, LPSYSTEMTIME pst)
{
  FILETIME ft;
  UnixTimeToFileTime(t, &ft);
  FileTimeToSystemTime(&ft, pst);
}
static time_t FileTimeToUnixTime(const FILETIME* pft)
{
  LONGLONG ll = ((LONGLONG)pft->dwHighDateTime) << 32 | (LONGLONG)pft->dwLowDateTime;
  ll -= 116444736000000000ULL;
  ll /= 10000000ULL;
  return (time_t)ll;
}
static time_t SystemTimeToUnixTime(const SYSTEMTIME* pst)
{
  FILETIME ft;
  SystemTimeToFileTime(pst, &ft);
  return FileTimeToUnixTime(&ft);
}

FILETIME Timestamp::AsFileTime()
{
  FILETIME ft;
  SystemTimeToFileTime(&m_value, &ft);
  return ft;
}

void Timestamp::SetWindowsFileTime(const FILETIME* pFileTime)
{
  FileTimeToSystemTime(pFileTime, &m_value);
}

Timestamp Timestamp::FromWindowsFileTime(const FILETIME* pFileTime)
{
  Timestamp ts;
  ts.SetWindowsFileTime(pFileTime);
  return ts;
}

#endif
