// Copyright 2023 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 <cstddef>

#include "gtest/gtest.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/substitute.h"

namespace {

TEST(CharFormatting, Char) {
  const char v = 'A';

  // Desired behavior: does not compile:
  // EXPECT_EQ(absl::StrCat(v, "B"), "AB");
  // EXPECT_EQ(absl::StrFormat("%vB", v), "AB");

  // Legacy behavior: format as char:
  EXPECT_EQ(absl::Substitute("$0B", v), "AB");
}

enum CharEnum : char {};
TEST(CharFormatting, CharEnum) {
  auto v = static_cast<CharEnum>('A');

  // Desired behavior: format as decimal
  EXPECT_EQ(absl::StrFormat("%vB", v), "65B");
  EXPECT_EQ(absl::StrCat(v, "B"), "65B");

  // Legacy behavior: format as character:

  // Some older versions of gcc behave differently in this one case
#if !defined(__GNUC__) || defined(__clang__)
  EXPECT_EQ(absl::Substitute("$0B", v), "AB");
#endif
}

enum class CharEnumClass: char {};
TEST(CharFormatting, CharEnumClass) {
  auto v = static_cast<CharEnumClass>('A');

  // Desired behavior: format as decimal:
  EXPECT_EQ(absl::StrFormat("%vB", v), "65B");
  EXPECT_EQ(absl::StrCat(v, "B"), "65B");

  // Legacy behavior: format as character:
  EXPECT_EQ(absl::Substitute("$0B", v), "AB");
}

TEST(CharFormatting, UnsignedChar) {
  const unsigned char v = 'A';

  // Desired behavior: format as decimal:
  EXPECT_EQ(absl::StrCat(v, "B"), "65B");
  EXPECT_EQ(absl::Substitute("$0B", v), "65B");
  EXPECT_EQ(absl::StrFormat("%vB", v), "65B");

  // Signedness check
  const unsigned char w = 255;
  EXPECT_EQ(absl::StrCat(w, "B"), "255B");
  EXPECT_EQ(absl::Substitute("$0B", w), "255B");
  // EXPECT_EQ(absl::StrFormat("%vB", v), "255B");
}

TEST(CharFormatting, SignedChar) {
  const signed char v = 'A';

  // Desired behavior: format as decimal:
  EXPECT_EQ(absl::StrCat(v, "B"), "65B");
  EXPECT_EQ(absl::Substitute("$0B", v), "65B");
  EXPECT_EQ(absl::StrFormat("%vB", v), "65B");

  // Signedness check
  const signed char w = -128;
  EXPECT_EQ(absl::StrCat(w, "B"), "-128B");
  EXPECT_EQ(absl::Substitute("$0B", w), "-128B");
}

enum UnsignedCharEnum : unsigned char {};
TEST(CharFormatting, UnsignedCharEnum) {
  auto v = static_cast<UnsignedCharEnum>('A');

  // Desired behavior: format as decimal:
  EXPECT_EQ(absl::StrCat(v, "B"), "65B");
  EXPECT_EQ(absl::Substitute("$0B", v), "65B");
  EXPECT_EQ(absl::StrFormat("%vB", v), "65B");

  // Signedness check
  auto w = static_cast<UnsignedCharEnum>(255);
  EXPECT_EQ(absl::StrCat(w, "B"), "255B");
  EXPECT_EQ(absl::Substitute("$0B", w), "255B");
  EXPECT_EQ(absl::StrFormat("%vB", w), "255B");
}

enum SignedCharEnum : signed char {};
TEST(CharFormatting, SignedCharEnum) {
  auto v = static_cast<SignedCharEnum>('A');

  // Desired behavior: format as decimal:
  EXPECT_EQ(absl::StrCat(v, "B"), "65B");
  EXPECT_EQ(absl::Substitute("$0B", v), "65B");
  EXPECT_EQ(absl::StrFormat("%vB", v), "65B");

  // Signedness check
  auto w = static_cast<SignedCharEnum>(-128);
  EXPECT_EQ(absl::StrCat(w, "B"), "-128B");
  EXPECT_EQ(absl::Substitute("$0B", w), "-128B");
  EXPECT_EQ(absl::StrFormat("%vB", w), "-128B");
}

enum class UnsignedCharEnumClass : unsigned char {};
TEST(CharFormatting, UnsignedCharEnumClass) {
  auto v = static_cast<UnsignedCharEnumClass>('A');

  // Desired behavior: format as decimal:
  EXPECT_EQ(absl::StrCat(v, "B"), "65B");
  EXPECT_EQ(absl::Substitute("$0B", v), "65B");
  EXPECT_EQ(absl::StrFormat("%vB", v), "65B");

  // Signedness check
  auto w = static_cast<UnsignedCharEnumClass>(255);
  EXPECT_EQ(absl::StrCat(w, "B"), "255B");
  EXPECT_EQ(absl::Substitute("$0B", w), "255B");
  EXPECT_EQ(absl::StrFormat("%vB", w), "255B");
}

enum SignedCharEnumClass : signed char {};
TEST(CharFormatting, SignedCharEnumClass) {
  auto v = static_cast<SignedCharEnumClass>('A');

  // Desired behavior: format as decimal:
  EXPECT_EQ(absl::StrCat(v, "B"), "65B");
  EXPECT_EQ(absl::Substitute("$0B", v), "65B");
  EXPECT_EQ(absl::StrFormat("%vB", v), "65B");

  // Signedness check
  auto w = static_cast<SignedCharEnumClass>(-128);
  EXPECT_EQ(absl::StrCat(w, "B"), "-128B");
  EXPECT_EQ(absl::Substitute("$0B", w), "-128B");
  EXPECT_EQ(absl::StrFormat("%vB", w), "-128B");
}

#ifdef __cpp_lib_byte
TEST(CharFormatting, StdByte) {
  auto v = static_cast<std::byte>('A');
  // Desired behavior: format as 0xff
  // (No APIs do this today.)

  // Legacy behavior: format as decimal:
  EXPECT_EQ(absl::StrCat(v, "B"), "65B");
  EXPECT_EQ(absl::Substitute("$0B", v), "65B");
  EXPECT_EQ(absl::StrFormat("%vB", v), "65B");
}
#endif  // _cpp_lib_byte

}  // namespace