// file      : build/diagnostics -*- C++ -*-
// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
// license   : MIT; see accompanying LICENSE file

#ifndef BUILD_DIAGNOSTICS
#define BUILD_DIAGNOSTICS

#include <cstddef> // size_t
#include <cstdint>
#include <utility>
#include <cassert>
#include <sstream>
#include <ostream>
#include <exception>
#include <type_traits>

#include <butl/path>

#include <build/types>
#include <build/path-io>

namespace build
{
  struct diag_record;

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

  // Flag that indicates whether paths should be inserted relative
  // into this stream.
  //
  extern const int relative_index;

  inline bool
  relative (std::ostream& os) {return os.iword (relative_index);}

  inline void
  relative (std::ostream& os, bool v) {os.iword (relative_index) = v ? 1 : 0;}

  // Print process commmand line. If the number of elements is specified
  // (or the second version is used), then it will print the piped multi-
  // process command line, if present. In this case, the expected format
  // is as follows:
  //
  // name1 arg arg ... nullptr
  // name2 arg arg ... nullptr
  // ...
  // nameN arg arg ... nullptr nullptr
  //
  void
  print_process (diag_record&, const char* const* args, std::size_t n = 0);

  void
  print_process (const char* const* args, std::size_t n = 0);

  inline void
  print_process (diag_record& dr, const cstrings& args)
  {
    print_process (dr, args.data (), args.size ());
  }

  inline void
  print_process (const cstrings& args)
  {
    print_process (args.data (), args.size ());
  }

  // Verbosity level.
  //
  // 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 helpful to the user (e.g., why a rule did not match)
  // 5 - information helpful to the developer
  // 6 - even more detailed information
  //
  // While uint8 is more than enough, use uint16 for the ease of printing.
  //
  extern std::uint16_t verb;

  template <typename F> inline void level1 (const F& f) {if (verb >= 1) f ();}
  template <typename F> inline void level2 (const F& f) {if (verb >= 2) f ();}
  template <typename F> inline void level3 (const F& f) {if (verb >= 3) f ();}
  template <typename F> inline void level4 (const F& f) {if (verb >= 4) f ();}
  template <typename F> inline void level5 (const F& f) {if (verb >= 5) f ();}
  template <typename F> inline void level6 (const F& f) {if (verb >= 6) f ();}

  // Diagnostic facility, base infrastructure (potentially reusable).
  //
  extern std::ostream* diag_stream;

  template <typename> struct diag_prologue;
  template <typename> struct diag_mark;

  typedef void (*diag_epilogue) (const diag_record&);

  struct diag_record
  {
    template <typename T>
    friend const diag_record&
    operator<< (const diag_record& r, const T& x)
    {
      r.os_ << x;
      return r;
    }

    diag_record (): empty_ (true), epilogue_ (nullptr) {}

    template <typename B>
    explicit
    diag_record (const diag_prologue<B>& p)
        : empty_ (true), epilogue_ (nullptr) { *this << p;}

    template <typename B>
    explicit
    diag_record (const diag_mark<B>& m)
        : empty_ (true), epilogue_ (nullptr) { *this << m;}

    ~diag_record () noexcept(false);

    void
    append (diag_epilogue e) const
    {
      if (e != nullptr)
      {
        assert (epilogue_ == nullptr); // No multiple epilogues support.
        epilogue_ = e;
      }

      if (empty_)
        empty_ = false;
      else
        os_ << "\n  ";
    }

    // Move constructible-only type.
    //
    /*
    @@ libstdc++ doesn't yet have the ostringstream move support.

    diag_record (diag_record&& r)
        : os_ (std::move (r.os_))
    {
      empty_ = r.empty_;
      r.empty_ = true;

      epilogue_ = r.epilogue_;
      r.epilogue_ = nullptr;
    }
    */

    diag_record (diag_record&& r)
    {
      empty_ = r.empty_;
      epilogue_ = r.epilogue_;

      if (!empty_)
      {
        assert (false); //@@ Relative flag will not be transferred.
        os_ << r.os_.str ();

        r.empty_ = true;
        r.epilogue_ = nullptr;
      }
    }

    diag_record& operator= (diag_record&&) = delete;

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

  public:
    mutable std::ostringstream os_;

  private:
    mutable bool empty_;
    mutable diag_epilogue epilogue_;
  };

  template <typename B>
  struct diag_prologue: B
  {
    diag_prologue (diag_epilogue e = nullptr): B (), epilogue_ (e) {}

