// 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 <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
{
  using butl::path;
  using butl::dir_path;

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

  // In addition to calling relative(), this function also uses shorter
  // notations such as '~/'.
  //
  std::string
  diag_relative (const path&);

  // As above but also adds trailing '/'. If the path is the same as
  // base, returns "./" if current is true and empty string otherwise.
  //
  std::string
  diag_relative (const dir_path&, bool current = true);

  //
  //
  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;}


  // Action phrases, e.g., "configure update exe{foo}", "updating exe{foo}",
  // and "updating exe{foo} already configured".
  //
  struct action;
  class target;

  std::string
  diag_do (const action&, const target&);

  std::string
  diag_doing (const action&, const target&);

  std::string
  diag_already_done (const action&, const target&);

  // Print process commmand line.
  //
  void
  print_process (const char* const* args);

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

  // Trace verbosity level.
  //
  // 0 - tracing disabled.
  // 1 - command lines to update explicit targets (e.g., .o)
  // 2 - command lines to update implicit targets (e.g., .d)
  // 3 - things didn't work out (e.g., rule did not match)
  // 4 - additional information
  // 5 - more additional 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 ();}

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

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

  struct diag_record;

  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 0.
  //
  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 == 0);
    }
  };
  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