/*
   Copyright (C) 2001-2006, William Joseph.
   All Rights Reserved.

   This file is part of GtkRadiant.

   GtkRadiant 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.

   GtkRadiant 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 GtkRadiant; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "stacktrace.h"
#include "globaldefs.h"

#include "stream/textstream.h"

#include "environment.h"

#if GDEF_OS_LINUX
#include <execinfo.h>

void write_stack_trace( TextOutputStream& outputStream ){
	const unsigned int MAX_SYMBOLS = 256;
	void* symbols[MAX_SYMBOLS];

	// get return addresses
	int symbol_count = backtrace( symbols, MAX_SYMBOLS );

	if ( !symbol_count ) {
		return;
	}

	// resolve and print names
	char** symbol_names = backtrace_symbols( symbols, symbol_count );
	if ( symbol_names ) {
		for ( int i = 0; ( i < symbol_count ); ++i )
			outputStream << symbol_names[i] << "\n";

		// not a memleak, see www.gnu.org/software/libc/manual (Debugging Support, Backtraces)
		free( symbol_names );
	}
}
#elif GDEF_COMPILER_MSVC

#include "windows.h"
#include "winnt.h"
#include "dbghelp.h"

class Address
{
public:
void* m_value;
Address( void* value ) : m_value( value ){
}
};

/// \brief Writes an address \p p to \p ostream in hexadecimal form.
template<typename TextOutputStreamType>
inline TextOutputStreamType& ostream_write( TextOutputStreamType& ostream, const Address& p ){
	const std::size_t bufferSize = ( sizeof( void* ) * 2 ) + 1;
	char buf[bufferSize];
	ostream.write( buf, snprintf( buf, bufferSize, "%0p", p.m_value ) );
	return ostream;
}

class Offset
{
public:
void* m_value;
Offset( void* value ) : m_value( value ){
}
};

/// \brief Writes an address \p p to \p ostream in hexadecimal form.
template<typename TextOutputStreamType>
inline TextOutputStreamType& ostream_write( TextOutputStreamType& ostream, const Offset& p ){
	const std::size_t bufferSize = ( sizeof( void* ) * 2 ) + 1;
	char buf[bufferSize];
	ostream.write( buf, snprintf( buf, bufferSize, "%X", p.m_value ) );
	return ostream;
}

/// \brief Writes a WCHAR string \p s to \p ostream.
template<typename TextOutputStreamType>
inline TextOutputStreamType& ostream_write( TextOutputStreamType& ostream, const WCHAR* s ){
	const std::size_t bufferSize = 1024;
	char buf[bufferSize];
	ostream.write( buf, snprintf( buf, bufferSize, "%ls", s ) );
	return ostream;
}

struct EnumerateSymbolsContext
{
	STACKFRAME64& sf;
	TextOutputStream& outputStream;
	std::size_t count;
	EnumerateSymbolsContext( STACKFRAME64& sf, TextOutputStream& outputStream ) : sf( sf ), outputStream( outputStream ), count( 0 ){
	}
};

void write_symbol( PSYMBOL_INFO pSym, STACKFRAME64& sf, TextOutputStream& outputStream, std::size_t& count ){
#if 0
	if ( pSym->Flags & SYMFLAG_PARAMETER ) {

		DWORD basicType;
		if ( SymGetTypeInfo( GetCurrentProcess(), pSym->ModBase, pSym->TypeIndex,
							 TI_GET_BASETYPE, &basicType ) ) {
			int bleh = 0;
		}
		else
		{
			DWORD typeId;
			if ( SymGetTypeInfo( GetCurrentProcess(), pSym->ModBase, pSym->TypeIndex,
								 TI_GET_TYPEID, &typeId ) ) {
				if ( SymGetTypeInfo( GetCurrentProcess(), pSym->ModBase, pSym->TypeIndex,
									 TI_GET_BASETYPE, &basicType ) ) {
					int bleh = 0;
				}
				else
				{
					const char* FormatGetLastError();
					const char* error = FormatGetLastError();
					int bleh = 0;

					WCHAR* name;
					if ( SymGetTypeInfo( GetCurrentProcess(), pSym->ModBase, typeId,
										 TI_GET_SYMNAME, &name ) ) {
						outputStream << name << " ";
						LocalFree( name );
						int bleh = 0;
					}
					else
					{
						const char* FormatGetLastError();
						const char* error = FormatGetLastError();
						int bleh = 0;
					}
				}
			}
			else
			{
				const char* FormatGetLastError();
				const char* error = FormatGetLastError();
				int bleh = 0;
			}
		}
		if ( count != 0 ) {
			outputStream << ", ";
		}
		outputStream << pSym->Name;
		++count;
	}
#endif
}

BOOL CALLBACK
EnumerateSymbolsCallback(
	PSYMBOL_INFO pSymInfo,
	ULONG SymbolSize,
	PVOID UserContext ){
	write_symbol( pSymInfo, ( (EnumerateSymbolsContext*)UserContext )->sf, ( (EnumerateSymbolsContext*)UserContext )->outputStream, ( (EnumerateSymbolsContext*)UserContext )->count );


	return TRUE;
}

void write_stack_trace( PCONTEXT pContext, TextOutputStream& outputStream ){
	HANDLE m_hProcess = GetCurrentProcess();
	DWORD dwMachineType = 0;

	CONTEXT context = *pContext;

	// Could use SymSetOptions here to add the SYMOPT_DEFERRED_LOADS flag
	if ( !SymInitialize( m_hProcess, (PSTR)environment_get_app_path(), TRUE ) ) {
		return;
	}

	STACKFRAME64 sf;
	memset( &sf, 0, sizeof( sf ) );
	sf.AddrPC.Mode         = AddrModeFlat;
	sf.AddrStack.Mode      = AddrModeFlat;
	sf.AddrFrame.Mode      = AddrModeFlat;

#ifdef _M_IX86
	// Initialize the STACKFRAME structure for the first call.  This is only
	// necessary for Intel CPUs, and isn't mentioned in the documentation.
	sf.AddrPC.Offset       = context.Eip;
	sf.AddrStack.Offset    = context.Esp;
	sf.AddrFrame.Offset    = context.Ebp;

	dwMachineType = IMAGE_FILE_MACHINE_I386;
#elif _M_X64
	sf.AddrPC.Offset       = context.Rip;
	sf.AddrStack.Offset    = context.Rsp;

	// MSDN: x64:  The frame pointer is RBP or RDI. This value is not always used.
	// very funny, we'll try Rdi for now
	sf.AddrFrame.Offset    = context.Rdi;

	dwMachineType = IMAGE_FILE_MACHINE_AMD64;
#endif

	const unsigned int max_sym_name = 1024; // should be enough

	while ( 1 )
	{
		// Get the next stack frame
		if ( !StackWalk64( dwMachineType,
						   m_hProcess,
						   GetCurrentThread(),
						   &sf,
						   &context,
						   0,
						   SymFunctionTableAccess64,
						   SymGetModuleBase64,
						   0 ) ) {
			break;
		}

		if ( 0 == sf.AddrFrame.Offset ) { // Basic sanity check to make sure
			break;                // the frame is OK.  Bail if not.

		}
		// Get the name of the function for this stack frame entry
		BYTE symbolBuffer[ sizeof( SYMBOL_INFO ) + max_sym_name ];
		PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)symbolBuffer;
		pSymbol->SizeOfStruct = sizeof( SYMBOL_INFO );
		pSymbol->MaxNameLen = max_sym_name;

		DWORD64 symDisplacement = 0; // Displacement of the input address,
		                             // relative to the start of the symbol

		IMAGEHLP_MODULE64 module = { sizeof( IMAGEHLP_MODULE64 ) };
		if ( SymGetModuleInfo64( m_hProcess, sf.AddrPC.Offset, &module ) ) {
			outputStream << module.ModuleName << "!";

			if ( SymFromAddr( m_hProcess, sf.AddrPC.Offset, &symDisplacement, pSymbol ) ) {
				char undecoratedName[max_sym_name];
				UnDecorateSymbolName( pSymbol->Name, undecoratedName, max_sym_name, UNDNAME_COMPLETE );

				outputStream << undecoratedName;

				outputStream << "(";
				// Use SymSetContext to get just the locals/params for this frame
				IMAGEHLP_STACK_FRAME imagehlpStackFrame;
				imagehlpStackFrame.InstructionOffset = sf.AddrPC.Offset;
				SymSetContext( m_hProcess, &imagehlpStackFrame, 0 );

				// Enumerate the locals/parameters
				EnumerateSymbolsContext context( sf, outputStream );
				SymEnumSymbols( m_hProcess, 0, 0, EnumerateSymbolsCallback, &context );
				outputStream << ")";

				outputStream << " + " << Offset( reinterpret_cast<void*>( symDisplacement ) );

				// Get the source line for this stack frame entry
				IMAGEHLP_LINE64 lineInfo = { sizeof( IMAGEHLP_LINE64 ) };
				DWORD dwLineDisplacement;
				if ( SymGetLineFromAddr64( m_hProcess, sf.AddrPC.Offset,
										   &dwLineDisplacement, &lineInfo ) ) {
					outputStream << " " << lineInfo.FileName << " line " << Unsigned( lineInfo.LineNumber );
				}
			}
			else
			{
				outputStream << Address( reinterpret_cast<void*>( sf.AddrPC.Offset ) );
			}
		}

		outputStream << "\n";
	}

	SymCleanup( m_hProcess );

	return;
}

void write_stack_trace( TextOutputStream& outputStream ){
	__try { RaiseException( 0,0,0,0 ); } __except( write_stack_trace( ( GetExceptionInformation() )->ContextRecord, outputStream ), EXCEPTION_CONTINUE_EXECUTION ) {
	}
}

#elif GDEF_OS_WINDOWS
void write_stack_trace( TextOutputStream& outputStream ){
	outputStream << "\nStacktrace is disabled on this compiler\n";
}
#else
void write_stack_trace( TextOutputStream& outputStream ){
	outputStream << "\nStacktrace is disabled on this platform\n";
}
#endif
