/*
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 "stream/textstream.h"

#include "environment.h"

#ifdef __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 defined (WIN32) && defined (_MSC_VER)

#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 defined (WIN32)
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
