#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"
#include "pls/internal/scheduling/thread_state_static.h"

namespace pls {
namespace internal {
namespace scheduling {

class scheduler_memory {
  // Note: scheduler_memory is a pure interface and has no data.
  //       By not having an initialization routine we can do our 'static and heap specialization'
  //       without running into any ordering problems in the initialization sequence.

 protected:
  thread_state **thread_states_array_{nullptr};

 public:
  virtual size_t max_threads() const = 0;
  virtual base::thread &thread_for(size_t id) = 0;
  thread_state &thread_state_for(size_t id) {
    return *thread_states_array_[id];
  }
};

template<size_t MAX_THREADS, size_t NUM_TASKS, size_t STACK_SIZE>
class static_scheduler_memory : public scheduler_memory {
 public:
  static_scheduler_memory() : scheduler_memory{} {
    for (size_t i = 0; i < MAX_THREADS; i++) {
      thread_state_pointers_[i] = &thread_states_[i].get_thread_state();
    }
    thread_states_array_ = thread_state_pointers_.data();
  }

  size_t max_threads() const override {
    return MAX_THREADS;
  }

  base::thread &thread_for(size_t id) override {
    return threads_[id];
  }
 private:
  using thread_state_type = thread_state_static<NUM_TASKS, STACK_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_;
  alignas(base::system_details::CACHE_LINE_SIZE) std::array<thread_state *, MAX_THREADS> thread_state_pointers_;
};

template<size_t NUM_TASKS, size_t STACK_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_state_pointers_{} {
    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();
      thread_state_pointers_.emplace_back(&thread_state_vector_[i].get_thread_state());
    }
    thread_states_array_ = thread_state_pointers_.data();
  }

  size_t max_threads() const override {
    return max_threads_;
  }

  base::thread &thread_for(size_t id) override {
    return thread_vector_[id];
  }
 private:
  using thread_state_type = thread_state_static<NUM_TASKS, STACK_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_;
  std::vector<thread_state *> thread_state_pointers_;
};

}
}
}

#endif //PLS_SCHEDULER_MEMORY_H
