//  Copyright (c) 2017 Denis Blank
//
//  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/pack_traversal/detail/pack_traversal_async_impl.hpp>

#include <utility>

namespace pika::util {
    /// A tag which is passed to the `operator()` of the visitor
    /// if an element is visited synchronously.
    using detail::async_traverse_visit_tag;
    /// A tag which is passed to the `operator()` of the visitor
    /// if an element is visited after the traversal was detached.
    using detail::async_traverse_detach_tag;
    /// A tag which is passed to the `operator()` of the visitor
    /// if the asynchronous pack traversal was finished.
    using detail::async_traverse_complete_tag;

    /// A tag to identify that a mapper shall be constructed in-place
    /// from the first argument passed.
    using detail::async_traverse_in_place_tag;

    /// Traverses the pack with the given visitor in an asynchronous way.
    ///
    /// This function works in the same way as `traverse_pack`,
    /// however, we are able to suspend and continue the traversal at
    /// later time.
    /// Thus we require a visitor callable object which provides three
    /// `operator()` overloads as depicted by the code sample below:
    ///    ```cpp
    ///    struct my_async_visitor
    ///    {
    ///        /// The synchronous overload is called for each object,
    ///        /// it may return false to suspend the current control.
    ///        /// In that case the overload below is called.
    ///        template <typename T>
    ///        bool operator()(async_traverse_visit_tag, T&& element)
    ///        {
    ///            return true;
    ///        }
    ///
    ///        /// The asynchronous overload this is called when the
    ///        /// synchronous overload returned false.
    ///        /// In addition to the current visited element the overload is
    ///        /// called with a continuation callable object which resumes the
    ///        /// traversal when it's called later.
    ///        /// The continuation next may be stored and called later or
    ///        /// dropped completely to abort the traversal early.
    ///        template <typename T, typename N>
    ///        void operator()(async_traverse_detach_tag, T&& element, N&& next)
    ///        {
    ///        }
    ///
    ///        /// The overload is called when the traversal was finished.
    ///        /// As argument the whole pack is passed over which we
    ///        /// traversed asynchronously.
    ///        template <typename T>
    ///        void operator()(async_traverse_complete_tag, T&& pack)
    ///        {
    ///        }
    ///    };
    ///    ```
    ///
    /// \param   visitor A visitor object which provides the three `operator()`
    ///                  overloads that were described above.
    ///                  Additionally the visitor must be compatible
    ///                  for referencing it from a `pika::intrusive_ptr`.
    ///                  The visitor should must have a virtual destructor!
    ///
    /// \param   pack    The arbitrary parameter pack which is traversed
    ///                  asynchronously. Nested objects inside containers and
    ///                  tuple like types are traversed recursively.
    ///
    /// \returns         A pika::intrusive_ptr that references an instance of
    ///                  the given visitor object.
    ///
    /// See `traverse_pack` for a detailed description about the
    /// traversal behavior and capabilities.
    ///
    template <typename Visitor, typename... T>
    auto traverse_pack_async(Visitor&& visitor, T&&... pack)
        -> decltype(detail::apply_pack_transform_async(
            PIKA_FORWARD(Visitor, visitor), PIKA_FORWARD(T, pack)...))
    {
        return detail::apply_pack_transform_async(
            PIKA_FORWARD(Visitor, visitor), PIKA_FORWARD(T, pack)...);
    }

    /// Traverses the pack with the given visitor in an asynchronous way.
    ///
    /// This function works in the same way as `traverse_pack`,
    /// however, we are able to suspend and continue the traversal at
    /// later time.
    /// Thus we require a visitor callable object which provides three
    /// `operator()` overloads as depicted by the code sample below:
    ///    ```cpp
    ///    struct my_async_visitor
    ///    {
    ///        /// The synchronous overload is called for each object,
    ///        /// it may return false to suspend the current control.
    ///        /// In that case the overload below is called.
    ///        template <typename T>
    ///        bool operator()(async_traverse_visit_tag, T&& element)
    ///        {
    ///            return true;
    ///        }
    ///
    ///        /// The asynchronous overload this is called when the
    ///        /// synchronous overload returned false.
    ///        /// In addition to the current visited element the overload is
    ///        /// called with a continuation callable object which resumes the
    ///        /// traversal when it's called later.
    ///        /// The continuation next may be stored and called later or
    ///        /// dropped completely to abort the traversal early.
    ///        template <typename T, typename N>
    ///        void operator()(async_traverse_detach_tag, T&& element, N&& next)
    ///        {
    ///        }
    ///
    ///        /// The overload is called when the traversal was finished.
    ///        /// As argument the whole pack is passed over which we
    ///        /// traversed asynchronously.
    ///        template <typename T>
    ///        void operator()(async_traverse_complete_tag, T&& pack)
    ///        {
    ///        }
    ///    };
    ///    ```
    ///
    /// \param   visitor A visitor object which provides the three `operator()`
    ///                  overloads that were described above.
    ///                  Additionally the visitor must be compatible
    ///                  for referencing it from a `pika::intrusive_ptr`.
    ///                  The visitor should must have a virtual destructor!
    ///
    /// \param   pack    The arbitrary parameter pack which is traversed
    ///                  asynchronously. Nested objects inside containers and
    ///                  tuple like types are traversed recursively.
    /// \param  alloc    Allocator instance to use to create the traversal
    ///                  frame.
    ///
    /// \returns         A pika::intrusive_ptr that references an instance of
    ///                  the given visitor object.
    ///
    /// See `traverse_pack` for a detailed description about the
    /// traversal behavior and capabilities.
    ///
    template <typename Allocator, typename Visitor, typename... T>
    auto traverse_pack_async_allocator(Allocator const& alloc, Visitor&& visitor, T&&... pack)
        -> decltype(detail::apply_pack_transform_async_allocator(
            alloc, PIKA_FORWARD(Visitor, visitor), PIKA_FORWARD(T, pack)...))
    {
        return detail::apply_pack_transform_async_allocator(
            alloc, PIKA_FORWARD(Visitor, visitor), PIKA_FORWARD(T, pack)...);
    }
}    // namespace pika::util
