// slviewer -- Second Life Viewer Source Code
//
//! @file debug.cc
//! @brief This file contains the definitions of debug related objects and functions.
//
// Copyright (C) 2008, by
//
// Carlo Wood, Run on IRC <carlo@alinoe.com>
// RSA-1024 0x624ACAD5 1997-01-26                    Sign & Encrypt
// Fingerprint16 = 32 EC A7 B6 AC DB 65 A6  F6 F6 55 DD 1C DC FF 61
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#ifndef USE_PCH
#include "sys.h"                        // Needed for platform-specific code
#endif

#ifdef CWDEBUG

#ifndef USE_PCH
#include <cctype>                       // Needed for std::isprint
#include <iomanip>                      // Needed for setfill and setw
#include <map>
#include <string>
#include <sstream>
#include <fstream>
#include "debug.h"
#ifdef USE_LIBCW
#include <libcw/memleak.h>		// memleak_filter
#endif
#include <sys/types.h>
#include <unistd.h>
#endif // USE_PCH

#define BACKTRACE_AQUIRE_LOCK           libcwd::_private_::mutex_tct<libcwd::_private_::backtrace_instance>::lock()
#define BACKTRACE_RELEASE_LOCK          libcwd::_private_::mutex_tct<libcwd::_private_::backtrace_instance>::unlock()

namespace debug {

#if CWDEBUG_LOCATION
void BackTrace::dump_backtrace(void) const
{
  for (int frame = 0; frame < frames(); ++frame)
  {
    libcwd::location_ct loc((char*)buffer()[frame] + libcwd::builtin_return_address_offset);
    Dout(dc::notice|continued_cf, '#' << std::left << std::setw(3) << frame <<
	std::left << std::setw(16) << buffer()[frame] << ' ' << loc << "\n                  in ");
    char const* mangled_function_name = loc.mangled_function_name();
    if (mangled_function_name != libcwd::unknown_function_c)
    {
      std::string demangled_function_name;
      libcwd::demangle_symbol(mangled_function_name, demangled_function_name);
      Dout(dc::finish, demangled_function_name);
    }
    else
      Dout(dc::finish, mangled_function_name);
  }
}
#endif // CWDEBUG_LOCATION

#if CWDEBUG_ALLOC && CWDEBUG_LOCATION
typedef std::map<BackTrace, int, std::less<BackTrace>, libcwd::_private_::internal_allocator> backtrace_map_t;
backtrace_map_t* backtrace_map;
static int total_calls = 0;
static int number_of_stack_traces = 0;

void my_backtrace_hook(void** buffer, int frames LIBCWD_COMMA_TSD_PARAM)
{
  ++total_calls;

  backtrace_map_t::iterator iter;

  set_alloc_checking_off(__libcwd_tsd);
  {
    BackTrace backtrace(buffer, frames);
    std::pair<backtrace_map_t::iterator, bool> res = backtrace_map->insert(backtrace_map_t::value_type(backtrace, 0));
    if (res.second)
      ++number_of_stack_traces;
    ++res.first->second;
    iter = res.first;
  }
  set_alloc_checking_on(__libcwd_tsd);
#if 0
  // Dump the stack trace.
  iter->first.dump_backtrace();
#endif
}

void start_recording_backtraces(void)
{
  BACKTRACE_AQUIRE_LOCK;
  libcwd::backtrace_hook = my_backtrace_hook;
  BACKTRACE_RELEASE_LOCK;
  //Debug(dc::malloc.on());
  LIBCWD_TSD_DECLARATION;
  set_alloc_checking_off(__libcwd_tsd);
  backtrace_map = new backtrace_map_t;
  set_alloc_checking_on(__libcwd_tsd);
}

struct Compare {
  bool operator()(backtrace_map_t::const_iterator const& iter1, backtrace_map_t::const_iterator const& iter2)
  {
    return iter1->second > iter2->second;
  }
};

void stop_recording_backtraces(void)
{
  //Debug(dc::malloc.off());
  BACKTRACE_AQUIRE_LOCK;
  libcwd::backtrace_hook = NULL;

  if (!backtrace_map)
  {
    Dout(dc::notice, "Not recording; call cwdebug_start() first.");
    return;
  }

  Dout(dc::notice, "Total number of calls: " << total_calls);
  Dout(dc::notice, "Number of different stack traces: " << number_of_stack_traces);
  Dout(dc::notice, "First 10 stack traces:");
  std::list<backtrace_map_t::const_iterator> entries;
  for (backtrace_map_t::const_iterator iter = backtrace_map->begin(); iter != backtrace_map->end(); ++iter)
    entries.push_back(iter);
  entries.sort(Compare());
  int count = 0;
  for (std::list<backtrace_map_t::const_iterator>::iterator iter = entries.begin(); iter != entries.end(); ++iter, ++count)
  {
    Dout(dc::notice, "Used: " << (*iter)->second);
    // Dump the stack trace.
    (*iter)->first.dump_backtrace();
    if (count == 10)
      break;
  }

  // Clear all data.
  LIBCWD_TSD_DECLARATION;
  set_alloc_checking_off(__libcwd_tsd);
  delete backtrace_map;
  set_alloc_checking_on(__libcwd_tsd);
  backtrace_map = NULL;
  total_calls = 0;
  number_of_stack_traces = 0;

  BACKTRACE_RELEASE_LOCK;
}
#endif // CWDEBUG_ALLOC && CWDEBUG_LOCATION

