//===----------------------------------------------------------------------===//
//
// 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>
//
//  template<class ElementType, class Extents, class LayoutPolicy = layout_right,
//           class AccessorPolicy = default_accessor<ElementType>>
//  class mdspan {
//  public:
//    using extents_type = Extents;
//    using layout_type = LayoutPolicy;
//    using accessor_type = AccessorPolicy;
//    using mapping_type = typename layout_type::template mapping<extents_type>;
//    using element_type = ElementType;
//    using value_type = remove_cv_t<element_type>;
//    using index_type = typename extents_type::index_type;
//    using size_type = typename extents_type::size_type;
//    using rank_type = typename extents_type::rank_type;
//    using data_handle_type = typename accessor_type::data_handle_type;
//    using reference = typename accessor_type::reference;
//    ...
//  };

#include <mdspan>
#include <type_traits>
#include <concepts>
#include <cassert>

#include "test_macros.h"

#include "../MinimalElementType.h"
#include "CustomTestAccessors.h"
#include "../CustomTestLayouts.h"

// Calculated expected size of an mdspan
// Note this expects that only default_accessor is empty
template<class MDS>
constexpr size_t expected_size() {
  size_t sizeof_dht = sizeof(typename MDS::data_handle_type);
  size_t result = sizeof_dht;
  if(MDS::rank_dynamic() > 0) {
    size_t alignof_idx = alignof(typename MDS::index_type);
    size_t sizeof_idx = sizeof(typename MDS::index_type);
    // add alignment if necessary
    result += sizeof_dht%alignof_idx == 0?0:alignof_idx - (sizeof_dht%alignof_idx);
    // add sizeof stored extents
    result += MDS::rank_dynamic() * sizeof_idx;
  }
  using A = typename MDS::accessor_type;
  if(!std::is_same_v<A, std::default_accessor<typename MDS::element_type>>) {
    size_t alignof_acc = alignof(A);
    size_t sizeof_acc = sizeof(A);
    // add alignment if necessary
    result += result%alignof_acc == 0?0:alignof_acc - (result%alignof_acc);
    // add sizeof stored accessor
    result += sizeof_acc;
  }
  // add alignment of the mdspan itself
  result += result%alignof(MDS) == 0?0:alignof(MDS) - (result%alignof(MDS));
  return result;
}

// check triviality
template <class T>
constexpr bool trv_df_ctor = std::is_trivially_default_constructible_v<T>;
template <class T>
constexpr bool trv_cp_ctor = std::is_trivially_copy_constructible_v<T>;
template <class T>
constexpr bool trv_mv_ctor = std::is_trivially_move_constructible_v<T>;
template <class T>
constexpr bool trv_dstruct = std::is_trivially_destructible_v<T>;
template <class T>
constexpr bool trv_cp_asgn = std::is_trivially_copy_assignable_v<T>;
template <class T>
constexpr bool trv_mv_asgn = std::is_trivially_move_assignable_v<T>;

template <class MDS, bool default_ctor, bool copy_ctor, bool move_ctor, bool destr, bool copy_assign, bool move_assign>
void check_triviality() {
  static_assert(trv_df_ctor<MDS> == default_ctor);
  static_assert(trv_cp_ctor<MDS> == copy_ctor);
  static_assert(trv_mv_ctor<MDS> == move_ctor);
  static_assert(trv_dstruct<MDS> == destr);
  static_assert(trv_cp_asgn<MDS> == copy_assign);
  static_assert(trv_mv_asgn<MDS> == move_assign);
}

