//  Copyright (c) 2017-2021 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 <pika/async_base/traits/is_launch_policy.hpp>
#include <pika/concepts/has_member_xxx.hpp>
#include <pika/execution/traits/is_execution_policy.hpp>
#include <pika/execution_base/traits/is_executor.hpp>
#include <pika/type_support/detected.hpp>

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

namespace pika {
    template <typename R>
    class future;
}    // namespace pika

namespace pika::execution {
    ///////////////////////////////////////////////////////////////////////////
    struct static_chunk_size;

    ///////////////////////////////////////////////////////////////////////////
    struct sequenced_execution_tag;
    struct parallel_execution_tag;
    struct unsequenced_execution_tag;
}    // namespace pika::execution

namespace pika::parallel::execution {
    ///////////////////////////////////////////////////////////////////////////
    namespace detail {
        PIKA_HAS_MEMBER_XXX_TRAIT_DEF(post)
        PIKA_HAS_MEMBER_XXX_TRAIT_DEF(sync_execute)
        PIKA_HAS_MEMBER_XXX_TRAIT_DEF(async_execute)
        PIKA_HAS_MEMBER_XXX_TRAIT_DEF(then_execute)
        PIKA_HAS_MEMBER_XXX_TRAIT_DEF(bulk_sync_execute)
        PIKA_HAS_MEMBER_XXX_TRAIT_DEF(bulk_async_execute)
        PIKA_HAS_MEMBER_XXX_TRAIT_DEF(bulk_then_execute)
    }    // namespace detail

    template <typename T, typename Enable = void>
    struct has_post_member : detail::has_post<std::decay_t<T>>
    {
    };

    template <typename T, typename Enable = void>
    struct has_sync_execute_member : detail::has_sync_execute<std::decay_t<T>>
    {
    };

    template <typename T, typename Enable = void>
    struct has_async_execute_member : detail::has_async_execute<std::decay_t<T>>
    {
    };

    template <typename T, typename Enable = void>
    struct has_then_execute_member : detail::has_then_execute<std::decay_t<T>>
    {
    };

    template <typename T, typename Enable = void>
    struct has_bulk_sync_execute_member : detail::has_bulk_sync_execute<std::decay_t<T>>
    {
    };

    template <typename T, typename Enable = void>
    struct has_bulk_async_execute_member : detail::has_bulk_async_execute<std::decay_t<T>>
    {
    };

    template <typename T, typename Enable = void>
    struct has_bulk_then_execute_member : detail::has_bulk_then_execute<std::decay_t<T>>
    {
    };

    ///////////////////////////////////////////////////////////////////////////
    template <typename Executor>
    struct executor_context
    {
        using type = std::decay_t<decltype(std::declval<Executor const&>().context())>;
    };

    ///////////////////////////////////////////////////////////////////////////
    // Components that create groups of execution agents may use execution
    // categories to communicate the forward progress and ordering guarantees
    // of these execution agents with respect to other agents within the same
    // group.

    ///////////////////////////////////////////////////////////////////////////
    template <typename Executor>
    struct executor_execution_category
    {
    private:
        template <typename T>
        using execution_category = typename T::execution_category;

    public:
        using type = pika::detail::detected_or_t<pika::execution::unsequenced_execution_tag,
            execution_category, Executor>;
    };

    ///////////////////////////////////////////////////////////////////////////
    template <typename Executor>
    struct executor_shape
    {
    private:
        template <typename T>
        using shape_type = typename T::shape_type;

    public:
        using type = pika::detail::detected_or_t<std::size_t, shape_type, Executor>;
    };

    ///////////////////////////////////////////////////////////////////////////
    template <typename Executor>
    struct executor_index
    {
    private:
        // exposition only
        template <typename T>
        using index_type = typename T::index_type;

    public:
        using type = pika::detail::detected_or_t<typename executor_shape<Executor>::type,
            index_type, Executor>;
    };

    ///////////////////////////////////////////////////////////////////////////
    template <typename Executor>
    struct executor_parameters_type
    {
    private:
        template <typename T>
        using parameters_type = typename T::parameters_type;

    public:
        using type = pika::detail::detected_or_t<pika::execution::static_chunk_size,
            parameters_type, Executor>;
    };

    ///////////////////////////////////////////////////////////////////////////
    namespace detail {
        template <typename Executor, typename T, typename Ts, typename Enable = void>
        struct executor_future;

        template <typename Executor, typename T, typename Enable = void>
        struct exposes_future_type : std::false_type
        {
        };

        template <typename Executor, typename T>
        struct exposes_future_type<Executor, T,
            std::void_t<typename Executor::template future_type<T>>> : std::true_type
        {
        };

        template <typename Executor, typename T, typename... Ts>
        struct executor_future<Executor, T, pika::util::detail::pack<Ts...>,
            std::enable_if_t<pika::traits::is_two_way_executor_v<Executor> &&
                exposes_future_type<Executor, T>::value>>
        {
            using type = typename Executor::template future_type<T>;
        };

