#pragma once

#include <memory>
#include <type_traits>

namespace drake {

/** @cond */

namespace is_cloneable_internal {

// Default case; assumes that a class is *not* cloneable.
template <typename T, class>
struct is_cloneable_helper : std::false_type {};

// Special sauce for SFINAE. Only compiles if it can finds the method
// `unique_ptr<T> T::Clone() const`. If this exists, the is_cloneable implicitly
// prefers this overload over the default overload.
template <typename T>
struct is_cloneable_helper<
    T, typename std::enable_if_t<
           std::is_same_v<decltype(std::declval<const T>().Clone().release()),
                          typename std::remove_const_t<T>*>>> : std::true_type {
};

}  // namespace is_cloneable_internal

/** @endcond */

/**
 @anchor is_cloneable_doc
 Provides method for determining at run time if a class is "cloneable".

 __Usage__

 This gets used like `type_traits` functions (e.g., `is_copy_constructible`,
 `is_same`, etc.) To determine if a class is cloneable simply invoke:

 @code
 bool value = drake::is_cloneable<Foo>::value;
 @endcode

 If `Foo` is cloneable, it will evaluate to true. It can also be used in
 compile-time tests (e.g., SFINAE and `static_assert`s):

 @code
 static_assert(is_cloneable<Foo>::value, "This method requires its classes to "
                                         "be cloneable.");
 @endcode

 __Definition of "cloneability"__

 To be cloneable, the class `Foo` must have a _public_ method of the form:
 @code
 unique_ptr<Foo> Foo::Clone() const;
 @endcode
 Note that "friend" access for the %is_cloneable-using class is not sufficient.
 The `Foo::Clone()` method must actually be public.

 <!-- Developer note: if you change or extend the definition of an acceptable
      clone method here, be sure to consider whether
      copyable_unique_ptr::can_clone() should be changed as well. -->

 The pointer contained in the returned `unique_ptr` must point to a
 heap-allocated deep copy of the _concrete_ object. This test can confirm the
 proper signature, but cannot confirm the heap-allocated deep copy. A Clone()
 method that doesn't return such a copy of the _concrete_ object should be
 considered a malformed function.

 @warning It is important to note, that a `Clone()` method that returns a
 `unique_ptr` to a _super_ class is _not_ sufficient to be cloneable. In other
 words the presence of:
 @code
 unique_ptr<Base> Derived::Clone() const;
 @endcode
 will not make the `Derived` class cloneable.

 @tparam  T  The class to test for cloneability.
 */
template <typename T>
using is_cloneable = is_cloneable_internal::is_cloneable_helper<T, void>;

}  // namespace drake
