// Copyright (C) 2020-2022 Free Software Foundation, Inc.
//
// This file is part of the GNU ISO C++ Library.  This library is free
// software; you can redistribute it and/or modify it under the
// terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3, or (at your option)
// any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this library; see the file COPYING3.  If not see
// <http://www.gnu.org/licenses/>.

#ifndef VC_TESTS_METAHELPERS_H_
#define VC_TESTS_METAHELPERS_H_

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

namespace vir
{
  namespace test
  {
    template <class A, class B, class Op>
      constexpr bool
      operator_is_substitution_failure_impl(float)
      { return true; }

    template <class A, class B, class Op>
      constexpr typename std::conditional<true, bool, decltype(
	  Op()(std::declval<A>(), std::declval<B>()))>::type
      operator_is_substitution_failure_impl(int)
      { return false; }

    template <class... Ts>
      constexpr bool
      operator_is_substitution_failure()
      { return operator_is_substitution_failure_impl<Ts...>(int()); }

    template <class... Args, class F>
      constexpr auto
      sfinae_is_callable_impl(int, F &&f) -> typename std::conditional<
	true, std::true_type,
	decltype(std::forward<F>(f)(std::declval<Args>()...))>::type;

    template <class... Args, class F>
      constexpr std::false_type
      sfinae_is_callable_impl(float, const F &);

    template <class... Args, class F>
      constexpr bool
      sfinae_is_callable(F &&)
      {
	return decltype(
	    sfinae_is_callable_impl<Args...>(int(), std::declval<F>()))::value;
      }

    template <class... Args, class F>
      constexpr auto sfinae_is_callable_t(F &&f)
	-> decltype(sfinae_is_callable_impl<Args...>(int(), std::declval<F>()));

    template <class A, class B>
      constexpr bool
      has_less_bits()
      { return std::__digits_v<A> < std::__digits_v<B>; }

  }  // namespace test
}  // namespace vir

struct assignment
{
  template <class A, class B>
    constexpr decltype(std::declval<A>() = std::declval<B>())
    operator()(A &&a, B &&b) const noexcept(noexcept(
	std::forward<A>(a) = std::forward<B>(b)))
    { return std::forward<A>(a) = std::forward<B>(b); }
};

struct bit_shift_left
{
  template <class A, class B>
    constexpr decltype(std::declval<A>() << std::declval<B>())
    operator()(A &&a, B &&b) const noexcept(noexcept(
	std::forward<A>(a) << std::forward<B>(b)))
    { return std::forward<A>(a) << std::forward<B>(b); }
};

struct bit_shift_right
{
  template <class A, class B>
    constexpr decltype(std::declval<A>() >> std::declval<B>())
    operator()(A &&a, B &&b) const noexcept(noexcept(
	std::forward<A>(a) >> std::forward<B>(b)))
    { return std::forward<A>(a) >> std::forward<B>(b); }
};

struct assign_modulus
{
  template <class A, class B>
    constexpr decltype(std::declval<A>() %= std::declval<B>())
    operator()(A &&a, B &&b) const noexcept(noexcept(
	std::forward<A>(a) %= std::forward<B>(b)))
    { return std::forward<A>(a) %= std::forward<B>(b); }
};

struct assign_bit_and
{
  template <class A, class B>
    constexpr decltype(std::declval<A>() &= std::declval<B>())
    operator()(A &&a, B &&b) const noexcept(noexcept(
	std::forward<A>(a) &= std::forward<B>(b)))
    { return std::forward<A>(a) &= std::forward<B>(b); }
};

struct assign_bit_or
{
  template <class A, class B>
    constexpr decltype(std::declval<A>() |= std::declval<B>())
    operator()(A &&a, B &&b) const noexcept(noexcept(
	std::forward<A>(a) |= std::forward<B>(b)))
    { return std::forward<A>(a) |= std::forward<B>(b); }
};

struct assign_bit_xor
{
  template <class A, class B>
    constexpr decltype(std::declval<A>() ^= std::declval<B>())
    operator()(A &&a, B &&b) const noexcept(noexcept(
	std::forward<A>(a) ^= std::forward<B>(b)))
    { return std::forward<A>(a) ^= std::forward<B>(b); }
};

struct assign_bit_shift_left
{
  template <class A, class B>
    constexpr decltype(std::declval<A>() <<= std::declval<B>())
    operator()(A &&a, B &&b) const noexcept(noexcept(
	std::forward<A>(a) <<= std::forward<B>(b)))
    { return std::forward<A>(a) <<= std::forward<B>(b); }
};

struct assign_bit_shift_right
{
  template <class A, class B>
    constexpr decltype(std::declval<A>() >>= std::declval<B>())
    operator()(A &&a, B &&b) const noexcept(noexcept(
	std::forward<A>(a) >>= std::forward<B>(b)))
    { return std::forward<A>(a) >>= std::forward<B>(b); }
};

template <class A, class B, class Op = std::plus<>>
  constexpr bool is_substitution_failure
    = vir::test::operator_is_substitution_failure<A, B, Op>();

using vir::test::sfinae_is_callable;

using vir::test::has_less_bits;

#endif  // VC_TESTS_METAHELPERS_H_
