#include <Time/ServerTime.h>

#include <RKHeap.h>

#include <time.h>
#include <limits.h>
#include <stdlib.h>
#include <algorithm>
#include <cmath>
#include <sstream>
#include <iomanip>
#include <cstring>

#if defined(GAME_WIN32)
#if !defined(snprintf)
#define snprintf _snprintf
#endif
#endif

#if defined(GAME_WIN32)
#define gmtime_safe(out, in) gmtime_s(out, in)
#define localtime_safe(out, in) localtime_s(out, in)
#else
#define gmtime_safe(out, in) gmtime_r(in, out)
#define localtime_safe(out, in) localtime_r(in, out)
#endif

const Timestamp Timestamp::INVALID_TIMESTAMP = Timestamp(0);
const Timestamp Timestamp::BEGINNING_OF_TIME = Timestamp(1);
const Timestamp Timestamp::MAX_TIMESTAMP = Timestamp(INT_MAX);
const int32 Timestamp::SECONDS_IN_DAY = 24 * 60 * 60;
const Timestamp Timestamp::ONE_DAY = Timestamp(SECONDS_IN_DAY);

ServerTimeManager* ServerTimeManager::m_instance = nullptr;

Timestamp ServerTimeManager::GetApproximateServerTime()
{
  ServerTimeManager* thisPtr = Get();
  if (thisPtr)
  {
    return Timestamp(thisPtr->m_lastServerClock + Timestamp(thisPtr->m_leftoverTime + thisPtr->m_timer.GetUpTime()));
  }
  else
  {
    return Timestamp::Now();
  }
}

void ServerTimeManager::OnServerTimestampReceived(Timestamp nServerTimestamp)
{
  m_lastServerClock = nServerTimestamp;

  // Store the remaining sub-second times from last server timestamp for less jerky countdowns.
  double discardedPart;
  m_leftoverTime = modf(m_leftoverTime + m_timer.GetUpTime(), &discardedPart);
  
  // Stay in the past so that events are not sent ahead of time
  if (m_leftoverTime > 0.0f)
  {
    m_leftoverTime -= 1.0f;
  }

  // Reset the timer.
  m_timer.Init();
}

void ServerTimeManager::RegisterService()
{
  if (!m_instance)
  {
    m_instance = new ServerTimeManager();
  }
}

void ServerTimeManager::UnregisterService()
{
  delete m_instance;
  m_instance = nullptr;
}

int64_t ServerTimeManager::ConvertUTCTimeStringToSeconds(const std::string & serverTime)
{
  if (serverTime.empty())
  {
    return 0;
  }

  time_t timeAsSecs = 0;
  tm stagingTm = {};
  std::memset(&stagingTm, 0, sizeof(struct tm));
  // Convert to tm time
#if defined(GAME_WIN32)
  std::stringstream serverTimeAsStream(serverTime);
  serverTimeAsStream >> std::get_time(&stagingTm, "%Y-%m-%d%t%H:%M:%S");
#else
  // Our linux build isn't fully C++11 compliant!!!
  std::string strGMTDate = serverTime;
  strGMTDate += " GMT";
  strptime(strGMTDate.c_str(), "%Y-%m-%d %H:%M:%SZ %Z", &stagingTm);
#endif
  timeAsSecs = mktime(&stagingTm);

#if defined(OS_ANDROID) || defined(WIN32) || defined(OS_LINUX)
  // Calculate the time offset for our time zone
  time_t rawtime = 0;
  tm timeinfo;
  ::time(&rawtime);

  localtime_safe(&timeinfo, &rawtime);

#ifndef OS_ANDROID  //tam.dnn fix 8916228
  timeinfo.tm_isdst = 0;
#endif
  time_t addSec = mktime(&timeinfo);

  gmtime_safe(&timeinfo, &rawtime);
  addSec -= mktime(&timeinfo);

  timeAsSecs += addSec;
#endif

  return timeAsSecs;
}

bool bne::Duration::operator==(const bne::Duration& other) const
{
  return GetStartTime() == other.GetStartTime() && GetEndTime() == other.GetEndTime();
}

bool bne::Duration::operator!=(const bne::Duration& other) const
{
  return !(*this == other);
}

int64_t bne::Duration::GetTotalDuration() const
{
  return ServerTimeManager::GetTimeDifference(m_end, m_start);
}

int64_t bne::Duration::GetRemainingDuration(Timestamp now) const
{
  return std::max(ServerTimeManager::GetTimeDifference(m_end, now), int64_t(0));
}

int64_t bne::Duration::GetElapsedDuration(Timestamp now) const
{
  return ServerTimeManager::GetTimeDifference(now, m_start);
}

bool bne::Duration::IsComplete(Timestamp now) const
{
  return ServerTimeManager::GetTimeDifference(now, GetEndTime()) >= 0;
}

bool bne::Duration::IsActive() const
{
  return GetStartTime() != Timestamp::INVALID_TIMESTAMP && GetEndTime() != Timestamp::INVALID_TIMESTAMP;
}

void bne::Duration::Start(int32 a_duration)
{
  SetStartTime(ServerTimeManager::GetApproximateServerTime());
  SetEndTime(Timestamp(a_duration) + GetStartTime());
}

void bne::Duration::Stop()
{
  SetStartTime(Timestamp::INVALID_TIMESTAMP);
  SetEndTime(Timestamp::INVALID_TIMESTAMP);
}

void bne::Duration::ShiftTime(int32 a_secOffset)
{
  Timestamp offset(a_secOffset);
  SetStartTime(offset + GetStartTime());
  SetEndTime(offset + GetEndTime());
  if (GetEndTime() < GetStartTime()) { SetEndTime(GetStartTime()); }
}