  namespace channels {	// namespace DEBUGCHANNELS
    namespace dc {

#ifndef DOXYGEN
#define DDCN(x) (x)
#endif
      // Add new debug channels here.

      channel_ct viewer DDCN("VIEWER");		//!< This debug channel is used for the normal debugging out of the viewer.
      channel_ct primbackup DDCN("PRIMBACKUP");	//!< This debug channel is used for output related to primbackup.
      channel_ct gtk DDCN("GTK");		//!< This debug channel is used for output related to gtk.
      channel_ct sdl DDCN("SDL");		//!< This debug channel is used for output related to sdl locking.
      channel_ct backtrace DDCN("BACKTRACE");	//!< This debug channel is used for backtraces.
      channel_ct statemachine DDCN("STATEMACHINE");	//!< This debug channel is used for output related to class AIStateMachine.
      channel_ct caps DDCN("CAPS");		//!< This debug channel is used for output related to Capabilities.

    } // namespace dc
  } // namespace DEBUGCHANNELS

  // Anonymous namespace, this map and its initialization functions are private to this file
  // for Thead-safeness reasons.
  namespace {

    /*! @brief The type of rcfile_dc_states.
     * @internal
     */
    typedef std::map<std::string, bool> rcfile_dc_states_type;

    /*! @brief Map containing the default debug channel states used at the start of each new thread.
     * @internal
     *
     * The first thread calls main, which calls debug::init which will initialize this
     * map with all debug channel labels and whether or not they were turned on in the
     * rcfile or not.
     */
    rcfile_dc_states_type rcfile_dc_states;

    /*! @brief Set the default state of debug channel \a dc_label.
     * @internal
     *
     * This function is called once for each debug channel.
     */
    void set_state(char const* dc_label, bool is_on)
    {
      std::pair<rcfile_dc_states_type::iterator, bool> res =
          rcfile_dc_states.insert(rcfile_dc_states_type::value_type(std::string(dc_label), is_on));
      if (!res.second)
        Dout(dc::warning, "Calling set_state() more than once for the same label!");
      return;
    }

    /*! @brief Save debug channel states.
     * @internal
     *
     * One time initialization function of rcfile_dc_state.
     * This must be called from debug::init after reading the rcfile.
     */
    void save_dc_states(void)
    {
      // We may only call this function once: it reflects the states as stored
      // in the rcfile and that won't change.  Therefore it is not needed to
      // lock `rcfile_dc_states', it is only written to by the first thread
      // (once, via main -> init) when there are no other threads yet.
      static bool second_time = false;
      if (second_time)
      {
        Dout(dc::warning, "Calling save_dc_states() more than once!");
	return;
      }
      second_time = true;
      ForAllDebugChannels( set_state(debugChannel.get_label(), debugChannel.is_on()) );
    }

  } // anonymous namespace

  /*! @brief Returns the the original state of a debug channel.
   * @internal
   *
   * For a given \a dc_label, which must be the exact name (<tt>channel_ct::get_label</tt>) of an
   * existing debug channel, this function returns \c true when the corresponding debug channel was
   * <em>on</em> at the startup of the application, directly after reading the libcwd runtime
   * configuration file (.libcwdrc).
   *
   * If the label/channel did not exist at the start of the application, it will return \c false
   * (note that libcwd disallows adding debug channels to modules - so this would probably
   * a bug).
   */
  bool is_on_in_rcfile(char const* dc_label)
  {
    rcfile_dc_states_type::const_iterator iter = rcfile_dc_states.find(std::string(dc_label));
    if (iter == rcfile_dc_states.end())
    {
      Dout(dc::warning, "is_on_in_rcfile(\"" << dc_label << "\"): \"" << dc_label << "\" is an unknown label!");
      return false;
    }
    return iter->second;
  }

#if LIBCWD_THREAD_SAFE
  pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  // I can cause I'm the maintainer of libcwd ;).
  libcwd::_private_::pthread_lock_interface_ct cout_mutex(&mutex);
  libcwd::_private_::lock_interface_base_ct* cout_mutex_ptr(&cout_mutex);
#endif

