
#ifndef PLS_ALIGNED_STACK_H
#define PLS_ALIGNED_STACK_H

#include <cstdint>
#include <cstdlib>
#include <array>
#include <memory>

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

namespace pls {
namespace internal {
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 only call the deconstructor when explicitly using pop<T>().
 *            In this case you have to be sure that push<T, ...>() and pop<T>() calls
 *            match up through out your program.
 *
 * Usage:
 * aligned_static_stack<SIZE> stack; or heap_aligned_stack stack(size);
 * 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 and deconstruct it
 */
class aligned_stack {
 public:
  typedef size_t stack_offset;

  aligned_stack(char *memory_pointer, size_t size);
  aligned_stack(char *memory_pointer, size_t size, size_t original_size);

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

  template<typename T>
  void pop();

  char *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; }

 protected:
  // Keep bounds of our memory block
  char *unaligned_memory_pointer_;
  char *memory_pointer_;

  stack_offset max_offset_;
  stack_offset current_offset_;
};

template<size_t SIZE>
class static_aligned_stack {
 public:
  static_aligned_stack();
  aligned_stack &get_stack() { return aligned_stack_; }

 private:
  alignas(base::system_details::CACHE_LINE_SIZE) std::array<char, SIZE> memory_;
  aligned_stack aligned_stack_;
};

class heap_aligned_stack {
 public:
  explicit heap_aligned_stack(size_t size) :
      unaligned_memory_size_{base::alignment::next_alignment(size)},
      unaligned_memory_pointer_{new char[unaligned_memory_size_]},
      aligned_stack_{unaligned_memory_pointer_, size, unaligned_memory_size_} {}

  ~heap_aligned_stack() {
    delete[] unaligned_memory_pointer_;
  }

  aligned_stack &get_stack() { return aligned_stack_; }

 private:
  size_t unaligned_memory_size_;
  char *unaligned_memory_pointer_;
  aligned_stack aligned_stack_;
};

}
}
}
#include "aligned_stack_impl.h"

#endif //PLS_ALIGNED_STACK_H
