#pragma once

#include <compare>
#include <concepts>
#include <type_traits>

namespace util {

template <typename T>
concept Number = std::integral<T> || std::floating_point<T>;

template <auto T, auto U>
requires std::equality_comparable_with<decltype(T), decltype(T)>
struct is_equal : public std::integral_constant<bool, T == U> {
};

template <auto T, auto U>
inline constexpr auto is_equal_v = is_equal<T, U>::value;

template <auto T, auto U>
requires std::three_way_comparable_with<decltype(T), decltype(T)>
struct cmp {
  constexpr static std::partial_ordering ordering = T <=> U;
};

template <auto T, auto U> inline constexpr auto cmp_v = cmp<T, U>::ordering;

template <std::partial_ordering ordering, auto T, auto U>
concept Compare = is_equal_v<cmp_v<T, U>, ordering>;

template <auto T, auto U>
concept Equal = is_equal_v<T, U>;

template <typename T, typename... Pack>
concept all_same_as = (std::same_as<T, Pack> && ...);

template <typename T, typename U>
concept explicit_convertible_to = requires(const T ct, T t, const U &cu) {
  {t = cu};
  { ct.operator U() } -> std::same_as<U>;
}
|| std::same_as<T, U>;

template <typename From, typename... To>
concept explicit_convertible_to_any_of =
    (explicit_convertible_to<std::remove_cv_t<From>, To> || ...);

template <typename From, typename... To>
struct is_explicit_convertible_to_any_of
    : std::bool_constant<explicit_convertible_to_any_of<From, To...>> {};

template <typename T>
concept explicit_convertible_to_integral =
    std::integral<T> || explicit_convertible_to_any_of < std::remove_cv_t<T>,
bool, char, signed char, unsigned char, wchar_t,
#ifdef __cpp_char8_t
    char8_t,
#endif // __cpp_char8_t
    char16_t, char32_t, short, unsigned short, int, unsigned int, long,
    unsigned long, long long, unsigned long long > ;

template <typename T>
concept explicit_convertible_to_floating_point = std::floating_point<T> ||
    explicit_convertible_to_any_of<T, float, double, long double>;

template <typename From, typename To>
struct is_explicit_convertible
    : std::bool_constant<explicit_convertible_to<From, To>> {};

template <typename From, typename To>
inline constexpr bool is_explicit_convertible_v =
    is_explicit_convertible<From, To>::value;

template <typename Fn, typename R, typename... Args>
concept with_return_type = requires(Fn fn, Args... args) {
  { fn(args...) } -> std::same_as<R>;
};

} // namespace util