//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20

// <mdspan>

// Test default iteration:
//
// template<class... Indices>
//   constexpr reference operator[](Indices...) const noexcept;
//
// Constraints:
//   * sizeof...(Indices) == extents_type::rank() is true,
//   * (is_convertible_v<Indices, index_type> && ...) is true, and
//   * (is_nothrow_constructible_v<index_type, Indices> && ...) is true.
//
// Preconditions:
//   * extents_type::index-cast(i) is a multidimensional index in extents_.

// GCC warns about comma operator changing its meaning inside [] in C++23
#if defined(__GNUC__) && !defined(__clang_major__)
#  pragma GCC diagnostic push
#  pragma GCC diagnostic ignored "-Wcomma-subscript"
#endif

#include <mdspan>
#include <cassert>
#include <cstdint>
#include <span> // dynamic_extent

#include "test_macros.h"

#include "../ConvertibleToIntegral.h"
#include "../CustomTestLayouts.h"

// Clang 16 does not support argument packs as input to operator []
#if defined(__clang_major__) && __clang_major__ < 17
template <class MDS>
constexpr auto& access(MDS mds) {
  return mds[];
}
template <class MDS>
constexpr auto& access(MDS mds, int64_t i0) {
  return mds[i0];
}
template <class MDS>
constexpr auto& access(MDS mds, int64_t i0, int64_t i1) {
  return mds[i0, i1];
}
template <class MDS>
constexpr auto& access(MDS mds, int64_t i0, int64_t i1, int64_t i2) {
  return mds[i0, i1, i2];
}
template <class MDS>
constexpr auto& access(MDS mds, int64_t i0, int64_t i1, int64_t i2, int64_t i3) {
  return mds[i0, i1, i2, i3];
}
#endif

template <class MDS, class... Indices>
concept operator_constraints = requires(MDS m, Indices... idxs) {
  { std::is_same_v<decltype(m[idxs...]), typename MDS::reference> };
};

template <class MDS, class... Indices>
  requires(operator_constraints<MDS, Indices...>)
constexpr bool check_operator_constraints(MDS m, Indices... idxs) {
  (void)m[idxs...];
  return true;
}

template <class MDS, class... Indices>
constexpr bool check_operator_constraints(MDS, Indices...) {
  return false;
}

template <class MDS, class... Args>
constexpr void iterate(MDS mds, Args... args) {
  constexpr int r = static_cast<int>(MDS::extents_type::rank()) - 1 - static_cast<int>(sizeof...(Args));
  if constexpr (-1 == r) {
#if defined(__clang_major__) && __clang_major__ < 17
    int* ptr1 = &access(mds, args...);
#else
    int* ptr1 = &mds[args...];
#endif
    int* ptr2 = &(mds.accessor().access(mds.data_handle(), mds.mapping()(args...)));
    assert(ptr1 == ptr2);

    std::array<typename MDS::index_type, MDS::rank()> args_arr{static_cast<typename MDS::index_type>(args)...};
    int* ptr3 = &mds[args_arr];
    assert(ptr3 == ptr2);
    int* ptr4 = &mds[std::span(args_arr)];
    assert(ptr4 == ptr2);
  } else {
    for (typename MDS::index_type i = 0; i < mds.extents().extent(r); i++) {
      iterate(mds, i, args...);
    }
  }
}

template <class Mapping>
constexpr void test_iteration(Mapping m) {
  std::array<int, 1024> data;
  using MDS = std::mdspan<int, typename Mapping::extents_type, typename Mapping::layout_type>;
  MDS mds(data.data(), m);

  iterate(mds);
}

