/*
 * Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
 *
 * SPDX-License-Identifier: MIT
 */

#include "thread/thread.hpp"
#include "thread/monitor.hpp"
#include "os/os.hpp"

#if defined(_WIN32) || defined(__CYGWIN__)
#include <windows.h>
#endif  // _WIN32

namespace amd {

void Thread::create() {
  selfSuspendLock_ = new Monitor();
  data_ = NULL;
  handle_ = NULL;
  setState(CREATED);
}

Thread::Thread(const std::string& name, size_t stackSize, bool spawn)
    : handle_(NULL), name_(name), stackSize_(stackSize) {
  create();

  if (!spawn) return;

  if ((handle_ = Os::createOsThread(this))) {
    // Now we need to wait for Thread::main() to report back.
    ScopedLock sl(selfSuspendLock_);
    // Wait for main() to update state as INITIALIZED
    while (state() != Thread::INITIALIZED) {
      selfSuspendLock_->wait();
    }
  }
}

Thread::~Thread() {
#if defined(_WIN32)
  if (handle_ != NULL) {
    ::CloseHandle((HANDLE)handle_);
  }
#endif
  delete selfSuspendLock_;
}

void* Thread::main() {
#ifdef DEBUG
  Os::setCurrentThreadName(name().c_str());
#endif  // DEBUG
  Os::currentStackInfo(&stackBase_, &stackSize_);
  setCurrent();

  // Notify the parent thread that we are up and running.
  {
    ScopedLock sl(selfSuspendLock_);
    // Notify parent thread that the state is update as INITIALIZED
    setState(INITIALIZED);
    selfSuspendLock_->notify();
  }

  // Now we need to wait for Thread::start() to report back.
  {
    ScopedLock sl(selfSuspendLock_);
    // wait for start() to update state as RUNNABLE
    while (state() != Thread::RUNNABLE) {
      selfSuspendLock_->wait();
    }
  }


  if (state() == RUNNABLE) {
    run(data_);
  }

  setState(FINISHED);
  return NULL;
}

bool Thread::start(void* data) {
  if (state() != INITIALIZED) {
    return false;
  }

  data_ = data;
  // Notify the thread that the parent thread are up and running.
  {
    ScopedLock sl(selfSuspendLock_);
    // Notify main() that the state is updated as RUNNABLE
    setState(RUNNABLE);
    selfSuspendLock_->notify();
  }

  return true;
}

void Thread::resume() {
  ScopedLock sl(selfSuspendLock_);
  selfSuspendLock_->notify();
}

#if defined(__linux__)

namespace details {

__thread Thread* thread_ __attribute__((tls_model("initial-exec")));

}  // namespace details

void Thread::registerStack(address base, address top) {
  // Nothing to do.
}

void Thread::setCurrent() { details::thread_ = this; }

#elif defined(_WIN32)

namespace details {

#if defined(USE_DECLSPEC_THREAD)
__declspec(thread) Thread* thread_;
#else   // !USE_DECLSPEC_THREAD
DWORD threadIndex_ = TlsAlloc();
#endif  // !USE_DECLSPEC_THREAD

}  // namespace details

void Thread::registerStack(address base, address top) {
  // Nothing to do.
}

void Thread::setCurrent() {
#if defined(USE_DECLSPEC_THREAD)
  details::thread_ = this;
#else   // !USE_DECLSPEC_THREAD
  TlsSetValue(details::threadIndex_, this);
#endif  // !USE_DECLSPEC_THREAD
}

#endif

bool Thread::init() {
  static bool initialized_ = false;

  // We could use InitOnceExecuteOnce/pthread_once here:
  if (initialized_) {
    return true;
  }
  initialized_ = true;

  return true;
}

void Thread::tearDown() {
#if defined(_WIN32) && !defined(USE_DECLSPEC_THREAD)
  if (details::threadIndex_ != TLS_OUT_OF_INDEXES) {
    TlsFree(threadIndex_);
  }
#endif  // _WIN32 && !USE_DECLSPEC_THREAD
}

}  // namespace amd
