//===-- Unittests for string ----------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

#include "src/__support/CPP/string.h"
#include "test/UnitTest/Test.h"

using LIBC_NAMESPACE::cpp::string;
using LIBC_NAMESPACE::cpp::string_view;
using LIBC_NAMESPACE::cpp::to_string;

TEST(LlvmLibcStringTest, InitializeEmpty) {
  const string s;
  ASSERT_EQ(s.size(), size_t(0));
  ASSERT_TRUE(s.empty());
  ASSERT_STREQ(s.data(), "");
  ASSERT_STREQ(s.c_str(), "");
  ASSERT_EQ(s.data(), s.c_str());
  ASSERT_EQ(s.capacity(), size_t(0));
}

TEST(LlvmLibcStringTest, InitializeCString) {
  const char *const str = "abc";
  const string s(str);
  ASSERT_EQ(s.size(), size_t(3));
  ASSERT_FALSE(s.empty());
  ASSERT_NE(s.data(), &str[0]);
  ASSERT_EQ(s[0], 'a');
  ASSERT_EQ(s[1], 'b');
  ASSERT_EQ(s[2], 'c');
  ASSERT_EQ(s.front(), 'a');
  ASSERT_EQ(s.back(), 'c');
  ASSERT_EQ(s.data(), s.c_str());
}

TEST(LlvmLibcStringTest, ToCString) {
  const char *const str = "abc";
  string s(str);
  const char *cstr = s.c_str();
  ASSERT_EQ(s.size(), size_t(3));
  ASSERT_STREQ(str, cstr);
}

TEST(LlvmLibcStringTest, ToStringView) {
  const char *const str = "abc";
  string s(str);
  string_view view = s;
  ASSERT_EQ(view, string_view(str));
}

TEST(LlvmLibcStringTest, InitializeCStringWithSize) {
  const char *const str = "abc";
  const string s(str, 2);
  ASSERT_EQ(s.size(), size_t(2));
  ASSERT_EQ(s[0], 'a');
  ASSERT_EQ(s[1], 'b');
  ASSERT_EQ(s.front(), 'a');
  ASSERT_EQ(s.back(), 'b');
}

TEST(LlvmLibcStringTest, InitializeStringView) {
  const string_view str = "ab";
  const string s(str);
  ASSERT_EQ(s.size(), size_t(2));
  ASSERT_EQ(s[0], 'a');
  ASSERT_EQ(s[1], 'b');
  ASSERT_EQ(s.front(), 'a');
  ASSERT_EQ(s.back(), 'b');
}

TEST(LlvmLibcStringTest, InitializeRepeatedChar) {
  const string s(4, '1');
  ASSERT_EQ(string_view(s), string_view("1111"));
}

TEST(LlvmLibcStringTest, InitializeZeorChar) {
  const string s(0, '1');
  ASSERT_TRUE(s.empty());
}

TEST(LlvmLibcStringTest, CopyConstruct) {
  const char *const str = "abc";
  string a(str);
  string b(a);
  // Same content
  ASSERT_STREQ(a.c_str(), str);
  ASSERT_STREQ(b.c_str(), str);
  // Different pointers
  ASSERT_NE(a.data(), b.data());
}

string &&move(string &value) { return static_cast<string &&>(value); }

TEST(LlvmLibcStringTest, CopyAssign) {
  const char *const str = "abc";
  string a(str);
  string b;
  b = a;
  // Same content
  ASSERT_STREQ(a.c_str(), str);
  ASSERT_STREQ(b.c_str(), str);
  // Different pointers
  ASSERT_NE(a.data(), b.data());
}

TEST(LlvmLibcStringTest, MoveConstruct) {
  const char *const str = "abc";
  string a(str);
  string b(move(a));
  ASSERT_STREQ(b.c_str(), str);
  ASSERT_STREQ(a.c_str(), "");
}

TEST(LlvmLibcStringTest, MoveAssign) {
  const char *const str = "abc";
  string a(str);
  string b;
  b = move(a);
  ASSERT_STREQ(b.c_str(), str);
  ASSERT_STREQ(a.c_str(), "");
}

