//
// This file is part of the aMule Project.
//
// Copyright (C) 2005-2009 Mikkel Schubert ( xaignar@users.sourceforge.net )
// Copyright (C) 2005-2009 aMule Team ( admin@amule.org / http://www.amule.org )
//
// Any parts of this program derived from the xMule, lMule or eMule project,
// or contributed by third-party developers are copyrighted by their
// respective authors.
//
// 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, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
//

#include <cstdlib>			// Needed for std::abort()

#ifdef HAVE_CONFIG_H
#	include "config.h"		// Needed for HAVE_CXXABI and HAVE_EXECINFO
#endif

#include "MuleDebug.h"			// Interface declaration
#include "StringFunctions.h"		// Needed for unicode2char

#ifdef HAVE_EXECINFO
#	include <execinfo.h>
#endif

#ifdef HAVE_CXXABI
#	include <cxxabi.h>
#endif

#include <wx/thread.h> // Do_not_auto_remove (Old wx < 2.7)
#include <wx/utils.h> // Do_not_auto_remove (Old wx < 2.7)

#if wxUSE_STACKWALKER && defined(__WXMSW__)
	#include <wx/stackwalk.h> // Do_not_auto_remove
#elif defined(HAVE_BFD)
	#include <ansidecl.h> // Do_not_auto_remove
	#include <bfd.h> // Do_not_auto_remove
#endif

#include <vector>


/**
 * This functions displays a verbose description of 
 * any unhandled exceptions that occour and then
 * terminate the program by raising SIGABRT.
 */
void OnUnhandledException()
{
	// Revert to the original exception handler, to avoid
	// infinate recursion, in case something goes wrong in
	// this function.
	std::set_terminate(std::abort);	

#ifdef HAVE_CXXABI
	std::type_info *t = __cxxabiv1::__cxa_current_exception_type();
	FILE* output = stderr;
#else 
	FILE* output = stdout;
	bool t = true;
#endif
	if (t) {
		int status = -1;
		char *dem = 0;
#ifdef HAVE_CXXABI
		// Note that "name" is the mangled name.
		char const *name = t->name();

		dem = __cxxabiv1::__cxa_demangle(name, 0, 0, &status);
#else
		const char* name = "Unknown";
#endif
		fprintf(output, "\nTerminated after throwing an instance of '%s'\n", (status ? name : dem));
		free(dem);

		try {
			throw;
		} catch (const std::exception& e) {
			fprintf(output, "\twhat(): %s\n", e.what());
		} catch (const CMuleException& e) {
			fprintf(output, "\twhat(): %s\n", (const char*)unicode2char(e.what()));
		} catch (const wxString& e) {
			fprintf(output, "\twhat(): %s\n", (const char*)unicode2char(e));
		} catch (...) {
			// Unable to retrieve cause of exception
		}

		fprintf(output, "\tbacktrace:\n%s\n", (const char*)unicode2char(get_backtrace(1)));
	}
	std::abort();
}


void InstallMuleExceptionHandler()
{
	std::set_terminate(OnUnhandledException);
}


// Make it 1 for getting the file path also
#define TOO_VERBOSE_BACKTRACE 0 

#if wxUSE_STACKWALKER && defined(__WXMSW__)

// Derived class to define the actions to be done on frame print.
// I was tempted to name it MuleSkyWalker
class MuleStackWalker : public wxStackWalker
{
public:
	MuleStackWalker() {};
	~MuleStackWalker() {};

	void OnStackFrame(const wxStackFrame& frame)
	{
		wxString btLine = wxString::Format(wxT("[%u] "), frame.GetLevel());
		wxString filename = frame.GetName();

		if (!filename.IsEmpty()) {
			btLine += filename + wxT(" (") +
#if TOO_VERBOSE_BACKTRACE
			        frame.GetModule()
#else
				frame.GetModule().AfterLast(wxT('/'))
#endif
			        + wxT(")");
		} else {
			btLine += wxString::Format(wxT("0x%lx"), frame.GetAddress());
		}

		if (frame.HasSourceLocation()) {
			btLine += wxT(" at ") +
#if TOO_VERBOSE_BACKTRACE
			        frame.GetFileName()
#else
				frame.GetFileName().AfterLast(wxT('/'))
#endif
			        + wxString::Format(wxT(":%u"),frame.GetLine());
		} else {
			btLine += wxT(" (Unknown file/line)");
		}

		//! Contains the entire backtrace
		m_trace += btLine + wxT("\n");
	}

	wxString m_trace;
};


wxString get_backtrace(unsigned n)
{
	MuleStackWalker walker; // Texas ranger?
	walker.Walk(n); // Skip this one and Walk() also!

	return walker.m_trace;
}

#elif defined(__LINUX__)

#ifdef HAVE_BFD

