/**
 * PANDA 3D SOFTWARE
 * Copyright (c) Carnegie Mellon University.  All rights reserved.
 *
 * All use of this software is subject to the terms of the revised BSD
 * license.  You should have received a copy of this license along
 * with this source code in a file named "LICENSE."
 *
 * @file pnotify.h
 * @author drose
 * @date 2000-02-28
 */

#ifndef NOTIFY_H
#define NOTIFY_H

#include "dtoolbase.h"
#include "notifySeverity.h"
#include <map>

#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif

class NotifyCategory;
class AndroidLogStream;
class EmscriptenLogStream;

/**
 * An object that handles general error reporting to the user.  It contains a
 * pointer to an ostream, initially cerr, which can be reset at will to point
 * to different output devices, according to the needs of the application.
 * All output generated within Panda should vector through the Notify ostream.
 *
 * This also includes a collection of Categories and Severities, which may be
 * independently enabled or disabled, so that error messages may be squelched
 * or respected according to the wishes of the user.
 */
class EXPCL_DTOOL_PRC Notify {
PUBLISHED:
  Notify();
  ~Notify();

#if defined(CPPPARSER) && defined(HAVE_PYTHON)
  PY_EXTEND(void set_ostream_ptr(PyObject *ostream_ptr, bool delete_later));
#else // CPPPARSER && HAVE_PYTHON
  void set_ostream_ptr(std::ostream *ostream_ptr, bool delete_later);
#endif // CPPPARSER && HAVE_PYTHON
  std::ostream *get_ostream_ptr() const;

  typedef bool AssertHandler(const char *expression, int line,
                             const char *source_file);

  void set_assert_handler(AssertHandler *assert_handler);
  void clear_assert_handler();
  bool has_assert_handler() const;
  AssertHandler *get_assert_handler() const;

  INLINE bool has_assert_failed() const;
  INLINE const std::string &get_assert_error_message() const;
  INLINE void clear_assert_failed();

  NotifyCategory *get_top_category();
  NotifyCategory *get_category(const std::string &basename,
                               NotifyCategory *parent_category);
  NotifyCategory *get_category(const std::string &basename,
                               const std::string &parent_fullname);
  NotifyCategory *get_category(const std::string &fullname);

  static std::ostream &out(NotifySeverity severity);
  static std::ostream &out();
  static std::ostream &null();
  static void write_string(const std::string &str);
  static Notify *ptr();

public:
  static ios_fmtflags get_literal_flag();

  bool assert_failure(const std::string &expression, int line,
                      const char *source_file);
  bool assert_failure(const char *expression, int line,
                      const char *source_file);

  static NotifySeverity string_severity(const std::string &string);

  static void config_initialized();

private:
  std::ostream *_ostream_ptr;
  bool _owns_ostream_ptr;
  std::ostream *_null_ostream_ptr;

  AssertHandler *_assert_handler;
  bool _assert_failed;
  std::string _assert_error_message;

  // This shouldn't be a pmap, since it might be invoked before we initialize
  // the global malloc pointers.
  typedef std::map<std::string, NotifyCategory *> Categories;
  Categories _categories;

#if defined(ANDROID)
  AndroidLogStream *_log_streams[NS_fatal + 1];
#elif defined(__EMSCRIPTEN__)
  EmscriptenLogStream *_log_streams[NS_fatal + 1];
#endif

  static Notify *_global_ptr;
};


// This defines the symbol nout in the same way that cerr and cout are
// defined, for compactness of C++ code that uses Notify in its simplest form.
// Maybe it's a good idea to define this symbol and maybe it's not, but it
// does seem that "nout" isn't likely to collide with any other name.

#define nout (Notify::out())

// Here are a couple of assert-type functions.  These are designed to avoid
// simply dumping core, since that's quite troublesome when the programmer is
// working in a higher-level environment that is calling into the C++ layer.

// nassertr() is intended to be used in functions that have return values; it
// returns the indicated value if the assertion fails.

// nassertv() is intended to be used in functions that do not have return
// values; it simply returns if the assertion fails.

// nassertd() does not return from the function, but instead executes the
// following block of code (like an if statement) if the assertion fails.

// nassertr_always() and nassertv_always() are like nassertr() and nassertv(),
// except that they will not get completely compiled out if NDEBUG is set.
// Instead, they will quietly return from the function.  These macros are
// appropriate, for instance, for sanity checking user input parameters, where
// optimal performance is not paramount.

// nassert_static() is a compile-time assertion.  It should only be used with
// constant expressions and compilation will fail if the assertion is not
// true.

#ifdef __GNUC__
// Tell the optimizer to optimize for the case where the condition is true.
#define _nassert_check(condition) (__builtin_expect(!(condition), 0))
#else
#define _nassert_check(condition) (!(condition))
#endif

#ifdef NDEBUG

#define nassertr(condition, return_value)
#define nassertv(condition)
#define nassertd(condition) if (false)
// We trust the compiler to optimize the above out.

#define nassertr_always(condition, return_value) \
  { \
    if (_nassert_check(condition)) { \
      return return_value; \
    } \
  }

#define nassertv_always(condition) \
  { \
    if (_nassert_check(condition)) { \
      return; \
    } \
  }

#define nassert_raise(message) Notify::write_string(message)

#elif defined(__clang_analyzer__)

// We define these so that clang-tidy can generally assume that assertions will
// not occur, since we don't really care about things like minor memory leaks
// on assertion failure.

#define nassertr(condition, return_value) \
  { \
    if (!(condition)) { \
      abort(); \
      return return_value; \
    } \
  }

#define nassertv(condition) \
  { \
    if (!(condition)) { \
      abort(); \
      return; \
    } \
  }

#define nassertd(condition) \
  if (!(condition))

#define nassertr_always(condition, return_value) nassertr(condition, return_value)
#define nassertv_always(condition) nassertv(condition)

#define nassert_raise(message) abort()

#else   // NDEBUG

#define nassertr(condition, return_value) \
  { \
    if (_nassert_check(condition)) { \
      if (Notify::ptr()->assert_failure(#condition, __LINE__, __FILE__)) { \
        return return_value; \
      } \
    } \
  }

#define nassertv(condition) \
  { \
    if (_nassert_check(condition)) { \
      if (Notify::ptr()->assert_failure(#condition, __LINE__, __FILE__)) { \
        return; \
      } \
    } \
  }

#define nassertd(condition) \
  if (_nassert_check(condition) && \
      Notify::ptr()->assert_failure(#condition, __LINE__, __FILE__))

#define nassertr_always(condition, return_value) nassertr(condition, return_value)
#define nassertv_always(condition) nassertv(condition)

#define nassert_raise(message) Notify::ptr()->assert_failure(message, __LINE__, __FILE__)

#endif  // NDEBUG

#if __cplusplus >= 201103
#define __nassert_static(condition, line, file) static_assert((condition), #condition " at line " #line " of " file)
#define _nassert_static(condition, line, file) __nassert_static(condition, line, file)
#define nassert_static(condition) _nassert_static(condition, __LINE__, __FILE__)
#else
#define __nassert_static(condition, suffix) typedef char nassert_static_ ## suffix [(condition) ? 1 : -1];
#define _nassert_static(condition, suffix) __nassert_static(condition, suffix)
#define nassert_static(condition) _nassert_static(condition, __COUNTER__)
#endif

#include "pnotify.I"

#endif
