//  Copyright (c) 2017 Hartmut Kaiser
//
//  SPDX-License-Identifier: BSL-1.0
//  Distributed under the Boost Software License, Version 1.0. (See accompanying
//  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#pragma once

#include <pika/config.hpp>

#include <type_traits>
#include <utility>

namespace pika::detail {
    // pika::detail::nonesuch is a class type used by
    // pika::detail::detected_t to indicate detection failure.
    struct nonesuch
    {
        nonesuch() = delete;
        ~nonesuch() = delete;
        nonesuch(nonesuch const&) = delete;
        void operator=(nonesuch const&) = delete;
    };

    template <typename Default, typename AlwaysVoid, template <typename...> class Op,
        typename... Args>
    struct detector
    {
        using value_t = std::false_type;
        using type = Default;
    };

    template <typename Default, template <typename...> class Op, typename... Args>
    struct detector<Default, std::void_t<Op<Args...>>, Op, Args...>
    {
        using value_t = std::true_type;
        using type = Op<Args...>;
    };

    // The alias template is_detected is an alias for std::true_type if the
    // template-id Op<Args...> is valid; otherwise it is an alias for
    // std::false_type.
    template <template <typename...> class Op, typename... Args>
    using is_detected = typename detector<nonesuch, void, Op, Args...>::value_t;

    // The alias template detected_t is an alias for Op<Args...> if that
    // template-id is valid; otherwise it is an alias for the class
    // pika::detail::nonesuch.
    template <template <typename...> class Op, typename... Args>
    using detected_t = typename detector<nonesuch, void, Op, Args...>::type;

    // The alias template detected_or is an alias for an unspecified class type
    // with two public member typedefs value_t and type, which are defined as
    // follows:
    //
    // - If the template-id Op<Args...> is valid, then value_t is an alias for
    //   std::true_type, and type is an alias for Op<Args...>;
    // - Otherwise, value_t is an alias for std::false_type and type is an
    //   alias for Default.
    template <typename Default, template <typename...> class Op, typename... Args>
    using detected_or = detector<Default, void, Op, Args...>;

    template <typename Default, template <typename...> class Op, typename... Args>
    using detected_or_t = typename detected_or<Default, Op, Args...>::type;

    // The alias template is_detected_exact checks whether
    // detected_t<Op, Args...> is Expected.
    template <typename Expected, template <typename...> class Op, typename... Args>
    using is_detected_exact = std::is_same<Expected, detected_t<Op, Args...>>;

    // The alias template is_detected_convertible checks whether
    // detected_t<Op, Args...> is convertible to To.
    template <typename To, template <typename...> class Op, typename... Args>
    using is_detected_convertible = std::is_convertible<detected_t<Op, Args...>, To>;
}    // namespace pika::detail