static bfd* s_abfd;
static asymbol** s_symbol_list;
static bool s_have_backtrace_symbols = false;
static const char* s_file_name;
static const char* s_function_name;
static unsigned int s_line_number;
static int s_found;


/*
* read all symbols in the executable into an array
* and return the pointer to the array in symbol_list.
* Also return the number of actual symbols read
* If there's any error, return -1
*/
static int get_backtrace_symbols(bfd *abfd, asymbol ***symbol_list_ptr)
{
	int vectorsize = bfd_get_symtab_upper_bound(abfd);

	if (vectorsize < 0) {
		fprintf (stderr, "Error while getting vector size for backtrace symbols : %s",
			bfd_errmsg(bfd_get_error()));
		return -1;
	}

	if (vectorsize == 0) {
		fprintf (stderr, "Error while getting backtrace symbols : No symbols (%s)",
			bfd_errmsg(bfd_get_error()));
		return -1;
	}

	*symbol_list_ptr = (asymbol**)malloc(vectorsize);

	if (*symbol_list_ptr == NULL) {
		fprintf (stderr, "Error while getting backtrace symbols : Cannot allocate memory");
		return -1;
	}

	vectorsize = bfd_canonicalize_symtab(abfd, *symbol_list_ptr);

	if (vectorsize < 0) {
		fprintf(stderr, "Error while getting symbol table : %s",
			bfd_errmsg(bfd_get_error()));
		return -1;
	}

	return vectorsize;
}


/*
* print file, line and function information for address
* The info is actually set into global variables. This
* function is called from the iterator bfd_map_over_sections
*
*/
void init_backtrace_info()
{
	bfd_init();
	s_abfd = bfd_openr("/proc/self/exe", NULL);

	if (s_abfd == NULL) {
		fprintf(stderr, "Error while opening file for backtrace symbols : %s",
			bfd_errmsg(bfd_get_error()));
		return;
	}

	if (!(bfd_check_format_matches(s_abfd, bfd_object, NULL))) {
		fprintf (stderr, "Error while init. backtrace symbols : %s",
			bfd_errmsg (bfd_get_error ()));
		bfd_close(s_abfd);
		return;
	}

	s_have_backtrace_symbols = (get_backtrace_symbols(s_abfd, &s_symbol_list) > 0); 
}


void get_file_line_info(bfd *abfd, asection *section, void* _address)
{
	wxASSERT(s_symbol_list);

	if (s_found) {
		return;
	}

	if ((section->flags & SEC_ALLOC) == 0) {
		return;
	}

	bfd_vma vma = bfd_get_section_vma(abfd, section);

	unsigned long address = (unsigned long)_address;
	if (address < vma) {
		return;
	}

	bfd_size_type size = bfd_section_size(abfd, section);
	if (address > (vma + size)) {
		return;
	}

	s_found =  bfd_find_nearest_line(abfd, section, s_symbol_list,
		address - vma, &s_file_name, &s_function_name, &s_line_number);
}

#endif // HAVE_BFD

wxString demangle(const wxString& function)
{
#ifdef HAVE_CXXABI
	wxString result;
	
	if (function.Mid(0,2) == wxT("_Z")) {
		int status;
		char *demangled = abi::__cxa_demangle(function.mb_str(), NULL, NULL, &status);
		
		if (!status) {
			result = wxConvCurrent->cMB2WX(demangled);
		}
		
		if (demangled) {
			free(demangled);
		}
	}
	
	return result;
#else
	return wxEmptyString;
#endif
}


