/** @file

    $Id$

    $Date$
    $Author$

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

*/
#include <mrw/stacktrace.hpp>
#include <mrw/exception.hpp>
#include <csignal>
#include <exception>
#include <log4cxx/logger.h>


// support for log4cxx 0.9.7 and for new CVS version
#ifndef LOG4CXX_LOCATION
#  define MRW_LOG4CXX_LOCATION __FILE__, __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(__FILE__, __FUNCTION__, __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 {

  /** @addtogroup AutoTrace

      @section trclog4cxx Trace using the log4cxx Library

      If you link to the library @c libmrwexclog4cxx using a linker
      option such as: @c -lmrwexclog4cxx, then an unexpected and a
      terminate handler are registered, that trace a fatal error using
      the log4cxx library. Also for all know signals there's a signal
      handler to trace the stack before termination (except in case of
      a @c SIGTRAP, wehere the program is not terminated). You don't
      need to change a single line in your code!

      The log4cxx library is located at:
       - http://logging.apache.org/log4cxx

      @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.
  */
  //@{

  /** @brief unexpected handler, that traces using log4cxx

      The unexpected handler is installed automatically when you link
      to @c -lmrwexclog4cxx. The implementation of this unexpected
      handler is as follows:

      @code
  void unexpected_log4cxx() {
    log4cxx::LoggerPtr logger(log4cxx::Logger::getLogger("mrw.stacktrace"));
    logger->fatal("Unexpected Exception", __FILE__, __LINE__);
    StackTrace::createSymtable();
    std::string st((std::string)StackTrace());
    try {
      throw;
    } catch (const mrw::exception& x) {
      logger->fatal(std::string("Reason:\n")+x.what()
                    +"\nStack:+\n"+x.stacktrace());
    } catch (const std::exception& x) {
      logger->fatal(std::string("Reason:\n")+x.what()
                    +"\nStack:\n"+st);
    } catch (...) {
      logger->fatal(std::string("Reason: **** not available ****")
                   +"\nStack:\n"+st);
    }
    throw std::bad_exception();
  }
      @endcode

  */
  void unexpected_log4cxx() {
    static const std::string name("mrw.stacktrace");
    log4cxx::LoggerPtr logger
      (log4cxx::Logger::getLogger(log4cxx::String(name.begin(), name.end())));
    static const std::string txt("Unexpected Exception");
    logger->fatal(log4cxx::String(txt.begin(), txt.end()), MRW_LOG4CXX_LOCATION);
    StackTrace::createSymtable();
    std::string st((std::string)StackTrace());
    try {
      throw;
    } catch (const mrw::exception& x) {
      std::string txt(std::string("Reason:\n")+x.what()
                      +"\nStack:\n"+x.stacktrace());
      logger->fatal(log4cxx::String(txt.begin(), txt.end()));
    } catch (const std::exception& x) {
      std::string txt(std::string("Reason:\n")+x.what()
                      +"\nStack:\n"+st);
      logger->fatal(log4cxx::String(txt.begin(), txt.end()));
    } catch (...) {
      std::string txt(std::string("Reason: **** not available ****")
                      +"\nStack:\n"+st);
      logger->fatal(log4cxx::String(txt.begin(), txt.end()));
    }
    throw std::bad_exception();
  }

  /** @brief terminate handler, that traces using log4cxx

      The terminate handler is installed automatically when you link
      to @c -lmrwexclog4cxx. The implementation of this terminate
      handler is as follows:

      @code
  void terminate_log4cxx() {
    log4cxx::LoggerPtr logger(log4cxx::Logger::getLogger("mrw.stacktrace"));
    logger->fatal("Uncaught Exception", __FILE__, __LINE__);
    StackTrace::createSymtable();
    std::string st((std::string)StackTrace());
    try {
      throw;
      exit(0);
    } catch (const mrw::exception& x) {
      logger->fatal(std::string("Reason:\n")+x.what()
                    +"\nStack:+\n"+x.stacktrace());
    } catch (const std::exception& x) {
      logger->fatal(std::string("Reason:\n")+x.what()
                    +"\nStack:\n"+st);
    } catch (...) {
      logger->fatal(std::string("Reason: **** not available ****")
                    +"\nStack:\n"+st);
    }
    exit(1);
  }
      @endcode

  */
  void terminate_log4cxx() {
    static const std::string name("mrw.stacktrace");
    log4cxx::LoggerPtr logger
      (log4cxx::Logger::getLogger(log4cxx::String(name.begin(), name.end())));
    static const std::string txt("Uncaught Exception");
    logger->fatal(log4cxx::String(txt.begin(), txt.end()), MRW_LOG4CXX_LOCATION);
    StackTrace::createSymtable();
    std::string st((std::string)StackTrace());
    try {
      throw;
    } catch (const mrw::exception& x) {
      std::string txt(std::string("Reason:\n")+x.what()
                      +"\nStack:\n"+x.stacktrace());
      logger->fatal(log4cxx::String(txt.begin(), txt.end()));
    } catch (const std::exception& x) {
      std::string txt(std::string("Reason:\n")+x.what()
                      +"\nStack:\n"+st);
      logger->fatal(log4cxx::String(txt.begin(), txt.end()));
    } catch (...) {
      std::string txt(std::string("Reason: **** not available ****")
                      +"\nStack:\n"+st);
      logger->fatal(log4cxx::String(txt.begin(), txt.end()));
    }
    exit(1);
  }
  
  /** @brief segmentation-fault handler, that traces using log4cxx

      The segmentation-fault handler is installed automatically when you link
      to @c -lmrwexclog4cxx. The implementation of this handler is as follows:

      @code
  void signal_log4cxx(int sig) {
    std::string txt;
    bool abort(true);
    switch sig {
      case SIGFPE: txt="SIGFPE: Arithmetic error."; break;
      case SIGILL: txt="SIGILL: Illegal instruction."; break;
      case SIGSEGV: txt="SIGSEGV: Segmentation fault"
        " (invalid access to valid memory)."; break;
      case SIGBUS: txt="SIGBUS: Invalid pointer dereference"
        " (access to an invalid memory address)."; break;
      case SIGABRT: txt="SIGABRT: Funktion 'abort' was called."; break;
      case SIGIOT: txt="SIGIOT: PDP-11 'iot' instruction"; break;
      case SIGTRAP: txt="SIGTRAP: Breakpoint instruction."; abort=false; break;
      case SIGEMT: txt="SIGEMT: Emulator trap."; break;
      case SIGSYS: txt="SIGSYS: Bad system call."; break;
      default: txt="Unknown Signal Nr. "+sig; break;
    }
    StackTrace::createSymtable();
    std::string st((std::string)StackTrace());
    log4cxx::LoggerPtr logger(log4cxx::Logger::getLogger("mrw.stacktrace"));
    logger->fatal("Aborted by signal: "+txt+"\nStack:\n"+st);
    if (abort) exit(1);
  }
      @endcode

  */
  void signal_log4cxx(int sig) {
    std::string txt;
    bool abort(true);
    switch (sig) {
      case SIGFPE: txt="SIGFPE: Arithmetic error."; break;
      case SIGILL: txt="SIGILL: Illegal instruction."; break;
      case SIGSEGV: txt="SIGSEGV: Segmentation fault"
        " (invalid access to valid memory)."; break;
      case SIGBUS: txt="SIGBUS: Invalid pointer dereference"
        " (access to an invalid memory address)."; break;
      case SIGABRT: txt="SIGABRT: Funktion 'abort' was called."; break;
      case SIGTRAP: txt="SIGTRAP: Breakpoint instruction."; abort=false; break;
      case SIGSYS: txt="SIGSYS: Bad system call."; break;
      default: txt="Unknown Signal Nr. "+sig; break;
    }
    StackTrace::createSymtable();
    std::string st((std::string)StackTrace());
    static const std::string name("mrw.stacktrace");
    log4cxx::LoggerPtr logger
      (log4cxx::Logger::getLogger(log4cxx::String(name.begin(), name.end())));
    std::string errtxt("Aborted by signal: "+txt+"\nStack:\n"+st);
    logger->fatal(log4cxx::String(errtxt.begin(), errtxt.end()));
    if (abort) exit(1);
  }

  class AutoStackTrace {
    public:
    AutoStackTrace() {
      std::set_unexpected(&mrw::unexpected_log4cxx);
      std::set_terminate(&mrw::terminate_log4cxx);
      std::signal(SIGFPE, &mrw::signal_log4cxx);
      std::signal(SIGILL, &mrw::signal_log4cxx);
      std::signal(SIGSEGV, &mrw::signal_log4cxx);
      std::signal(SIGBUS, &mrw::signal_log4cxx);
      std::signal(SIGTRAP, &mrw::signal_log4cxx);
      std::signal(SIGSYS, &mrw::signal_log4cxx);
    }
  };

  //@}

  // initialize stack traces (load symbols)
  static AutoStackTrace _autoStackTrace;

}
