/* ====================================================================
 * Copyright (c) 2003-2006, 2008  Martin Hauner
 *                                http://subcommander.tigris.org
 *
 * Subcommander is licensed as described in the file doc/COPYING, which
 * you should have received as part of this distribution.
 * ====================================================================
 */

// sc
#include "Stacktrace.h"
#include "StackWalk.h"
#include "StdException.h"
#include "sublib/version.out.h"
#include "sublib/Utility.h"
#include "util/apr.h"
#include "util/ExceptionHandler.h"
#include "util/Stackframe.h"

// qt
#include <QtCore/QString>

// apr
#include <apr_time.h>
#include <apr_strings.h>
#include <apr_file_io.h>

// sys
#include <windows.h>
#include <eh.h>
#include <dbghelp.h>


// set_se_tranlator handler
void __cdecl handleException( unsigned int e, EXCEPTION_POINTERS* pExp );

// our exception handler
LONG WINAPI ScUnhandledExceptionFilter( EXCEPTION_POINTERS* pExp );


// if set it is called from our exception handler with a stacktrace
// and the name of the crash dump.
static ExceptionHandler* _ExceptionHandler = NULL;

// member
bool Stacktrace::_dump = false;


void Stacktrace::setExceptionHandler( ExceptionHandler* handler )
{
  _ExceptionHandler = handler;
}

void Stacktrace::setupProcess()
{
  DWORD options = SymGetOptions();

  options = options
            | SYMOPT_DEFERRED_LOADS
            | SYMOPT_UNDNAME
            | SYMOPT_DEBUG
            | SYMOPT_LOAD_LINES;

  SymSetOptions( options );

  if( ! SymInitialize( GetCurrentProcess(),0,true) )
  {
    // error handling?
  }

  SetUnhandledExceptionFilter(ScUnhandledExceptionFilter);

  // surpress system application error dialog
  SetErrorMode( SEM_NOGPFAULTERRORBOX );
}

void Stacktrace::shutdownProcess()
{
  if( ! SymCleanup(GetCurrentProcess()) )
  {
    // error handling?
  }
}

void Stacktrace::setupThread()
{
  //_set_se_translator(handleException);
}

void Stacktrace::shutdownThread()
{
   //_set_se_translator(0);
}

void Stacktrace::setDump( bool dump )
{
  _dump = dump;
}

bool Stacktrace::getDump()
{
  return _dump;
}


///////////////////////////////////////////////////////////////////////

static sc::String sExceptions[] =
{
  sc::String("EXCEPTION_ACCESS_VIOLATION"),
  sc::String("EXCEPTION_DATATYPE_MISALIGNMENT"),
  sc::String("EXCEPTION_BREAKPOINT"),
  sc::String("EXCEPTION_SINGLE_STEP"),
  sc::String("EXCEPTION_ARRAY_BOUNDS_EXCEEDED"),
  sc::String("EXCEPTION_FLT_DENORMAL_OPERAND"),
  sc::String("EXCEPTION_FLT_DIVIDE_BY_ZERO"),
  sc::String("EXCEPTION_FLT_INEXACT_RESULT"),
  sc::String("EXCEPTION_FLT_INVALID_OPERATION"),
  sc::String("EXCEPTION_FLT_OVERFLOW"),
  sc::String("EXCEPTION_FLT_STACK_CHECK"),
  sc::String("EXCEPTION_FLT_UNDERFLOW"),
  sc::String("EXCEPTION_INT_DIVIDE_BY_ZERO"),
  sc::String("EXCEPTION_INT_OVERFLOW"),
  sc::String("EXCEPTION_PRIV_INSTRUCTION"),
  sc::String("EXCEPTION_IN_PAGE_ERROR"),
  sc::String("EXCEPTION_ILLEGAL_INSTRUCTION"),
  sc::String("EXCEPTION_NONCONTINUABLE_EXCEPTION"),
  sc::String("EXCEPTION_STACK_OVERFLOW"),
  sc::String("EXCEPTION_INVALID_DISPOSITION"),
  sc::String("EXCEPTION_GUARD_PAGE"),
  sc::String("EXCEPTION_INVALID_HANDLE"),
  sc::String("EXCEPTION_UNKNOWN")
};

