#include "tile/base/internal/meta/base.h"
#include "tile/base/internal/meta/index_sequence.h"
#include "tile/base/internal/meta/invoke.h"

#include <functional>

namespace tile {
namespace detail {
namespace apply {
template<typename T>
struct is_tuple_impl : std::false_type {};

template<typename... Args>
struct is_tuple_impl<std::tuple<Args...>> : std::true_type {};

template<typename T>
struct is_tuple : is_tuple_impl<remove_reference_t<remove_cvref_t<T>>> {};

template<typename F, typename Tuple>
struct is_tuple_callable {
private:
    struct unsupport_type_tag_t {
        explicit unsupport_type_tag_t() = default;
    };

    using RawTuple = remove_reference_t<remove_cvref_t<Tuple>>;

    template<typename FF, typename... Ts, typename = enable_if_t<is_invocable<FF, Ts...>::value>>
    static int Test(FF &&, std::tuple<Ts...> &&);
    template<typename...>
    static unsupport_type_tag_t Test(...);

public:
    static constexpr bool value = is_tuple<RawTuple>::value
        && !std::is_same<decltype(Test(std::declval<F>(), std::declval<RawTuple>())), unsupport_type_tag_t>::value;
};

template<typename F, typename Tuple>
struct tuple_invoke_result {
    using RawTuple = remove_reference_t<remove_cvref_t<Tuple>>;
    template<typename FF, typename... Ts>
    static auto Test(FF &&, std::tuple<Ts...> &&) -> invoke_result_t<FF, Ts...>;

    using type = decltype(Test(std::declval<F>(), std::declval<RawTuple>()));
};

template<typename F, typename Tuple>
using tuple_invoke_result_t = typename tuple_invoke_result<F, Tuple>::type;

static_assert(is_tuple_callable<std::function<void()>, std::tuple<>>::value, "");

static_assert(is_tuple_callable<std::function<char()>, std::tuple<>>::value, "");

static_assert(is_tuple_callable<std::function<void(int)>, std::tuple<int>>::value, "");

static_assert(is_tuple_callable<std::function<int(int)>, std::tuple<int>>::value, "");

static_assert(is_tuple_callable<std::function<int(int, int)>, std::tuple<int, char>>::value, "");

template<typename F, typename... Args, int... Idxs>
auto
tuple_invoke_impl(F &&f, const std::tuple<Args...> &tp, index_sequence<Idxs...>) -> invoke_result_t<F, Args...>
{
    return invoke(std::forward<F>(f), std::forward<Args>(std::get<Idxs>(tp))...);
}

template<typename F, typename... Args, int... Idxs>
auto
tuple_invoke_impl(F &&f, std::tuple<Args...> &tp, index_sequence<Idxs...>) -> invoke_result_t<F, Args...>
{
    return invoke(std::forward<F>(f), std::forward<Args>(std::get<Idxs>(tp))...);
}

template<typename F, typename... Args, int... Idxs>
auto
tuple_invoke_impl(F &&f, std::tuple<Args...> &&tp, index_sequence<Idxs...>) -> invoke_result_t<F, Args...>
{
    return invoke(std::forward<F>(f), std::forward<Args>(std::get<Idxs>(tp))...);
}

template<typename F, typename Tuple>
auto
tuple_invoke(F &&f, Tuple &&tp) -> tuple_invoke_result_t<F, Tuple>
{
    using RawTuple = remove_reference_t<remove_cvref_t<Tuple>>;
    return tuple_invoke_impl(
        std::forward<F>(f), std::forward<Tuple>(tp), make_index_sequence_t<std::tuple_size<RawTuple>::value>{});
}

}// namespace apply
}// namespace detail

template<typename F, typename Tuple>
auto
apply(F &&f, Tuple &&tp) -> detail::apply::tuple_invoke_result_t<F, Tuple>
{
    return detail::apply::tuple_invoke(std::forward<F>(f), std::forward<Tuple>(tp));
}

}// namespace tile