TEST(LlvmLibcStringTest, StringViewAssign) {
  const string_view str = "ab";
  string s;
  s = str;
  ASSERT_EQ(s.size(), size_t(2));
  ASSERT_EQ(s[0], 'a');
  ASSERT_EQ(s[1], 'b');
  ASSERT_EQ(s.front(), 'a');
  ASSERT_EQ(s.back(), 'b');
}

TEST(LlvmLibcStringTest, Concat) {
  const char *const str = "abc";
  string a(str);
  string b;
  b += a;
  ASSERT_STREQ(b.c_str(), "abc");
  b += a;
  ASSERT_STREQ(b.c_str(), "abcabc");
}

TEST(LlvmLibcStringTest, AddChar) {
  string a;
  a += 'a';
  ASSERT_STREQ(a.c_str(), "a");
  a += 'b';
  ASSERT_STREQ(a.c_str(), "ab");
}

TEST(LlvmLibcStringTest, ResizeCapacityAndNullTermination) {
  string a;
  // Empty
  ASSERT_EQ(a.capacity(), size_t(0));
  ASSERT_EQ(a.data()[0], '\0');
  // Still empty
  a.resize(0);
  ASSERT_EQ(a.capacity(), size_t(0));
  ASSERT_EQ(a.data()[0], '\0');
  // One char
  a.resize(1);
  ASSERT_EQ(a.size(), size_t(1));
  ASSERT_GE(a.capacity(), size_t(2));
  ASSERT_EQ(a.data()[1], '\0');
  // Clear
  a.resize(0);
  ASSERT_EQ(a.size(), size_t(0));
  ASSERT_GE(a.capacity(), size_t(2));
  ASSERT_EQ(a.data()[0], '\0');
  // Resize and check zero initialized
  a.resize(10);
  ASSERT_EQ(a.size(), size_t(10));
  ASSERT_GE(a.capacity(), size_t(10));
  for (size_t i = 0; i < 10; ++i)
    ASSERT_EQ(a[i], '\0');
}

TEST(LlvmLibcStringTest, ConcatWithCString) {
  ASSERT_STREQ((string("a") + string("b")).c_str(), "ab");
  ASSERT_STREQ((string("a") + "b").c_str(), "ab");
  ASSERT_STREQ(("a" + string("b")).c_str(), "ab");
}

TEST(LlvmLibcStringTest, Comparison) {
  // Here we simply check that comparison of string and string_view have the
  // same semantic.
  struct CStringPair {
    const char *const a;
    const char *const b;
  } kTestPairs[] = {{"a", "b"}, {"", "xyz"}};
  for (const auto [pa, pb] : kTestPairs) {
    const string sa(pa);
    const string sb(pb);
    const string_view sva(pa);
    const string_view svb(pb);
    ASSERT_EQ(sa == sb, sva == svb);
    ASSERT_EQ(sa != sb, sva != svb);
    ASSERT_EQ(sa >= sb, sva >= svb);
    ASSERT_EQ(sa <= sb, sva <= svb);
    ASSERT_EQ(sa < sb, sva < svb);
    ASSERT_EQ(sa > sb, sva > svb);
  }
}

TEST(LlvmLibcStringTest, ToString) {
  struct CStringPair {
    const int value;
    const string str;
  } kTestPairs[] = {{123, "123"}, {0, "0"}, {-321, "-321"}};
  for (const auto &[value, str] : kTestPairs) {
    ASSERT_EQ(to_string((int)(value)), str);
    ASSERT_EQ(to_string((long)(value)), str);
    ASSERT_EQ(to_string((long long)(value)), str);
    if (value >= 0) {
      ASSERT_EQ(to_string((unsigned int)(value)), str);
      ASSERT_EQ(to_string((unsigned long)(value)), str);
      ASSERT_EQ(to_string((unsigned long long)(value)), str);
    }
  }
}
