/*
    Copyright (c) 2005-2023 Intel Corporation

    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.
*/

#ifndef __TBB_detail__template_helpers_H
#define __TBB_detail__template_helpers_H

#include "_utils.h"
#include "_config.h"

#include <cstddef>
#include <cstdint>
#include <utility>
#include <type_traits>
#include <memory>
#include <iterator>

namespace tbb {
namespace detail {
inline namespace d0 {

// An internal implementation of void_t, which can be used in SFINAE contexts
template <typename...>
struct void_impl {
    using type = void;
}; // struct void_impl

template <typename... Args>
using void_t = typename void_impl<Args...>::type;

// Generic SFINAE helper for expression checks, based on the idea demonstrated in ISO C++ paper n4502
template <typename T, typename, template <typename> class... Checks>
struct supports_impl {
    using type = std::false_type;
};

template <typename T, template <typename> class... Checks>
struct supports_impl<T, void_t<Checks<T>...>, Checks...> {
    using type = std::true_type;
};

template <typename T, template <typename> class... Checks>
using supports = typename supports_impl<T, void, Checks...>::type;

//! A template to select either 32-bit or 64-bit constant as compile time, depending on machine word size.
template <unsigned u, unsigned long long ull >
struct select_size_t_constant {
    // Explicit cast is needed to avoid compiler warnings about possible truncation.
    // The value of the right size,   which is selected by ?:, is anyway not truncated or promoted.
    static const std::size_t value = static_cast<std::size_t>((sizeof(std::size_t)==sizeof(u)) ? u : ull);
};

// TODO: do we really need it?
//! Cast between unrelated pointer types.
/** This method should be used sparingly as a last resort for dealing with
  situations that inherently break strict ISO C++ aliasing rules. */
// T is a pointer type because it will be explicitly provided by the programmer as a template argument;
// U is a referent type to enable the compiler to check that "ptr" is a pointer, deducing U in the process.
template<typename T, typename U>
inline T punned_cast( U* ptr ) {
    std::uintptr_t x = reinterpret_cast<std::uintptr_t>(ptr);
    return reinterpret_cast<T>(x);
}

template<class T, size_t S, size_t R>
struct padded_base : T {
    char pad[S - R];
};
template<class T, size_t S> struct padded_base<T, S, 0> : T {};

//! Pads type T to fill out to a multiple of cache line size.
template<class T, size_t S = max_nfs_size>
struct padded : padded_base<T, S, sizeof(T) % S> {};

#if __TBB_CPP14_INTEGER_SEQUENCE_PRESENT

using std::index_sequence;
using std::make_index_sequence;

#else

template<std::size_t... S> class index_sequence {};

template<std::size_t N, std::size_t... S>
struct make_index_sequence_impl : make_index_sequence_impl < N - 1, N - 1, S... > {};

template<std::size_t... S>
struct make_index_sequence_impl <0, S...> {
    using type = index_sequence<S...>;
};

template<std::size_t N>
using make_index_sequence = typename make_index_sequence_impl<N>::type;

#endif /* __TBB_CPP14_INTEGER_SEQUENCE_PRESENT */

#if __TBB_CPP17_LOGICAL_OPERATIONS_PRESENT
using std::conjunction;
using std::disjunction;
#else // __TBB_CPP17_LOGICAL_OPERATIONS_PRESENT

template <typename...>
struct conjunction : std::true_type {};

template <typename First, typename... Args>
struct conjunction<First, Args...>
    : std::conditional<bool(First::value), conjunction<Args...>, First>::type {};

template <typename T>
struct conjunction<T> : T {};

template <typename...>
struct disjunction : std::false_type {};

template <typename First, typename... Args>
struct disjunction<First, Args...>
    : std::conditional<bool(First::value), First, disjunction<Args...>>::type {};

template <typename T>
struct disjunction<T> : T {};

#endif // __TBB_CPP17_LOGICAL_OPERATIONS_PRESENT

template <typename Iterator>
using iterator_value_t = typename std::iterator_traits<Iterator>::value_type;

template <typename Iterator>
using iterator_key_t = typename std::remove_const<typename iterator_value_t<Iterator>::first_type>::type;

template <typename Iterator>
using iterator_mapped_t = typename iterator_value_t<Iterator>::second_type;

template <typename Iterator>
using iterator_alloc_pair_t = std::pair<typename std::add_const<iterator_key_t<Iterator>>::type,
                                        iterator_mapped_t<Iterator>>;

template <typename A> using alloc_value_type = typename A::value_type;
template <typename A> using alloc_ptr_t = typename std::allocator_traits<A>::pointer;
template <typename A> using has_allocate = decltype(std::declval<alloc_ptr_t<A>&>() = std::declval<A>().allocate(0));
template <typename A> using has_deallocate = decltype(std::declval<A>().deallocate(std::declval<alloc_ptr_t<A>>(), 0));

// alloc_value_type should be checked first, because it can be used in other checks
template <typename T>
using is_allocator = supports<T, alloc_value_type, has_allocate, has_deallocate>;

#if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
template <typename T>
inline constexpr bool is_allocator_v = is_allocator<T>::value;
#endif

// Template class in which the "type" determines the type of the element number N in pack Args
template <std::size_t N, typename... Args>
struct pack_element {
    using type = void;
};

template <std::size_t N, typename T, typename... Args>
struct pack_element<N, T, Args...> {
    using type = typename pack_element<N-1, Args...>::type;
};

template <typename T, typename... Args>
struct pack_element<0, T, Args...> {
    using type = T;
};

template <std::size_t N, typename... Args>
using pack_element_t = typename pack_element<N, Args...>::type;

template <typename Func>
class raii_guard {
public:
    static_assert(
        std::is_nothrow_copy_constructible<Func>::value &&
        std::is_nothrow_move_constructible<Func>::value,
        "Throwing an exception during the Func copy or move construction cause an unexpected behavior."
    );

    raii_guard( Func f ) noexcept : my_func(f), is_active(true) {}

    raii_guard( raii_guard&& g ) noexcept : my_func(std::move(g.my_func)), is_active(g.is_active) {
        g.is_active = false;
    }

    ~raii_guard() {
        if (is_active) {
            my_func();
        }
    }

    void dismiss() {
        is_active = false;
    }
private:
    Func my_func;
    bool is_active;
}; // class raii_guard

template <typename Func>
raii_guard<Func> make_raii_guard( Func f ) {
    return raii_guard<Func>(f);
}

template <typename Body>
struct try_call_proxy {
    try_call_proxy( Body b ) : body(b) {}

    template <typename OnExceptionBody>
    void on_exception( OnExceptionBody on_exception_body ) {
        auto guard = make_raii_guard(on_exception_body);
        body();
        guard.dismiss();
    }

    template <typename OnCompletionBody>
    void on_completion(OnCompletionBody on_completion_body) {
        auto guard = make_raii_guard(on_completion_body);
        body();
    }

    Body body;
}; // struct try_call_proxy

// Template helper function for API
// try_call(lambda1).on_exception(lambda2)
// Executes lambda1 and if it throws an exception - executes lambda2
template <typename Body>
try_call_proxy<Body> try_call( Body b ) {
    return try_call_proxy<Body>(b);
}

#if __TBB_CPP17_IS_SWAPPABLE_PRESENT
using std::is_nothrow_swappable;
using std::is_swappable;
#else // __TBB_CPP17_IS_SWAPPABLE_PRESENT
namespace is_swappable_detail {
using std::swap;

template <typename T>
using has_swap = decltype(swap(std::declval<T&>(), std::declval<T&>()));

#if _MSC_VER && _MSC_VER <= 1900 && !__INTEL_COMPILER
// Workaround for VS2015: it fails to instantiate noexcept(...) inside std::integral_constant.
template <typename T>
struct noexcept_wrapper {
    static const bool value = noexcept(swap(std::declval<T&>(), std::declval<T&>()));
};
template <typename T>
struct is_nothrow_swappable_impl : std::integral_constant<bool, noexcept_wrapper<T>::value> {};
#else
template <typename T>
struct is_nothrow_swappable_impl : std::integral_constant<bool, noexcept(swap(std::declval<T&>(), std::declval<T&>()))> {};
#endif
}

template <typename T>
struct is_swappable : supports<T, is_swappable_detail::has_swap> {};

template <typename T>
struct is_nothrow_swappable
    : conjunction<is_swappable<T>, is_swappable_detail::is_nothrow_swappable_impl<T>> {};
#endif // __TBB_CPP17_IS_SWAPPABLE_PRESENT

//! Allows to store a function parameter pack as a variable and later pass it to another function
template< typename... Types >
struct stored_pack;

template<>
struct stored_pack<>
{
    using pack_type = stored_pack<>;
    stored_pack() {}

    // Friend front-end functions
    template< typename F, typename Pack > friend void call(F&& f, Pack&& p);
    template< typename Ret, typename F, typename Pack > friend Ret call_and_return(F&& f, Pack&& p);

protected:
    // Ideally, ref-qualified non-static methods would be used,
    // but that would greatly reduce the set of compilers where it works.
    template< typename Ret, typename F, typename... Preceding >
    static Ret call(F&& f, const pack_type& /*pack*/, Preceding&&... params) {
        return std::forward<F>(f)(std::forward<Preceding>(params)...);
    }
    template< typename Ret, typename F, typename... Preceding >
    static Ret call(F&& f, pack_type&& /*pack*/, Preceding&&... params) {
        return std::forward<F>(f)(std::forward<Preceding>(params)...);
    }
};

template< typename T, typename... Types >
struct stored_pack<T, Types...> : stored_pack<Types...>
{
    using pack_type = stored_pack<T, Types...>;
    using pack_remainder = stored_pack<Types...>;

    // Since lifetime of original values is out of control, copies should be made.
    // Thus references should be stripped away from the deduced type.
    typename std::decay<T>::type leftmost_value;

    // Here rvalue references act in the same way as forwarding references,
    // as long as class template parameters were deduced via forwarding references.
    stored_pack(T&& t, Types&&... types)
    : pack_remainder(std::forward<Types>(types)...), leftmost_value(std::forward<T>(t)) {}

    // Friend front-end functions
    template< typename F, typename Pack > friend void call(F&& f, Pack&& p);
    template< typename Ret, typename F, typename Pack > friend Ret call_and_return(F&& f, Pack&& p);

protected:
    template< typename Ret, typename F, typename... Preceding >
    static Ret call(F&& f, pack_type& pack, Preceding&&... params) {
        return pack_remainder::template call<Ret>(
            std::forward<F>(f), static_cast<pack_remainder&>(pack),
            std::forward<Preceding>(params)... , pack.leftmost_value
        );
    }

    template< typename Ret, typename F, typename... Preceding >
    static Ret call(F&& f, pack_type&& pack, Preceding&&... params) {
        return pack_remainder::template call<Ret>(
            std::forward<F>(f), static_cast<pack_remainder&&>(pack),
            std::forward<Preceding>(params)... , std::move(pack.leftmost_value)
        );
    }
};

//! Calls the given function with arguments taken from a stored_pack
template< typename F, typename Pack >
void call(F&& f, Pack&& p) {
    std::decay<Pack>::type::template call<void>(std::forward<F>(f), std::forward<Pack>(p));
}

template< typename Ret, typename F, typename Pack >
Ret call_and_return(F&& f, Pack&& p) {
    return std::decay<Pack>::type::template call<Ret>(std::forward<F>(f), std::forward<Pack>(p));
}

template< typename... Types >
stored_pack<Types...> save_pack(Types&&... types) {
    return stored_pack<Types...>(std::forward<Types>(types)...);
}

// A structure with the value which is equal to Trait::value
// but can be used in the immediate context due to parameter T
template <typename Trait, typename T>
struct dependent_bool : std::integral_constant<bool, bool(Trait::value)> {};

template <typename Callable>
struct body_arg_detector;

template <typename Callable, typename ReturnType, typename Arg>
struct body_arg_detector<ReturnType(Callable::*)(Arg)> {
    using arg_type = Arg;
};

template <typename Callable, typename ReturnType, typename Arg>
struct body_arg_detector<ReturnType(Callable::*)(Arg) const> {
    using arg_type = Arg;
};

template <typename Callable>
struct argument_detector;

template <typename Callable>
struct argument_detector {
    using type = typename body_arg_detector<decltype(&Callable::operator())>::arg_type;
};

template <typename ReturnType, typename Arg>
struct argument_detector<ReturnType(*)(Arg)> {
    using type = Arg;
};

// Detects the argument type of callable, works for callable with one argument.
template <typename Callable>
using argument_type_of = typename argument_detector<typename std::decay<Callable>::type>::type;

template <typename T>
struct type_identity {
    using type = T;
};

template <typename T>
using type_identity_t = typename type_identity<T>::type;

} // inline namespace d0
} // namespace detail
} // namespace tbb

#endif // __TBB_detail__template_helpers_H
