// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#ifndef RUNTIME_PLATFORM_ASSERT_H_
#define RUNTIME_PLATFORM_ASSERT_H_

#include "platform/globals.h"

#if !defined(DEBUG) && !defined(NDEBUG)
#error neither DEBUG nor NDEBUG defined
#elif defined(DEBUG) && defined(NDEBUG)
#error both DEBUG and NDEBUG defined
#endif

// TODO(5411406): include sstream for now, once we have a Utils::toString()
// implemented for all the primitive types we can replace the usage of
// sstream by Utils::toString()
#if defined(DEBUG) || defined(TESTING)
#include <string.h>

#include <sstream>
#include <string>
#endif

namespace dart {

class DynamicAssertionHelper {
 public:
  DynamicAssertionHelper(const char* file, int line)
      : file_(file), line_(line) {}

 protected:
  void Print(const char* format,
             va_list arguments,
             bool will_abort = false) const;

  const char* const file_;
  const int line_;

  DISALLOW_IMPLICIT_CONSTRUCTORS(DynamicAssertionHelper);
};

class Assert : public DynamicAssertionHelper {
 public:
  Assert(const char* file, int line) : DynamicAssertionHelper(file, line) {}

  DART_NORETURN void Fail(const char* format, ...) const PRINTF_ATTRIBUTE(2, 3);

  template <typename T>
  T NotNull(const T p);
};

class Expect : public DynamicAssertionHelper {
 public:
  Expect(const char* file, int line) : DynamicAssertionHelper(file, line) {}

  void Fail(const char* format, ...) const PRINTF_ATTRIBUTE(2, 3);

#if defined(TESTING)
  template <typename E, typename A>
  void Equals(const E& expected, const A& actual);

  template <typename E, typename A>
  void NotEquals(const E& not_expected, const A& actual);

  template <typename E, typename A>
  void BitEquals(const E& expected, const A& actual);

  template <typename E, typename A, typename T>
  void FloatEquals(const E& expected, const A& actual, const T& tol);

  void StringEquals(const char* expected, const char* actual);

  void IsSubstring(const char* needle, const char* haystack);

  void IsNotSubstring(const char* needle, const char* haystack);

  template <typename E, typename A>
  void LessThan(const E& left, const A& right);

  template <typename E, typename A>
  void LessEqual(const E& left, const A& right);

  template <typename E, typename A>
  void GreaterThan(const E& left, const A& right);

  template <typename E, typename A>
  void GreaterEqual(const E& left, const A& right);

  template <typename T>
  void NotNull(const T p);

  template <typename T>
  void Null(const T p);
#endif

  static bool failed() { return failed_; }

