#pragma once

#include <c10/util/in_place.h>

namespace c10 {

// See example implementations in TensorBody.h and intrusive_ptr.h.
// Synopsis:
//
// repr_type -- type to use to store an owned T in ExclusivelyOwned.
//
// pointer_type -- pointer-esque type to return from
// ExclusivelyOwned's get() and operator*() methods.
//
// const_pointer_type -- similar to pointer_type, used for the const methods.
//
// static repr_type nullRepr() -- return a null instance of repr_type.
//
// template <class... Args>
// static repr_type createInPlace(Args&&... args) -- used by the in-place
// ExclusivelyOwned constructor.
//
// static repr_type moveToRepr(T&& x) -- move the given x into an
// instance of repr_type. used by the ExclusivelyOwned(T&&)
// constructor.
//
// static void destroyOwned(repr_type x) -- free memory for a
// known-exclusively-owned instance of x. Replaces calling repr_type's
// destructor. Being able to implement this more efficiently than
// repr_type's destructor is the main reason to use ExclusivelyOwned
// for a type.
//
// static T take(repr_type&) -- move out of the given repr_type into an owned T.
//
// static pointer_type getImpl(const repr_type&) -- return a pointer
// to the given repr_type. May take repr_type by value if that is more
// efficient.
template <typename T>
struct ExclusivelyOwnedTraits;

/// ExclusivelyOwned is a smart-pointer-like wrapper around an
/// exclusively-owned instance of some type T that normally has
/// mandatory reference counting (currently Tensor or
/// c10::intrusive_ptr). If you have an isolated piece of code that
/// knows that it has sole ownership of an object of one of these
/// types (i.e., because you created it directly or using a factory
/// function) and that object will not escape from that isolated piece
/// of code, then moving the object into an ExclusivelyOwned will
/// avoid an atomic reference count decrement at destruction time.
///
/// If you directly create the Tensor/intrusive_ptr in the first
/// place, you can use the in_place constructor of ExclusivelyOwned to
/// additionally avoid doing any stores to initialize the refcount &
/// weakcount. (Do note, however, that in this case you should
/// probably just use std::unique_ptr instead of intrusive_ptr if applicable.)
template <typename T>
class ExclusivelyOwned {
  using EOT = ExclusivelyOwnedTraits<T>;
  union {
    char dummy_;
    typename ExclusivelyOwnedTraits<T>::repr_type repr_;
  };

 public:
  ExclusivelyOwned() : repr_(EOT::nullRepr()) {}

  explicit ExclusivelyOwned(T&& t) : repr_(EOT::moveToRepr(std::move(t))) {}

  template <class... Args>
  explicit ExclusivelyOwned(in_place_t, Args&&... args)
      : repr_(EOT::createInPlace(std::forward<Args>(args)...)) {}

  ExclusivelyOwned(const ExclusivelyOwned&) = delete;

  ExclusivelyOwned(ExclusivelyOwned&& rhs) noexcept
      : repr_(std::move(rhs.repr_)) {
    rhs.repr_ = EOT::nullRepr();
  }

  ExclusivelyOwned& operator=(const ExclusivelyOwned&) = delete;

  ExclusivelyOwned& operator=(ExclusivelyOwned&& rhs) noexcept {
    EOT::destroyOwned(repr_);
    repr_ = std::move(rhs.repr_);
    rhs.repr_ = EOT::nullRepr();
    return *this;
  }

  ExclusivelyOwned& operator=(T&& rhs) noexcept {
    EOT::destroyOwned(repr_);
    repr_ = EOT::moveToRepr(std::move(rhs));
    return *this;
  }

  ~ExclusivelyOwned() {
    EOT::destroyOwned(repr_);
    // Don't bother to call the destructor of repr_, since we already
    // did specialized destruction for the exclusively-owned case in
    // destroyOwned!
  }

  // We don't provide this because it would require us to be able to
  // differentiate an owned-but-empty T from a lack of T. This is
  // particularly problematic for Tensor, which wants to use an
  // undefined Tensor as its null state.
  explicit operator bool() const noexcept = delete;

  operator T() && {
    return take();
  }

  // NOTE: the equivalent operation on MaybeOwned is a moving
  // operator*. For ExclusivelyOwned, take() and operator*() may well
  // have different return types (e.g., for intrusive_ptr, take()
  // returns c10::intrusive_ptr<T> whereas operator* returns T&), so
  // they are different functions.
  T take() && {
    return EOT::take(repr_);
  }

  typename EOT::const_pointer_type operator->() const {
    return get();
  }

  typename EOT::const_pointer_type get() const {
    return EOT::getImpl(repr_);
  }

  typename EOT::pointer_type operator->() {
    return get();
  }

  typename EOT::pointer_type get() {
    return EOT::getImpl(repr_);
  }

  std::remove_pointer_t<typename EOT::const_pointer_type>& operator*() const {
    return *get();
  }

  std::remove_pointer_t<typename EOT::pointer_type>& operator*() {
    return *get();
  }
};

} // namespace c10
