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

#ifndef UTIL_TEST_TEST_H_
#define UTIL_TEST_TEST_H_

#include <string.h>

#include <concepts>
#include <memory>
#include <optional>
#include <set>
#include <sstream>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <vector>

#include "base/strings/stringprintf.h"
#include "gn/err.h"

// This is a minimal googletest-like testing framework. It's originally derived
// from Ninja's src/test.h. You might prefer that one if you have different
// tradeoffs (in particular, if you don't need to stream message to assertion
// failures, Ninja's is a bit simpler.)
namespace testing {

class Test {
 public:
  Test() : failed_(false) {}
  virtual ~Test() {}
  virtual void SetUp() {}
  virtual void TearDown() {}
  virtual void Run() = 0;

  bool Failed() const { return failed_; }

 private:
  friend class TestResult;

  bool failed_;
};

extern testing::Test* g_current_test;

class TestResult {
 public:
  TestResult(bool condition, const char* error)
      : condition_(condition), error_(error) {
    if (!condition)
      g_current_test->failed_ = true;
  }

  operator bool() const { return condition_; }
  const char* error() const { return error_; }

 private:
  bool condition_;
  const char* error_;
};

class Message {
 public:
  Message() {}
  ~Message() { printf("%s\n\n", ss_.str().c_str()); }

  template <typename T>
  inline Message& operator<<(const T& val) {
    ss_ << val;
    return *this;
  }

 private:
  std::stringstream ss_;
};

class AssertHelper {
 public:
  AssertHelper(const char* file, int line, const TestResult& test_result)
      : file_(file), line_(line), error_(test_result.error()) {}

  void operator=(const Message& message) const {
    printf("\n*** FAILURE %s:%d: %s\n", file_, line_, error_);
  }

