// Copyright 2009-present MongoDB, Inc.
//
// 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.

#pragma once

#include <bsoncxx/v1/config/export.hpp>

#include <memory>

#pragma push_macro("BSONCXX_DETAIL_USE_STD_MAKE_UNIQUE")
#undef BSONCXX_DETAIL_USE_STD_MAKE_UNIQUE

#if (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L)
#define BSONCXX_DETAIL_USE_STD_MAKE_UNIQUE
#endif

// Only define bsoncxx implementation details when necessary.
#if !defined(BSONCXX_DETAIL_USE_STD_MAKE_UNIQUE) || !defined(__cpp_lib_smart_ptr_for_overwrite)

#include <cstddef>
#include <type_traits>
#include <utility>

#include <bsoncxx/stdx/type_traits.hpp>

namespace bsoncxx {
namespace detail {

// Switch backend of make_unique by the type we are creating.
// It would be easier to 'if constexpr' on whether we are an array and whether to direct-init or
// value-init, but we don't have if-constexpr and we need it to guard against an uterance of a
// possibly-illegal 'new' expression.
template <typename T>
struct make_unique_impl {
    // For make_unique:
    template <
        typename... Args,
        // Guard on constructible-from:
        typename = decltype(new T(std::declval<Args>()...))>
    static std::unique_ptr<T> make(std::true_type /* direct-init */, Args&&... args) {
        return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
    }

    // For make_unique_for_overwrite:
    template <
        typename U = T,
        // Guard on whether T is value-initializable:
        // (Hide behind a deduced 'U' to defer the evaluation of
        // this default template argument until overload resolution)
        typename = decltype(new U)>
    static std::unique_ptr<T> make(std::false_type /* value-init */) {
        return std::unique_ptr<T>(new T);
    }
};

// For unbounded arrays:
template <typename Elem>
struct make_unique_impl<Elem[]> {
    template <
        typename ShouldDirectInit,
        // Guard on whether the new-expression will be legal:
        typename = decltype(new Elem[std::declval<std::size_t>()])>
    static std::unique_ptr<Elem[]> make(ShouldDirectInit, std::size_t count) {
        // These can share a function via a plain if, because both new expressions
        // must be semantically valid
        if (ShouldDirectInit()) {
            return std::unique_ptr<Elem[]>(new Elem[count]());
        } else {
            return std::unique_ptr<Elem[]>(new Elem[count]);
        }
    }
};

// Bounded arrays are disallowed:
template <typename Elem, std::size_t N>
struct make_unique_impl<Elem[N]> {};

// References are nonsense:
template <typename T>
struct make_unique_impl<T&> {};

// References are nonsense:
template <typename T>
struct make_unique_impl<T&&> {};

} // namespace detail
} // namespace bsoncxx

#endif // !defined(BSONCXX_DETAIL_USE_STD_MAKE_UNIQUE) ||
       // !defined(__cpp_lib_smart_ptr_for_overwrite)

namespace bsoncxx {

// Unlike other C++17 polyfill features, this is a C++14 feature.
// Use feature testing rather than polyfill library selection macros.
#if defined(BSONCXX_DETAIL_USE_STD_MAKE_UNIQUE)
using std::make_unique;
#else

// Equivalent to `std::make_unique<T>(args...)` where `T` is a non-array type.
// @cond DOXYGEN_DISABLE
template <
    typename T,
    typename... Args,
    typename Impl = detail::make_unique_impl<T>,
    typename std::enable_if<
        !std::is_array<T>::value,
        decltype(Impl::make(std::true_type{}, std::declval<Args>()...), void())>::type* = nullptr>
std::unique_ptr<T> make_unique(Args&&... args) {
    return Impl::make(std::true_type{}, std::forward<Args>(args)...);
}
// @endcond

// Equivalent to `std::make_unique<T>(count)` where `T` is an array type.
// @cond DOXYGEN_DISABLE
template <
    typename T,
    typename Impl = detail::make_unique_impl<T>,
    typename std::enable_if<
        std::is_array<T>::value,
        decltype(Impl::make(std::true_type{}, std::declval<std::size_t>()), void())>::type* = nullptr>
std::unique_ptr<T> make_unique(std::size_t count) {
    return Impl::make(std::true_type{}, count);
}
// @endcond DOXYGEN_DISABLE

#endif

// Unlike other C++17 polyfill features, this is a C++20 feature.
// Use feature testing rather than polyfill library selection macros.
#if defined(__cpp_lib_smart_ptr_for_overwrite)
using std::make_unique_for_overwrite;
#else

// Equivalent to `std::make_unique_for_overwrite<T>()` where `T` is a non-array type.
template <
    typename T,
    typename Impl = detail::make_unique_impl<T>,
    typename std::enable_if<!std::is_array<T>::value, decltype(Impl::make(std::false_type{}), void())>::type* = nullptr>
std::unique_ptr<T> make_unique_for_overwrite() {
    return Impl::make(std::false_type{});
}

// Equivalent to `std::make_unique_for_overwrite<T>(count)` where `T` is an array type.
template <
    typename T,
    typename Impl = detail::make_unique_impl<T>,
    typename std::enable_if<
        std::is_array<T>::value,
        decltype(Impl::make(std::false_type{}, std::declval<std::size_t>()), void())>::type* = nullptr>
std::unique_ptr<T> make_unique_for_overwrite(std::size_t count) {
    return Impl::make(std::false_type{}, count);
}

#endif

} // namespace bsoncxx

#pragma pop_macro("BSONCXX_DETAIL_USE_STD_MAKE_UNIQUE")
