
#ifndef PLS_ALIGNED_STACK_H
#define PLS_ALIGNED_STACK_H

#include <cstdint>
#include <cstdlib>

#include "pls/internal/base/error_handling.h"
#include "pls/internal/base/alignment.h"

namespace pls {
namespace internal {
namespace scheduling {
namespace data_structures {

using base::system_details::pointer_t;

/**
 * Generic stack-like data structure that allows to allocate arbitrary objects in a given memory region.
 * The objects will be stored aligned in the stack, making the storage cache friendly and very fast
 * (as long as one can live with the stack restrictions).
 *
 * IMPORTANT: Does not call destructors on stored objects! Do not allocate resources in the objects!
 *
 * Usage:
 * aligned_stack stack{pointer_to_memory, size_of_memory};
 * T* pointer = stack.push<T>(constructor_arguments); // Perfect-Forward-Construct the object on top of stack
 * stack.pop<T>(); // Remove the top object of type T
 */
class aligned_stack {
 public:
  typedef size_t stack_offset;

  aligned_stack() : aligned_memory_start_{0}, aligned_memory_end_{0}, max_offset_{0}, current_offset_{0} {};
  aligned_stack(pointer_t memory_region, std::size_t size);
  aligned_stack(char *memory_region, std::size_t size);

  template<typename T, typename ...ARGS>
  T *push(ARGS &&... args);
  template<typename T>
  void *push_bytes();
  void *push_bytes(size_t size);
  template<typename T>
  T pop();

  void *memory_at_offset(stack_offset offset) const;

  stack_offset save_offset() const { return current_offset_; }
  void reset_offset(stack_offset new_offset) { current_offset_ = new_offset; }

 private:
  // Keep bounds of our memory block
  pointer_t aligned_memory_start_;
  pointer_t aligned_memory_end_;

  stack_offset max_offset_;
  stack_offset current_offset_;
};

}
}
}
}
#include "aligned_stack_impl.h"

#endif //PLS_ALIGNED_STACK_H
