/* SPDX-License-Identifier: GPL-3.0-or-later */
/* Copyright © 2020–2024 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH
 *                       Matthias Kretz <m.kretz@gsi.de>
 */

// expensive: * [1-9] * *
#include "bits/main.h"

template <class V>
  struct Convertible
  {
    operator V() const { return V(4); }
  };

template <class M, class T>
  constexpr bool
  where_is_ill_formed_impl(M, const T&, float)
  {
    return true;
  }

template <class M, class T>
  constexpr auto
  where_is_ill_formed_impl(M m, const T& v, int)
    -> std::conditional_t<true, bool, decltype(std::experimental::where(m, v))>
  {
    return false;
  }

template <class M, class T>
  constexpr bool
  where_is_ill_formed(M m, const T& v)
  {
    return where_is_ill_formed_impl(m, v, int());
  }

template <typename T>
  void
  where_fundamental()
  {
    using std::experimental::where;
    T x = T();
    where(true, x) = x + 1;
    COMPARE(x, T(1));
    where(false, x) = x - 1;
    COMPARE(x, T(1));
    where(true, x) += T(1);
    COMPARE(x, T(2));
  }

template <typename V>
  void
  test()
  {
    using M = typename V::mask_type;
    using T = typename V::value_type;
    where_fundamental<T>();
    VERIFY(!(sfinae_is_callable<V>(
	       [](auto x) -> decltype(where(true, x))* { return nullptr; })));

    const V indexes([](int i) { return i + 1; });
    const M alternating_mask = make_mask<M>({true, false});
    V x = 0;
    where(alternating_mask, x) = indexes;
    COMPARE(alternating_mask, x == indexes);

    where(!alternating_mask, x) = T(2);
    COMPARE(!alternating_mask, x == T(2)) << x;

    where(!alternating_mask, x) = Convertible<V>();
    COMPARE(!alternating_mask, x == T(4));

    x = 0;
    COMPARE(x, T(0));
    where(alternating_mask, x) += indexes;
    COMPARE(alternating_mask, x == indexes);

    x = 10;
    COMPARE(x, T(10));
    where(!alternating_mask, x) += T(1);
    COMPARE(!alternating_mask, x == T(11));
    where(alternating_mask, x) -= Convertible<V>();
    COMPARE(alternating_mask, x == T(6));
    constexpr bool fast_math =
#ifdef __FAST_MATH__
      true;
#else
      false;
#endif
    if constexpr (fast_math && std::is_floating_point_v<T>)
      where(alternating_mask, x) *= T(.5);
    else
    where(alternating_mask, x) /= T(2);
    COMPARE(alternating_mask, x == T(3)) << "\nx = " << x;
    where(alternating_mask, x) *= T(3);
    COMPARE(alternating_mask, x == T(9));
    COMPARE(!alternating_mask, x == T(11));

    x = 10;
    where(alternating_mask, x)++;
    COMPARE(alternating_mask, x == T(11));
    ++where(alternating_mask, x);
    COMPARE(alternating_mask, x == T(12));
    where(alternating_mask, x)--;
    COMPARE(alternating_mask, x == T(11));
    --where(alternating_mask, x);
    --where(alternating_mask, x);
    COMPARE(alternating_mask, x == T(9));
    COMPARE(alternating_mask, -where(alternating_mask, x) == T(-T(9)));

    const auto y = x;
    VERIFY(where_is_ill_formed(true, y));
    VERIFY(where_is_ill_formed(true, x));
    VERIFY(where_is_ill_formed(true, V(x)));

    M test = alternating_mask;
    where(alternating_mask, test) = M(true);
    COMPARE(test, alternating_mask);
    where(alternating_mask, test) = M(false);
    COMPARE(test, M(false));
    where(alternating_mask, test) = M(true);
    COMPARE(test, alternating_mask);
  }
