// file      : bbot/diagnostics.hxx -*- C++ -*-
// license   : MIT; see accompanying LICENSE file

#ifndef BBOT_DIAGNOSTICS_HXX
#define BBOT_DIAGNOSTICS_HXX

#include <libbutl/diagnostics.hxx>

#include <bbot/types.hxx> // 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, const char* data)
        : type_ (type), name_ (name), data_ (data) {}

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

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

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

    // If data if not NULL, then we print it as (data) after name. For
    // example:
    //
    // trace: main(foo): bar
    //
    explicit
    basic_mark_base (const char* type,
                     const char* indent = "\n  ",
                     const char* name = nullptr,
                     const char* 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_, data_);
    }

  public:
    const char* type_;
    const char* name_;
    const char* 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 char* data = nullptr);
  };
  using trace_mark = butl::diag_mark<trace_mark_base>;

  class tracer: public trace_mark
  {
  public:
    using trace_mark::trace_mark;

    // process_run_callback() command tracer interface.
    //
    void
    operator() (const char* const [], std::size_t) const;
  };

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

    // If hard is false, then we fail as an error (but still throw).
    //
    simple_prologue
    operator() (bool hard = true) const
    {
      if (hard)
        return simple_prologue (indent_, epilogue_, type_, name_, data_);

      simple_prologue r (error ());
      r.epilogue = epilogue_;
      return r;
    }
  };

  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 (see fail hard).
  //
  void
  systemd_diagnostics (bool with_critical);
}

#endif // BBOT_DIAGNOSTICS_HXX