// Print a stack backtrace if available
wxString get_backtrace(unsigned n)
{
#ifdef HAVE_EXECINFO
	// (stkn) create backtrace
	void *bt_array[100];	// 100 should be enough ?!?
	char **bt_strings;
	int num_entries;

	if ((num_entries = backtrace(bt_array, 100)) < 0) {
		fprintf(stderr, "* Could not generate backtrace\n");
		return wxEmptyString;
	}

	if ((bt_strings = backtrace_symbols(bt_array, num_entries)) == NULL) {
		fprintf(stderr, "* Could not get symbol names for backtrace\n");
		return wxEmptyString;
	}

	std::vector<wxString> libname(num_entries);
	std::vector<wxString> funcname(num_entries);
	std::vector<wxString> address(num_entries);
	wxString AllAddresses;
	
	for (int i = 0; i < num_entries; ++i) {
		wxString wxBtString = wxConvCurrent->cMB2WX(bt_strings[i]);
		int posLPar = wxBtString.Find(wxT('('));
		int posRPar = wxBtString.Find(wxT(')'));
		int posLBra = wxBtString.Find(wxT('['));
		int posRBra = wxBtString.Find(wxT(']'));
		bool hasFunction = true;
		if (posLPar == -1 || posRPar == -1) {
			if (posLBra == -1 || posRBra == -1) {
				/* It is important to have exactly num_entries
				* addresses in AllAddresses */
				AllAddresses += wxT("0x0000000 ");
				continue;
			}
			posLPar = posLBra;
			hasFunction = false;
		}
		/* Library name */
		int len = posLPar;
		libname[i] = wxBtString.Mid(0, len);
		/* Function name */
		if (hasFunction) {
			int posPlus = wxBtString.Find(wxT('+'), true);
			if (posPlus == -1)
				posPlus = posRPar;
			len = posPlus - posLPar - 1;
			funcname[i] = wxBtString.Mid(posLPar + 1, len);
			wxString demangled = demangle(funcname[i]);
			if (!demangled.IsEmpty()) {
				funcname[i] = demangled;
			}
		}
		/* Address */
		if ( posLBra == -1 || posRBra == -1) {
			address[i] = wxT("0x0000000");
		} else {
			len = posRBra - posLBra - 1;
			address[i] = wxBtString.Mid(posLBra + 1, len);
			AllAddresses += address[i] + wxT(" ");
		}
	}
	free(bt_strings);

	/* Get line numbers from addresses */
	wxArrayString out;
	bool hasLineNumberInfo = false;

#ifdef HAVE_BFD
	if (!s_have_backtrace_symbols) {
		init_backtrace_info();
		wxASSERT(s_have_backtrace_symbols);
	}

	for (int i = 0; i < num_entries; ++i) {
		s_file_name = NULL;
		s_function_name = NULL;
		s_line_number = 0;
		s_found = false ;

		unsigned long addr;
		address[i].ToULong(&addr,0); // As it's "0x" prepended, wx will read it as base 16. Hopefully.

		bfd_map_over_sections(s_abfd, get_file_line_info, (void*)addr);

		if (s_found) {
			wxString function = wxConvCurrent->cMB2WX(s_function_name);
			wxString demangled = demangle(function);
			if (!demangled.IsEmpty()) {
				function = demangled;
				funcname[i] = demangled;
			}
			out.Insert(wxConvCurrent->cMB2WX(s_function_name),i*2);
			out.Insert(wxConvCurrent->cMB2WX(s_file_name) + wxString::Format(wxT(":%u"), s_line_number),i*2+1);
		} else {
			out.Insert(wxT("??"),i*2);
			out.Insert(wxT("??"),i*2+1);
		}
	}

	hasLineNumberInfo = true;

#else	/* !HAVE_BFD */
	if (wxThread::IsMain()) {
		wxString command;
		command << wxT("addr2line -C -f -s -e /proc/") <<
		getpid() << wxT("/exe ") << AllAddresses;
		// The output of the command is this wxArrayString, in which
		// the even elements are the function names, and the odd elements
		// are the line numbers.

		hasLineNumberInfo = wxExecute(command, out) != -1;
	}

#endif	/* HAVE_BFD / !HAVE_BFD */

	wxString trace;
	// Remove 'n+1' first entries (+1 because of this function)
	for (int i = n+1; i < num_entries; ++i) {
		/* If we have no function name, use the result from addr2line */
		if (funcname[i].IsEmpty()) {
			if (hasLineNumberInfo) {
				funcname[i] = out[2*i];
			} else {
				funcname[i] = wxT("??");
			}
		}
		wxString btLine;
		btLine << wxT("[") << i << wxT("] ") << funcname[i] << wxT(" in ");
		/* If addr2line did not find a line number, use bt_string */
		if (!hasLineNumberInfo || out[2*i+1].Mid(0,2) == wxT("??")) {
			btLine += libname[i] + wxT("[") + address[i] + wxT("]");
		} else if (hasLineNumberInfo) {
#if TOO_VERBOSE_BACKTRACE
			btLine += out[2*i+1];
#else
			btLine += out[2*i+1].AfterLast(wxT('/'));
#endif
		} else {
			btLine += libname[i];
		}
		
		trace += btLine + wxT("\n");
	}

	return trace;
#else /* !HAVE_EXECINFO */
	fprintf(stderr, "--== cannot generate backtrace ==--\n\n");
	return wxEmptyString;
#endif /* HAVE_EXECINFO */
}

#else	/* !__LINUX__ */

wxString get_backtrace(unsigned WXUNUSED(n))
{
	fprintf(stderr, "--== no BACKTRACE for your platform ==--\n\n");
	return wxEmptyString;
}

#endif /* !__LINUX__ */

void print_backtrace(unsigned n)
{
	wxString trace = get_backtrace(n);

	// This is because the string is ansi anyway, and the conv classes are very slow
	fprintf(stderr, "%s\n", (const char*)unicode2char(trace.c_str()));
}

// File_checked_for_headers
