
#ifndef PLS_ALIGNMENT_H
#define PLS_ALIGNMENT_H

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

#include "system_details.h"

namespace pls::internal::base::alignment {

constexpr system_details::pointer_t next_alignment(system_details::pointer_t size,
                                                   size_t alignment = system_details::CACHE_LINE_SIZE) {
  return (size % alignment) == 0 ?
         size :
         size + (alignment - (size % alignment));
}

constexpr system_details::pointer_t previous_alignment(system_details::pointer_t size,
                                                       size_t alignment = system_details::CACHE_LINE_SIZE) {
  return (size % alignment) == 0 ?
         size :
         size - (size % alignment);
}

char *next_alignment(char *pointer, size_t alignment = system_details::CACHE_LINE_SIZE);

/**
 * Forces alignment requirements on a type equal to the given size.
 * This can be useful to store the elements aligned in an array or to allocate them using new.
 *
 * The object is constructed using perfect forwarding. Thus initialization looks identical to the
 * wrapped object (can be used in containers), access to the elements has to be done through
 * the pointer returned by pointer() or moved out using object().
 *
 * (This is required, as C++11 does not allow 'over_aligned' types, meaning alignments higher
 * than the max_align_t (most times 16 bytes) wont be respected properly.)
 */
template<typename T, size_t ALIGNMENT>
struct alignment_wrapper {
  static_assert(alignof(T) <= ALIGNMENT, "Must not wrap an type to an alignment smaller than required by the type!");

 private:
  std::array<char, sizeof(T) + ALIGNMENT> memory_;
  T *data_;
 public:
  template<typename ...ARGS>
  explicit alignment_wrapper(ARGS &&...args)
      : memory_{}, data_{reinterpret_cast<T *>(next_alignment(memory_.data(), ALIGNMENT))} {
    new(data_) T(std::forward<ARGS>(args)...);
  }
  ~alignment_wrapper() {
    data_->~T();
  }

  T &object() { return *data_; }
  T *pointer() { return data_; }
};

/**
 * Our common case or over aligning types is when we want to hit cache lines.
 * The most portable way is to use the above alignment wrapper, removing any alignment
 * requirements from the wrapped type itself (here the cache line) and manually filling
 * up padding/fill bytes as needed.
 */
template<typename T>
using cache_alignment_wrapper = alignment_wrapper<T, system_details::CACHE_LINE_SIZE>;


}

#endif //PLS_ALIGNMENT_H
