// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/strings/to_string.h"

#include <ios>
#include <ostream>
#include <string>

#include "testing/gtest/include/gtest/gtest.h"

namespace gurl_base {
namespace {

class NotStringifiable {};
class HasToString {
 public:
  std::string ToString() const { return "yay!"; }
};

// .ToString() support on structs.
static_assert(!internal::SupportsToString<NotStringifiable>,
              "value without ToString() shouldn't be marked SupportsToString");
static_assert(!internal::SupportsToString<NotStringifiable&>,
              "& without ToString() shouldn't be marked SupportsToString");
static_assert(!internal::SupportsToString<const NotStringifiable&>,
              "const& without ToString() shouldn't be marked SupportsToString");
static_assert(!internal::SupportsToString<NotStringifiable&&>,
              "&& without ToString() shouldn't be marked SupportsToString");
static_assert(internal::SupportsToString<HasToString>,
              "value with ToString() should be marked SupportsToString");
static_assert(internal::SupportsToString<HasToString&>,
              "& with ToString() should be marked SupportsToString");
static_assert(internal::SupportsToString<const HasToString&>,
              "const& with ToString() should be marked SupportsToString");
static_assert(internal::SupportsToString<HasToString&&>,
              "&& with ToString() should be marked SupportsToString");

TEST(ToStringTest, Streamable) {
  // Types with built-in <<.
  EXPECT_EQ(ToString("foo"), "foo");
  EXPECT_EQ(ToString(123), "123");
}

enum class StreamableTestEnum { kGreeting, kLocation };

std::ostream& operator<<(std::ostream& os, const StreamableTestEnum& value) {
  switch (value) {
    case StreamableTestEnum::kGreeting:
      return os << "hello";
    case StreamableTestEnum::kLocation:
      return os << "world";
  }
}

TEST(ToStringTest, UserDefinedStreamable) {
  // Type with user-defined <<.
  EXPECT_EQ(ToString(StreamableTestEnum::kGreeting), "hello");
  EXPECT_EQ(ToString(StreamableTestEnum::kGreeting, " ",
                     StreamableTestEnum::kLocation),
            "hello world");
}

TEST(ToStringTest, UserDefinedToString) {
  // Type with user-defined ToString().
  EXPECT_EQ(ToString(HasToString()), "yay!");
}

class UnusualToString {
 public:
  HasToString ToString() const { return HasToString(); }
};

TEST(ToStringTest, ToStringReturnsNonStdString) {
  // Types with a ToString() that does not directly return a std::string should
  // still work.
  EXPECT_EQ(ToString(UnusualToString()), "yay!");
}

enum class NonStreamableTestEnum { kGreeting = 0, kLocation };

TEST(ToStringTest, ScopedEnum) {
  // Scoped enums without a defined << should print as their underlying type.
  EXPECT_EQ(ToString(NonStreamableTestEnum::kLocation), "1");
}

TEST(ToStringTest, IoManip) {
  // I/O manipulators should have their expected effect, not be printed as
  // function pointers.
  EXPECT_EQ(ToString("42 in hex is ", std::hex, 42), "42 in hex is 2a");
}

TEST(ToStringTest, Tuple) {
  // Tuples should correctly format the contained types.
  EXPECT_EQ(ToString(std::make_tuple(StreamableTestEnum::kGreeting,
                                     HasToString(), "a string")),
            "<hello, yay!, a string>");
}

void Func() {}

TEST(ToStringTest, FunctionPointer) {
  // We don't care about the actual address, but a function pointer should not
  // be implicitly converted to bool.
  EXPECT_NE(ToString(&Func), ToString(true));

  // Functions should be treated like function pointers.
  EXPECT_EQ(ToString(Func), ToString(&Func));
}

class OverloadsAddressOp {
 public:
  OverloadsAddressOp* operator&() { return nullptr; }
  const OverloadsAddressOp* operator&() const { return nullptr; }
};

TEST(ToStringTest, NonStringifiable) {
  // Non-stringifiable types should be printed using a fallback.
  EXPECT_NE(ToString(NotStringifiable()).find("-byte object at 0x"),
            std::string::npos);

  // Non-stringifiable types which overload operator& should print their real
  // address.
  EXPECT_NE(ToString(OverloadsAddressOp()),
            ToString(static_cast<OverloadsAddressOp*>(nullptr)));
}

}  // namespace
}  // namespace base