        template <typename Executor, typename T, typename... Ts>
        struct executor_future<Executor, T, pika::util::detail::pack<Ts...>,
            std::enable_if_t<pika::traits::is_two_way_executor_v<Executor> &&
                !exposes_future_type<Executor, T>::value>>
        {
            using type = decltype(std::declval<Executor&&>().async_execute(
                std::declval<T (*)(Ts...)>(), std::declval<Ts>()...));
        };

        template <typename Executor, typename T, typename Ts>
        struct executor_future<Executor, T, Ts,
            std::enable_if_t<!pika::traits::is_two_way_executor_v<Executor>>>
        {
            using type = pika::future<T>;
        };
    }    // namespace detail

    template <typename Executor, typename T, typename... Ts>
    struct executor_future
      : detail::executor_future<std::decay_t<Executor>, T,
            pika::util::detail::pack<std::decay_t<Ts>...>>
    {
    };

    template <typename Executor, typename T, typename... Ts>
    using executor_future_t = typename executor_future<Executor, T, Ts...>::type;

}    // namespace pika::parallel::execution

namespace pika::traits {
    ///////////////////////////////////////////////////////////////////////////
    template <typename T, typename Enable = void>
    struct has_post_member : parallel::execution::has_post_member<std::decay_t<T>>
    {
    };

    template <typename T, typename Enable = void>
    struct has_sync_execute_member : parallel::execution::has_sync_execute_member<std::decay_t<T>>
    {
    };

    template <typename T, typename Enable = void>
    struct has_async_execute_member : parallel::execution::has_async_execute_member<std::decay_t<T>>
    {
    };

    template <typename T, typename Enable = void>
    struct has_then_execute_member : parallel::execution::has_then_execute_member<std::decay_t<T>>
    {
    };

    template <typename T, typename Enable = void>
    struct has_bulk_sync_execute_member
      : parallel::execution::has_bulk_sync_execute_member<std::decay_t<T>>
    {
    };

    template <typename T, typename Enable = void>
    struct has_bulk_async_execute_member
      : parallel::execution::has_bulk_async_execute_member<std::decay_t<T>>
    {
    };

    template <typename T, typename Enable = void>
    struct has_bulk_then_execute_member
      : parallel::execution::has_bulk_then_execute_member<std::decay_t<T>>
    {
    };

    template <typename T>
    inline constexpr bool has_post_member_v = has_post_member<T>::value;

    template <typename T>
    inline constexpr bool has_sync_execute_member_v = has_sync_execute_member<T>::value;

    template <typename T>
    inline constexpr bool has_async_execute_member_v = has_async_execute_member<T>::value;

    template <typename T>
    inline constexpr bool has_then_execute_member_v = has_then_execute_member<T>::value;

    template <typename T>
    inline constexpr bool has_bulk_sync_execute_member_v = has_bulk_sync_execute_member<T>::value;

    template <typename T>
    inline constexpr bool has_bulk_async_execute_member_v = has_bulk_async_execute_member<T>::value;

    template <typename T>
    inline constexpr bool has_bulk_then_execute_member_v = has_bulk_then_execute_member<T>::value;

    ///////////////////////////////////////////////////////////////////////////
    template <typename Executor, typename Enable = void>
    struct executor_context : parallel::execution::executor_context<std::decay_t<Executor>>
    {
    };

    template <typename Executor>
    using executor_context_t = typename executor_context<Executor>::type;

    template <typename Executor, typename Enable = void>
    struct executor_execution_category
      : parallel::execution::executor_execution_category<std::decay_t<Executor>>
    {
    };

    template <typename Executor>
    using executor_execution_category_t = typename executor_execution_category<Executor>::type;

    template <typename Executor, typename Enable = void>
    struct executor_shape : parallel::execution::executor_shape<std::decay_t<Executor>>
    {
    };

    template <typename Executor>
    using executor_shape_t = typename executor_shape<Executor>::type;

    template <typename Executor, typename Enable = void>
    struct executor_index : parallel::execution::executor_index<std::decay_t<Executor>>
    {
    };

    template <typename Executor>
    using executor_index_t = typename executor_index<Executor>::type;

    template <typename Executor, typename T, typename... Ts>
    struct executor_future
      : parallel::execution::executor_future<std::decay_t<Executor>, T, std::decay_t<Ts>...>
    {
    };

    template <typename Executor, typename T, typename... Ts>
    using executor_future_t = typename executor_future<Executor, T, Ts...>::type;

    ///////////////////////////////////////////////////////////////////////////
    // extension
    template <typename Executor, typename Enable = void>
    struct executor_parameters_type
      : parallel::execution::executor_parameters_type<std::decay_t<Executor>>
    {
    };

    template <typename Executor>
    using executor_parameters_type_t = typename executor_parameters_type<Executor>::type;

}    // namespace pika::traits
