/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

#ifndef mozilla_UniqueOrNonOwningPtr_h
#define mozilla_UniqueOrNonOwningPtr_h

#include <cstdint>
#include <utility>

#include "mozilla/Assertions.h"

namespace mozilla {

template <typename T>
class UniqueOrNonOwningPtr;

namespace detail {

template <typename T>
struct UniqueOfUniqueOrNonOwningSelector {
  using SingleObject = UniqueOrNonOwningPtr<T>;
};

template <typename T>
struct UniqueOfUniqueOrNonOwningSelector<T[]>;

template <typename T, decltype(sizeof(int)) N>
struct UniqueOfUniqueOrNonOwningSelector<T[N]>;

}  // namespace detail

// `mozilla::MakeUnique` equivalent, with the same set of advantages.
// Non-owning case doesn't need this since there's no allocation.
// See below as to why only SingleObject case is supported.
template <typename T, typename... Args>
typename detail::UniqueOfUniqueOrNonOwningSelector<T>::SingleObject
MakeUniqueOfUniqueOrNonOwning(Args&&... aArgs) {
  return UniqueOrNonOwningPtr<T>::UniquelyOwning(
      new T(std::forward<Args>(aArgs)...));
}

/**
 * A pointer that is either:
 *   * Uniquely-owning, as if `std::unique_ptr`/`mozilla::UniquePtr`, or
 *   * Non-owning, as if raw pointer.
 *
 * Overall, it behaves like `mozilla::Variant<T*, UniquePtr<T>>`, but more
 * compact. It may be helpful if you are mostly referencing existing data type
 * of significant size, but sometimes generate a modified copy and refer to
 * that.
 *
 * Usage notes:
 *   * Ownership: This structure makes ownership tracking harder. It is the
 *     caller's responsibility to ensure that, in the non-owning case, the data
 *     outlives this pointer.
 *   * (Ab)using the lowest bit: Owning state is tagged inline in the lowest
 * bit, which is set for uniquely-owning data. It does not work with a
 * byte-aligned data types, or members of a packed struct. There are asserts to
 * try and catch this as early as possible.
 *
 * TODO(dshin): This lacks support for things that `mozilla::UniquePtr` supports
 * - however, these cases will fail to compile.
 *   * Deleter support (Even stateless ones)
 *   * Interconversion (Pointing to derived from base pointer)
 *   * T[]
 */
template <typename T>
class UniqueOrNonOwningPtr {
 public:
  // Check to make sure we can take on non-owning pointer to stack.
  static_assert(alignof(T) != 1,
                "Can't support data aligned to byte boundaries.");
  // Standard guarantees the null pointer value to be integer 0.
  UniqueOrNonOwningPtr() : mBits{0} {}
  UniqueOrNonOwningPtr(const UniqueOrNonOwningPtr&) = delete;
  UniqueOrNonOwningPtr(UniqueOrNonOwningPtr&& aOther) : mBits{aOther.mBits} {
    // "Release" the other one.
    aOther.mBits = 0;
  }
  ~UniqueOrNonOwningPtr() {
    if (IsUniquelyOwning()) {
      delete get();
    }
  }
  UniqueOrNonOwningPtr& operator=(const UniqueOrNonOwningPtr& aOther) = delete;
  UniqueOrNonOwningPtr& operator=(UniqueOrNonOwningPtr&& aOther) {
    mBits = aOther.mBits;
    // "Release" the other one.
    aOther.mBits = 0;
    return *this;
  }

  static UniqueOrNonOwningPtr UniquelyOwning(T* aPtr) {
    MOZ_ASSERT(aPtr, "Passing in null pointer as owning?");
    const uintptr_t bits = reinterpret_cast<uintptr_t>(aPtr);
    MOZ_ASSERT((bits & kUniquelyOwningBit) == 0, "Odd-aligned owning pointer?");
    return UniqueOrNonOwningPtr{bits | kUniquelyOwningBit};
  }

  static UniqueOrNonOwningPtr NonOwning(T* aPtr) {
    const uintptr_t bits = reinterpret_cast<uintptr_t>(aPtr);
    MOZ_ASSERT((bits & kUniquelyOwningBit) == 0,
               "Odd-aligned non-owning pointer?");
    return UniqueOrNonOwningPtr{bits};
  }

  std::add_lvalue_reference_t<T> operator*() const {
    MOZ_ASSERT(
        get(),
        "dereferencing a UniqueOrNonOwningPtr containing nullptr with *");
    return *get();
  }

  T* operator->() const {
    MOZ_ASSERT(
        get(),
        "dereferencing a UniqueOrNonOwningPtr containing nullptr with ->");
    return get();
  }

  explicit operator bool() const { return get() != nullptr; }

  T* get() const { return reinterpret_cast<T*>(mBits & ~kUniquelyOwningBit); }

 private:
  bool IsUniquelyOwning() const { return (mBits & kUniquelyOwningBit) != 0; }

  // Bit for tracking uniquely-owning vs non-owning status. Check usage notes
  // in the main comment block.
  // NOTE: A null pointer constant has a guarantee on being integer literal 0.
  static constexpr uintptr_t kUniquelyOwningBit = 1;
  explicit UniqueOrNonOwningPtr(uintptr_t aValue) : mBits{aValue} {}
  uintptr_t mBits;
};

// Unsupported
template <typename T>
class UniqueOrNonOwningPtr<T[]>;

}  // namespace mozilla

#endif