 private:
  const char* file_;
  int line_;
  const char* error_;
};

std::string Indent(std::string_view value);
std::string DiffStrings(std::string_view expected, std::string_view actual);

std::string Pretty(bool value);
std::string Pretty(const char* value);
std::string Pretty(const Err& err);

// Explicitly write this for enum, because otherwise it tries to cast enums
// to bools.
template <typename T>
  requires std::is_enum_v<T>
std::string Pretty(T value) {
  return "Enum value " +
         std::to_string(static_cast<std::underlying_type_t<T>>(value));
}

template <typename T>
  requires std::is_convertible_v<T, std::string>
std::string Pretty(const T& value) {
  return value;
}

template <typename T>
  requires std::is_arithmetic_v<T>
std::string Pretty(T value) {
  return std::to_string(value);
}

// Order matters for these templates.
// The requires clause requires that the clause can already be met.
// The TLDR is that you must order them so that your inner types come first.
// For example, since we put T* before vector<T>, we support vector<T*> but not
// vector<T>*.

// Pointer and options should come first because we pretty much universally
// point to fixed types rather than templates.
template <typename T>
  requires requires(T t) { Pretty(t); }
std::string Pretty(const T* value) {
  if (value == nullptr) {
    return "NULL";
  }
  auto pretty = Pretty(*value);
  return base::StringPrintf("%p -> %.*s", value,
                            static_cast<int>(pretty.size()), pretty.data());
}

template <typename T>
  requires requires(T t) { Pretty(t); }
std::string Pretty(const std::unique_ptr<T>& value) {
  return "unique_ptr " + Pretty(value.get());
}

template <typename T>
  requires requires(T t) { Pretty(t); }
std::string Pretty(const std::optional<T>& value) {
  if (!value) {
    return "nullopt";
  }
  return "Optional(" + Pretty(*value) + ")";
}

// Containers of pairs are far more common than pairs of containers.
template <typename T, typename U>
  requires requires(T t) { Pretty(t); } && requires(U u) { Pretty(u); }
std::string Pretty(const std::pair<T, U>& value) {
  std::stringstream ss;
  ss << "Pair(\n";
  ss << Indent(Pretty(value.first)) << ",\n";
  ss << Indent(Pretty(value.second)) << ",\n";
  ss << ")";
  return ss.str();
}

// Containers are usually the outer layer, so they come last.
template <typename T>
  requires requires(T t) { Pretty(t); }
std::string Pretty(const std::vector<T>& value) {
  std::stringstream ss;
  ss << "[\n";
  for (const auto& v : value) {
    ss << Indent(Pretty(v)) << ",\n";
  }
  ss << "]";
  return ss.str();
}

template <typename T>
  requires requires(T t) { Pretty(t); }
std::string Pretty(const std::set<T>& value) {
  std::stringstream ss;
  ss << "{\n";
  for (const auto& v : value) {
    ss << Indent(Pretty(v)) << ",\n";
  }
  ss << "}";
  return ss.str();
}

template <typename T>
std::string Pretty(const Result<T>& result) {
  if (!result.has_value()) {
    return "Err(" + Pretty(result.error()) + ")";
  }
  if constexpr (requires { Pretty(*result); }) {
    return "Ok(" + Pretty(*result) + ")";
  } else {
    return "Ok(<unprintable value>)";
  }
}

template <typename T, typename U>
std::string TryDiffStrings(const T& expected, const U& actual) {
  if constexpr (requires {
                  Pretty(expected);
                  Pretty(actual);
                }) {
    auto expected_pretty = Pretty(expected);
    auto actual_pretty = Pretty(actual);
    // Ensure git diff doesn't complain about missing newlines.
    if (!expected_pretty.ends_with("\n") && !actual_pretty.ends_with("\n")) {
      return DiffStrings(expected_pretty + "\n", actual_pretty + "\n");
    }
    return DiffStrings(expected_pretty, actual_pretty);
  } else {
    return "";
  }
}

inline std::optional<std::string> CheckSuccess(const auto& err) {
  if (err.has_error()) {
    return "Expected success but got error:\n" + err.message();
  }
  return std::nullopt;
}

template <typename T, typename U>
inline std::optional<std::string> CheckSuccess(const Result<T>& result,
                                               const U& expected) {
  if (auto msg = CheckSuccess(result); msg.has_value()) {
    return msg;
  }
  if (*result != expected) {
    return ::testing::TryDiffStrings(*result, expected);
  }
  return std::nullopt;
}

inline std::optional<std::string> CheckFailure(
    auto&& result,
    std::string_view expected_msg = "") {
  if (!result.has_error()) {
    return "Expected failure but got " + Pretty(result);
  }
  if (!expected_msg.empty() && !result.message().contains(expected_msg)) {
    return "Expected error message to contain \"" + std::string(expected_msg) +
           "\", but actual was:\n" + result.message();
  }
  return std::nullopt;
}

}  // namespace testing

void RegisterTest(testing::Test* (*)(), const char*);

#define TEST_F_(x, y, name)                                                    \
  struct y : public x {                                                        \
    static testing::Test* Create() { return testing::g_current_test = new y; } \
    virtual void Run();                                                        \
  };                                                                           \
  struct Register##y {                                                         \
    Register##y() { RegisterTest(y::Create, name); }                           \
  };                                                                           \
  Register##y g_register_##y;                                                  \
  void y::Run()

#define TEST_F(x, y) TEST_F_(x, x##y, #x "." #y)
#define TEST(x, y) TEST_F_(testing::Test, x##y, #x "." #y)

#define FRIEND_TEST(x, y) friend class x##y

// Some compilers emit a warning if nested "if" statements are followed by an
// "else" statement and braces are not used to explicitly disambiguate the
// "else" binding.  This leads to problems with code like:
//
//   if (something)
//     ASSERT_TRUE(condition) << "Some message";
#define TEST_AMBIGUOUS_ELSE_BLOCKER_ \
  switch (0)                         \
  case 0:                            \
  default:

#define TEST_ASSERT_(expression, on_failure)                  \
  TEST_AMBIGUOUS_ELSE_BLOCKER_                                \
  if (const ::testing::TestResult test_result = (expression)) \
    ;                                                         \
  else                                                        \
    on_failure(test_result)

