/* 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] * *
// timeout-factor: 2
#include "bits/main.h"

template <typename V, typename U>
  void
  load_store()
  {
    // types, tags, and constants
    using T = typename V::value_type;
    auto&& gen = make_vec<V>;
    using std::experimental::element_aligned;
    using std::experimental::vector_aligned;

    // stride_alignment: consider V::size() == 6. The only reliable alignment is
    // 2 * sizeof(U). I.e. if the first address is aligned to 8 * sizeof(U),
    // then the next address is 6 * sizeof(U) larger, thus only aligned to 2 *
    // sizeof(U).
    // => the LSB determines the stride alignment
    constexpr size_t stride_alignment = size_t(1) << __builtin_ctz(V::size());
    using stride_aligned_t = std::conditional_t<
      V::size() == stride_alignment, decltype(vector_aligned),
      std::experimental::overaligned_tag<stride_alignment * sizeof(U)>>;
    constexpr stride_aligned_t stride_aligned = {};
    constexpr size_t alignment
      = 2 * std::experimental::memory_alignment_v<V, U>;
    constexpr auto overaligned = std::experimental::overaligned<alignment>;
    const V indexes_from_0([](auto i) { return i; });
    for (std::size_t i = 0; i < V::size(); ++i)
      {
	COMPARE(indexes_from_0[i], T(i));
      }

    // loads
    cvt_inputs<T, U> test_values;

    constexpr auto mem_size
      = test_values.size() > 3 * V::size() ? test_values.size() : 3 * V::size();
    alignas(std::experimental::memory_alignment_v<V, U> * 2) U mem[mem_size]
      = {};
    alignas(std::experimental::memory_alignment_v<V, T> * 2)
      T reference[mem_size]
      = {};
    for (std::size_t i = 0; i < test_values.size(); ++i)
      {
	const U value = test_values[i];
	mem[i] = value;
	reference[i] = static_cast<T>(value);
      }
    for (std::size_t i = test_values.size(); i < mem_size; ++i)
      {
	mem[i] = U(i);
	reference[i] = mem[i];
      }

    V x(&mem[V::size()], stride_aligned);
    auto&& compare = [&](const std::size_t offset) {
      static int n = 0;
      const V ref(&reference[offset], element_aligned);
      for (auto i = 0ul; i < V::size(); ++i)
	{
	  if (is_conversion_undefined<T>(mem[i + offset]))
	    {
	      continue;
	    }
	  COMPARE(x[i], reference[i + offset])
	    << "\nbefore conversion: " << mem[i + offset]
	    << "\n   offset = " << offset << "\n        x = " << x
	    << "\nreference = " << ref << "\nx == ref  = " << (x == ref)
	    << "\ncall no. " << n;
	}
      ++n;
    };
    compare(V::size());
    x = V{mem, overaligned};
    compare(0);
    x = {&mem[1], element_aligned};
    compare(1);

    x.copy_from(&mem[V::size()], stride_aligned);
    compare(V::size());
    x.copy_from(&mem[1], element_aligned);
    compare(1);
    x.copy_from(mem, vector_aligned);
    compare(0);

    for (std::size_t i = 0; i < mem_size - V::size(); ++i)
      {
	x.copy_from(&mem[i], element_aligned);
	compare(i);
      }

    for (std::size_t i = 0; i < test_values.size(); ++i)
      {
	mem[i] = U(i);
      }
    x = indexes_from_0;
    using M = typename V::mask_type;
    const M alternating_mask = make_mask<M>({0, 1});
    where(alternating_mask, x).copy_from(&mem[V::size()], stride_aligned);

    const V indexes_from_size = gen({T(V::size())}, 1);
    COMPARE(x == indexes_from_size, alternating_mask)
      << "x: " << x << "\nindexes_from_size: " << indexes_from_size;
    COMPARE(x == indexes_from_0, !alternating_mask);
    where(alternating_mask, x).copy_from(&mem[1], element_aligned);

    const V indexes_from_1 = gen({1, 2, 3, 4}, 4);
    COMPARE(x == indexes_from_1, alternating_mask);
    COMPARE(x == indexes_from_0, !alternating_mask);
    where(!alternating_mask, x).copy_from(mem, overaligned);
    COMPARE(x == indexes_from_0, !alternating_mask);
    COMPARE(x == indexes_from_1, alternating_mask);

    x = where(alternating_mask, V()).copy_from(&mem[V::size()], stride_aligned);
    COMPARE(x == indexes_from_size, alternating_mask);
    COMPARE(x == 0, !alternating_mask);

    x = where(!alternating_mask, V()).copy_from(&mem[1], element_aligned);
    COMPARE(x == indexes_from_1, !alternating_mask);
    COMPARE(x == 0, alternating_mask);

    // stores
    auto&& init_mem = [&mem](U init) {
      for (auto i = mem_size; i; --i)
	{
	  mem[i - 1] = init;
	}
    };
    init_mem(-1);
    x = indexes_from_1;
    x.copy_to(&mem[V::size()], stride_aligned);
    std::size_t i = 0;
    for (; i < V::size(); ++i)
      {
	COMPARE(mem[i], U(-1)) << "i: " << i;
      }
    for (; i < 2 * V::size(); ++i)
      {
	COMPARE(mem[i], U(i - V::size() + 1)) << "i: " << i;
      }
    for (; i < 3 * V::size(); ++i)
      {
	COMPARE(mem[i], U(-1)) << "i: " << i;
      }

    init_mem(-1);
    x.copy_to(&mem[1], element_aligned);
    COMPARE(mem[0], U(-1));
    for (i = 1; i <= V::size(); ++i)
      {
	COMPARE(mem[i], U(i));
      }
    for (; i < 3 * V::size(); ++i)
      {
	COMPARE(mem[i], U(-1));
      }

    init_mem(-1);
    x.copy_to(mem, vector_aligned);
    for (i = 0; i < V::size(); ++i)
      {
	COMPARE(mem[i], U(i + 1));
      }
    for (; i < 3 * V::size(); ++i)
      {
	COMPARE(mem[i], U(-1));
      }

    init_mem(-1);
    where(alternating_mask, indexes_from_0)
      .copy_to(&mem[V::size()], stride_aligned);
    for (i = 0; i < V::size() + 1; ++i)
      {
	COMPARE(mem[i], U(-1));
      }
    for (; i < 2 * V::size(); i += 2)
      {
	COMPARE(mem[i], U(i - V::size()));
      }
    for (i = V::size() + 2; i < 2 * V::size(); i += 2)
      {
	COMPARE(mem[i], U(-1));
      }
    for (; i < 3 * V::size(); ++i)
      {
	COMPARE(mem[i], U(-1));
      }
  }

template <typename V>
  void
  test()
  {
    load_store<V, long double>();
    load_store<V, double>();
    load_store<V, float>();
    load_store<V, long long>();
    load_store<V, unsigned long long>();
    load_store<V, unsigned long>();
    load_store<V, long>();
    load_store<V, int>();
    load_store<V, unsigned int>();
    load_store<V, short>();
    load_store<V, unsigned short>();
    load_store<V, char>();
    load_store<V, signed char>();
    load_store<V, unsigned char>();
    load_store<V, char32_t>();
    load_store<V, char16_t>();
    load_store<V, wchar_t>();
  }