template <class Layout>
constexpr void test_layout() {
  constexpr size_t D = std::dynamic_extent;
  test_iteration(construct_mapping(Layout(), std::extents<int>()));
  test_iteration(construct_mapping(Layout(), std::extents<unsigned, D>(1)));
  test_iteration(construct_mapping(Layout(), std::extents<unsigned, D>(7)));
  test_iteration(construct_mapping(Layout(), std::extents<unsigned, 7>()));
  test_iteration(construct_mapping(Layout(), std::extents<unsigned, 7, 8>()));
  test_iteration(construct_mapping(Layout(), std::extents<signed char, D, D, D, D>(1, 1, 1, 1)));

// TODO(LLVM 20): Enable this once AppleClang is upgraded
#ifndef TEST_COMPILER_APPLE_CLANG
  int data[1];
  // Check operator constraint for number of arguments
  static_assert(check_operator_constraints(std::mdspan(data, construct_mapping(Layout(), std::extents<int, D>(1))), 0));
  static_assert(
      !check_operator_constraints(std::mdspan(data, construct_mapping(Layout(), std::extents<int, D>(1))), 0, 0));

  // Check operator constraint for convertibility of arguments to index_type
  static_assert(
      check_operator_constraints(std::mdspan(data, construct_mapping(Layout(), std::extents<int, D>(1))), IntType(0)));
  static_assert(!check_operator_constraints(
      std::mdspan(data, construct_mapping(Layout(), std::extents<unsigned, D>(1))), IntType(0)));

  // Check operator constraint for no-throw-constructibility of index_type from arguments
  static_assert(!check_operator_constraints(
      std::mdspan(data, construct_mapping(Layout(), std::extents<unsigned char, D>(1))), IntType(0)));

  // Check that mixed integrals work: note the second one tests that mdspan casts: layout_wrapping_integral does not accept IntType
  static_assert(check_operator_constraints(
      std::mdspan(data, construct_mapping(Layout(), std::extents<unsigned char, D, D>(1, 1))), int(0), size_t(0)));
  static_assert(check_operator_constraints(
      std::mdspan(data, construct_mapping(Layout(), std::extents<int, D, D>(1, 1))), unsigned(0), IntType(0)));

  constexpr bool t = true;
  constexpr bool o = false;
  static_assert(!check_operator_constraints(
      std::mdspan(data, construct_mapping(Layout(), std::extents<int, D, D>(1, 1))),
      unsigned(0),
      IntConfig<o, o, t, t>(0)));
  static_assert(check_operator_constraints(
      std::mdspan(data, construct_mapping(Layout(), std::extents<int, D, D>(1, 1))),
      unsigned(0),
      IntConfig<o, t, t, t>(0)));
  static_assert(check_operator_constraints(
      std::mdspan(data, construct_mapping(Layout(), std::extents<int, D, D>(1, 1))),
      unsigned(0),
      IntConfig<o, t, o, t>(0)));
  static_assert(!check_operator_constraints(
      std::mdspan(data, construct_mapping(Layout(), std::extents<int, D, D>(1, 1))),
      unsigned(0),
      IntConfig<t, o, o, t>(0)));
  static_assert(check_operator_constraints(
      std::mdspan(data, construct_mapping(Layout(), std::extents<int, D, D>(1, 1))),
      unsigned(0),
      IntConfig<t, o, t, o>(0)));

  // layout_wrapped wouldn't quite work here the way we wrote the check
  // IntConfig has configurable conversion properties: convert from const&, convert from non-const, no-throw-ctor from const&, no-throw-ctor from non-const
  if constexpr (std::is_same_v<Layout, std::layout_left>) {
    static_assert(!check_operator_constraints(
        std::mdspan(data, construct_mapping(Layout(), std::extents<int, D>(1))), std::array{IntConfig<o, o, t, t>(0)}));
    static_assert(!check_operator_constraints(
        std::mdspan(data, construct_mapping(Layout(), std::extents<int, D>(1))), std::array{IntConfig<o, t, t, t>(0)}));
    static_assert(!check_operator_constraints(
        std::mdspan(data, construct_mapping(Layout(), std::extents<int, D>(1))), std::array{IntConfig<t, o, o, t>(0)}));
    static_assert(!check_operator_constraints(
        std::mdspan(data, construct_mapping(Layout(), std::extents<int, D>(1))), std::array{IntConfig<t, t, o, t>(0)}));
    static_assert(check_operator_constraints(
        std::mdspan(data, construct_mapping(Layout(), std::extents<int, D>(1))), std::array{IntConfig<t, o, t, o>(0)}));
    static_assert(check_operator_constraints(
        std::mdspan(data, construct_mapping(Layout(), std::extents<int, D>(1))), std::array{IntConfig<t, t, t, t>(0)}));

    {
      std::array idx{IntConfig<o, o, t, t>(0)};
      std::span s(idx);
      assert(!check_operator_constraints(std::mdspan(data, construct_mapping(Layout(), std::extents<int, D>(1))), s));
    }
    {
      std::array idx{IntConfig<o, o, t, t>(0)};
      std::span s(idx);
      assert(!check_operator_constraints(std::mdspan(data, construct_mapping(Layout(), std::extents<int, D>(1))), s));
    }
    {
      std::array idx{IntConfig<o, o, t, t>(0)};
      std::span s(idx);
      assert(!check_operator_constraints(std::mdspan(data, construct_mapping(Layout(), std::extents<int, D>(1))), s));
    }
    {
      std::array idx{IntConfig<o, o, t, t>(0)};
      std::span s(idx);
      assert(!check_operator_constraints(std::mdspan(data, construct_mapping(Layout(), std::extents<int, D>(1))), s));
    }
    {
      std::array idx{IntConfig<o, o, t, t>(0)};
      std::span s(idx);
      assert(!check_operator_constraints(std::mdspan(data, construct_mapping(Layout(), std::extents<int, D>(1))), s));
    }
    {
      std::array idx{IntConfig<o, o, t, t>(0)};
      std::span s(idx);
      assert(!check_operator_constraints(std::mdspan(data, construct_mapping(Layout(), std::extents<int, D>(1))), s));
    }
  }
#endif // TEST_COMPILER_APPLE_CLANG
}

template <class Layout>
constexpr void test_layout_large() {
  constexpr size_t D = std::dynamic_extent;
  test_iteration(construct_mapping(Layout(), std::extents<int64_t, D, 4, D, D>(3, 5, 6)));
  test_iteration(construct_mapping(Layout(), std::extents<int64_t, D, 4, 1, D>(3, 6)));
}

// mdspan::operator[] casts to index_type before calling mapping
// mapping requirements only require the index operator to mixed integer types not anything convertible to index_type
constexpr void test_index_cast_happens() {}

constexpr bool test() {
  test_layout<std::layout_left>();
  test_layout<std::layout_right>();
  test_layout<layout_wrapping_integral<4>>();
  return true;
}

constexpr bool test_large() {
  test_layout_large<std::layout_left>();
  test_layout_large<std::layout_right>();
  return true;
}

int main(int, char**) {
  test();
  static_assert(test());

  // The large test iterates over ~10k loop indices.
  // With assertions enabled this triggered the maximum default limit
  // for steps in consteval expressions. Assertions roughly double the
  // total number of instructions, so this was already close to the maximum.
  test_large();
  return 0;
}
#if defined(__GNUC__) && !defined(__clang_major__)
#  pragma GCC diagnostic pop
#endif