  /*! @brief Initialize debugging code from new threads.
   *
   * This function needs to be called at the start of each new thread,
   * because a new thread starts in a completely reset state.
   *
   * The function turns on all debug channels that were turned on
   * after reading the rcfile at the start of the application.
   * Furthermore it initializes the debug ostream, its mutex and the
   * margin of the default debug object (Dout).
   */
  void init_thread(void)
  {
    // Turn on all debug channels that are turned on as per rcfile configuration.
    ForAllDebugChannels(
        if (!debugChannel.is_on() && is_on_in_rcfile(debugChannel.get_label()))
	  debugChannel.on();
    );

    // Turn on debug output.
    Debug( libcw_do.on() );
    static std::ofstream debug_file;
    static bool debug_out_opened = false;
    if (!debug_out_opened)
    {
      debug_out_opened = true;		// Thread-safe, cause the main thread calls this first while it's still alone.
      std::ostringstream os;
      os << "debug.out." << getpid();
      debug_file.open(os.str().c_str());
    }
    static debug::TeeStream debug_stream(std::cout, debug_file);
#if LIBCWD_THREAD_SAFE
    Debug( libcw_do.set_ostream(&debug_stream, cout_mutex_ptr) );
#else
    Debug( libcw_do.set_ostream(&debug_stream) );
#endif

    static bool first_thread = true;
    if (!first_thread)			// So far, the application has only one thread.  So don't add a thread id.
    {
      // Set the thread id in the margin.
      char margin[18];
      sprintf(margin, "0x%-14lx ", pthread_self());
      Debug( libcw_do.margin().assign(margin, 17) );
    }
    first_thread = false;
  }

  /*! @brief Initialize debugging code from main.
   *
   * This function initializes the debug code.
   */
  void init(void)
  {
#if CWDEBUG_ALLOC && defined(USE_LIBCW)
    // Tell the memory leak detector which parts of the code are
    // expected to leak so that we won't get an alarm for those.
    {
      std::vector<std::pair<std::string, std::string> > hide_list;
      hide_list.push_back(std::pair<std::string, std::string>("libdl.so.2", "_dlerror_run"));
      hide_list.push_back(std::pair<std::string, std::string>("libstdc++.so.6", "__cxa_get_globals"));
      // The following is actually necessary because of a bug in glibc
      // (see http://sources.redhat.com/bugzilla/show_bug.cgi?id=311).
      hide_list.push_back(std::pair<std::string, std::string>("libc.so.6", "dl_open_worker"));
      memleak_filter().hide_functions_matching(hide_list);
    }
    {
      std::vector<std::string> hide_list;
      // Also because of http://sources.redhat.com/bugzilla/show_bug.cgi?id=311
      hide_list.push_back(std::string("ld-linux.so.2"));
      memleak_filter().hide_objectfiles_matching(hide_list);
    }
    memleak_filter().set_flags(libcwd::show_objectfile|libcwd::show_function);
#endif

    // The following call allocated the filebuf's of cin, cout, cerr, wcin, wcout and wcerr.
    // Because this causes a memory leak being reported, make them invisible.
    Debug(set_invisible_on());

    // You want this, unless you mix streams output with C output.
    // Read  http://gcc.gnu.org/onlinedocs/libstdc++/27_io/howto.html#8 for an explanation.
    //std::ios::sync_with_stdio(false);

    // Cancel previous call to set_invisible_on.
    Debug(set_invisible_off());

    // This will warn you when you are using header files that do not belong to the
    // shared libcwd object that you linked with.
    Debug( check_configuration() );

    Debug(
      libcw_do.on();		// Show which rcfile we are reading!
      ForAllDebugChannels(
        while (debugChannel.is_on())
	  debugChannel.off()	// Print as little as possible though.
      );
      read_rcfile();		// Put 'silent = on' in the rcfile to suppress most of the output here.
      libcw_do.off()
    );
    save_dc_states();

    init_thread();
  }

#if CWDEBUG_LOCATION
  /*! @brief Return call location.
   *
   * @param return_addr The return address of the call.
   */
  std::string call_location(void const* return_addr)
  {
    libcwd::location_ct loc((char*)return_addr + libcwd::builtin_return_address_offset);
    std::ostringstream convert;
    convert << loc;
    return convert.str();
  }

  std::vector<BackTrace> __attribute__ ((visibility("default"))) backtraces;
  pthread_mutex_t __attribute__ ((visibility("default"))) backtrace_mutex = PTHREAD_MUTEX_INITIALIZER;
#endif

} // namespace debug

#if CWDEBUG_ALLOC
// These can be called from gdb.
void cwdebug_start()
{
  debug::start_recording_backtraces();
}

void cwdebug_stop()
{
  debug::stop_recording_backtraces();
}
#endif

#if CWDEBUG_LOCATION
void cwdebug_backtrace(int n)
{
  if (0 < n && n <= debug::backtraces.size())
  {
    Dout(dc::backtrace, "Backtrace #" << n << ":");
    debug::backtraces[n - 1].dump_backtrace();
  }
  else
    std::cout << "No such backtrace nr. Max is " << debug::backtraces.size() << std::endl;
}
#endif

#endif // CWDEBUG
