/**
 * Abstraction for threading to allow porting.
 * Currently using either pthread or C++ 11 threads.
 */

#ifndef PLS_THREAD_H
#define PLS_THREAD_H

#include <functional>
#include <pthread.h>
#include <atomic>
#include <time.h>

#include "system_details.h"

namespace pls {
namespace internal {
namespace base {

using thread_entrypoint = void();

/**
 * Static methods than can be performed on the current thread.
 *
 * usage:
 * this_thread::yield();
 * T* state = this_thread::state<T>();
 *
 * PORTABILITY:
 * Current implementation is based on pthreads.
 */
class this_thread {
  template<typename Function, typename State>
  friend
  class thread;
#ifdef PLS_THREAD_SPECIFIC_PTHREAD
  static pthread_key_t local_storage_key_;
  static bool local_storage_key_initialized_;
#endif
#ifdef PLS_THREAD_SPECIFIC_COMPILER
  static __thread void *local_state_;
#endif
 public:
  static void yield() {
    pthread_yield();
  }

  static void sleep(long microseconds) {
    timespec time{0, 1000 * microseconds};
    nanosleep(&time, nullptr);
  }

  /**
   * Retrieves the local state pointer.
   *
   * @tparam T The type of the state that is stored.
   * @return The state pointer hold for this thread.
   */
  template<typename T>
  static T *state();

  /**
   * Stores a pointer to the thread local state object.
   * The memory management for this has to be done by the user,
   * we only keep the pointer.
   *
   * @tparam T The type of the state that is stored.
   * @param state_pointer A pointer to the threads state object.
   */
  template<typename T>
  static void set_state(T *state_pointer);
};

/**
 * Abstraction for starting a function in a separate thread.
 *
 * @tparam Function Lambda being started on the new thread.
 * @tparam State State type held for this thread.
 *
 * usage:
 * T* state;
 * auto thread = start_thread([] {
 *      // Run on new thread
 * }, state);
 * thread.join(); // Wait for it to finish
 *
 * PORTABILITY:
 * Current implementation is based on pthreads.
 */
template<typename Function, typename State>
class thread {
  friend class this_thread;
  // Keep a copy of the function (lambda) in this object to make sure it is valid when called!
  Function function_;
  State *state_pointer_;

  // 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_;

  // Keep handle to native implementation
  pthread_t pthread_thread_;

  static void *start_pthread_internal(void *thread_pointer);

 public:
  explicit thread(const Function &function, State *state_pointer);

 public:
  void join();

  // make object move only
  thread(thread &&) noexcept = default;
  thread &operator=(thread &&) noexcept = default;

  thread(const thread &) = delete;
  thread &operator=(const thread &) = delete;
};

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

}
}
}
#include "thread_impl.h"

#endif //PLS_THREAD_H
