/*---------------------------------------------------------------------\
|                          ____ _   __ __ ___                          |
|                         |__  / \ / / . \ . \                         |
|                           / / \ V /|  _/  _/                         |
|                          / /__ | | | | | |                           |
|                         /_____||_| |_| |_|                           |
|                                                                      |
\---------------------------------------------------------------------*/
/** \file zypp/base/Logger.h
 *
*/
#ifndef ZYPP_BASE_LOGGER_H
#define ZYPP_BASE_LOGGER_H
#include <cstring>
#include <iosfwd>
#include <string>

#include <zypp-core/Globals.h>

///////////////////////////////////////////////////////////////////
namespace zypp::debug {
  // impl in LogControl.cc, but not in ZYPP_NDEBUG; BLOCKTRACE logs MIL
  struct BlockTraceBase
  {
    BlockTraceBase( const BlockTraceBase & ) =delete;
    BlockTraceBase & operator=( const BlockTraceBase & ) =delete;
    BlockTraceBase( const char * file_r, const char * fnc_r, int line_r, std::string msg_r=std::string() )
    : _file { file_r }
    , _fnc  { fnc_r  }
    , _line { line_r }
    , _msg  { std::move(msg_r) }
    {}
  protected:
    static unsigned _depth;
    const char *    _file;
    const char *    _fnc;
    int             _line;
    std::string     _msg;
  };

  // Log code location and block leave as MIL
  struct BlockTrace : private BlockTraceBase
  {
    BlockTrace( const BlockTrace & ) =delete;
    BlockTrace & operator=( const BlockTrace & ) =delete;
    BlockTrace( const char * file_r, const char * fnc_r, int line_r, std::string msg_r=std::string() );
    ~BlockTrace();
  private:
    static unsigned _depth;
  };
#define BLOCKTRACE(M) ::zypp::debug::BlockTrace _BlockTrace( L_BASEFILE, __FUNCTION__, __LINE__, M )
} // namespace zypp::debug

///////////////////////////////////////////////////////////////////
#ifndef ZYPP_NDEBUG
namespace zypp::debug {
  // impl in LogControl.cc, and in ZYPP_NDEBUG (due to OSD)

  // Log code location and block leave as \ref Osd
  // Indent if nested
  struct ZYPP_API TraceLeave : private BlockTraceBase
  {
    TraceLeave( const TraceLeave & ) =delete;
    TraceLeave & operator=( const TraceLeave & ) =delete;
    TraceLeave( const char * file_r, const char * fnc_r, int line_r, std::string msg_r=std::string() );
    ~TraceLeave();
  private:
    static unsigned _depth;
  };
#define TRACE(M) ::zypp::debug::TraceLeave _TraceLeave( __FILE__, __FUNCTION__, __LINE__, M );
#define XTRACE ::zypp::debug::TraceLeave _TraceLeave( __FILE__, __FUNCTION__, __LINE__ );

  // OnScreenDebug messages colored to stderr
  struct ZYPP_API Osd
  {
    Osd( std::ostream &, int = 0 );
    ~Osd();

    template<class Tp>
    Osd & operator<<( Tp && val )
    {
      _strout << std::forward<Tp>(val);
      _strlog << std::forward<Tp>(val);
      return *this;
    }

    Osd & operator<<( std::ostream& (*iomanip)( std::ostream& ) );

  private:
    std::ostream & _strout;
    std::ostream & _strlog;
  };

  Osd & getOSD() ZYPP_API;
#define OSD ::zypp::debug::getOSD()
} // namespace zypp::debug
#endif // ZYPP_NDEBUG

///////////////////////////////////////////////////////////////////

/** \defgroup ZYPP_BASE_LOGGER_MACROS ZYPP_BASE_LOGGER_MACROS
 *  Convenience macros for logging.
 *
 * The macros finally call @ref getStream, providing appropriate arguments,
 * to return the log stream.
 *
 * @code
 * L_DBG("foo") << ....
 * @endcode
 * Logs a debug message for group @a "foo".
 *
 * @code
 * #undef ZYPP_BASE_LOGGER_LOGGROUP
 * #define ZYPP_BASE_LOGGER_LOGGROUP "foo"
 *
 * DBG << ....
 * @endcode
 * Defines group @a "foo" as default for log messages and logs a
 * debug message.
 */
/*@{*/

#ifndef ZYPP_BASE_LOGGER_LOGGROUP
/** Default log group if undefined. */
#define ZYPP_BASE_LOGGER_LOGGROUP "DEFINE_LOGGROUP"
#endif

