/******************************************************************************
 * The MIT License (MIT)
 *
 * Copyright (c) 2015-2017 Baldur Karlsson
 * Copyright (c) 2014 Crytek
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 ******************************************************************************/

#include <time.h>
#include "os/os_specific.h"

double Timing::GetTickFrequency()
{
  LARGE_INTEGER li;
  QueryPerformanceFrequency(&li);
  return double(li.QuadPart) / 1000.0;
}

uint64_t Timing::GetTick()
{
  LARGE_INTEGER li;
  QueryPerformanceCounter(&li);

  return li.QuadPart;
}

uint64_t Timing::GetUnixTimestamp()
{
  return (uint64_t)time(NULL);
}

namespace Atomic
{
int32_t Inc32(volatile int32_t *i)
{
  return (int32_t)InterlockedIncrement((volatile LONG *)i);
}

int32_t Dec32(volatile int32_t *i)
{
  return (int32_t)InterlockedDecrement((volatile LONG *)i);
}

int64_t Inc64(volatile int64_t *i)
{
  return (int64_t)InterlockedIncrement64((volatile LONG64 *)i);
}

int64_t Dec64(volatile int64_t *i)
{
  return (int64_t)InterlockedDecrement64((volatile LONG64 *)i);
}

int64_t ExchAdd64(volatile int64_t *i, int64_t a)
{
  return (int64_t)InterlockedExchangeAdd64((volatile LONG64 *)i, a);
}

int32_t CmpExch32(volatile int32_t *dest, int32_t oldVal, int32_t newVal)
{
  return (int32_t)InterlockedCompareExchange((volatile LONG *)dest, newVal, oldVal);
}
};

namespace Threading
{
CriticalSection::CriticalSectionTemplate()
{
  InitializeCriticalSection(&m_Data);
}

CriticalSection::~CriticalSectionTemplate()
{
  DeleteCriticalSection(&m_Data);
}

void CriticalSection::Lock()
{
  EnterCriticalSection(&m_Data);
}

bool CriticalSection::Trylock()
{
  return TryEnterCriticalSection(&m_Data) == TRUE;
}

void CriticalSection::Unlock()
{
  LeaveCriticalSection(&m_Data);
}

struct ThreadInitData
{
  ThreadEntry entryFunc;
  void *userData;
};

static DWORD __stdcall sThreadInit(void *init)
{
  ThreadInitData *data = (ThreadInitData *)init;

  // delete before going into entry function so lifetime is limited.
  ThreadInitData local = *data;
  delete data;

  local.entryFunc(local.userData);

  return 0;
}

// to not exhaust OS slots, we only allocate one that points
// to our own array
DWORD OSTLSHandle;
int64_t nextTLSSlot = 0;

struct TLSData
{
  vector<void *> data;
};

static CriticalSection *m_TLSListLock = NULL;
static vector<TLSData *> *m_TLSList = NULL;

void Init()
{
  OSTLSHandle = TlsAlloc();
  if(OSTLSHandle == TLS_OUT_OF_INDEXES)
    RDCFATAL("Can't allocate OS TLS slot");

  m_TLSListLock = new CriticalSection();
  m_TLSList = new vector<TLSData *>();
}

void Shutdown()
{
  for(size_t i = 0; i < m_TLSList->size(); i++)
    delete m_TLSList->at(i);

  delete m_TLSList;
  delete m_TLSListLock;

  TlsFree(OSTLSHandle);
}

// allocate a TLS slot in our per-thread vectors with an atomic increment.
// Note this is going to be 1-indexed because Inc64 returns the post-increment
// value
uint64_t AllocateTLSSlot()
{
  return Atomic::Inc64(&nextTLSSlot);
}

// look up our per-thread vector.
void *GetTLSValue(uint64_t slot)
{
  TLSData *slots = (TLSData *)TlsGetValue(OSTLSHandle);
  if(slots == NULL || slot - 1 >= slots->data.size())
    return NULL;
  return slots->data[(size_t)slot - 1];
}

void SetTLSValue(uint64_t slot, void *value)
{
  TLSData *slots = (TLSData *)TlsGetValue(OSTLSHandle);

  // resize or allocate slot data if needed.
  // We don't need to lock this, as it is by definition thread local so we are
  // blocking on the only possible concurrent access.
  if(slots == NULL || slot - 1 >= slots->data.size())
  {
    if(slots == NULL)
    {
      slots = new TLSData;
      TlsSetValue(OSTLSHandle, slots);

      // in the case where this thread is entirely new, we globally lock so we can
      // store its data for shutdown (as we might not get notified of every thread
      // that exits). This only happens once, so we take the hit of the lock.
      m_TLSListLock->Lock();
      m_TLSList->push_back(slots);
      m_TLSListLock->Unlock();
    }

    if(slot - 1 >= slots->data.size())
    slots->data.resize((size_t)slot);
  }

  slots->data[(size_t)slot - 1] = value;
}

ThreadHandle CreateThread(ThreadEntry entryFunc, void *userData)
{
  ThreadInitData *initData = new ThreadInitData;
  initData->entryFunc = entryFunc;
  initData->userData = userData;

  HANDLE h = ::CreateThread(NULL, 0, &sThreadInit, (void *)initData, 0, NULL);

  return (ThreadHandle)h;
}

uint64_t GetCurrentID()
{
  return (uint64_t)::GetCurrentThreadId();
}

void JoinThread(ThreadHandle handle)
{
  if(handle == 0)
    return;
  WaitForSingleObject((HANDLE)handle, INFINITE);
}

void CloseThread(ThreadHandle handle)
{
  if(handle == 0)
    return;
  CloseHandle((HANDLE)handle);
}

static HMODULE ownModuleHandle = 0;

void KeepModuleAlive()
{
  // deliberately omitting GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT to bump refcount
  GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (const char *)&ownModuleHandle,
                     &ownModuleHandle);
}

void ReleaseModuleExitThread()
{
  FreeLibraryAndExitThread(ownModuleHandle, 0);
}

void Sleep(uint32_t milliseconds)
{
  ::Sleep((DWORD)milliseconds);
}
};
