
#ifndef PLS_THREAD_IMPL_H
#define PLS_THREAD_IMPL_H

namespace pls {
namespace internal {
namespace base {

template<typename T>
T *this_thread::state() {
#ifdef PLS_THREAD_SPECIFIC_PTHREAD
  return reinterpret_cast<T*>(pthread_getspecific(local_storage_key_));
#endif
#ifdef PLS_THREAD_SPECIFIC_COMPILER
  return reinterpret_cast<T *>(local_state_);
#endif
}

template<typename T>
void this_thread::set_state(T *state_pointer) {
#ifdef PLS_THREAD_SPECIFIC_PTHREAD
  pthread_setspecific(this_thread::local_storage_key_, (void*)state_pointer);
#endif
#ifdef PLS_THREAD_SPECIFIC_COMPILER
  local_state_ = state_pointer;
#endif
}

template<typename Function, typename State>
void *thread<Function, State>::start_pthread_internal(void *thread_pointer) {
  auto my_thread = reinterpret_cast<thread *>(thread_pointer);
  Function my_function_copy = my_thread->function_;
  State *my_state_pointer_copy = my_thread->state_pointer_;

  // Now we have copies of everything we need on the stack.
  // The original thread object can be moved freely (no more
  // references to its memory location).
  my_thread->startup_flag_->clear();

  this_thread::set_state(my_state_pointer_copy);
  my_function_copy();

  // Finished executing the user function
  pthread_exit(nullptr);
}

template<typename Function, typename State>
thread<Function, State>::thread(const Function &function, State *state_pointer):
    function_{function},
    state_pointer_{state_pointer},
    startup_flag_{nullptr},
    pthread_thread_{} {

#ifdef PLS_THREAD_SPECIFIC_PTHREAD
  if (!this_thread::local_storage_key_initialized_) {
          pthread_key_create(&this_thread::local_storage_key_, nullptr);
          this_thread::local_storage_key_initialized_ = true;
      }
#endif

  // We only need this during startup, will be destroyed when out of scope
  std::atomic_flag startup_flag{ATOMIC_FLAG_INIT};
  startup_flag_ = &startup_flag;

  startup_flag.test_and_set(); // Set the flag, pthread will clear it when it is safe to return
  pthread_create(&pthread_thread_, nullptr, start_pthread_internal, (void *) (this));
  while (startup_flag.test_and_set()); // Busy waiting for the starting flag to clear
}

template<typename Function, typename State>
void thread<Function, State>::join() {
  pthread_join(pthread_thread_, nullptr);
}

template<typename Function, typename State>
thread<Function, State> start_thread(const Function &function, State *state_pointer) {
  return thread<Function, State>(function, state_pointer);
}

template<typename Function>
thread<Function, void> start_thread(const Function &function) {
  return thread<Function, void>(function, nullptr);
}

}
}
}

#endif //PLS_THREAD_IMPL_H