#define XXX L_XXX( ZYPP_BASE_LOGGER_LOGGROUP )
#define DBG L_DBG( ZYPP_BASE_LOGGER_LOGGROUP )
#define MIL L_MIL( ZYPP_BASE_LOGGER_LOGGROUP )
#define WAR L_WAR( ZYPP_BASE_LOGGER_LOGGROUP )
#define ERR L_ERR( ZYPP_BASE_LOGGER_LOGGROUP )
#define SEC L_SEC( ZYPP_BASE_LOGGER_LOGGROUP )
#define INT L_INT( ZYPP_BASE_LOGGER_LOGGROUP )
#define USR L_USR( ZYPP_BASE_LOGGER_LOGGROUP )

#define L_XXX(GROUP) ZYPP_BASE_LOGGER_LOG( GROUP, zypp::base::logger::E_XXX )
#define L_DBG(GROUP) ZYPP_BASE_LOGGER_LOG( GROUP"++", zypp::base::logger::E_MIL )
#define L_MIL(GROUP) ZYPP_BASE_LOGGER_LOG( GROUP, zypp::base::logger::E_MIL )
#define L_WAR(GROUP) ZYPP_BASE_LOGGER_LOG( GROUP, zypp::base::logger::E_WAR )
#define L_ERR(GROUP) ZYPP_BASE_LOGGER_LOG( GROUP, zypp::base::logger::E_ERR )
#define L_SEC(GROUP) ZYPP_BASE_LOGGER_LOG( GROUP, zypp::base::logger::E_SEC )
#define L_INT(GROUP) ZYPP_BASE_LOGGER_LOG( GROUP, zypp::base::logger::E_INT )
#define L_USR(GROUP) ZYPP_BASE_LOGGER_LOG( GROUP, zypp::base::logger::E_USR )


#define L_ENV_CONSTR_DEFINE_FUNC(ENV) \
    namespace zypp::log { \
      bool has_env_constr_##ENV () \
      { \
          static bool has_##ENV = (::getenv(#ENV) != NULL); \
          return has_##ENV; \
      } \
      const char *empty_or_group_if_##ENV ( const char *group ) \
      { \
        return has_env_constr_##ENV() ? group : nullptr; \
      } \
    }

#define L_ENV_CONSTR_FWD_DECLARE_FUNC(ENV) namespace zypp::log { bool has_env_constr_##ENV (); const char *empty_or_group_if_##ENV ( const char *group ); }
#define L_ENV_CONSTR(ENV,GROUP,LEVEL) ZYPP_BASE_LOGGER_LOG( zypp::log::empty_or_group_if_##ENV( GROUP ), LEVEL )

#define L_BASEFILE ( *__FILE__ == '/' ? strrchr( __FILE__, '/' ) + 1 : __FILE__ )

/** Actual call to @ref getStream. */
#define ZYPP_BASE_LOGGER_LOG(GROUP,LEVEL) \
        zypp::base::logger::getStream( GROUP, LEVEL, L_BASEFILE, __FUNCTION__, __LINE__ )

/*@}*/

///////////////////////////////////////////////////////////////////
namespace zypp
{ /////////////////////////////////////////////////////////////////
  ///////////////////////////////////////////////////////////////////
  namespace base
  { /////////////////////////////////////////////////////////////////

    ///////////////////////////////////////////////////////////////////
    namespace logger
    { /////////////////////////////////////////////////////////////////

      /** Definition of log levels.
       *
       * @see getStream
      */
      enum LogLevel {
        E_XXX = 999, /**< Excessive logging. */
        E_DBG = 0,   /**< Debug or verbose. */
        E_MIL,       /**< Milestone. */
        E_WAR,       /**< Warning. */
        E_ERR,       /**< Error. */
        E_SEC,       /**< Secutrity related. */
        E_INT,       /**< Internal error. */
        E_USR        /**< User log. */
      };

      /** Return a log stream to write on.
       *
       * The returned log stream is determined by @a group_r and
       * @a level_r. The remaining arguments @a file_r, @a func_r
       * and @a line_r are expected to denote the location in the
       * source code that issued the message.
       *
       * @note You won't call @ref getStream directly, but use the
       * @ref ZYPP_BASE_LOGGER_MACROS.
      */
      extern std::ostream & getStream( const char * group_r,
                                       LogLevel     level_r,
                                       const char * file_r,
                                       const char * func_r,
                                       const int    line_r ) ZYPP_API;
      extern bool isExcessive() ZYPP_API;

      /////////////////////////////////////////////////////////////////
    } // namespace logger
    ///////////////////////////////////////////////////////////////////

    /////////////////////////////////////////////////////////////////
  } // namespace base
  ///////////////////////////////////////////////////////////////////
  /////////////////////////////////////////////////////////////////
} // namespace zypp
///////////////////////////////////////////////////////////////////
#endif // ZYPP_BASE_LOGGER_H
