
#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 delayed_initialization {
 public:
  delayed_initialization() : memory_{}, initialized_{false} {}
  delayed_initialization(delayed_initialization &&other) noexcept {
    initialized_ = other.initialized_;
    if (other.initialized_) {
      object() = std::move(other.object());
      other.initialized_ = false;
    }
  }

  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());
  }

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

  bool initialized() const { return initialized_; }

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

}
}
}

#endif // PLS_INTERNAL_DATA_STRUCTURES_DELAYED_INITIALIZATION_H_
