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

#if defined(LIBMAUS2_HAVE_LIBUNWIND_H)
#include <libunwind.h>
#endif

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

#include <setjmp.h>

libmaus2::stacktrace::StackTraceBufferContainer::id_trace_type libmaus2::stacktrace::StackTraceBufferContainer::traces[StackTraceBufferContainer_MAXTRACES];
std::atomic<std::size_t> libmaus2::stacktrace::StackTraceBufferContainer::numtraces(0);
libmaus2::stacktrace::StackTraceBufferContainer::libmaus2_sighandler_t libmaus2::stacktrace::StackTraceBufferContainer::prevsig = nullptr;
jmp_buf libmaus2::stacktrace::StackTraceBufferContainer::segenv;

static int parseVal(char const * p)
{
	unsigned int v = 0;

	while ( *p )
	{
		if ( *p < '0' || *p > '9' )
			return -1;
		else
		{
			v *= 10;
			v += *p-'0';
			p++;
		}
	}
	return v;
}

static int initStackTraceBufferContainer()
{
	char const * checkseg = getenv("LIBMAUS2_TRACE_SEGFAULT");

	if ( checkseg && parseVal(checkseg) > 0 )
	{
		std::cerr << "[V] running libmaus2::stacktrace::StackTraceBufferContainer::setup()" << std::endl;
		return libmaus2::stacktrace::StackTraceBufferContainer::setup();
	}
	else
		return 0;
}

static int initStackTraceBufferContainerTranslate()
{
	char const * checkseg = getenv("LIBMAUS2_TRACE_SEGFAULT_TRANSLATE");

	if ( checkseg && parseVal(checkseg) > 0 )
		return 1;
	else
		return 0;
}

int libmaus2::stacktrace::StackTraceBufferContainer::init       = initStackTraceBufferContainer();
int libmaus2::stacktrace::StackTraceBufferContainer::itranslate = initStackTraceBufferContainerTranslate();

void libmaus2::stacktrace::StackTraceBufferContainer::reset()
{
	numtraces.store(0);
	signal(SIGSEGV,prevsig);
}

int libmaus2::stacktrace::StackTraceBufferContainer::setup()
{
	numtraces.store(0);

	prevsig = signal(SIGSEGV,sigseghandler);

	if ( setjmp(segenv) )
	{
		// signal(SIGSEGV,prevsig);

		{
			std::cerr << "[E] libmaus2::stacktrace::StackTraceBufferContainer: setjmp returned non zero (segfault)" << std::endl;

			std::thread::id const id = ::std::this_thread::get_id();

			std::size_t const loopend = std::min(
				static_cast<std::size_t>(StackTraceBufferContainer_MAXTRACES),
				static_cast<std::size_t>(numtraces)
			);

			for ( ::std::size_t i = 0; i < loopend; ++i )
				if ( traces[i].first == id )
				{
					StackTraceBuffer & buf = traces[i].second;

					if ( buf.stacktracebufn > 0 )
					{
						#if defined(LIBMAUS2_HAVE_EXECINFO_H) && defined(LIBMAUS2_HAVE_BACKTRACE)
						char ** syms = backtrace_symbols(&buf.stacktracebuf[0],buf.stacktracebufn);

						for ( int i = 0; i < buf.stacktracebufn; ++i )
						{
							std::string const s = StackLineTranslator::translate(syms[i],itranslate);
							std::cerr << "[" << i << "]=" << s << "\n";
						}

						free(syms);
						#endif
					}

				}
		}

		abort();
	}

	return 0;
}

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

#include <cstring>

void libmaus2::stacktrace::StackTraceBufferContainer::sigseghandler(int)
{
	uint64_t const traceid = numtraces++;

	// if ( traceid < StackTraceBufferContainer_MAXTRACES )
	if ( !traceid )
	{
		std::thread::id const id = ::std::this_thread::get_id();
		traces[traceid].first = id;

		#if defined(LIBMAUS2_HAVE_EXECINFO_H) && defined(LIBMAUS2_HAVE_BACKTRACE)
		StackTraceBuffer & lbuf = traces[traceid].second;
		lbuf.stacktracebufn = backtrace(&(lbuf.stacktracebuf[0]),StackTraceBufferContainer_STACKTRACEBUFSIZE);
		#endif

		longjmp(segenv,1);
	}
	else
	{
		while ( true )
		{
			::sleep(1);
		}
	}
}

template<typename type>
struct Array
{
	type * p;

	void allocate(::std::size_t const n)
	{
		try
		{
			p = new type[n];
		}
		catch(...)
		{
			p = nullptr;
		}
	}

	Array()
	: p(nullptr)
	{

	}

	Array(::std::size_t const n)
	: p(nullptr)
	{
		allocate(n);
	}

	~Array()
	{
		if ( p )
			delete [] p;
	}
};

#include <sstream>

#if defined(LIBMAUS2_HAVE_SYS_TYPES_H)
#include <sys/types.h>
#endif

#if defined(LIBMAUS2_HAVE_SYS_WAIT_H)
#include <sys/wait.h>
#endif

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

