/** @file

    $Id$

    $Date$
    $Author$

    @copy &copy; Marc W&auml;ckerlin
    @license LGPL, see file <a href="license.html">COPYING</a>

         1         2         3         4         5         6         7         8
    5678901234567890123456789012345678901234567890123456789012345678901234567890
*/

#ifndef __GNUG__
#error GNU C++ Compiler is required for automatical function trace
#endif

#include <mrw/string.hxx>
#include <sstream>
#include <iomanip>

#include <mrw/stacktrace.hxx>
#include <log4cxx/logger.h>

#if (__GNUC__==3 && __GNUC_MINOR__<4 || __GNUC__<3) \
  && defined(_REENTRANT) && !defined(_MT)
#define _MT
#endif

// these are special built in functions of GNU Compiler Collection
extern "C" void __cyg_profile_func_enter (void *, void *) __attribute__((no_instrument_function));
extern "C" void __cyg_profile_func_exit (void *, void *) __attribute__((no_instrument_function));


// support for log4cxx 0.9.7 and for new CVS version
#ifndef LOG4CXX_LOCATION
#  define MRW_LOG4CXX_LOCATION pos.file.c_str(), pos.line
#define MRW_LEVEL_DEBUG ::log4cxx::Level::DEBUG
#define MRW_LEVEL_INFO ::log4cxx::Level::INFO
#define MRW_LEVEL_WARN ::log4cxx::Level::WARN
#define MRW_LEVEL_ERROR ::log4cxx::Level::ERROR
#define MRW_LEVEL_FATAL ::log4cxx::Level::FATAL
#else
#  define LOG4CXX_CVS
#  define MRW_LOG4CXX_LOCATION \
  ::log4cxx::spi::LocationInfo(pos.file.c_str(), pos.function.c_str(), pos.line)
#define MRW_LEVEL_DEBUG ::log4cxx::Level::getDebug()
#define MRW_LEVEL_INFO ::log4cxx::Level::getInfo()
#define MRW_LEVEL_WARN ::log4cxx::Level::getWarn()
#define MRW_LEVEL_ERROR ::log4cxx::Level::getError()
#define MRW_LEVEL_FATAL ::log4cxx::Level::getFatal()
#endif

namespace mrw {
  
  struct ThreadInfo {
      int level;
      bool recurse;
  };

#ifdef _MT
  static __thread ThreadInfo info = {0, false};
#else
  static ThreadInfo info = {0, false};
#endif

  static bool mainPassed(false);

  class Lock {
    public:
      Lock() __attribute__((no_instrument_function));
      ~Lock() __attribute__((no_instrument_function));
  };
  // workaround doxygen problem:
  //  - the C++ compiler compiles the following code
  //  - doxygen ignores it
#ifndef LET_DOXYGEN_IGNORE_THIS // doxygen can't match with __attribute__ above
  Lock::Lock() {
    info.recurse = true;
  }
  Lock::~Lock() {
    info.recurse = false;
  }
#endif
}

extern "C" int main(int, char**);

/** @addtogroup FunctionTrace

    @note There is also a fully automagic function trace, similar to
          @ref AutoTrace. It is described in @ref AutoFunctionTrace
          and requires the GNU Compiler Collection gcc, because it
          makes use of a proprietary compiler feature. */
//@{

/** @defgroup AutoFunctionTrace Automatic Function Trace for GNU g++

    If you compile your program with GNU g++&nbsp;/ gcc, and you want
    to add function tracing without changing a single line of your
    code, here's the solution:

    If you link to the library @c libmrwautofunctiontracelog4cxx using
    a linker option such as: @c -lmrwautofunctiontracelog4cxx and you
    must enable the GNU Compiler Collection specific function trace
    feature with compile and link option @c -finstrument-functions
    then you get an automatical function trace, that traces to level
    @c DEBUG using the log4cxx library. You don't need to change a single
    line in your code!

    This feature depends on:
     - the GNU C++ compiler gcc
     - @ref mrw::StackTrace
     - log4cxx http://logging.apache.org/log4cxx
     - optional: boost for thread support http://boost.org

    The logger hiararchy name is: @c
    "mrw.fn.<full-method-name>" where @c
    <full-method-name> is the full name of the method, including
    namespace and class name, seperated not by double colon @c :: but
    by single dots @c . as it is common use in log4cxx. This way, you
    can enable or disable the function trace per namespace, per class
    or even per method.

    The function trace does not start, before the program @c main is
    reached! Only the calls within the @c main are traced, but not during
    the initialisation and destruction of static members. This is
    necessary, because otherwise access the incompletely initialized
    MRW-C++ library itself may cause a crash.

    A good default configuration is given in @ref AutoInitLog4cxx.
    Depending on your log4cxx configuration, a function trace may look
    e.g. like this (very simple configuration with very few additional
    information):

    @verbatim
mrw.fn.anotherFunction                \ anotherFunction()
mrw.fn.HalloWelt.fn1                   \ HalloWelt::fn1()
mrw.fn.HalloWelt.fn                     \ HalloWelt::fn()
mrw.fn.HalloWelt.A.method                \ HalloWelt::A::method()
mrw.fn.HalloWelt.A.method                / HalloWelt::A::method()
mrw.fn.HalloWelt.A.method                \ HalloWelt::A::method()
mrw.fn.HalloWelt.A.method                / HalloWelt::A::method()
mrw.fn.HalloWelt.fn                     / HalloWelt::fn()
mrw.fn.HalloWelt.fn                     \ HalloWelt::fn()
mrw.fn.HalloWelt.A.method                \ HalloWelt::A::method()
mrw.fn.HalloWelt.A.method                / HalloWelt::A::method()
mrw.fn.HalloWelt.A.method                \ HalloWelt::A::method()
mrw.fn.HalloWelt.A.method                / HalloWelt::A::method()
mrw.fn.HalloWelt.fn                     / HalloWelt::fn()
mrw.fn.HalloWelt.fn1                   / HalloWelt::fn1()
mrw.fn.anotherFunction                / anotherFunction()
    @endverbatim

    @note The configurator is not installed automatically. If you
          want to trace e.g. on the console, you have to call @c
          log4cxx::BasicConfigurator::configure(); as first statement
          in your @c main().

    @see @ref AutoInitLog4cxx if you also want to automatically
         configure @c log4cxx.

    @section AutoFunctionTraceThread Multithreading

    Multithreading is also supported, then you need to link to the
    thread safe compilat of the library @c
    libmrwautofunctiontracelog4cxx-mt with option @c
    -libmrwautofunctiontracelog4cxx-mt and you need the option @c
    -pthread in addition to @c -finstrument-functions which is still
    required.

    Thread support requires the boost thread library. See
    http://boost.org for details.
    */
