
#ifndef PLS_INTERNAL_DATA_STRUCTURES_DELAYED_INITIALIZATION_H_
#define PLS_INTERNAL_DATA_STRUCTURES_DELAYED_INITIALIZATION_H_

#include <array>
#include <utility>

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

namespace pls {
namespace internal {
namespace data_structures {

/**
 * Allows to reserve space for an uninitialized member variable.
 * The member must be initialized before usage using the provided
 * perfect forwarding constructor method.
 *
 * Takes care of the de-construction the contained object if one is active.
 */
template<typename T>
class alignas(alignof(T)) delayed_initialization {
 public:
  delayed_initialization() : memory_{}, initialized_{false} {}
  delayed_initialization(const delayed_initialization &) = delete;
  delayed_initialization(delayed_initialization &&other) noexcept {
    if (other.initialized()) {
      new((void *) memory_.data()) T(std::move(other.object()));
      other.initialized_ = false;
      initialized_ = true;
    }
  }
  delayed_initialization &operator=(const delayed_initialization &) = delete;
  delayed_initialization &operator=(delayed_initialization &&other) noexcept {
    if (&other == this) {
      return *this;
    }

    if (initialized() && other.initialized()) {
      object() = std::move(other.object());
      other.initialized_ = false;
      initialized_ = true;
      return *this;
    }

    if (!initialized() && other.initialized_) {
      new((void *) memory_.data()) T(std::move(other.object()));
      other.initialized_ = false;
      initialized_ = true;
      return *this;
    }

    return *this;
  }

  template<typename ...ARGS>
  explicit delayed_initialization(ARGS &&...args): memory_{}, initialized_{true} {
    new(memory_.data()) T(std::forward<ARGS>(args)...);
  }

  ~delayed_initialization() {
    if (initialized_) {
      object().~T();
    }
  }

  template<typename ...ARGS>
  void initialize(ARGS &&...args) {
    PLS_ASSERT(!initialized_, "Can only initialize delayed wrapper object once!");

    new((void *) memory_.data()) T(std::forward<ARGS>(args)...);
    initialized_ = true;
  }

  void destroy() {
    PLS_ASSERT(initialized_, "Can only destroy initialized objects!")

    object().~T();
    initialized_ = false;
  }

  T &object() {
    PLS_ASSERT(initialized_, "Can not use an uninitialized delayed wrapper object!");

    return *reinterpret_cast<T *>(memory_.data());
  }

  const T &object() const {
    PLS_ASSERT(initialized_, "Can not use an uninitialized delayed wrapper object!");

    return *reinterpret_cast<const T *>(memory_.data());
  }

  T &operator*() {
    return object();
  }

  const T &operator*() const {
    return object();
  }

  bool initialized() const { return initialized_; }

 private:
  alignas(alignof(T)) std::array<char, sizeof(T)> memory_;
  bool initialized_;
};

}
}
}

#endif // PLS_INTERNAL_DATA_STRUCTURES_DELAYED_INITIALIZATION_H_
