
#ifndef PLS_SCHEDULER_H
#define PLS_SCHEDULER_H

#include <array>
#include <iostream>

#include "pls/internal/helpers/profiler.h"

#include "pls/internal/data_structures/aligned_stack.h"

#include "pls/internal/base/thread.h"
#include "pls/internal/base/barrier.h"

#include "pls/internal/scheduling/scheduler_memory.h"
#include "pls/internal/scheduling/thread_state.h"
#include "pls/internal/scheduling/task.h"

namespace pls {
namespace internal {
namespace scheduling {

/**
 * The scheduler is the central part of the dispatching-framework.
 * It manages a pool of worker threads (creates, sleeps/wakes up, destroys)
 * and allows to execute parallel sections.
 *
 * It works in close rellation with the 'task' class for scheduling.
 */
class scheduler {
  friend class task;
  const unsigned int num_threads_;
  const bool reuse_thread_;
  scheduler_memory *memory_;

  base::barrier sync_barrier_;

  task *main_thread_root_task_;
  std::atomic<bool> work_section_done_;

  bool terminated_;
 public:
  /**
   * Initializes a scheduler instance with the given number of threads.
   * This will spawn the threads and put them to sleep, ready to process an
   * upcoming parallel section.
   *
   * @param memory All memory is allocated statically, thus the user is required to provide the memory instance.
   * @param num_threads The number of worker threads to be created.
   */
  explicit scheduler(scheduler_memory *memory, unsigned int num_threads, bool reuse_thread = true);

  /**
   * The scheduler is implicitly terminated as soon as it leaves the scope.
   */
  ~scheduler();

  /**
   * Wakes up the thread pool.
   * Code inside the Function lambda can invoke all parallel APIs.
   * This is meant to cleanly sleep and wake up the scheduler during an application run,
   * e.g. to run parallel code on a timer loop/after interrupts.
   *
   * @param work_section generic function or lambda to be executed in the scheduler's context.
   */
  template<typename Function>
  void perform_work(Function work_section);

  /**
   * Explicitly terminate the worker threads. Scheduler must not be used after this.
   */
  void terminate();

  /**
   * Helper to spawn a child on the currently running task.
   *
   * @tparam T type of the new task
   * @tparam ARGS Constructor argument types
   * @param args constructor arguments
   */
  template<typename T, typename ...ARGS>
  static void spawn_child(ARGS &&... args);

  /**
   * Helper to spawn a child on the currently running task and waiting for it (skipping over the task-deque).
   *
   * @tparam T type of the new task
   * @tparam ARGS Constructor argument types
   * @param args constructor arguments
   */
  template<typename T, typename ...ARGS>
  static void spawn_child_and_wait(ARGS &&... args);

  /**
   * Helper to wait for all children of the currently executing task.
   */
  static void wait_for_all();

  unsigned int num_threads() const { return num_threads_; }

 private:
  static void worker_routine();
  thread_state *thread_state_for(size_t id);

  task *get_local_task();
  task *steal_task();

  bool try_execute_local();
  bool try_execute_stolen();
};

}
}
}
#include "scheduler_impl.h"

#endif //PLS_SCHEDULER_H
