#ifndef PLS_SCHEDULER_MEMORY_H
#define PLS_SCHEDULER_MEMORY_H

#include <vector>

#include "pls/internal/base/thread.h"
#include "pls/internal/scheduling/thread_state.h"

namespace pls {
namespace internal {
namespace scheduling {

void worker_routine();

class scheduler_memory {
  // We first worried about performance of this being virtual.
  // However, we decided that only thread_state_for is used during the
  // runtime and that only when stealing. As stealing is expensive anyways,
  // this should not add too much overhead.
 public:
  virtual size_t max_threads() const = 0;
  virtual base::thread &thread_for(size_t id) const = 0;
  virtual thread_state &thread_state_for(size_t id) const = 0;
};

template<size_t MAX_THREADS, size_t NUM_TASKS, size_t MAX_TASK_STACK_SIZE, size_t NUM_CONTS, size_t MAX_CONT_SIZE>
class static_scheduler_memory : public scheduler_memory {
 public:
  size_t max_threads() const override {
    return MAX_THREADS;
  }

  base::thread &thread_for(size_t id) const override {
    return threads_[id];
  }

  thread_state &thread_state_for(size_t id) const override {
    return thread_states_[id];
  }

 private:
  using thread_state_type = static_thread_state<NUM_TASKS, MAX_TASK_STACK_SIZE, NUM_CONTS, MAX_CONT_SIZE>;

  alignas(base::system_details::CACHE_LINE_SIZE) std::array<base::thread, MAX_THREADS> threads_;
  alignas(base::system_details::CACHE_LINE_SIZE) std::array<thread_state_type, MAX_THREADS> thread_states_;
};

template<size_t NUM_TASKS, size_t MAX_TASK_STACK_SIZE, size_t NUM_CONTS, size_t MAX_CONT_SIZE>
class heap_scheduler_memory : public scheduler_memory {
 public:
  explicit heap_scheduler_memory(size_t max_threads) : max_threads_{max_threads},
                                                       thread_vector_{},
                                                       thread_state_vector_{} {
    thread_vector_.reserve(max_threads);
    thread_state_vector_.reserve(max_threads);

    for (size_t i = 0; i < max_threads; i++) {
      thread_vector_.emplace_back();
      thread_state_vector_.emplace_back();
    }
  }

  size_t max_threads() const override {
    return max_threads_;
  }

  base::thread &thread_for(size_t id) const override {
    return thread_vector_[id];
  }

  thread_state &thread_state_for(size_t id) const override {
    return thread_state_vector_[id].object();
  }

 private:
  using thread_state_type = static_thread_state<NUM_TASKS, MAX_TASK_STACK_SIZE, NUM_CONTS, MAX_CONT_SIZE>;
  // thread_state_type is aligned at the cache line and therefore overaligned (C++ 11 does not require
  // the new operator to obey alignments bigger than 16, cache lines are usually 64).
  // To allow this object to be allocated using 'new' (which the vector does internally),
  // we need to wrap it in an non aligned object.
  using thread_state_wrapper = base::alignment::cache_alignment_wrapper<thread_state_type>;

  size_t max_threads_;
  std::vector<base::thread> thread_vector_;
  std::vector<thread_state_wrapper> thread_state_vector_;
};

}
}
}

#endif //PLS_SCHEDULER_MEMORY_H
