/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <memory>
#include <utility>

#include <folly/Portability.h>
#include <folly/lang/Exception.h>

namespace folly {

namespace detail {
template <typename T>
struct is_not_null_helper : std::false_type {};
template <typename T>
struct is_not_null_helper<not_null<T>> : std::true_type {};
template <typename T>
struct is_not_null
    : is_not_null_helper<std::remove_cv_t<std::remove_reference_t<T>>> {};
template <typename T>
inline constexpr bool is_not_null_v = is_not_null<T>::value;

template <typename T, typename = std::enable_if_t<!is_not_null_v<T>>>
auto maybeUnwrap(T&& t) {
  return std::forward<T>(t);
}
template <typename T>
auto maybeUnwrap(const not_null_base<T>& t) {
  return t.unwrap();
}
template <typename T>
auto maybeUnwrap(not_null_base<T>&& t) {
  return std::move(t).unwrap();
}

template <typename T>
struct maybe_unwrap_not_null {
  using type = T;
};
template <typename T>
struct maybe_unwrap_not_null<const T> {
  using type = const typename maybe_unwrap_not_null<T>::type;
};
template <typename T>
struct maybe_unwrap_not_null<T&> {
  using type = typename maybe_unwrap_not_null<T>::type&;
};
template <typename T>
struct maybe_unwrap_not_null<T&&> {
  using type = typename maybe_unwrap_not_null<T>::type&&;
};
template <typename PtrT>
struct maybe_unwrap_not_null<not_null<PtrT>> {
  using type = PtrT;
};

template <typename FromT, typename ToT>
struct is_not_null_convertible
    : std::is_convertible<
          typename maybe_unwrap_not_null<FromT>::type,
          typename maybe_unwrap_not_null<ToT>::type> {};

template <typename FromT, typename ToPtrT>
struct is_not_null_nothrow_constructible
    : std::integral_constant<
          bool,
          is_not_null_v<FromT> &&
              std::is_nothrow_constructible_v<ToPtrT, FromT>> {};

struct secret_guaranteed_not_null : guaranteed_not_null_provider {
  static guaranteed_not_null get() {
    return guaranteed_not_null_provider::guaranteed_not_null();
  }
};

// In order to be able to cast from not_null<PtrT> to ToT:
//  - It must not already be castable, otherwise the compiler will raise an
//    ambiguity error
//  - PtrT must be castable to ToT
template <typename FromPtrT, typename ToT>
struct is_not_null_castable
    : std::integral_constant<
          bool,
          std::is_convertible_v<const FromPtrT&, ToT> &&
              !std::is_convertible_v<const not_null<FromPtrT>&, ToT>> {};
template <typename FromPtrT, typename ToT>
struct is_not_null_move_castable
    : std::integral_constant<
          bool,
          std::is_convertible_v<FromPtrT&&, ToT> &&
              !std::is_convertible_v<not_null<FromPtrT>&&, ToT>> {};

template <typename T, typename = decltype(*std::declval<T*>() == nullptr)>
inline std::true_type is_comparable_to_nullptr_fn(const T&) {
  return {};
}
inline std::false_type is_comparable_to_nullptr_fn(...) {
  return {};
}
template <typename T>
constexpr bool is_comparable_to_nullptr_v =
    decltype(is_comparable_to_nullptr_fn(*std::declval<T*>()))::value;
} // namespace detail

template <typename PtrT>
template <typename U>
not_null_base<PtrT>::not_null_base(U&& u, private_tag)
    : ptr_(detail::maybeUnwrap(std::forward<U>(u))) {
  if constexpr (!detail::is_not_null_v<U>) {
    throw_if_null();
  }
}

template <typename PtrT>
not_null_base<PtrT>::not_null_base(
    PtrT&& ptr, guaranteed_not_null_provider::guaranteed_not_null) noexcept
    : ptr_(std::move(ptr)) {}

template <typename PtrT>
template <typename U, typename>
not_null_base<PtrT>::not_null_base(U&& u, implicit_tag<true>) noexcept(
    detail::is_not_null_nothrow_constructible<U&&, PtrT>::value)
    : not_null_base(std::forward<U>(u), private_tag{}) {}

template <typename PtrT>
template <typename U, typename>
not_null_base<PtrT>::not_null_base(U&& u, implicit_tag<false>) noexcept(
    detail::is_not_null_nothrow_constructible<U&&, PtrT>::value)
    : not_null_base(std::forward<U>(u), private_tag{}) {}

template <typename PtrT>
typename not_null_base<PtrT>::element_type& not_null_base<PtrT>::operator*()
    const noexcept {
  return *unwrap();
}

template <typename PtrT>
const PtrT& not_null_base<PtrT>::operator->() const noexcept {
  return unwrap();
}

template <typename PtrT>
not_null_base<PtrT>::operator const PtrT&() const& noexcept {
  return unwrap();
}

template <typename PtrT>
not_null_base<PtrT>::operator PtrT&&() && noexcept {
  return std::move(*this).unwrap();
}

template <typename PtrT>
template <typename U, typename>
not_null_base<PtrT>::operator U() const& noexcept(
    std::is_nothrow_constructible_v<U, const PtrT&>) {
  if constexpr (detail::is_not_null_v<U>) {
    return U(*this);
  }
  return U(unwrap());
}

template <typename PtrT>
template <typename U, typename>
not_null_base<PtrT>::operator U() && noexcept(
    std::is_nothrow_constructible_v<U, PtrT&&>) {
  if constexpr (detail::is_not_null_v<U>) {
    return U(std::move(*this));
  }
  return U(std::move(*this).unwrap());
}

template <typename PtrT>
void not_null_base<PtrT>::swap(not_null_base& other) noexcept {
  mutable_unwrap().swap(other.mutable_unwrap());
}

template <typename PtrT>
const PtrT& not_null_base<PtrT>::unwrap() const& noexcept {
  if constexpr (folly::kIsDebug) {
    terminate_if_null(ptr_);
  }
  return ptr_;
}

template <typename PtrT>
PtrT&& not_null_base<PtrT>::unwrap() && noexcept {
  if constexpr (folly::kIsDebug) {
    terminate_if_null(ptr_);
  }
  return std::move(ptr_);
}

template <typename PtrT>
PtrT& not_null_base<PtrT>::mutable_unwrap() noexcept {
  return const_cast<PtrT&>(const_cast<const not_null_base&>(*this).unwrap());
}

template <typename PtrT>
void not_null_base<PtrT>::throw_if_null() const {
  throw_if_null(ptr_);
}

template <typename PtrT>
template <typename T>
void not_null_base<PtrT>::throw_if_null(const T& ptr) {
  if (ptr == nullptr) {
    folly::throw_exception<std::invalid_argument>("non_null<PtrT> is null");
  }
}

template <typename PtrT>
template <typename T>
void not_null_base<PtrT>::terminate_if_null(const T& ptr) {
  if (ptr == nullptr) {
    folly::terminate_with<std::runtime_error>(
        "not_null internal pointer is null");
  }
}

template <typename PtrT>
template <typename Deleter>
Deleter&& not_null_base<PtrT>::forward_or_throw_if_null(Deleter&& deleter) {
  if constexpr (detail::is_comparable_to_nullptr_v<Deleter>) {
    if (deleter == nullptr) {
      folly::throw_exception<std::invalid_argument>(
          "non_null<PtrT> deleter is null");
    }
  }
  return std::forward<Deleter>(deleter);
}

/**
 * not_null<std::unique_ptr<>> specialization.
 */
template <typename T, typename Deleter>
not_null<std::unique_ptr<T, Deleter>>::not_null(pointer p, const Deleter& d)
    : not_null_base<std::unique_ptr<T, Deleter>>(
          std::unique_ptr<T, Deleter>(
              std::move(p).unwrap(), this->forward_or_throw_if_null(d)),
          guaranteed_not_null_provider::guaranteed_not_null()) {}

template <typename T, typename Deleter>
not_null<std::unique_ptr<T, Deleter>>::not_null(pointer p, Deleter&& d)
    : not_null_base<std::unique_ptr<T, Deleter>>(
          std::unique_ptr<T, Deleter>(
              std::move(p).unwrap(),
              this->forward_or_throw_if_null(std::move(d))),
          guaranteed_not_null_provider::guaranteed_not_null()) {}

template <typename T, typename Deleter>
void not_null<std::unique_ptr<T, Deleter>>::reset(pointer ptr) noexcept {
  this->mutable_unwrap().reset(ptr.unwrap());
}

template <typename T, typename Deleter>
typename not_null<std::unique_ptr<T, Deleter>>::pointer
not_null<std::unique_ptr<T, Deleter>>::get() const noexcept {
  return pointer(
      this->unwrap().get(),
      guaranteed_not_null_provider::guaranteed_not_null());
}

template <typename T, typename Deleter>
Deleter& not_null<std::unique_ptr<T, Deleter>>::get_deleter() noexcept {
  return this->mutable_unwrap().get_deleter();
}

template <typename T, typename Deleter>
const Deleter& not_null<std::unique_ptr<T, Deleter>>::get_deleter()
    const noexcept {
  return this->unwrap().get_deleter();
}

template <typename T, typename... Args>
not_null_unique_ptr<T> make_not_null_unique(Args&&... args) {
  return not_null_unique_ptr<T>(
      std::make_unique<T>(std::forward<Args>(args)...),
      detail::secret_guaranteed_not_null::get());
}

/**
 * not_null<std::shared_ptr<>> specialization.
 */
template <typename T>
template <typename U, typename Deleter>
not_null<std::shared_ptr<T>>::not_null(U* ptr, Deleter d)
    : not_null_base<std::shared_ptr<T>>(std::shared_ptr<T>(
          ptr, this->forward_or_throw_if_null(std::move(d)))) {}

template <typename T>
template <typename U, typename Deleter>
not_null<std::shared_ptr<T>>::not_null(not_null<U*> ptr, Deleter d)
    : not_null_base<std::shared_ptr<T>>(
          std::shared_ptr<T>(
              ptr.unwrap(), this->forward_or_throw_if_null(std::move(d))),
          guaranteed_not_null_provider::guaranteed_not_null()) {}

template <typename T>
template <typename U>
not_null<std::shared_ptr<T>>::not_null(
    const std::shared_ptr<U>& r, not_null<element_type*> ptr) noexcept
    : not_null_base<std::shared_ptr<T>>(
          std::shared_ptr<T>(r, ptr.unwrap()),
          guaranteed_not_null_provider::guaranteed_not_null()) {}

template <typename T>
template <typename U>
not_null<std::shared_ptr<T>>::not_null(
    const not_null<std::shared_ptr<U>>& r, not_null<element_type*> ptr) noexcept
    : not_null_base<std::shared_ptr<T>>(
          std::shared_ptr<T>(r.unwrap(), ptr.unwrap()),
          guaranteed_not_null_provider::guaranteed_not_null()) {}

template <typename T>
template <typename U>
not_null<std::shared_ptr<T>>::not_null(
    std::shared_ptr<U>&& r, not_null<element_type*> ptr) noexcept
    : not_null_base<std::shared_ptr<T>>(
          std::shared_ptr<T>(std::move(r), ptr.unwrap()),
          guaranteed_not_null_provider::guaranteed_not_null()) {}

template <typename T>
template <typename U>
not_null<std::shared_ptr<T>>::not_null(
    not_null<std::shared_ptr<U>>&& r, not_null<element_type*> ptr) noexcept
    : not_null_base<std::shared_ptr<T>>(
          std::shared_ptr<T>(std::move(r).unwrap(), ptr.unwrap()),
          guaranteed_not_null_provider::guaranteed_not_null()) {}

template <typename T>
template <typename U>
void not_null<std::shared_ptr<T>>::reset(U* ptr) {
  this->throw_if_null(ptr);
  this->mutable_unwrap().reset(ptr);
}

template <typename T>
template <typename U>
void not_null<std::shared_ptr<T>>::reset(not_null<U*> ptr) noexcept {
  this->mutable_unwrap().reset(ptr.unwrap());
}

template <typename T>
template <typename U, typename Deleter>
void not_null<std::shared_ptr<T>>::reset(U* ptr, Deleter d) {
  this->throw_if_null(ptr);
  this->mutable_unwrap().reset(
      ptr, this->forward_or_throw_if_null(std::move(d)));
}

template <typename T>
template <typename U, typename Deleter>
void not_null<std::shared_ptr<T>>::reset(not_null<U*> ptr, Deleter d) {
  this->mutable_unwrap().reset(
      ptr.unwrap(), this->forward_or_throw_if_null(std::move(d)));
}

template <typename T>
typename not_null<std::shared_ptr<T>>::pointer
not_null<std::shared_ptr<T>>::get() const noexcept {
  return pointer(
      this->unwrap().get(),
      guaranteed_not_null_provider::guaranteed_not_null());
}

template <typename T>
long not_null<std::shared_ptr<T>>::use_count() const noexcept {
  return this->unwrap().use_count();
}

template <typename T>
template <typename U>
bool not_null<std::shared_ptr<T>>::owner_before(
    const std::shared_ptr<U>& other) const noexcept {
  return this->unwrap().owner_before(other);
}

template <typename T>
template <typename U>
bool not_null<std::shared_ptr<T>>::owner_before(
    const not_null<std::shared_ptr<U>>& other) const noexcept {
  return this->unwrap().owner_before(other.unwrap());
}

template <typename T, typename... Args>
not_null_shared_ptr<T> make_not_null_shared(Args&&... args) {
  return not_null_shared_ptr<T>(
      std::make_shared<T>(std::forward<Args>(args)...),
      detail::secret_guaranteed_not_null::get());
}

template <typename T, typename Alloc, typename... Args>
not_null_shared_ptr<T> allocate_not_null_shared(
    const Alloc& alloc, Args&&... args) {
  return not_null_shared_ptr<T>(
      std::allocate_shared<T, Alloc, Args...>(
          alloc, std::forward<Args>(args)...),
      detail::secret_guaranteed_not_null::get());
}

/**
 * Comparators.
 */
#define FB_NOT_NULL_MK_OP(op)                                 \
  template <typename PtrT, typename T>                        \
  bool operator op(const not_null<PtrT>& lhs, const T& rhs) { \
    return lhs.unwrap() op rhs;                               \
  }                                                           \
  template <typename PtrT, typename T, typename>              \
  bool operator op(const T& lhs, const not_null<PtrT>& rhs) { \
    return lhs op rhs.unwrap();                               \
  }

FB_NOT_NULL_MK_OP(==)
FB_NOT_NULL_MK_OP(!=)
FB_NOT_NULL_MK_OP(<)
FB_NOT_NULL_MK_OP(<=)
FB_NOT_NULL_MK_OP(>)
FB_NOT_NULL_MK_OP(>=)
#undef FB_NOT_NULL_MK_OP

/**
 * Output.
 */
template <typename U, typename V, typename PtrT>
std::basic_ostream<U, V>& operator<<(
    std::basic_ostream<U, V>& os, const not_null<PtrT>& ptr) {
  return os << ptr.unwrap();
}

/**
 * Swap
 */
template <typename PtrT>
void swap(not_null<PtrT>& lhs, not_null<PtrT>& rhs) noexcept {
  lhs.swap(rhs);
}

/**
 * Getters
 */
template <typename Deleter, typename T>
Deleter* get_deleter(const not_null_shared_ptr<T>& ptr) {
  return std::get_deleter<Deleter>(ptr.unwrap());
}

/**
 * Casting
 */
template <typename T, typename U>
not_null_shared_ptr<T> static_pointer_cast(const not_null_shared_ptr<U>& r) {
  auto p = std::static_pointer_cast<T, U>(r.unwrap());
  return not_null_shared_ptr<T>(
      std::move(p), detail::secret_guaranteed_not_null::get());
}
template <typename T, typename U>
not_null_shared_ptr<T> static_pointer_cast(not_null_shared_ptr<U>&& r) {
  auto p = std::static_pointer_cast<T, U>(std::move(r).unwrap());
  return not_null_shared_ptr<T>(
      std::move(p), detail::secret_guaranteed_not_null::get());
}
template <typename T, typename U>
std::shared_ptr<T> dynamic_pointer_cast(const not_null_shared_ptr<U>& r) {
  return std::dynamic_pointer_cast<T, U>(r.unwrap());
}
template <typename T, typename U>
std::shared_ptr<T> dynamic_pointer_cast(not_null_shared_ptr<U>&& r) {
  return std::dynamic_pointer_cast<T, U>(std::move(r).unwrap());
}
template <typename T, typename U>
not_null_shared_ptr<T> const_pointer_cast(const not_null_shared_ptr<U>& r) {
  auto p = std::const_pointer_cast<T, U>(r.unwrap());
  return not_null_shared_ptr<T>(
      std::move(p), detail::secret_guaranteed_not_null::get());
}
template <typename T, typename U>
not_null_shared_ptr<T> const_pointer_cast(not_null_shared_ptr<U>&& r) {
  auto p = std::const_pointer_cast<T, U>(std::move(r).unwrap());
  return not_null_shared_ptr<T>(
      std::move(p), detail::secret_guaranteed_not_null::get());
}
template <typename T, typename U>
not_null_shared_ptr<T> reinterpret_pointer_cast(
    const not_null_shared_ptr<U>& r) {
  auto p = std::reinterpret_pointer_cast<T, U>(r.unwrap());
  return not_null_shared_ptr<T>(
      std::move(p), detail::secret_guaranteed_not_null::get());
}
template <typename T, typename U>
not_null_shared_ptr<T> reinterpret_pointer_cast(not_null_shared_ptr<U>&& r) {
  auto p = std::reinterpret_pointer_cast<T, U>(std::move(r).unwrap());
  return not_null_shared_ptr<T>(
      std::move(p), detail::secret_guaranteed_not_null::get());
}

} // namespace folly
