// file      : bbot/diagnostics -*- C++ -*-
// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd
// license   : TBC; see accompanying LICENSE file

#ifndef BBOT_DIAGNOSTICS
#define BBOT_DIAGNOSTICS

#include <butl/diagnostics>

#include <bbot/types> // Note: not <bbot/utility>.

namespace bbot
{
  using butl::diag_record;

  // Throw this exception to terminate the process. The handler should
  // assume that the diagnostics has already been issued.
  //
  class failed: public std::exception {};

  // Verbosity level. Update documentation for --verbose if changing.
  //
  // 0 - disabled
  // 1 - high-level information messages
  // 2 - essential underlying commands that are being executed
  // 3 - all underlying commands that are being executed
  // 4 - information that could be helpful to the user
  // 5 - information that could be helpful to the developer
  // 6 - even more detailed information
  //
  // While uint8 is more than enough, use uint16 for the ease of printing.
  //
  extern uint16_t verb;

  template <typename F> inline void l1 (const F& f) {if (verb >= 1) f ();}
  template <typename F> inline void l2 (const F& f) {if (verb >= 2) f ();}
  template <typename F> inline void l3 (const F& f) {if (verb >= 3) f ();}
  template <typename F> inline void l4 (const F& f) {if (verb >= 4) f ();}
  template <typename F> inline void l5 (const F& f) {if (verb >= 5) f ();}
  template <typename F> inline void l6 (const F& f) {if (verb >= 6) f ();}

  // Diagnostic facility, base infrastructure.
  //
  using butl::diag_stream;
  using butl::diag_epilogue;

  // Diagnostic facility, project specifics.
  //
  struct simple_prologue_base
  {
    explicit
    simple_prologue_base (const char* type, const char* name)
        : type_ (type), name_ (name) {}

    void
    operator() (const diag_record& r) const;

  private:
    const char* type_;
    const char* name_;
  };

  struct basic_mark_base
  {
    using simple_prologue = butl::diag_prologue<simple_prologue_base>;

    explicit
    basic_mark_base (const char* type,
                     const char* indent = "\n  ",
                     const char* name = nullptr,
                     const void* data = nullptr,
                     diag_epilogue* epilogue = nullptr)
        : type_ (type), name_ (name), data_ (data),
          indent_ (indent), epilogue_ (epilogue) {}

    simple_prologue
    operator() () const
    {
      return simple_prologue (indent_, epilogue_, type_, name_);
    }

  public:
    const char* type_;
    const char* name_;
    const void* data_;

    const char* indent_;
    diag_epilogue* const epilogue_;
  };
  using basic_mark = butl::diag_mark<basic_mark_base>;

  extern basic_mark error;
  extern basic_mark warn;
  extern basic_mark info;
  extern basic_mark text;

  // trace
  //
  extern const char* trace_type;
  extern const char* trace_indent;

  struct trace_mark_base: basic_mark_base
  {
    explicit
    trace_mark_base (const char* name, const void* data = nullptr);
  };
  using trace_mark = butl::diag_mark<trace_mark_base>;

  // using tracer = trace_mark;
  class tracer: public trace_mark
  {
  public: using trace_mark::trace_mark;
  };

  // fail
  //
  struct fail_mark_base: basic_mark_base
  {
    explicit
    fail_mark_base (const char* type,
                    const char* indent = "\n  ",
                    const void* data = nullptr)
        : basic_mark_base (type,
                           indent,
                           nullptr,
                           data,
                           [](const diag_record& r)
                           {
                             r.flush ();
                             throw failed ();
                           }) {}
  };

  using fail_mark = butl::diag_mark<fail_mark_base>;

  struct fail_end_base
  {
    [[noreturn]] void
    operator() (const diag_record& r) const
    {
      // If we just throw then the record's destructor will see an active
      // exception and will not flush the record.
      //
      r.flush ();
      throw failed ();
    }
  };
  using fail_end = butl::diag_noreturn_end<fail_end_base>;

  extern fail_mark fail;
  extern const fail_end endf;

  // Map to systemd severity prefixes (see sd-daemon(3) for details). Note
  // that here we assume we will never have location (like file name which
  // would end up being before the prefix).
  //
  // If with_critical is true, then distinguish between fail (critical error,
  // daemon terminates) and error (non-fatal error, daemon continues to run).
  // Note that this means we should be careful not to use fail to report
  // normal errors and vice-versa.
  //
  void
  systemd_diagnostics (bool with_critical);
}

#endif // BBOT_DIAGNOSTICS