#ifndef _TILE_BASE_META_META_H
#define _TILE_BASE_META_META_H

#pragma once

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

#include <functional>
#include <tuple>
#include <type_traits>
#include <utility>

namespace tile {
namespace internal {
namespace detail {

template<typename F>
struct return_traits : return_traits<decltype(&F::operator())> {};

template<typename T>
struct return_traits<T *> : return_traits<T> {};

template<typename R, typename... Args>
struct return_traits<R (*)(Args...)> {
    using type = R;
};

#define TILE_INTERNAL_DETAIL_RETURN_TRAITS_FOR_MEM_FUN(...)                                                            \
    template<typename R, typename C, typename... Args>                                                                 \
    struct return_traits<R (C::*)(Args...) __VA_ARGS__> {                                                              \
        using type = R;                                                                                                \
    }
TILE_INTERNAL_DETAIL_RETURN_TRAITS_FOR_MEM_FUN();
TILE_INTERNAL_DETAIL_RETURN_TRAITS_FOR_MEM_FUN(const);
TILE_INTERNAL_DETAIL_RETURN_TRAITS_FOR_MEM_FUN(volatile);
TILE_INTERNAL_DETAIL_RETURN_TRAITS_FOR_MEM_FUN(const volatile);
#undef TILE_INTERNAL_DETAIL_RETURN_TRAITS_FOR_MEM_FUN

}// namespace detail
}// namespace internal
}// namespace tile

namespace tile {

// underlying_type
// underlying_type_t
template<typename T, typename = void>
struct underlying_type {
    using type = void;
};

template<typename T>
struct underlying_type<T, enable_if_t<std::is_enum<T>::value>> {
    using type = typename std::underlying_type<T>::type;
};

template<typename T>
using underlying_type_t = typename underlying_type<T>::type;

// template <typename F, typename... Args>
// struct is_invocable
//     : std::is_constructible<
//           std::function<void(Args...)>,
//           std::reference_wrapper<typename std::remove_reference<F>::type>>
//           {};
// template <typename R, typename F, typename... Args>
// struct is_invocable_r
//     : std::is_constructible<
//           std::function<R(Args...)>,
//           std::reference_wrapper<typename std::remove_reference<F>::type>>
//           {};

namespace internal {

template<typename F, typename... Args>
struct is_valid {
    constexpr static bool value = tile::is_invocable<F, Args...>::value;
};

template<typename S, typename T>
struct is_streamable {
    template<typename SS, typename TT, typename = decltype(std::declval<SS &>() << std::declval<TT>())>
    static int Test(int);
    template<typename, typename>
    static char Test(...);
    static constexpr bool value = std::is_same<decltype(Test<S, T>(0)), int>::value;
};

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

template<typename T, typename... Args>
using first_element_t = typename first_element<T, Args...>::type;

template<typename T>
typename std::remove_const<T>::type *
RemoveConstPtr(T *p)
{
    return const_cast<typename std::remove_const<T>::type *>(p);
}

template<typename Op, typename A1, typename A2>
auto
InvokeOp(Op &&op, A1 &&a1, A2 &&a2) -> decltype(op(std::forward<A1>(a1), std::forward<A2>(a2)))
{
    return op(std::forward<A1>(a1), std::forward<A2>(a2));
}

template<typename Op, typename A1, typename... Ts>
auto
InvokeOp(Op &&op, A1 &&a1, Ts &&...ts) -> decltype(op(a1, InvokeOp(op, ts...)))
{
    return op(op, a1, InvokeOp(op, ts...));
}

template<typename T>
using remove_cv_t = typename std::remove_cv<T>::type;

template<typename T>
struct remove_cvref {
    using type = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
};

template<typename T>
using remove_cvref_t = typename remove_cvref<T>::type;

template<typename T>
using remove_reference_t = typename std::remove_reference<T>::type;

template<typename...>
using void_t = void;

template<bool value, typename TrueType, typename FalseType>
using conditional_t = typename std::conditional<value, TrueType, FalseType>::type;

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

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

template<typename T, typename... Ts>
struct conjunction<T, Ts...> : conditional_t<bool(T::value), conjunction<Ts...>, T> {};

template<typename T>
using add_lvalue_reference_t = typename std::add_lvalue_reference<T>::type;
template<typename T>
using add_rvalue_reference_t = typename std::add_rvalue_reference<T>::type;

template<typename T>
using decay_t = typename std::decay<T>::type;

template<typename T>
struct is_pointer_noref : std::is_pointer<remove_reference_t<T>> {};

template<typename T>
struct is_memfunc_noref : std::is_member_function_pointer<remove_reference_t<T>> {};

// Check is std::reference_wrapper
template<typename T>
struct is_wrapper : std::false_type {};

template<typename T>
struct is_wrapper<std::reference_wrapper<T>> : std::true_type {};

template<typename T>
struct is_wrapper_noref : is_wrapper<remove_reference_t<remove_cv_t<T>>> {};

}// namespace internal

}// namespace tile

#endif// _TILE_BASE_META_META_H
