/*
    libmaus2
    Copyright (C) 2009-2020 German Tischler
    Copyright (C) 2011-2013 Genome Research Limited

    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 3 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/>.
*/
#include <libmaus2/stacktrace/StackTrace.hpp>

#if defined(LIBMAUS2_HAVE_EXECINFO_H)
#include <execinfo.h>
#endif

#include <libmaus2/stacktrace/StackLineTranslator.hpp>
#include <libmaus2/demangle/Demangle.hpp>
#include <fstream>

#if defined(LIBMAUS2_HAVE_UNISTD_H)
#include <unistd.h>
#endif

#if defined(__linux__)
#include <libmaus2/posix/PosixExecute.hpp>
#endif

#if defined(__linux__) && defined(LIBMAUS2_DEBUG_COMPILED)
static std::string chomp(std::string s)
{
        while ( s.size() && isspace(s[s.size()-1]) )
                s = s.substr(0,s.size()-1);
        return s;
}
#endif

libmaus2::stacktrace::StackTrace::~StackTrace() {}

std::string libmaus2::stacktrace::StackTrace::getExecPath()
{
	#if defined(__linux__)
	char buf[PATH_MAX+1];
	std::fill(
		&buf[0],&buf[sizeof(buf)/sizeof(buf[0])],0);
	if ( readlink("/proc/self/exe", &buf[0], PATH_MAX) < 0 )
		throw std::runtime_error("readlink(/proc/self/exe) failed.");
	return std::string(&buf[0]);
	#else
	return "program";
	#endif
}

#if defined(__linux__) && defined(LIBMAUS2_DEBUG_COMPILED)
static std::string readCommandLine()
{
	try
	{
		::std::ifstream istr_cmdline("/proc/self/cmdline");
		if ( ! istr_cmdline.is_open() )
			return std::string();

		std::ostringstream ocmdline;
		int c;
		while ( (c=istr_cmdline.get()) != std::istream::traits_type::eof() )
			ocmdline.put(c);
		istr_cmdline.close();
		return ocmdline.str();
	}
	catch(...)
	{
		return std::string();
	}
}
#endif

#if defined(LIBMAUS2_DEBUG_COMPILED)
static std::string findIfSelf(std::string const & exec)
{
	#if defined(__linux__)
	try
	{
		std::string cmdline = readCommandLine();
		if ( cmdline.find('\0') != std::string::npos )
			cmdline = cmdline.substr(0,cmdline.find('\0'));
		if ( cmdline != exec )
		{
			return exec;
		}
		return libmaus2::stacktrace::StackTrace::getExecPath();
	}
	catch(...)
	{
		return exec;
	}
	#else
	return exec;
	#endif
}
#endif

std::string libmaus2::stacktrace::StackTrace::toString(bool const
#if defined(__linux)
translate
#endif
) const
{
        std::ostringstream ostr;

        #if defined(__linux__)
        if ( translate )
        {
                for ( uint64_t i = 0; i < trace.size(); ++i )
                {
                        std::pair < std::string, std::string > A = components(trace[i],'[',']');
                        std::pair < std::string, std::string > B = components(A.first,'(',')');

                        #if defined(LIBMAUS2_DEBUG_COMPILED)
                        #if 1
                        ostr << libmaus2::stacktrace::StackLineTranslator::translate(trace[i].c_str(),1 /* deep */) << std::endl;
                        #else
                        std::string const execname = findIfSelf(B.first);
                        std::string const & addr = A.second;

                        std::ostringstream comlinestr;
                        comlinestr << "addr2line" << " --exe=" << execname << " " << addr;
                        std::string const comline = comlinestr.str();

                        std::string addrout,addrerr;
                        std::ostringstream errstr;
                        int const retcode = ::libmaus2::posix::PosixExecute::execute(comline,addrout,addrerr,errstr);

                        if ( retcode == EXIT_SUCCESS )
                        {
	                        addrout = chomp(addrout);
			}
			else
			{
				addrout = "addr2line failed";
			}

                        /*
                        ostr << ":::" << execname << ":::\n";
                        ostr << ":::" << addr << ":::\n";
                        ostr << ":::" << comline << ":::\n";
                        ostr << ":::" << addrout << ":::\n";
                        ostr << trace[i] << "\n";
                        */

                        if ( B.second.find_last_of('+') != std::string::npos )
                        {
                        	std::string const mangled = B.second.substr(0,B.second.find_last_of('+'));
                        	std::string const offset = B.second.substr(B.second.find_last_of('+')+1);
                        	B.second = libmaus2::demangle::Demangle::demangleName(mangled) + "+" + offset;
                        }

                        //ostr << B.first << "(" << B.second << ")" << "[" << A.second << ":" << addrout << "]\n";
                        ostr << execname << "(" << B.second << ")" << "[" << A.second << ":" << addrout << "]\n";
                        #endif
                        #else
                        if ( B.second.find_last_of('+') != std::string::npos )
                        {
                        	std::string const mangled = B.second.substr(0,B.second.find_last_of('+'));
                        	std::string const offset = B.second.substr(B.second.find_last_of('+')+1);
                        	B.second = libmaus2::demangle::Demangle::demangleName(mangled) + "+" + offset;
                        }


                        ostr << B.first << "(" << B.second << ")" << "[" << A.second << "]\n";
                        #endif
                }
        }
        else
        #endif
        {
                for ( uint64_t i = 0; i < trace.size(); ++i )
                        ostr << trace[i] << std::endl;
        }

        return ostr.str();
}


void libmaus2::stacktrace::StackTrace::simpleStackTrace(std::ostream &
#if defined(LIBMAUS2_HAVE_BACKTRACE)
ostr
#endif
)
{
	#if defined(LIBMAUS2_HAVE_BACKTRACE)
	unsigned int const depth = 20;
	void *array[depth];
	size_t const size = backtrace(array,depth);
	char ** strings = backtrace_symbols(array,size);

	for ( size_t i = 0; i < size; ++i )
		ostr << "[" << i << "]" << strings[i] << std::endl;

	free(strings);
	#endif
}

libmaus2::stacktrace::StackTrace::StackTrace() : trace()
{
	#if defined(LIBMAUS2_HAVE_BACKTRACE)
	unsigned int const depth = 20;
	void *array[depth];
	size_t const size = backtrace(array,depth);
	char ** strings = backtrace_symbols(array,size);

	for ( size_t i = 0; i < size; ++i )
		trace.push_back(strings[i]);

	free(strings);
	#endif
}


std::pair < std::string, std::string > libmaus2::stacktrace::StackTrace::components(std::string line, char const start, char const end)
{
	uint64_t i = line.size();

	while ( i != 0 )
		if ( line[--i] == end )
			break;

	if ( line[i] != end || i == 0 )
		return std::pair<std::string,std::string>(std::string(),std::string());

	uint64_t const addrend = i;

	while ( i != 0 )
		if ( line[--i] == start )
			break;

	return std::pair<std::string,std::string>(
		line.substr(0,i),
		line.substr(i+1, addrend-(i+1) ));
}

std::string libmaus2::stacktrace::StackTrace::getStackTrace(bool translate)
{
	StackTrace st;
	return st.toString(translate);
}