static const sc::String& strException(  unsigned int e )
{
  switch( e )
  {
    case EXCEPTION_ACCESS_VIOLATION:         return sExceptions[0];
    case EXCEPTION_DATATYPE_MISALIGNMENT:    return sExceptions[1];
    case EXCEPTION_BREAKPOINT:               return sExceptions[2];
    case EXCEPTION_SINGLE_STEP:              return sExceptions[3];
    case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:    return sExceptions[4];
    case EXCEPTION_FLT_DENORMAL_OPERAND:     return sExceptions[5];
    case EXCEPTION_FLT_DIVIDE_BY_ZERO:       return sExceptions[6];
    case EXCEPTION_FLT_INEXACT_RESULT:       return sExceptions[7];
    case EXCEPTION_FLT_INVALID_OPERATION:    return sExceptions[8];
    case EXCEPTION_FLT_OVERFLOW:             return sExceptions[9];
    case EXCEPTION_FLT_STACK_CHECK:          return sExceptions[10];
    case EXCEPTION_FLT_UNDERFLOW:            return sExceptions[11];
    case EXCEPTION_INT_DIVIDE_BY_ZERO:       return sExceptions[12];
    case EXCEPTION_INT_OVERFLOW:             return sExceptions[13];
    case EXCEPTION_PRIV_INSTRUCTION:         return sExceptions[14];
    case EXCEPTION_IN_PAGE_ERROR:            return sExceptions[15];
    case EXCEPTION_ILLEGAL_INSTRUCTION:      return sExceptions[16];
    case EXCEPTION_NONCONTINUABLE_EXCEPTION: return sExceptions[17];
    case EXCEPTION_STACK_OVERFLOW:           return sExceptions[18];
    case EXCEPTION_INVALID_DISPOSITION:      return sExceptions[19];
    case EXCEPTION_GUARD_PAGE:               return sExceptions[20];
    case EXCEPTION_INVALID_HANDLE:           return sExceptions[21];
    default: return sExceptions[22];
  }
}

///////////////////////////////////////////////////////////////////////

static void WriteDump( apr_pool_t* pool, const char* name, EXCEPTION_POINTERS* pExp )
{
  HANDLE file = CreateFile( name, FILE_ALL_ACCESS, 0, 0, CREATE_ALWAYS,
    FILE_ATTRIBUTE_NORMAL, 0 );

  if( file != INVALID_HANDLE_VALUE )
  {
    MINIDUMP_EXCEPTION_INFORMATION mee;
    mee.ThreadId          = GetCurrentThreadId();
    mee.ExceptionPointers = pExp;
    mee.ClientPointers    = TRUE;

    BOOL success = MiniDumpWriteDump( GetCurrentProcess(), GetCurrentProcessId(),
      file, (MINIDUMP_TYPE)(
      MiniDumpNormal
      | MiniDumpWithProcessThreadData
      | MiniDumpWithIndirectlyReferencedMemory
      // too large.. | MiniDumpWithFullMemory
      ), &mee, 0, 0 );

    CloseHandle(file);
  }
}

static char* WriteVariable( apr_pool_t* pool, char* text, const Variable& var )
{
  char* temp;

  if( var._type == vtInt )
  {
    temp = apr_psprintf( pool, "int %s = 0x%08x", (const char*)var._name, (int)var, 0 );
    text = apr_pstrcat( pool, text, "    ", temp, "\n", 0 );
    return text;
  }

  return text;
}