    template <typename... A>
    diag_prologue (A&&... a)
        : B (std::forward<A> (a)...), epilogue_ (nullptr) {}

    template <typename... A>
    diag_prologue (diag_epilogue e, A&&... a)
        : B (std::forward<A> (a)...), epilogue_ (e) {}

    template <typename T>
    diag_record
    operator<< (const T& x) const
    {
      diag_record r;
      r.append (epilogue_);
      B::operator() (r);
      r << x;
      return r;
    }

    friend const diag_record&
    operator<< (const diag_record& r, const diag_prologue& p)
    {
      r.append (p.epilogue_);
      p (r);
      return r;
    }

  private:
    diag_epilogue epilogue_;
  };

  template <typename B>
  struct diag_mark: B
  {
    diag_mark (): B () {}

    template <typename... A>
    diag_mark (A&&... a): B (std::forward<A> (a)...) {}

    template <typename T>
    diag_record
    operator<< (const T& x) const
    {
      return B::operator() () << x;
    }

    friend const diag_record&
    operator<< (const diag_record& r, const diag_mark& m)
    {
      return r << m ();
    }
  };

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

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

  private:
    const char* type_;
    const char* name_;
    const bool relative_;
  };
  typedef diag_prologue<simple_prologue_base> simple_prologue;

  class location
  {
  public:
    location () {}
    location (const char* f, std::uint64_t l, std::uint64_t c)
        : file (f), line (l), column (c) {}

    const char* file;
    std::uint64_t line;
    std::uint64_t column;
  };

  struct location_prologue_base
  {
    location_prologue_base (const char* type,
                            const char* name,
                            const location& l,
                            bool rel)
        : type_ (type), name_ (name), loc_ (l), relative_ (rel) {}

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

  private:
    const char* type_;
    const char* name_;
    const location loc_;
    const bool relative_;
  };
  typedef diag_prologue<location_prologue_base> location_prologue;

  // Here is the absolute/relative path rationale: we want it absolute
  // in the error/warning/info streams to give the user the complete
  // picture. But in the text stream (e.g., command lines), we print
  // relative unless verbosity is greater than 1.
  //
  struct basic_mark_base
  {
    explicit
    basic_mark_base (const char* type,
                     const char* name = nullptr,
                     const void* data = nullptr)
        : type_ (type), name_ (name), data_ (data) {}

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

    location_prologue
    operator() (const location& l) const
    {
      return location_prologue (type_, name_, l, false);
    }

    template <typename L>
    location_prologue
    operator() (const L& l) const
    {
      return location_prologue (type_, name_, get_location (l, data_), false);
    }

  protected:
    const char* type_;
    const char* name_;
    const void* data_;
  };
  typedef diag_mark<basic_mark_base> basic_mark;

  extern const basic_mark error;
  extern const basic_mark warn;
  extern const basic_mark info;

  // text
  //
  struct text_mark_base: basic_mark_base
  {
    text_mark_base (): basic_mark_base (nullptr) {}

    simple_prologue
    operator() () const
    {
      return simple_prologue (type_, name_, verb <= 1);
    }
  };
  typedef diag_mark<text_mark_base> text_mark;

  extern const text_mark text;

  // trace
  //
  struct trace_mark_base: basic_mark_base
  {
    explicit
    trace_mark_base (const char* name, const void* data = nullptr)
        : basic_mark_base ("trace", name, data) {}
  };
  typedef diag_mark<trace_mark_base> trace_mark;

  typedef trace_mark tracer;

  // fail
  //
  template <typename E>
  struct fail_mark_base
  {
    explicit
    fail_mark_base (const void* data = nullptr): data_ (data) {}

    simple_prologue
    operator() () const
    {
      return simple_prologue (&epilogue, "error", nullptr, false);
    }

    location_prologue
    operator() (const location& l) const
    {
      return location_prologue (&epilogue, "error", nullptr, l, false);
    }

    template <typename L>
    location_prologue
    operator() (const L& l) const
    {
      return location_prologue (
        &epilogue, "error", nullptr, get_location (l, data_), false);
    }

    static void
    epilogue (const diag_record&) {throw E ();}

  private:
    const void* data_;
  };

  template <typename E>
  using fail_mark = diag_mark<fail_mark_base<E>>;

  extern const fail_mark<failed> fail;
}

#endif // BUILD_DIAGNOSTICS