/* Blinker
   Copyright 2011-2019 Stefan Schuermans <stefan@blinkenarea.org>
   Copyleft GNU public license - http://www.gnu.org/copyleft/gpl.html
   a blinkenarea.org project */

#include <winsock2.h> // not allowed after windows.h, so include here
#include <windows.h>
#include <iomanip>
#include <math.h>
#include <sstream>
#include <stdint.h>
#include <string>

#include "Time.h"

namespace Blinker {

const Time Time::zero(0); ///< zero time

/**
 * @brief get current time
 * @return current time
 */
Time Time::now()
{
  Time now;

  SYSTEMTIME st;
  GetSystemTime(&st);
  FILETIME ft;
  SystemTimeToFileTime(&st, &ft);
  now.fromFileTime(ft);

  return now;
}

/// constructor
Time::Time():
  m_sec(0),
  m_ns(0)
{
}

/**
 * @brief constructor from seconds
 * @param[in] t time in seconds
 */
Time::Time(time_t t):
  m_sec(t),
  m_ns(0)
{
}

/// comparison
//@{

int Time::compare(const Time &that) const
{
  if (m_sec < that.m_sec)
    return -1;
  if (m_sec > that.m_sec)
    return 1;
  if (m_ns < that.m_ns)
    return -1;
  if (m_ns > that.m_ns)
    return 1;
  return 0;
}

bool Time::operator==(const Time &that) const
{
  return compare(that) == 0;
}

bool Time::operator!=(const Time &that) const
{
  return compare(that) != 0;
}

bool Time::operator<(const Time &that) const
{
  return compare(that) < 0;
}

bool Time::operator>(const Time &that) const
{
  return compare(that) > 0;
}

bool Time::operator<=(const Time &that) const
{
  return compare(that) <= 0;
}

bool Time::operator>=(const Time &that) const
{
  return compare(that) >= 0;
}

//@}

/// arithmetic
//@{

const Time & Time::operator+=(const Time &that)
{
  m_sec += that.m_sec;
  m_ns += that.m_ns;
  fix();
  return *this;
}

const Time & Time::operator-=(const Time &that)
{
  m_sec -= that.m_sec;
  m_ns -= that.m_ns;
  fix();
  return *this;
}

Time Time::operator+(const Time &that) const
{
  Time result(*this);
  result += that;
  return result;
}

Time Time::operator-(const Time &that) const
{
  Time result(*this);
  result -= that;
  return result;
}

//@}

/**
 * @brief convert from floating point seconds
 * @param[in] s time in seconds
 */
void Time::fromFloatSec(float s)
{
  m_sec = (int64_t)truncf(s);
  m_ns  = (int64_t)((s - m_sec) * 1.0e9);
}

/**
 * @brief convert from milliseconds
 * @param[in] ms milliseconds
 */
void Time::fromMs(int ms)
{
  if (ms >= 0) {
    m_sec = ms / 1000;
    m_ns = (ms % 1000) * 1000000;
  } else {
    m_sec = -(-ms / 1000);
    m_ns = -(-ms % 1000) * 1000000;
  }
}

/**
 * @brief convert to seconds
 * @return seconds
 */
time_t Time::toSec() const
{
  if (m_ns >= 500000000)
    return m_sec + 1;
  else if (m_ns <= -500000000)
    return m_sec - 1;
  else
    return m_sec;
}

/**
 * @brief convert to floating point seconds
 * @return time in seconds
 */
float Time::toFloatSec() const
{
  return m_sec + m_ns * 1.0e-9f;
}

/**
 * @brief convert to milliseconds
 * @return milliseconds
 */
int Time::toMs() const
{
  if (m_sec > INT_MAX / 1000) {
    return INT_MAX;
  }
  if (m_sec < INT_MIN / 1000) {
    return INT_MIN;
  }
  int ms = m_sec * 1000;
  int ms2 = m_ns / 1000000;
  if (ms2 > 0 && ms > INT_MAX - ms2) {
    return INT_MAX;
  }
  if (ms2 < 0 && ms < INT_MIN - ms2) {
    return INT_MIN;
  }
  return ms + ms2;
}

/**
 * @brief convert to struct timeval
 * @param[out] tv struct timeval
 */
void Time::toTimeval(struct timeval &tv) const
{
  if (m_sec >= 0) {
    tv.tv_sec = m_sec;
    tv.tv_usec = (m_ns + 500) / 1000;
    if (tv.tv_usec >= 1000000) {
      ++tv.tv_sec;
      tv.tv_usec -= 1000000;
    }
  } else {
    tv.tv_sec = m_sec;
    tv.tv_usec = -((-m_ns + 500) / 1000);
    if (tv.tv_usec <= -1000000) {
      --tv.tv_sec;
      tv.tv_usec += 1000000;
    }
  }
}

/**
 * @brief convert from file time strcuture
 * @param[in] ft file time structure
 */
void Time::fromFileTime(FILETIME const &ft)
{
  union {
    FILETIME ft;
    ULARGE_INTEGER u;
  } ft_u;
  ft_u.ft = ft;
  m_sec = ft_u.u.QuadPart / 10000000ULL;
  m_ns = (ft_u.u.QuadPart % 10000000ULL) * 100ULL;
}

/**
 * @brief convert to human-readable string
 * @return human-readable string
 */
std::string Time::toStr() const
{
  time_t sec = m_sec;
  struct tm t;
  localtime_s(&t, &sec);
  std::stringstream strm;
  strm << std::setfill('0')
       << std::setw(4) << (t.tm_year + 1900)
       << "-" << std::setw(2) << (t.tm_mon + 1)
       << "-" << std::setw(2) << t.tm_mday
       << " " << std::setw(2) << t.tm_hour
       << ":" << std::setw(2) << t.tm_min
       << ":" << std::setw(2) << t.tm_sec
       << "." << std::setw(9) << m_ns;
  return strm.str();
}

/// fix internal time representation after calculation
void Time::fix()
{
  if (m_ns >= 1000000000) {
    m_sec += m_ns / 1000000000;
    m_ns = m_ns % 1000000000;
  } else if (m_ns <= -1000000000) {
    m_sec -= -m_ns / 1000000000;
    m_ns = -(-m_ns % 1000000000);
  }
  if (m_sec > 0 && m_ns < 0) {
    m_sec -= 1;
    m_ns += 1000000000;
  } else if (m_sec < 0 && m_ns > 0) {
    m_sec += 1;
    m_ns -= 1000000000;
  }
}

/// sleep for duration
void Time::sleepFor() const
{
  // do not sleep for negative time
  if (*this < zero)
    return;

  // sleep
  DWORD ms = m_ns / 1000000ULL + m_sec * 1000;
  Sleep(ms);
}

/// sleep until time
void Time::sleepUntil() const
{
  (*this - now()).sleepFor();
}

} // namespace Blinker