#if defined(LIBMAUS2_HAVE_FCNTL_H)
#include <fcntl.h>
#endif

#include <vector>

#include <cstdlib>
#include <cstring>
#include <libmaus2/demangle/Demangle.hpp>

#if defined(LIBMAUS2_HAVE_FORK) && defined(LIBMAUS2_HAVE_WAITPID) && defined(LIBMAUS2_HAVE_OPEN_2) && defined(LIBMAUS2_HAVE_MKSTEMP) && defined(LIBMAUS2_HAVE_CLOSE) && defined(LIBMAUS2_HAVE_DUP2) && defined(LIBMAUS2_HAVE_READ)
#define LIBMAUS2_HAVE_CALL_COMMAND_REQ
#endif

static int callCommand(
	std::vector<std::string> const &
		#if defined(LIBMAUS2_HAVE_CALL_COMMAND_REQ)
		V
		#endif
		,
	std::string const &
		#if defined(LIBMAUS2_HAVE_CALL_COMMAND_REQ)
		outtmp
		#endif
		,
	std::string &
		#if defined(LIBMAUS2_HAVE_CALL_COMMAND_REQ)
		r
		#endif
)
{
	#if defined(LIBMAUS2_HAVE_CALL_COMMAND_REQ)
	int rmk = -1;
	int ex = EXIT_SUCCESS;
	char * cp = nullptr;
	pid_t pid = -1;
	size_t todo = 0;
	char * pp = nullptr;
	off_t fs = 0;
	off_t fs0 = 0;
	size_t s = 0;

	for ( uint64_t i = 0; i < V.size(); ++i )
		s += V[i].size()+1;

	Array<char> Atemp(outtmp.size()+1);
	Array<char> Ctemp(s);
	Array<char *> Ptemp(V.size()+1);
	Array<char> T;

	if ( ! Atemp.p )
	{
		ex = EXIT_FAILURE;
		goto cleanup;
	}
	::std::fill(Atemp.p,Atemp.p+outtmp.size()+1,0);
	::std::copy(outtmp.begin(),outtmp.end(),Atemp.p);


	if ( ! Ctemp.p )
	{
		ex = EXIT_FAILURE;
		goto cleanup;
	}
	if ( ! Ptemp.p )
	{
		ex = EXIT_FAILURE;
		goto cleanup;
	}
	cp = Ctemp.p;
	for ( uint64_t i = 0; i < V.size(); ++i )
	{
		Ptemp.p[i] = cp;
		std::size_t const l = V[i].size();
		std::copy(V[i].begin(),V[i].end(),cp);
		cp += l;
		*(cp++) = 0;
	}
	Ptemp.p[V.size()] = nullptr;

	rmk = mkstemp(Atemp.p);

	if ( rmk < 0 )
	{
		ex = EXIT_FAILURE;
		goto cleanup;
	}

	pid = fork();

	if ( pid == static_cast<pid_t>(-1) )
	{
		ex = EXIT_FAILURE;
		goto cleanup;
	}
	else if ( pid == 0 )
	{
		int const nullfd = ::open("/dev/null",O_WRONLY);

		if ( nullfd < 0 )
			_exit(EXIT_FAILURE);

		::close(STDOUT_FILENO);
		::close(STDERR_FILENO);

		int const rdup = ::dup2(rmk,STDOUT_FILENO);

		if ( rdup < 0 )
			_exit(EXIT_FAILURE);

		int const rdup2 = ::dup2(nullfd,STDERR_FILENO);
		if ( rdup2 < 0 )
			_exit(EXIT_FAILURE);

		execv(Ptemp.p[0],Ptemp.p);

		_exit(EXIT_FAILURE);
	}

	int status;
	while ( true )
	{
		pid_t const rpid = waitpid(pid,&status,0);

		if ( rpid == pid )
			break;
		else if ( rpid == static_cast<pid_t>(-1) )
		{
			int const error = errno;

			switch ( error )
			{
				case EINTR:
				case EAGAIN:
					break;
				default:
				{
					ex = EXIT_FAILURE;
					goto cleanup;
				}
			}
		}
	}

	if ( status != 0 )
	{
		ex = EXIT_FAILURE;
		goto cleanup;
	}

	fs = lseek(rmk,0,SEEK_END);

	if ( fs == static_cast<off_t>(-1) )
	{
		ex = EXIT_FAILURE;
		goto cleanup;
	}

	fs0 = lseek(rmk,0,SEEK_SET);

	if ( fs0 == static_cast<off_t>(-1) )
	{
		ex = EXIT_FAILURE;
		goto cleanup;
	}

	T.allocate(fs);
	if ( ! T.p )
	{
		ex = EXIT_FAILURE;
		goto cleanup;
	}
	pp = T.p;
	todo = fs;

	while ( todo )
	{
		::ssize_t const r = ::read(rmk,pp,todo);

		if ( r < 0 )
		{
			int const error = errno;
			switch ( error )
			{
				case EINTR:
				case EAGAIN:
					break;
				default:
				{
					ex = EXIT_FAILURE;
					goto cleanup;
				}
			}
		}
		else if ( r == 0 )
		{
			ex = EXIT_FAILURE;
			goto cleanup;
		}
		else
		{
			pp += r;
			todo -= r;
		}
	}

	r = std::string(T.p,T.p+fs);

	cleanup:
	if ( rmk >= 0 )
	{
		::close(rmk);
		::remove(Atemp.p);
		rmk = -1;
	}

	return ex;
	#else
	return -1;
	#endif
}