#define TEST_NONFATAL_FAILURE_(message) \
  ::testing::AssertHelper(__FILE__, __LINE__, message) = ::testing::Message()

#define TEST_FATAL_FAILURE_(message)                            \
  return ::testing::AssertHelper(__FILE__, __LINE__, message) = \
             ::testing::Message()

#define EXPECT_EQ(a, ...)                                                    \
  TEST_AMBIGUOUS_ELSE_BLOCKER_                                               \
  if (const ::testing::TestResult test_result =                              \
          ::testing::TestResult(a == (__VA_ARGS__), #a " == " #__VA_ARGS__)) \
    ;                                                                        \
  else                                                                       \
    ::testing::AssertHelper(__FILE__, __LINE__, test_result) =               \
        ::testing::Message() << ::testing::TryDiffStrings(a, (__VA_ARGS__))

#define EXPECT_NE(a, ...)                                                \
  TEST_ASSERT_(                                                          \
      ::testing::TestResult(a != (__VA_ARGS__), #a " != " #__VA_ARGS__), \
      TEST_NONFATAL_FAILURE_)

#define EXPECT_LT(a, ...)                                              \
  TEST_ASSERT_(                                                        \
      ::testing::TestResult(a < (__VA_ARGS__), #a " < " #__VA_ARGS__), \
      TEST_NONFATAL_FAILURE_)

#define EXPECT_GT(a, ...)                                              \
  TEST_ASSERT_(                                                        \
      ::testing::TestResult(a > (__VA_ARGS__), #a " > " #__VA_ARGS__), \
      TEST_NONFATAL_FAILURE_)

#define EXPECT_LE(a, ...)                                                \
  TEST_ASSERT_(                                                          \
      ::testing::TestResult(a <= (__VA_ARGS__), #a " <= " #__VA_ARGS__), \
      TEST_NONFATAL_FAILURE_)

#define EXPECT_GE(a, ...)                                                \
  TEST_ASSERT_(                                                          \
      ::testing::TestResult(a >= (__VA_ARGS__), #a " >= " #__VA_ARGS__), \
      TEST_NONFATAL_FAILURE_)

#define EXPECT_TRUE(a)                                          \
  TEST_ASSERT_(::testing::TestResult(static_cast<bool>(a), #a), \
               TEST_NONFATAL_FAILURE_)

#define EXPECT_FALSE(a)                                          \
  TEST_ASSERT_(::testing::TestResult(!static_cast<bool>(a), #a), \
               TEST_NONFATAL_FAILURE_)

#define EXPECT_STREQ(a, ...)                                        \
  TEST_ASSERT_(::testing::TestResult(strcmp(a, (__VA_ARGS__)) == 0, \
                                     #a " str== " #__VA_ARGS__),    \
               TEST_NONFATAL_FAILURE_)

#define ASSERT_EQ(a, ...)                                                    \
  TEST_AMBIGUOUS_ELSE_BLOCKER_                                               \
  if (const ::testing::TestResult test_result =                              \
          ::testing::TestResult(a == (__VA_ARGS__), #a " == " #__VA_ARGS__)) \
    ;                                                                        \
  else                                                                       \
    return ::testing::AssertHelper(__FILE__, __LINE__, test_result) =        \
               ::testing::Message()                                          \
               << ::testing::TryDiffStrings(a, (__VA_ARGS__))

#define ASSERT_NE(a, ...)                                                \
  TEST_ASSERT_(                                                          \
      ::testing::TestResult(a != (__VA_ARGS__), #a " != " #__VA_ARGS__), \
      TEST_FATAL_FAILURE_)

#define ASSERT_LT(a, ...)                                              \
  TEST_ASSERT_(                                                        \
      ::testing::TestResult(a < (__VA_ARGS__), #a " < " #__VA_ARGS__), \
      TEST_FATAL_FAILURE_)

#define ASSERT_GT(a, ...)                                              \
  TEST_ASSERT_(                                                        \
      ::testing::TestResult(a > (__VA_ARGS__), #a " > " #__VA_ARGS__), \
      TEST_FATAL_FAILURE_)

#define ASSERT_LE(a, ...)                                                \
  TEST_ASSERT_(                                                          \
      ::testing::TestResult(a <= (__VA_ARGS__), #a " <= " #__VA_ARGS__), \
      TEST_FATAL_FAILURE_)

#define ASSERT_GE(a, ...)                                                \
  TEST_ASSERT_(                                                          \
      ::testing::TestResult(a >= (__VA_ARGS__), #a " >= " #__VA_ARGS__), \
      TEST_FATAL_FAILURE_)

#define ASSERT_TRUE(a)                                          \
  TEST_ASSERT_(::testing::TestResult(static_cast<bool>(a), #a), \
               TEST_FATAL_FAILURE_)

#define ASSERT_FALSE(a)                                          \
  TEST_ASSERT_(::testing::TestResult(!static_cast<bool>(a), #a), \
               TEST_FATAL_FAILURE_)

#define ASSERT_STREQ(a, ...)                                        \
  TEST_ASSERT_(::testing::TestResult(strcmp(a, (__VA_ARGS__)) == 0, \
                                     #a " str== " #__VA_ARGS__),    \
               TEST_FATAL_FAILURE_)

#define EXPECT_SUCCESS(err, ...)                                          \
  TEST_AMBIGUOUS_ELSE_BLOCKER_                                            \
  if (const std::optional<std::string> test_err_msg =                     \
          ::testing::CheckSuccess((err)__VA_OPT__(, (__VA_ARGS__)));      \
      !test_err_msg.has_value())                                          \
    ;                                                                     \
  else                                                                    \
    TEST_NONFATAL_FAILURE_(::testing::TestResult(                         \
        false, "EXPECT_SUCCESS(" #err __VA_OPT__(", " #__VA_ARGS__) ")")) \
        << *test_err_msg

#define ASSERT_SUCCESS(err, ...)                                          \
  TEST_AMBIGUOUS_ELSE_BLOCKER_                                            \
  if (const std::optional<std::string> test_err_msg =                     \
          ::testing::CheckSuccess((err)__VA_OPT__(, (__VA_ARGS__)));      \
      !test_err_msg.has_value())                                          \
    ;                                                                     \
  else                                                                    \
    TEST_FATAL_FAILURE_(::testing::TestResult(                            \
        false, "ASSERT_SUCCESS(" #err __VA_OPT__(", " #__VA_ARGS__) ")")) \
        << *test_err_msg

#define EXPECT_FAILURE(err, ...)                                          \
  TEST_AMBIGUOUS_ELSE_BLOCKER_                                            \
  if (const std::optional<std::string> test_err_msg =                     \
          ::testing::CheckFailure((err)__VA_OPT__(, (__VA_ARGS__)));      \
      !test_err_msg.has_value())                                          \
    ;                                                                     \
  else                                                                    \
    TEST_NONFATAL_FAILURE_(::testing::TestResult(                         \
        false, "EXPECT_FAILURE(" #err __VA_OPT__(", " #__VA_ARGS__) ")")) \
        << *test_err_msg

#define ASSERT_FAILURE(err, ...)                                          \
  TEST_AMBIGUOUS_ELSE_BLOCKER_                                            \
  if (const std::optional<std::string> test_err_msg =                     \
          ::testing::CheckFailure((err)__VA_OPT__(, (__VA_ARGS__)));      \
      !test_err_msg.has_value())                                          \
    ;                                                                     \
  else                                                                    \
    TEST_FATAL_FAILURE_(::testing::TestResult(                            \
        false, "ASSERT_FAILURE(" #err __VA_OPT__(", " #__VA_ARGS__) ")")) \
        << *test_err_msg

#endif  // UTIL_TEST_TEST_H_