 private:
  static bool failed_;
};

template <typename T>
T Assert::NotNull(const T p) {
  if (p != nullptr) return p;
  Fail("expected: not nullptr, found nullptr");
  return nullptr;
}

#if defined(TESTING)
// Only allow the expensive (with respect to code size) assertions
// in testing code.
template <typename E, typename A>
void Expect::Equals(const E& expected, const A& actual) {
  if (actual == expected) return;
  std::ostringstream ess, ass;
  ess << expected;
  ass << actual;
  std::string es = ess.str(), as = ass.str();
  Fail("expected: <%s> but was: <%s>", es.c_str(), as.c_str());
}

template <typename E, typename A>
void Expect::NotEquals(const E& not_expected, const A& actual) {
  if (actual != not_expected) return;
  std::ostringstream ness;
  ness << not_expected;
  std::string nes = ness.str();
  Fail("did not expect: <%s>", nes.c_str());
}

template <typename E, typename A>
void Expect::BitEquals(const E& expected, const A& actual) {
  static_assert(sizeof(E) == sizeof(A));
  if (memcmp(&expected, &actual, sizeof(E)) == 0) return;
  std::ostringstream ess, ass;
  ess << expected;
  ass << actual;
  std::string es = ess.str(), as = ass.str();
  Fail("expected: <%s> but was: <%s>", es.c_str(), as.c_str());
}

template <typename E, typename A, typename T>
void Expect::FloatEquals(const E& expected, const A& actual, const T& tol) {
  if (((expected - tol) <= actual) && (actual <= (expected + tol))) {
    return;
  }
  std::ostringstream ess, ass, tolss;
  ess << expected;
  ass << actual;
  tolss << tol;
  std::string es = ess.str(), as = ass.str(), tols = tolss.str();
  Fail("expected: <%s> but was: <%s> (tolerance: <%s>)", es.c_str(), as.c_str(),
       tols.c_str());
}

static void Escape(std::string& dst, const char* src, int len) {
  if (len == 0) return;
  const char* const end = src + len;
  for (char c = *src; src < end; src++, c = *src) {
    if (c == '\n') {
      dst += "\\n\"\n\"";
    } else if (c == '\'') {
      dst += "\\\'";
    } else if (c == '\"') {
      dst += "\\\"";
    } else if (c == '\\') {
      dst += "\\\\";
    } else if (c == '\0') {
      dst += "\\0";
    } else {
      dst += c;
    }
  }
}

// Splits two null-terminated strings into a common prefix and suffix
// and the mismatched parts of the two strings, escaping the outputs.
//
// If the two strings are the same, then an escaped version of
// the string is in the prefix and the other outputs are empty.
//
// If there is no common prefix and suffix or the size of the common
// prefix and suffix combined is small in comparison to the size
// of the two strings, then the prefix and suffix are empty to limit
// the noise in the output of Expect::StringEquals.
static void FindCommonPrefixAndSuffix(const char* expected,
                                      const char* actual,
                                      std::string& prefix,
                                      std::string& expected_mismatch,
                                      std::string& actual_mismatch,
                                      std::string& suffix) {
  const int expected_len = strlen(expected);
  const int actual_len = strlen(actual);
  int prefix_length = 0;
  while (expected[prefix_length] == actual[prefix_length]) {
    // Either the prefix or actual string is a prefix of the other.
    if (expected[prefix_length] == '\0' || actual[prefix_length] == '\0') {
      break;
    }
    prefix_length += 1;
  }

  if (prefix_length == expected_len && prefix_length == actual_len) {
    // The two are equal, so just escape the whole string into the prefix.
    Escape(prefix, expected, prefix_length);
    return;
  }

  int suffix_length = 0;
  while (expected[(expected_len - 1) - suffix_length] ==
         actual[(actual_len - 1) - suffix_length]) {
    if (prefix_length + suffix_length == expected_len ||
        prefix_length + suffix_length == actual_len) {
      // The prefix and suffix cover the entirety of one of the
      // two strings, meaning the actual string either has missing
      // or extra contents compared to the expected string.
      break;
    }
    suffix_length += 1;
  }

  // Set a minimum difference required to output a non-empty prefix and suffix,
  // so that they are not output if the common prefix and suffix of the expected
  // and actual strings is small.
  constexpr int kMinDifference = 10;
  if (prefix_length + suffix_length < kMinDifference) {
    prefix_length = 0;
    suffix_length = 0;
  }

  Escape(prefix, expected, prefix_length);
  Escape(expected_mismatch, expected + prefix_length,
         expected_len - prefix_length - suffix_length);
  Escape(actual_mismatch, actual + prefix_length,
         actual_len - prefix_length - suffix_length);
  Escape(suffix, expected + expected_len - suffix_length, suffix_length);
}

inline void Expect::StringEquals(const char* expected, const char* actual) {
  if (actual == nullptr) {
    Fail("expected:\n<\"%s\">\nbut was nullptr", expected);
  } else {
    if (strcmp(expected, actual) == 0) return;
    std::string prefix, expected_mismatch, actual_mismatch, suffix;
    FindCommonPrefixAndSuffix(expected, actual, prefix, expected_mismatch,
                              actual_mismatch, suffix);
    // Only print the prefix and/or suffix if non-empty.
    if (prefix.empty() && suffix.empty()) {
      Fail("expected:\n<\"%s\">\nbut was:\n<\"%s\">", expected_mismatch.c_str(),
           actual_mismatch.c_str());
    } else if (prefix.empty()) {
      Fail(
          "substring mismatch:\nexpected:\n  <\"%s\">\nbut was:\n  "
          "<\"%s\">\nsuffix:\n  <\"%s\">",
          expected_mismatch.c_str(), actual_mismatch.c_str(), suffix.c_str());
    } else if (suffix.empty()) {
      Fail("prefix:\n  <\"%s\">\nexpected:\n  <\"%s\">\nbut was:\n  <\"%s\">",
           prefix.c_str(), expected_mismatch.c_str(), actual_mismatch.c_str());
    } else {
      Fail(
          "prefix:\n  <\"%s\">\nexpected:\n  <\"%s\">\nbut was:\n  "
          "<\"%s\">\nsuffix:\n  <\"%s\">",
          prefix.c_str(), expected_mismatch.c_str(), actual_mismatch.c_str(),
          suffix.c_str());
    }
  }
}

inline void Expect::IsSubstring(const char* needle, const char* haystack) {
  if (strstr(haystack, needle) != nullptr) return;
  Fail("expected <\"%s\"> to be a substring of <\"%s\">", needle, haystack);
}

inline void Expect::IsNotSubstring(const char* needle, const char* haystack) {
  if (strstr(haystack, needle) == nullptr) return;
  Fail("expected <\"%s\"> to not be a substring of <\"%s\">", needle, haystack);
}

template <typename E, typename A>
void Expect::LessThan(const E& left, const A& right) {
  if (left < right) return;
  std::ostringstream ess, ass;
  ess << left;
  ass << right;
  std::string es = ess.str(), as = ass.str();
  Fail("expected: %s < %s", es.c_str(), as.c_str());
}

template <typename E, typename A>
void Expect::LessEqual(const E& left, const A& right) {
  if (left <= right) return;
  std::ostringstream ess, ass;
  ess << left;
  ass << right;
  std::string es = ess.str(), as = ass.str();
  Fail("expected: %s <= %s", es.c_str(), as.c_str());
}

template <typename E, typename A>
void Expect::GreaterThan(const E& left, const A& right) {
  if (left > right) return;
  std::ostringstream ess, ass;
  ess << left;
  ass << right;
  std::string es = ess.str(), as = ass.str();
  Fail("expected: %s > %s", es.c_str(), as.c_str());
}

template <typename E, typename A>
void Expect::GreaterEqual(const E& left, const A& right) {
  if (left >= right) return;
  std::ostringstream ess, ass;
  ess << left;
  ass << right;
  std::string es = ess.str(), as = ass.str();
  Fail("expected: %s >= %s", es.c_str(), as.c_str());
}

template <typename T>
void Expect::NotNull(const T p) {
  if (p != nullptr) return;
  Fail("expected: not nullptr, found nullptr");
}

template <typename T>
void Expect::Null(const T p) {
  if (p == nullptr) return;
  Fail("expected: nullptr, found not null pointer");
}
#endif

}  // namespace dart

