/*
   +----------------------------------------------------------------------+
   | HipHop for PHP                                                       |
   +----------------------------------------------------------------------+
   | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com)  |
   +----------------------------------------------------------------------+
   | This source file is subject to version 3.01 of the PHP license,      |
   | that is bundled with this package in the file LICENSE, and is        |
   | available through the world-wide-web at the following url:           |
   | http://www.php.net/license/3_01.txt                                  |
   | If you did not receive a copy of the PHP license and are unable to   |
   | obtain it through the world-wide-web, please send a note to          |
   | license@php.net so we can mail you a copy immediately.               |
   +----------------------------------------------------------------------+
*/
#ifndef incl_HPHP_ASSERTIONS_H_
#define incl_HPHP_ASSERTIONS_H_

#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <exception>
#include <functional>
#include <string>
#include <type_traits>
#include <utility>

#include <folly/Format.h>
#include <folly/Preprocessor.h>

#include "hphp/util/portability.h"

///////////////////////////////////////////////////////////////////////////////

#define IMPLIES(a, b) (!(a) || (b))

#if defined(__INTEL_COMPILER)
#define not_reached()                                                \
  do {                                                               \
    assert(false);                                                   \
  } while (true)
#elif defined(_MSC_VER)
#define not_reached() __assume(0)
#else
#define not_reached() /* gcc-4.5 supports __builtin_unreachable() */  \
  do {                                                                \
    assert(false);                                                    \
    __builtin_unreachable();                                          \
  } while (true)
#endif

#define not_implemented() do {                   \
  fprintf(stderr, "not implemented: %s:%d %s\n", \
          __FILE__, __LINE__, __FUNCTION__);     \
  always_assert(0);                              \
} while (0)

#define assert_not_implemented(pred) do {        \
  if (! (pred) ) {                               \
    not_implemented();                           \
  }                                              \
} while(0)

namespace HPHP {

///////////////////////////////////////////////////////////////////////////////

/*
 * Assertion abort and log functions.
 *
 * These are intended for use primarily by the assert macros below.
 */
[[noreturn]]
void assert_fail(const char* e,
                 const char* file,
                 unsigned int line,
                 const char* func,
                 const std::string& msg);

[[noreturn]]
void assert_fail_no_log(const char* e,
                        const char* file,
                        unsigned int line,
                        const char* func,
                        const std::string& msg);

void assert_log_failure(const char* title, const std::string& msg);

/*
 * Register a function for auxiliary assert logging.
 */
using AssertFailLogger = std::function<void(const char*, const std::string&)>;

void register_assert_fail_logger(AssertFailLogger);

///////////////////////////////////////////////////////////////////////////////

/*
 * Stack-allocated detailed assertion logger.
 */
struct AssertDetailImpl {
  /*
   * Prints the results of all registered detailers to stderr.  Returns true if
   * we had any registered detailers.
   */
  static bool log();

protected:
  explicit AssertDetailImpl(const char* name)
    : m_name(name)
    , m_next(s_head)
  {
#ifndef NDEBUG
    if (m_name == nullptr) std::abort();
#endif
    s_head = this;
  }
  ~AssertDetailImpl() { if (m_name) s_head = m_next; }

  AssertDetailImpl(AssertDetailImpl&& other) noexcept {
#ifndef NDEBUG
    if (s_head != &other) std::abort();
#endif
    m_name = other.m_name;
    m_next = other.m_next;
    s_head = this;
    other.m_name = nullptr; // prevents ~other from messing it up
  }

  AssertDetailImpl(const AssertDetailImpl&) = delete;
  AssertDetailImpl& operator=(const AssertDetailImpl&) = delete;

private:
  static bool log_impl(const AssertDetailImpl*);
  virtual std::string run() const = 0;

private:
  static __thread AssertDetailImpl* s_head;

  const char* m_name;
  AssertDetailImpl* m_next{nullptr};
};

template<class F>
struct AssertDetailT final : AssertDetailImpl {
  AssertDetailT(const char* name, F&& f)
    : AssertDetailImpl(name)
    , m_f(std::move(f))
  {}
  AssertDetailT(AssertDetailT&&) = default;

private:
  std::string run() const override { return m_f(); }

private:
  F m_f;
};

namespace detail {

struct AssertDetailScopeMaker {
  explicit AssertDetailScopeMaker(const char* name) : m_name(name) {}

  template <typename F>
  AssertDetailT<typename std::decay<F>::type> operator+(F&& f) {
    return AssertDetailT<typename std::decay<F>::type>(
        m_name, std::forward<F>(f));
  }

private:
  const char* m_name;
};

}

#define SCOPE_ASSERT_DETAIL(name)           \
  auto FB_ANONYMOUS_VARIABLE(SCOPE_ASSERT)  \
  = ::HPHP::detail::AssertDetailScopeMaker(name) + [&]()

///////////////////////////////////////////////////////////////////////////////

# if !defined __GNUC__ || defined __STRICT_ANSI__
#define assert_impl(cond, fail) \
  ((cond) ? static_cast<void>(0) : ((fail), static_cast<void>(0)))
#else
/*
 * This is preferred, because "cond" is not over-parenthesized, and
 * thus -Wparentheses can warn about errors like "always_assert(a=1)".
 * With -pedantic, we cannot use "({...})" statement expressions,
 * so must resort to the old way, above.
 */
#define assert_impl(cond, fail) \
  ({ if (cond) ; else { fail; } static_cast<void>(0); })
#endif

#define assert_fail_impl(e, msg) \
  ::HPHP::assert_fail(#e, __FILE__, __LINE__, __PRETTY_FUNCTION__, msg)

#define assert_fail_impl_no_log(e, msg) \
  ::HPHP::assert_fail_no_log(#e, __FILE__, __LINE__, __PRETTY_FUNCTION__, msg)

#define always_assert(e)            assert_impl(e, assert_fail_impl(e, ""))
#define always_assert_no_log(e)    assert_impl(e, \
                                        assert_fail_impl_no_log(e, ""))
#define always_assert_log(e, l)     assert_impl(e, assert_fail_impl(e, l()))
#define always_assert_flog(e, ...)  assert_impl(e, assert_fail_impl(e,        \
                                        ::folly::format(__VA_ARGS__).str()))

#undef assert

#ifndef NDEBUG
#define assert(e) always_assert(e)
#define assertx(e) always_assert(e)
#define assert_no_log(e) always_assert_no_log(e)
#define assert_log(e, l) always_assert_log(e, l)
#define assert_flog(e, ...) always_assert_flog(e, __VA_ARGS__)
#else
#define assert(e) static_cast<void>(0)
#define assertx(e) static_cast<void>(0)
#define assert_no_log(e) static_cast<void>(0)
#define assert_log(e, l) static_cast<void>(0)
#define assert_flog(e, ...) static_cast<void>(0)
#endif

const bool do_assert =
#ifdef NDEBUG
  false
#else
  true
#endif
  ;

///////////////////////////////////////////////////////////////////////////////

}

#endif