//@{

extern "C" void __cyg_profile_func_enter(void *this_fn, void*) {
  if (!mrw::mainPassed)
    if (this_fn == (void*)&::main) // not ANSI C++ conform...
      mrw::mainPassed=true;
    else
      return;
  try {
    if (mrw::info.recurse) return;
    mrw::Lock lock;
    {
      static bool init(mrw::StackTrace::createSymtable());
      if (!init) return;
      const mrw::StackTrace::CodePos& pos(mrw::StackTrace::translate(this_fn));
      std::string hierarchy(pos.function);
      for (std::string::size_type p(0);
           (p=hierarchy.find("::", p))!=std::string::npos;
           hierarchy.replace(p, 2, "."));
      hierarchy.erase(hierarchy.rfind('('));
      std::string::size_type p
        (hierarchy.rfind(' ', std::min(hierarchy.find('<'),
                                       hierarchy.find("operator"))));
      if (p!=std::string::npos) hierarchy.erase(0, p+1);
//       hierarchy.erase
//         (hierarchy.find_first_not_of
//          ("qwertzuiopasdfghjklyxcvbnmQWERTZUIOPASDFGHJKLYXCVBNM1234567890_."));
      if (hierarchy=="") hierarchy = "global";
      std::string name("mrw.fn."+hierarchy);
      log4cxx::Logger* logger
        (log4cxx::Logger::getLogger(log4cxx::LogString(name.begin(), name.end())));
      if (logger->isDebugEnabled()) {
        std::basic_ostringstream<log4cxx::LogString::value_type> ss;
        ss<<std::setw(2+mrw::info.level++)
          <<std::setfill(log4cxx::LogString::value_type(' '))
          <<"\\ "<<log4cxx::LogString(pos.function.begin(), pos.function.end());
        logger->forcedLog(MRW_LEVEL_DEBUG, ss.str(),
                          MRW_LOG4CXX_LOCATION);
      }
    }
  } catch (...) {}
}

extern "C" void __cyg_profile_func_exit(void *this_fn, void*) {
  if (!mrw::mainPassed)
    return;
  else
    if (this_fn == (void*)&::main) { // not ANSI C++ conform...
      mrw::mainPassed=false;
      return;
    }
  try {
    if (mrw::info.recurse || mrw::info.level==0) return;
    mrw::Lock lock;
    {
      const mrw::StackTrace::CodePos& pos(mrw::StackTrace::translate(this_fn));
      std::string hierarchy(pos.function);
      for (std::string::size_type p(0);
           (p=hierarchy.find("::", p))!=std::string::npos;
           hierarchy.replace(p, 2, "."));
      hierarchy.erase(hierarchy.rfind('('));
      std::string::size_type p
        (hierarchy.rfind(' ', std::min(hierarchy.find('<'),
                                       hierarchy.find("operator"))));
      if (p!=std::string::npos) hierarchy.erase(0, p+1);
//       hierarchy.erase
//         (hierarchy.find_first_not_of
//          ("qwertzuiopasdfghjklyxcvbnmQWERTZUIOPASDFGHJKLYXCVBNM1234567890_."));
      if (hierarchy=="") hierarchy = "global";
      std::string name("mrw.fn."+hierarchy);
      log4cxx::Logger* logger
        (log4cxx::Logger::getLogger(log4cxx::LogString(name.begin(), name.end())));
      if (logger->isDebugEnabled()) {
        std::basic_ostringstream<log4cxx::LogString::value_type> ss;
        ss<<std::setw(2+--mrw::info.level)
          <<std::setfill(log4cxx::LogString::value_type(' '))
          <<"/ "<<log4cxx::LogString(pos.function.begin(), pos.function.end());
        logger->forcedLog(MRW_LEVEL_DEBUG, ss.str(),
                          MRW_LOG4CXX_LOCATION);
      }
    }
  } catch (...) {}
}

//@}

//@}