#define FATAL(format, ...)                                                     \
  dart::Assert(__FILE__, __LINE__).Fail(format, ##__VA_ARGS__);

#define UNIMPLEMENTED() FATAL("unimplemented code")

#define UNREACHABLE() FATAL("unreachable code")

#define OUT_OF_MEMORY() FATAL("Out of memory.")

#if defined(DEBUG)
// DEBUG binaries use assertions in the code.
// Note: We wrap the if statement in a do-while so that we get a compile
//       error if there is no semicolon after ASSERT(condition). This
//       ensures that we get the same behavior on DEBUG and RELEASE builds.

#define ASSERT(cond)                                                           \
  do {                                                                         \
    if (!(cond)) dart::Assert(__FILE__, __LINE__).Fail("expected: %s", #cond); \
  } while (false)

#define ASSERT_EQUAL(actual, expected)                                         \
  do {                                                                         \
    if ((expected) != (actual)) {                                              \
      const std::string actual_str = std::to_string(actual);                   \
      const std::string expected_str = std::to_string(expected);               \
      dart::Assert(__FILE__, __LINE__)                                         \
          .Fail("expected \"%s\" = %s, actual \"%s\" = %s", #expected,         \
                expected_str.c_str(), #actual, actual_str.c_str());            \
    }                                                                          \
  } while (false)

#define ASSERT_LESS_OR_EQUAL(actual, expected)                                 \
  do {                                                                         \
    if ((actual) > (expected)) {                                               \
      const std::string actual_str = std::to_string(actual);                   \
      const std::string expected_str = std::to_string(expected);               \
      dart::Assert(__FILE__, __LINE__)                                         \
          .Fail("expected \"%s\" = %s >= actual \"%s\" = %s", #expected,       \
                expected_str.c_str(), #actual, actual_str.c_str());            \
    }                                                                          \
  } while (false)

#define ASSERT_IMPLIES(antecedent, consequent)                                 \
  do {                                                                         \
    if (antecedent) {                                                          \
      ASSERT(consequent);                                                      \
    }                                                                          \
  } while (false)

// DEBUG_ASSERT allows identifiers in condition to be undeclared in release
// mode.
#define DEBUG_ASSERT(cond) ASSERT(cond)

// Returns 'ptr'; useful for initializer lists:
//   class Foo { Foo(int* ptr) : ptr_(ASSERT_NOTNULL(ptr)) ...
#define ASSERT_NOTNULL(ptr) dart::Assert(__FILE__, __LINE__).NotNull((ptr))

#else  // if defined(DEBUG)

// In order to avoid variable unused warnings for code that only uses
// a variable in an ASSERT or EXPECT, we make sure to use the macro
// argument.
#define ASSERT(condition)                                                      \
  do {                                                                         \
  } while (false && (condition))

#define ASSERT_EQUAL(expected, actual)                                         \
  do {                                                                         \
  } while (false && ((expected) != (actual)))

#define ASSERT_LESS_OR_EQUAL(expected, actual)                                 \
  do {                                                                         \
  } while (false && ((actual) > (expected)))

#define ASSERT_IMPLIES(antecedent, consequent)                                 \
  do {                                                                         \
  } while (false && (!(antecedent) || (consequent)))

#define DEBUG_ASSERT(cond)

#define ASSERT_NOTNULL(ptr) (ptr)

#endif  // if defined(DEBUG)

#define RELEASE_ASSERT(cond)                                                   \
  do {                                                                         \
    if (!(cond)) dart::Assert(__FILE__, __LINE__).Fail("expected: %s", #cond); \
  } while (false)

#define RELEASE_ASSERT_WITH_MSG(cond, msg)                                     \
  do {                                                                         \
    if (!(cond)) {                                                             \
      dart::Assert(__FILE__, __LINE__).Fail("%s: expected: %s", msg, #cond);   \
    }                                                                          \
  } while (false)

#define COMPILE_ASSERT(expr) static_assert(expr, "")

#if defined(TESTING)

// EXPECT and FAIL are equivalent to ASSERT and FATAL except that they do not
// cause early termination of the unit test. This allows testing to proceed
// further to be able to report other failures before reporting the overall
// unit tests as failing.

#define EXPECT(condition)                                                      \
  if (!(condition)) {                                                          \
    dart::Expect(__FILE__, __LINE__).Fail("expected: %s", #condition);         \
  }

#define EXPECT_EQ(expected, actual)                                            \
  dart::Expect(__FILE__, __LINE__).Equals((expected), (actual))

#define EXPECT_NE(not_expected, actual)                                        \
  dart::Expect(__FILE__, __LINE__).NotEquals((not_expected), (actual))

#define EXPECT_BITEQ(expected, actual)                                         \
  dart::Expect(__FILE__, __LINE__).BitEquals((expected), (actual))

#define EXPECT_FLOAT_EQ(expected, actual, tol)                                 \
  dart::Expect(__FILE__, __LINE__).FloatEquals((expected), (actual), (tol))

#define EXPECT_STREQ(expected, actual)                                         \
  dart::Expect(__FILE__, __LINE__).StringEquals((expected), (actual))

#define EXPECT_SUBSTRING(needle, haystack)                                     \
  dart::Expect(__FILE__, __LINE__).IsSubstring((needle), (haystack))

#define EXPECT_NOTSUBSTRING(needle, haystack)                                  \
  dart::Expect(__FILE__, __LINE__).IsNotSubstring((needle), (haystack))

#define EXPECT_LT(left, right)                                                 \
  dart::Expect(__FILE__, __LINE__).LessThan((left), (right))

#define EXPECT_LE(left, right)                                                 \
  dart::Expect(__FILE__, __LINE__).LessEqual((left), (right))

#define EXPECT_GT(left, right)                                                 \
  dart::Expect(__FILE__, __LINE__).GreaterThan((left), (right))

#define EXPECT_GE(left, right)                                                 \
  dart::Expect(__FILE__, __LINE__).GreaterEqual((left), (right))

#define EXPECT_NOTNULL(ptr) dart::Expect(__FILE__, __LINE__).NotNull((ptr))

#define EXPECT_NULLPTR(ptr) dart::Expect(__FILE__, __LINE__).Null((ptr))

#define FAIL(format, ...)                                                      \
  dart::Expect(__FILE__, __LINE__).Fail(format, ##__VA_ARGS__)

#endif  // defined(TESTING)

#endif  // RUNTIME_PLATFORM_ASSERT_H_
