//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

// _Tp* __constexpr_memmove(_Tp* __dest, _Up* __src, __element_count __n);
//
// General tests for __constexpr_memmove.
//
// In particular, we try to ensure that __constexpr_memmove behaves like
// __builtin_memmove as closely as possible. This means that it produces the
// same effect, but also that it has the same type requirements.
//
// __builtin_memmove only requires that the types are TriviallyCopyable
// (which is interestingly different from both is_trivially_XXX_constructible
// and is_trivially_XXX_assignable), so we use some funky types to test these
// corner cases.

// XFAIL: FROZEN-CXX03-HEADERS-FIXME

#include <__string/constexpr_c_functions.h>
#include <cassert>
#include <cstdint>
#include <type_traits>

#include "test_macros.h"

// The following types are all TriviallyCopyable, but they are not all
// trivially_{copy,move}_{constructible,assignable}. TriviallyCopyable
// guarantees that the type is *at least* one of the four, but no more
// than that.
struct CopyConstructible {
  CopyConstructible() = default;
  int value           = 0;

  CopyConstructible(const CopyConstructible&)            = default;
  CopyConstructible(CopyConstructible&&)                 = delete;
  CopyConstructible& operator=(const CopyConstructible&) = delete;
  CopyConstructible& operator=(CopyConstructible&&)      = delete;
};

struct MoveConstructible {
  MoveConstructible() = default;
  int value           = 0;

  MoveConstructible(const MoveConstructible&)            = delete;
  MoveConstructible(MoveConstructible&&)                 = default;
  MoveConstructible& operator=(const MoveConstructible&) = delete;
  MoveConstructible& operator=(MoveConstructible&&)      = delete;
};

struct CopyAssignable {
  CopyAssignable() = default;
  int value        = 0;

  CopyAssignable(const CopyAssignable&)            = delete;
  CopyAssignable(CopyAssignable&&)                 = delete;
  CopyAssignable& operator=(const CopyAssignable&) = default;
  CopyAssignable& operator=(CopyAssignable&&)      = delete;
};

struct MoveAssignable {
  MoveAssignable() = default;
  int value        = 0;

  MoveAssignable(const MoveAssignable&)            = delete;
  MoveAssignable(MoveAssignable&&)                 = delete;
  MoveAssignable& operator=(const MoveAssignable&) = delete;
  MoveAssignable& operator=(MoveAssignable&&)      = default;
};

template <class Source, class Dest>
TEST_CONSTEXPR_CXX14 void test_user_defined_types() {
  static_assert(std::is_trivially_copyable<Source>::value, "test the test");
  static_assert(std::is_trivially_copyable<Dest>::value, "test the test");

  // Note that we can't just initialize with an initializer list since some of the types we use here
  // are not copy-constructible, which is required in pre-C++20 Standards for that syntax to work.
  Source src[3];
  src[0].value = 1;
  src[1].value = 2;
  src[2].value = 3;
  Dest dst[3];
  dst[0].value = 111;
  dst[1].value = 111;
  dst[2].value = 111;

  Dest* result = std::__constexpr_memmove(dst, src, std::__element_count(3));
  assert(result == dst);
  assert(dst[0].value == 1);
  assert(dst[1].value == 2);
  assert(dst[2].value == 3);
}

template <class Source, class Dest>
TEST_CONSTEXPR_CXX14 void test_builtin_types() {
  Source src[3] = {1, 2, 3};
  Dest dst[3]   = {111, 111, 111};

  Dest* result = std::__constexpr_memmove(dst, src, std::__element_count(3));
  assert(result == dst);
  assert(dst[0] == 1);
  assert(dst[1] == 2);
  assert(dst[2] == 3);
}

template <class SourcePtr, class DestPtr, class ObjectType>
TEST_CONSTEXPR_CXX14 void test_pointer_types() {
  ObjectType objs[3] = {1, 2, 3};

  SourcePtr src[3] = {objs + 0, objs + 1, objs + 2};
  DestPtr dst[3]   = {nullptr, nullptr, nullptr};

  DestPtr* result = std::__constexpr_memmove(dst, src, std::__element_count(3));
  assert(result == dst);
  assert(dst[0] == objs + 0);
  assert(dst[1] == objs + 1);
  assert(dst[2] == objs + 2);
}

TEST_CONSTEXPR_CXX14 bool test() {
  test_user_defined_types<CopyConstructible, CopyConstructible>();
  test_user_defined_types<MoveConstructible, MoveConstructible>();
  test_user_defined_types<CopyAssignable, CopyAssignable>();
  test_user_defined_types<MoveAssignable, MoveAssignable>();

  test_builtin_types<char, char>();
  test_builtin_types<short, short>();
  test_builtin_types<int, int>();
  test_builtin_types<long, long>();
  test_builtin_types<long long, long long>();

  // Cross-type
  test_builtin_types<std::int16_t, std::uint16_t>();
  test_builtin_types<std::int16_t, char16_t>();
  test_builtin_types<std::int32_t, std::uint32_t>();
  test_builtin_types<std::int32_t, char32_t>();

  test_pointer_types<char*, char*, char>();
  test_pointer_types<int*, int*, int>();
  test_pointer_types<long*, long*, long>();
  test_pointer_types<void*, void*, int>();
  test_pointer_types<int* const, int*, int>();

  return true;
}

int main(int, char**) {
  test();
#if TEST_STD_VER >= 14
  static_assert(test(), "");
#endif
  return 0;
}
