
#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>
struct thread_arguments {
  Function function_;
  State *state_;
  std::atomic_flag *startup_flag_;
};

template<typename Function, typename State>
void *thread::start_pthread_internal(void *thread_pointer) {
  // Actively copy all arguments into stack memory.
  thread_arguments<Function, State>
      arguments_copy = *reinterpret_cast<thread_arguments<Function, State> *>(thread_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).
  arguments_copy.startup_flag_->clear();

  this_thread::set_state(arguments_copy.state_);
  arguments_copy.function_();

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

template<typename Function, typename State>
thread::thread(const Function &function, State *state_pointer):
    pthread_thread_{},
    running_{true} {

#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

  // Wee need to wait for the started function to read
  // the function_ and state_pointer_ property before returning
  // from the constructor, as the object might be moved after this.
  std::atomic_flag startup_flag{ATOMIC_FLAG_INIT};

  thread_arguments<Function, State> arguments{function, state_pointer, &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 < Function, State > , (void *) (&arguments));
  while (startup_flag.test_and_set()); // Busy waiting for the starting flag to clear
}

template<typename Function>
thread::thread(const Function &function): thread{function, (void *) nullptr} {}

}
}
}

#endif //PLS_THREAD_IMPL_H