template <class T, class E, class L, class A>
void test_mdspan_types() {
  using MDS = std::mdspan<T, E, L, A>;

  ASSERT_SAME_TYPE(typename MDS::extents_type, E);
  ASSERT_SAME_TYPE(typename MDS::layout_type, L);
  ASSERT_SAME_TYPE(typename MDS::accessor_type, A);
  ASSERT_SAME_TYPE(typename MDS::mapping_type, typename L::template mapping<E>);
  ASSERT_SAME_TYPE(typename MDS::element_type, T);
  ASSERT_SAME_TYPE(typename MDS::value_type, std::remove_cv_t<T>);
  ASSERT_SAME_TYPE(typename MDS::index_type, typename E::index_type);
  ASSERT_SAME_TYPE(typename MDS::size_type, typename E::size_type);
  ASSERT_SAME_TYPE(typename MDS::rank_type, typename E::rank_type);
  ASSERT_SAME_TYPE(typename MDS::data_handle_type, typename A::data_handle_type);
  ASSERT_SAME_TYPE(typename MDS::reference, typename A::reference);

// This miserably failed with clang-cl - likely because it doesn't honor/enable
// no-unique-address fully by default
#ifndef _WIN32
  // check the size of mdspan
  if constexpr (std::is_same_v<L, std::layout_left> || std::is_same_v<L, std::layout_right>) {
    LIBCPP_STATIC_ASSERT(sizeof(MDS) == expected_size<MDS>());
  }
#endif

  // check default template parameters:
  ASSERT_SAME_TYPE(std::mdspan<T, E>, std::mdspan<T, E, std::layout_right, std::default_accessor<T>>);
  ASSERT_SAME_TYPE(std::mdspan<T, E, L>, std::mdspan<T, E, L, std::default_accessor<T>>);

  // check triviality
  using DH = typename MDS::data_handle_type;
  using MP = typename MDS::mapping_type;

  check_triviality<MDS,
                   false, // mdspan is never trivially constructible right now
                   trv_cp_ctor<DH> && trv_cp_ctor<MP> && trv_cp_ctor<A>,
                   trv_mv_ctor<DH> && trv_mv_ctor<MP> && trv_mv_ctor<A>,
                   trv_dstruct<DH> && trv_dstruct<MP> && trv_dstruct<A>,
                   trv_cp_asgn<DH> && trv_cp_asgn<MP> && trv_cp_asgn<A>,
                   trv_mv_asgn<DH> && trv_mv_asgn<MP> && trv_mv_asgn<A>>();
}

template <class T, class L, class A>
void mixin_extents() {
  constexpr size_t D = std::dynamic_extent;
  test_mdspan_types<T, std::extents<int>, L, A>();
  test_mdspan_types<T, std::extents<signed char, D>, L, A>();
  test_mdspan_types<T, std::dextents<signed char, 7>, L, A>();
  test_mdspan_types<T, std::dextents<signed char, 9>, L, A>();
  test_mdspan_types<T, std::extents<unsigned, 7>, L, A>();
  test_mdspan_types<T, std::extents<unsigned, D, D, D>, L, A>();
  test_mdspan_types<T, std::extents<size_t, D, 7, D>, L, A>();
  test_mdspan_types<T, std::extents<int64_t, D, 7, D, 4, D, D>, L, A>();
}

template <class T, class A>
void mixin_layout() {
  mixin_extents<T, std::layout_left, A>();
  mixin_extents<T, std::layout_right, A>();
  mixin_extents<T, layout_wrapping_integral<4>, A>();
}

template <class T>
void mixin_accessor() {
  mixin_layout<T, std::default_accessor<T>>();
  mixin_layout<T, checked_accessor<T>>();
}

int main(int, char**) {
  mixin_accessor<int>();
  mixin_accessor<const int>();
  mixin_accessor<double>();
  mixin_accessor<const double>();
  mixin_accessor<MinimalElementType>();
  mixin_accessor<const MinimalElementType>();

  // sanity checks for triviality
  check_triviality<std::mdspan<int, std::extents<int>>, false, true, true, true, true, true>();
  check_triviality<std::mdspan<int, std::dextents<int, 1>>, false, true, true, true, true, true>();
  check_triviality<std::mdspan<int, std::dextents<int, 1>, std::layout_right, checked_accessor<int>>,
                   false,
                   true,
                   false,
                   true,
                   true,
                   true>();
  return 0;
}
