// Copyright 2017 The Abseil Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "absl/strings/internal/utf8.h"

#include <cstddef>
#include <cstdint>
#include <cstring>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>

#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/base/port.h"
#include "absl/strings/string_view.h"

namespace {

using ::absl::strings_internal::kMaxEncodedUTF8Size;
using ::absl::strings_internal::ShiftState;
using ::absl::strings_internal::WideToUtf8;
using ::testing::StartsWith;
using ::testing::TestParamInfo;
using ::testing::TestWithParam;
using ::testing::ValuesIn;

#if !defined(__cpp_char8_t)
#if defined(__clang__)
// #pragma clang diagnostic push
// #pragma clang diagnostic ignored "-Wc++2a-compat"
#endif
TEST(EncodeUTF8Char, BasicFunction) {
  std::pair<char32_t, std::string> tests[] = {{0x0030, u8"\u0030"},
                                              {0x00A3, u8"\u00A3"},
                                              {0x00010000, u8"\U00010000"},
                                              {0x0000FFFF, u8"\U0000FFFF"},
                                              {0x0010FFFD, u8"\U0010FFFD"}};
  for (auto& test : tests) {
    char buf0[7] = {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'};
    char buf1[7] = {'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF'};
    char* buf0_written =
        &buf0[absl::strings_internal::EncodeUTF8Char(buf0, test.first)];
    char* buf1_written =
        &buf1[absl::strings_internal::EncodeUTF8Char(buf1, test.first)];
    int apparent_length = 7;
    while (buf0[apparent_length - 1] == '\x00' &&
           buf1[apparent_length - 1] == '\xFF') {
      if (--apparent_length == 0) break;
    }
    EXPECT_EQ(apparent_length, buf0_written - buf0);
    EXPECT_EQ(apparent_length, buf1_written - buf1);
    EXPECT_EQ(apparent_length, test.second.length());
    EXPECT_EQ(std::string(buf0, apparent_length), test.second);
    EXPECT_EQ(std::string(buf1, apparent_length), test.second);
  }
  char buf[32] = "Don't Tread On Me";
  EXPECT_LE(absl::strings_internal::EncodeUTF8Char(buf, 0x00110000),
            absl::strings_internal::kMaxEncodedUTF8Size);
  char buf2[32] = "Negative is invalid but sane";
  EXPECT_LE(absl::strings_internal::EncodeUTF8Char(buf2, -1),
            absl::strings_internal::kMaxEncodedUTF8Size);
}
#if defined(__clang__)
// #pragma clang diagnostic pop
#endif
#endif  // !defined(__cpp_char8_t)

struct WideToUtf8TestCase {
  std::string description;
  wchar_t input;
  std::string expected_utf8_str;
  size_t expected_bytes_written;
  ShiftState initial_state = {false, 0};
  ShiftState expected_state = {false, 0};
};

std::vector<WideToUtf8TestCase> GetWideToUtf8TestCases() {
  constexpr size_t kError = static_cast<size_t>(-1);
  std::vector<WideToUtf8TestCase> cases = {
      {"ASCII_A", L'A', "A", 1},
      {"NullChar", L'\0', std::string("\0", 1), 1},
      {"ASCII_Max_7F", L'\x7F', "\x7F", 1},

      {"TwoByte_Min_80", L'\u0080', "\xC2\x80", 2},
      {"PoundSign_A3", L'\u00A3', "\xC2\xA3", 2},
      {"TwoByte_Max_7FF", L'\u07FF', "\xDF\xBF", 2},

      {"ThreeByte_Min_800", L'\u0800', "\xE0\xA0\x80", 3},
      {"EuroSign_20AC", L'\u20AC', "\xE2\x82\xAC", 3},
      {"BMP_MaxBeforeSurrogates_D7FF", L'\uD7FF', "\xED\x9F\xBF", 3},
      {"BMP_FFFF", L'\uFFFF', "\xEF\xBF\xBF", 3},

      {"IsolatedHighSurr_D800", L'\xD800', "\xF0\x90", 2, {}, {true, 0}},
      {"IsolatedHighSurr_DBFF", L'\xDBFF', "\xF4\x8F", 2, {}, {true, 3}},

      {"HighSurr_D800_after_HighD800",
       L'\xD800',
       "\xF0\x90",
       2,
       {true, 0},
       {true, 0}},
      {"HighSurr_DBFF_after_HighDBFF",
       L'\xDBFF',
       "\xF4\x8F",
       2,
       {true, 3},
       {true, 3}},

      {"LowSurr_DC00_after_HighD800", L'\xDC00', "\x80\x80", 2, {true, 0}, {}},
      {"LowSurr_DFFD_after_HighDBFF", L'\xDFFD', "\xBF\xBD", 2, {true, 3}, {}},
      {"LowSurr_DC00_with_InitialState_saw_high_bits_1",
       L'\xDC00',
       "\x90\x80",
       2,
       {true, 1},
       {}},

      // Final state = initial on error.
      {"Error_IsolatedLowSurr_DC00_NoPriorHigh", L'\xDC00', "", kError, {}, {}},
      {"Error_IsolatedLowSurr_DFFF_NoPriorHigh", L'\xDFFF', "", kError, {}, {}},

#if (defined(WCHAR_MAX) && WCHAR_MAX > 0xFFFF)
      {"DirectSupplementaryChars_U10000", static_cast<wchar_t>(0x10000),
       "\xF0\x90\x80\x80", 4},
      {"DirectSupplementaryChars_U10FFFD", static_cast<wchar_t>(0x10FFFD),
       "\xF4\x8F\xBF\xBD", 4},
#endif
  };

  wchar_t minus_one = static_cast<wchar_t>(-1);
  if constexpr (sizeof(wchar_t) == 2) {
    cases.push_back({"WChar_MinusOne_as_FFFF", minus_one, "\xEF\xBF\xBF", 3});
  } else {
    cases.push_back(
        {"Error_WChar_MinusOne_as_FFFFFFFF", minus_one, "", kError, {}, {}});
  }

  if constexpr (sizeof(wchar_t) >= 4) {
#ifdef WCHAR_MAX
    if (static_cast<uintmax_t>(WCHAR_MAX) >= 0x110000UL) {
      cases.push_back({"Error_OutOfRange_110000",
                       static_cast<wchar_t>(0x110000UL),
                       "",
                       kError,
                       {},
                       {}});
    }
#else
    cases.push_back({"Error_OutOfRange_110000_fallback",
                     static_cast<wchar_t>(0x110000UL),
                     "",
                     kError,
                     {},
                     {}});
#endif
  }
  return cases;
}

class WideToUtf8ParamTest : public TestWithParam<WideToUtf8TestCase> {};

TEST_P(WideToUtf8ParamTest, SingleCharConversion) {
  const auto& test_case = GetParam();
  ShiftState state = test_case.initial_state;
  constexpr char kFillChar = '\xAB';
  std::string buffer(32, kFillChar);

  size_t bytes_written = WideToUtf8(test_case.input, buffer.data(), state);

  EXPECT_EQ(bytes_written, test_case.expected_bytes_written);
  EXPECT_THAT(buffer, StartsWith(test_case.expected_utf8_str));

  // The remaining bytes should be unchanged.
  ASSERT_LT(test_case.expected_utf8_str.length(), buffer.size());
  EXPECT_EQ(buffer[test_case.expected_utf8_str.length()], kFillChar);

  EXPECT_EQ(state.saw_high_surrogate,
            test_case.expected_state.saw_high_surrogate);
  EXPECT_EQ(state.bits, test_case.expected_state.bits);
}

INSTANTIATE_TEST_SUITE_P(WideCharToUtf8Conversion, WideToUtf8ParamTest,
                         ValuesIn(GetWideToUtf8TestCases()),
                         [](auto info) { return info.param.description; });

// Comprehensive test string for validating wchar_t to UTF-8 conversion.
// This string is designed to cover a variety of Unicode character types and
// sequences:
// 1. Basic ASCII characters (within names, numbers, and spacing).
// 2. Common 2-byte UTF-8 sequences:
//    - Accented Latin characters (e.g., 'á' in "Holá").
//    - Hebrew text with combining vowel points (e.g., "שָׁלוֹם").
// 3. Common 3-byte UTF-8 sequences:
//    - Currency symbols (e.g., '€').
//    - CJK characters (e.g., "你好", "中").
//    - Components of complex emojis like the Zero Width Joiner (ZWJ) and
//      Heart symbol.
// 4. Various 4-byte UTF-8 sequences (representing Supplementary Plane
// characters):
//    - An emoji with a skin tone modifier ("👍🏻").
//    - A flag emoji composed of regional indicators ("🇺🇸").
//    - A complex ZWJ emoji sequence ("👩‍❤️‍💋‍👨") combining
//      SP characters (👩, 💋, 👨) with BMP characters (ZWJ and ❤️).
//    - These are critical for testing the correct handling of surrogate pairs
//      when wchar_t is 2 bytes (e.g., on Windows).
// The goal is to ensure accurate conversion across a diverse set of
// characters.
//
// clang-format off
#define WIDE_STRING_LITERAL L"Holá €1 你好 שָׁלוֹם 👍🏻🇺🇸👩‍❤️‍💋‍👨 中"
#define UTF8_STRING_LITERAL u8"Holá €1 你好 שָׁלוֹם 👍🏻🇺🇸👩‍❤️‍💋‍👨 中"
// clang-format on

absl::string_view GetUtf8TestString() {
  // `u8""` forces UTF-8 encoding; MSVC will default to e.g. CP1252 (and warn)
  // without it. However, the resulting character type differs between pre-C++20
  // (`char`) and C++20 (`char8_t`). So deduce the right character type for all
  // C++ versions, init it with UTF-8, then `memcpy()` to get the result as a
  // `char*`
  static absl::string_view kUtf8TestString = [] {
    using ConstChar8T = std::remove_reference_t<decltype(*u8"a")>;
    constexpr ConstChar8T kOutputUtf8[] = UTF8_STRING_LITERAL;
    static char output[sizeof kOutputUtf8];
    std::memcpy(output, kOutputUtf8, sizeof kOutputUtf8);
    return output;
  }();

  return kUtf8TestString;
}

TEST(WideToUtf8, FullString) {
  std::string buffer(kMaxEncodedUTF8Size * sizeof(WIDE_STRING_LITERAL), '\0');
  char* buffer_ptr = buffer.data();

  ShiftState state;
  for (const wchar_t wc : WIDE_STRING_LITERAL) {
    buffer_ptr += WideToUtf8(wc, buffer_ptr, state);
  }

  EXPECT_THAT(buffer, StartsWith(GetUtf8TestString()));
}

#undef WIDE_STRING_LITERAL
#undef UTF8_STRING_LITERAL

}  // namespace