static void WriteCrashInfo( apr_pool_t* pool, const char* name, EXCEPTION_POINTERS* pExp )
{
  char* text;
  char* temp;

  // program info
  QString sbj = QString("%1 [%2]").arg(getLongAppName()).arg(SUBCOMMANDER_VERSION);
  sc::String prog(sbj.toUtf8());
  text = apr_pstrcat( pool, (const char*)prog, "\n\n", 0 );

  // command line
  const char *cmdLine = GetCommandLine();
  text = apr_pstrcat( pool, text, "Command Line:\n", cmdLine, "\n\n", 0 );

  // version info
  OSVERSIONINFO vi;
  vi.dwOSVersionInfoSize = sizeof(vi);
  GetVersionEx(&vi);

  text = apr_pstrcat( pool, text, "Platform Info:\n", 0 );
  temp = apr_psprintf( pool, "Windows %d.%d (build #%d) %s\n\n",
    vi.dwMajorVersion, vi.dwMinorVersion, vi.dwBuildNumber, vi.szCSDVersion);
  text = apr_pstrcat( pool, text, temp, "\n\n", 0 );

  // exception info
  sc::String type = strException(pExp->ExceptionRecord->ExceptionCode);
  text = apr_pstrcat( pool, text, (const char*)type, "\n\n", 0 );

  // stack info
  Stackwalk stack(pExp);
  stack.walk();
  const Stackframes& frames = stack.getFrames();

  for( Stackframes::const_iterator it = frames.begin(); it != frames.end(); it++ )
  {
    const Stackframe& f = *it;

    if(!f._error)
    {
      temp = apr_psprintf( pool, "0x%016" APR_UINT64_T_HEX_FMT, f._addr );
      text = apr_pstrcat( pool, text, temp, 0 );

      if(f._symbol)
      {
        text = apr_pstrcat( pool, text, " : ", (const char*)f._symbolName, 0 );
      }
      else
      {
        text = apr_pstrcat( pool, text, " : ", "unknown symbol", 0 );
      }

      if(f._module)
      {
        text = apr_pstrcat( pool, text, " (", (const char*)f._moduleName, ")", 0 );
      }
      else
      {
        text = apr_pstrcat( pool, text, " (", "unknown module", ")", 0 );
      }

      text = apr_pstrcat( pool, text, "\n  ", 0 );

      if(f._line)
      {
        temp = apr_psprintf( pool, "%ld + %04d (%s)\n", f._lineNr, f._lineDisp,
          (const char*)f._fileName );
        text = apr_pstrcat( pool, text, temp, 0 );
      }

      if( f._params.size() > 0 )
        text = apr_pstrcat( pool, text, "  parameters:\n", 0 ); 

      for( Parameters::const_iterator it = f._params.begin(); it != f._params.end(); it++ )
      {
        text = WriteVariable( pool, text, *it );
      }

      if( f._locals.size() > 0 )
        text = apr_pstrcat( pool, text, "  locals:\n", 0 );

      for( Locals::const_iterator it = f._locals.begin(); it != f._locals.end(); it++ )
      {
        text = WriteVariable( pool, text, *it );
      }
    }

    text = apr_pstrcat( pool, text, "\n", 0 );
  }

  printf("%s", text);

  apr_file_t* file;
  apr_status_t status;
  apr_size_t size = strlen(text);
  status = apr_file_open( &file, name, APR_CREATE|APR_WRITE, APR_OS_DEFAULT, pool );
  status = apr_file_write( file, text, &size );
  status = apr_file_close( file );
}

// set_se_translator
void __cdecl handleException( unsigned int e, EXCEPTION_POINTERS* pExp )
{
#if 0
  throw StdException(...);
#endif
}

// Note that this is NOT called when we run in the debugger.
LONG WINAPI ScUnhandledExceptionFilter( EXCEPTION_POINTERS* pExp )
{
  //__try
  {
    apr_pool_t*  pool    = apr::createPool();
    apr_time_t   now     = apr_time_now();
    char*        nowstr  = apr_psprintf( pool, "%" APR_TIME_T_FMT, now );
    const char*  tempdir = 0;
    apr_status_t status  = apr_temp_dir_get( &tempdir, pool );
    char*        tempdmp = apr_pstrcat( pool, tempdir, "\\sc-", nowstr, ".dmp", 0 );
    char*        temptxt = apr_pstrcat( pool, tempdir, "\\sc-", nowstr, ".txt", 0 );

    WriteDump( pool, tempdmp, pExp );
    WriteCrashInfo( pool, temptxt, pExp );

    if( _ExceptionHandler != NULL )
    {
      // get clean paths without ~ parts.
      char dmp[MAX_PATH+1] = {};
      char txt[MAX_PATH+1] = {};
      GetLongPathName( tempdmp, dmp, MAX_PATH );
      GetLongPathName( temptxt, txt, MAX_PATH );

      _ExceptionHandler->handleException( sc::String(txt), sc::String(dmp) );
    }

    apr::destroyPool(pool);
  }
  //__except( EXCEPTION_EXECUTE_HANDLER )
  {
    //return EXCEPTION_CONTINUE_SEARCH;
  }

  //return EXCEPTION_EXECUTE_HANDLER;
  return EXCEPTION_CONTINUE_SEARCH;
}