std::string libmaus2::stacktrace::StackLineTranslator::translate(char const * ip, int const deep)
{
	// /home/baer/src/git/libmaus2/src/.libs/libmaus2.so.2(_ZN8libmaus24util25StackTraceBufferContainer13sigseghandlerEi+0x70) [0x7f328cc1e980]
	if ( ! ip )
		return std::string();

	char const * path_a = ip;
	char const * path_e = path_a;

	while ( *path_e && *path_e != '(' )
		++path_e;

	if ( !*path_e )
		return std::string(ip);

	char const * symoffstart = path_e+1;
	char const * symoffend = symoffstart;

	while ( *symoffend && *symoffend != ')' )
		++symoffend;

	if ( !*symoffend )
		return std::string(ip);

	char const * symstart = symoffstart;
	char const * symend = symstart;

	while ( symend != symoffend && *symend != '+' )
		++symend;

	if ( symend == symoffend )
		return std::string(ip);

	char const * offstart = symend+1;
	char const * offend = symoffend;

	std::string const path(path_a,path_e);
	std::string const symoff(symoffstart,symoffend);
	std::string const sym(symstart,symend);
	std::string const off(offstart,offend);

	std::string p = path + "(" + libmaus2::demangle::Demangle::demangleName(sym) + "+" + off + ")";

	if ( ! deep )
		return p;

	std::string const tmpprefix = "/tmp/libmaus2_";
	std::ostringstream nmtmpostr;
	#if defined(LIBMAUS2_HAVE_GETPID)
	nmtmpostr << tmpprefix << getpid() << "_XXXXXX";
	#else
	nmtmpostr << tmpprefix << "_XXXXXX";
	#endif
	std::string const nmtmp = nmtmpostr.str();

	std::vector<std::string> Vcom;
	Vcom.push_back("/usr/bin/nm");
	Vcom.push_back(path);

	std::string rnm;
	int const r_nm = callCommand(Vcom,nmtmp,rnm);

	if ( r_nm != EXIT_SUCCESS )
		return p;

	// std::cerr << "path=" << path << " symoff=" << symoff << " sym=" << sym << " off=" << off << " nmtmp=" << nmtmp << std::endl;

	uint64_t uaddr = 0;
	bool haveaddr = false;

	std::istringstream nm_istr(rnm);
	std::string line;
	while ( std::getline(nm_istr,line) )
	{
		uint64_t low = 0;
		std::vector<std::string> V;
		while ( low < line.size() )
		{
			while ( low < line.size() && isspace(line[low]) )
				++low;

			if ( low < line.size() )
			{
				uint64_t high = low+1;
				while ( high < line.size() && !isspace(line[high]) )
					++high;

				V.push_back(line.substr(low,high-low));

				low = high;
			}
		}
		if ( 2 < V.size() && V[2] == sym )
		{
			std::string const saddr = V[0];

			char const * pa = saddr.c_str();
			char const * pe = pa + saddr.size();

			while ( pa != pe && *pa == '0' )
				++pa;

			std::string const laddr(pa,pe);

			if ( laddr.size() )
			{
				// std::string const paddr = std::string("0x")+laddr;
				std::istringstream istr(laddr);
				uint64_t luaddr;
				istr >> std::hex >> luaddr;

				if ( istr && istr.peek() == std::istream::traits_type::eof() )
				{
					uaddr = luaddr;
					haveaddr = true;
				}
			}
		}
	}

	if ( sym.size() && ! haveaddr )
		return p;


	if ( off.size() < 2 || off.substr(0,2) != "0x" )
		return p;

	std::istringstream offistr(off.substr(2));
	uint64_t uoff;
	offistr >> std::hex >> uoff;
	if ( !offistr && offistr.peek() != std::istream::traits_type::eof() )
		return p;

	std::ostringstream addrostr;
	addrostr << "0x" << std::hex << (uaddr+uoff);
	std::string const addr = addrostr.str();

	std::vector<std::string> Vaddr;
	Vaddr.push_back("/usr/bin/addr2line");
	Vaddr.push_back("-f");
	Vaddr.push_back("-e");
	Vaddr.push_back(path);
	Vaddr.push_back(addr);

	std::string raddrline;
	int const r_addrline = callCommand(Vaddr,nmtmp,raddrline);

	if ( r_addrline != EXIT_SUCCESS )
		return p;

	std::vector<std::string> Vad;
	std::istringstream adistr(raddrline);
	while ( std::getline(adistr,line) )
		Vad.push_back(line);

	if ( Vad.size() < 2 )
		return p;

	std::ostringstream outostr;
	outostr << path << "(" << libmaus2::demangle::Demangle::demangleName(Vad[0]) << "+" << off << ")" << "[" << Vad[1] << "]";

	return outostr.str();
}
