/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



/* system headers */
#include "system.h"
#include <tchar.h>

#include "file_url.h"
#include "path_helper.hxx"

#include <osl/diagnose.h>
#include <osl/mutex.h>
#include <osl/signal.h>
#ifndef __MINGW32__
#include <DbgHelp.h>
#endif
#include <ErrorRep.h>
#include <systools/win32/uwinapi.h>
#include <eh.h>
#include <stdexcept>

typedef struct _oslSignalHandlerImpl
{
	oslSignalHandlerFunction      Handler;
	void*			        	  pData;
	struct _oslSignalHandlerImpl* pNext;	
} oslSignalHandlerImpl;

static sal_Bool				  bErrorReportingEnabled = sal_True;
static sal_Bool  			  bInitSignal = sal_False;
static oslMutex 			  SignalListMutex;
static oslSignalHandlerImpl*  SignalList;

static long WINAPI SignalHandlerFunction(LPEXCEPTION_POINTERS lpEP);

static sal_Bool InitSignal(void)
{
	HMODULE	hFaultRep;

	SignalListMutex = osl_createMutex();

	SetUnhandledExceptionFilter(SignalHandlerFunction);

	hFaultRep = LoadLibrary( "faultrep.dll" );
	if ( hFaultRep )
	{
#ifdef __MINGW32__
typedef BOOL (WINAPI *pfn_ADDEREXCLUDEDAPPLICATIONW)(LPCWSTR);
#endif
		pfn_ADDEREXCLUDEDAPPLICATIONW		pfn = (pfn_ADDEREXCLUDEDAPPLICATIONW)GetProcAddress( hFaultRep, "AddERExcludedApplicationW" );
		if ( pfn )
			pfn( L"SOFFICE.EXE" );
		FreeLibrary( hFaultRep );
	}

	return sal_True;
}

static sal_Bool DeInitSignal(void)
{
	SetUnhandledExceptionFilter(NULL);

	osl_destroyMutex(SignalListMutex);

	return sal_False;
}

static oslSignalAction CallSignalHandler(oslSignalInfo *pInfo)
{
	oslSignalHandlerImpl* pHandler = SignalList;
	oslSignalAction Action = osl_Signal_ActCallNextHdl;

	while (pHandler != NULL)
	{
		if ((Action = pHandler->Handler(pHandler->pData, pInfo)) != osl_Signal_ActCallNextHdl)
			break;

		pHandler = pHandler->pNext;
	}

	return Action;
}

/*****************************************************************************/
/* SignalHandlerFunction	*/
/*****************************************************************************/

#define REPORTENV_PARAM		"-crashreportenv:"
#define REPORTENV_PARAM2	"/crashreportenv:"

static BOOL ReportCrash( LPEXCEPTION_POINTERS lpEP )
{
	BOOL	fSuccess = FALSE;
	BOOL	fAutoReport = FALSE;
	TCHAR	szBuffer[1024];
    ::osl::LongPathBuffer< sal_Char > aPath( MAX_LONG_PATH );
	LPTSTR	lpFilePart;
	PROCESS_INFORMATION	ProcessInfo;
	STARTUPINFO	StartupInfo;
	int		argi;

	if ( !bErrorReportingEnabled )
		return FALSE;

	/* Check if crash reporter was disabled by command line */

	for ( argi = 1; argi < __argc; argi++ )
	{
		if ( 
			0 == stricmp( __argv[argi], "-nocrashreport" ) || 
			0 == stricmp( __argv[argi], "/nocrashreport" )
			)
			return FALSE;
		else if (
			0 == stricmp( __argv[argi], "-autocrashreport" ) || 
			0 == stricmp( __argv[argi], "/autocrashreport" )
			)
			fAutoReport = TRUE;
		else if (
			0 == strnicmp( __argv[argi], REPORTENV_PARAM, strlen(REPORTENV_PARAM) ) || 
			0 == strnicmp( __argv[argi], REPORTENV_PARAM2, strlen(REPORTENV_PARAM2) )
			)
		{
			const char *envparam = __argv[argi] + strlen(REPORTENV_PARAM);
			const char *delim = strchr(envparam, '=' );

			if ( delim )
			{
				CHAR	*lpVariable;
				CHAR	*lpValue;
				const char *variable = envparam;
				size_t variable_len = delim - envparam;
				const char *value = delim + 1;
				size_t value_len = strlen(envparam) - variable_len - 1;
				
				if ( '\"' == *value )
				{
					const char *quote;

					value++;
					value_len--;

					quote = strchr( value, '\"' );
					if ( quote )
						value_len = quote - value;
				}

				lpVariable = reinterpret_cast< CHAR* >( _alloca( variable_len + 1 ) );
				memcpy( lpVariable, variable, variable_len );
				lpVariable[variable_len] = 0;

				lpValue = reinterpret_cast< CHAR* >( _alloca( value_len + 1) );
				memcpy( lpValue, value, value_len );
				lpValue[value_len] = 0;

				SetEnvironmentVariable( lpVariable, lpValue ); 
			}
		}
	}

	if ( SearchPath( NULL, TEXT( "crashrep.exe" ), NULL, aPath.getBufSizeInSymbols(), aPath, &lpFilePart ) )
	{
		ZeroMemory( &StartupInfo, sizeof(StartupInfo) );
		StartupInfo.cb = sizeof(StartupInfo.cb);


		sntprintf( szBuffer, elementsof(szBuffer), 
			_T("%s -p %u -excp 0x%p -t %u%s"), 
			static_cast<sal_Char*>( aPath ), 
			GetCurrentProcessId(), 
			lpEP, 
			GetCurrentThreadId(),
			fAutoReport ? _T(" -noui -send") : _T(" -noui") );

		if ( 
			CreateProcess( 
				NULL, 
				szBuffer, 
				NULL, 
				NULL, 
				FALSE, 
#ifdef UNICODE
				CREATE_UNICODE_ENVIRONMENT, 
#else
				0,
#endif
				NULL, NULL, &StartupInfo, &ProcessInfo ) 
			)
		{
			DWORD	dwExitCode;

			WaitForSingleObject( ProcessInfo.hProcess, INFINITE );
			if ( GetExitCodeProcess( ProcessInfo.hProcess, &dwExitCode ) && 0 == dwExitCode )
			
			fSuccess = TRUE;

		}
	}

	return fSuccess;
}

/*****************************************************************************/
/* SignalHandlerFunction	*/
/*****************************************************************************/

static BOOL WINAPI IsWin95A(void)
{
	OSVERSIONINFO	ovi;

	ZeroMemory( &ovi, sizeof(ovi) );
	ovi.dwOSVersionInfoSize = sizeof(ovi);

	if ( GetVersionEx( &ovi ) )
		/* See MSDN January 2000 documentation of GetVersionEx */
		return	(ovi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) && 
				(ovi.dwMajorVersion <= 4) && 
				(ovi.dwMinorVersion == 0) && 
				(ovi.dwBuildNumber == 0x040003B6);

	/* Something wrent wrong. So assume we have an older operating prior Win95 */

	return TRUE;
}

/* magic Microsoft C++ compiler exception constant */
#define EXCEPTION_MSC_CPP_EXCEPTION 0xe06d7363

static long WINAPI SignalHandlerFunction(LPEXCEPTION_POINTERS lpEP)
{
	static sal_Bool		bNested = sal_False;
	sal_Bool		bRaiseCrashReporter = sal_False;
	oslSignalInfo	Info;
	oslSignalAction	Action;

	Info.UserSignal = lpEP->ExceptionRecord->ExceptionCode;
	Info.UserData   = NULL;

	switch (lpEP->ExceptionRecord->ExceptionCode)
	{
        /* Transform unhandled exceptions into access violations.
		   Microsoft C++ compiler (add more for other compilers if necessary).
		 */
    	case EXCEPTION_MSC_CPP_EXCEPTION:
		case EXCEPTION_ACCESS_VIOLATION: 
			Info.Signal = osl_Signal_AccessViolation;
			bRaiseCrashReporter = sal_True;
			break; 

		case EXCEPTION_INT_DIVIDE_BY_ZERO: 
			Info.Signal = osl_Signal_IntegerDivideByZero;
			bRaiseCrashReporter = sal_True;
			break; 

		case EXCEPTION_FLT_DIVIDE_BY_ZERO: 
			Info.Signal = osl_Signal_FloatDivideByZero;
			bRaiseCrashReporter = sal_True;
			break; 

		case EXCEPTION_BREAKPOINT: 
			Info.Signal = osl_Signal_DebugBreak;
			break; 
			
		default:
			Info.Signal = osl_Signal_System;
			bRaiseCrashReporter = sal_True;
			break; 
	}

	if ( !bNested )
	{
		bNested = sal_True;

		if ( bRaiseCrashReporter && ReportCrash( lpEP ) || IsWin95A() )
		{
			CallSignalHandler(&Info);
			Action = osl_Signal_ActKillApp;
		}
		else
			Action = CallSignalHandler(&Info);
	}
	else
		Action = osl_Signal_ActKillApp;


	switch ( Action )
	{
		case osl_Signal_ActCallNextHdl:
			return (EXCEPTION_CONTINUE_SEARCH);

		case osl_Signal_ActAbortApp:
			return (EXCEPTION_EXECUTE_HANDLER);

		case osl_Signal_ActKillApp:
			SetErrorMode(SEM_NOGPFAULTERRORBOX);
			exit(255);
			break;
        default:
            break;
	}

	return (EXCEPTION_CONTINUE_EXECUTION);
}

/*****************************************************************************/
/* osl_addSignalHandler */
/*****************************************************************************/
oslSignalHandler SAL_CALL osl_addSignalHandler(oslSignalHandlerFunction Handler, void* pData)
{
	oslSignalHandlerImpl* pHandler;

	OSL_ASSERT(Handler != NULL);

	if (! bInitSignal)
		bInitSignal = InitSignal();

	pHandler = reinterpret_cast< oslSignalHandlerImpl* >( calloc( 1, sizeof(oslSignalHandlerImpl) ) );

	if (pHandler != NULL)
	{	
		pHandler->Handler = Handler;
		pHandler->pData   = pData;

		osl_acquireMutex(SignalListMutex);
	
		pHandler->pNext = SignalList;
		SignalList      = pHandler;

		osl_releaseMutex(SignalListMutex);

		return (pHandler);
	}

	return (NULL);
}

/*****************************************************************************/
/* osl_removeSignalHandler */
/*****************************************************************************/
sal_Bool SAL_CALL osl_removeSignalHandler(oslSignalHandler Handler)
{
	oslSignalHandlerImpl *pHandler, *pPrevious = NULL;

	OSL_ASSERT(Handler != NULL);

	if (! bInitSignal)
		bInitSignal = InitSignal();

	osl_acquireMutex(SignalListMutex);

	pHandler = SignalList;

	while (pHandler != NULL)
	{
		if (pHandler == Handler)
		{
			if (pPrevious)
				pPrevious->pNext = pHandler->pNext;
			else
				SignalList = pHandler->pNext;

			osl_releaseMutex(SignalListMutex);

			if (SignalList == NULL)
				bInitSignal = DeInitSignal();

			free(pHandler);

			return (sal_True);
		}

		pPrevious = pHandler;
		pHandler  = pHandler->pNext;
	}
			
	osl_releaseMutex(SignalListMutex);

	return (sal_False);
}

/*****************************************************************************/
/* osl_raiseSignal */
/*****************************************************************************/
oslSignalAction SAL_CALL osl_raiseSignal(sal_Int32 UserSignal, void* UserData)
{
	oslSignalInfo   Info;
	oslSignalAction Action;

	if (! bInitSignal)
		bInitSignal = InitSignal();

	osl_acquireMutex(SignalListMutex);

	Info.Signal     = osl_Signal_User;
	Info.UserSignal = UserSignal;
	Info.UserData   = UserData;

	Action = CallSignalHandler(&Info);
			
	osl_releaseMutex(SignalListMutex);

	return (Action);
}

/*****************************************************************************/
/* osl_setErrorReporting */
/*****************************************************************************/

void win_seh_translator( unsigned nSEHCode, _EXCEPTION_POINTERS* pExcPtrs)
{
	(void*)pExcPtrs; // currently unused, but useful inside a debugger
	const char* pSEHName = NULL;
	switch( nSEHCode)
	{
		case EXCEPTION_ACCESS_VIOLATION:         pSEHName = "SEH Exception: ACCESS VIOLATION"; break;
		case EXCEPTION_DATATYPE_MISALIGNMENT:    pSEHName = "SEH Exception: DATATYPE MISALIGNMENT"; break;
		case EXCEPTION_BREAKPOINT:               /*pSEHName = "SEH Exception: BREAKPOINT";*/ break;
		case EXCEPTION_SINGLE_STEP:              /*pSEHName = "SEH Exception: SINGLE STEP";*/ break;
		case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:    pSEHName = "SEH Exception: ARRAY BOUNDS EXCEEDED"; break;
		case EXCEPTION_FLT_DENORMAL_OPERAND:     pSEHName = "SEH Exception: DENORMAL FLOAT OPERAND"; break;
		case EXCEPTION_FLT_DIVIDE_BY_ZERO:       pSEHName = "SEH Exception: FLOAT DIVIDE_BY_ZERO"; break;
		case EXCEPTION_FLT_INEXACT_RESULT:       pSEHName = "SEH Exception: FLOAT INEXACT RESULT"; break;
		case EXCEPTION_FLT_INVALID_OPERATION:    pSEHName = "SEH Exception: INVALID FLOAT OPERATION"; break;
		case EXCEPTION_FLT_OVERFLOW:             pSEHName = "SEH Exception: FLOAT OVERFLOW"; break;
		case EXCEPTION_FLT_STACK_CHECK:          pSEHName = "SEH Exception: FLOAT STACK_CHECK"; break;
		case EXCEPTION_FLT_UNDERFLOW:            pSEHName = "SEH Exception: FLOAT UNDERFLOW"; break;
		case EXCEPTION_INT_DIVIDE_BY_ZERO:       pSEHName = "SEH Exception: INTEGER DIVIDE_BY_ZERO"; break;
		case EXCEPTION_INT_OVERFLOW:             pSEHName = "SEH Exception: INTEGER OVERFLOW"; break;
		case EXCEPTION_PRIV_INSTRUCTION:         pSEHName = "SEH Exception: PRIVILEDGED INSTRUCTION"; break;
		case EXCEPTION_IN_PAGE_ERROR:            pSEHName = "SEH Exception: IN_PAGE_ERROR"; break;
		case EXCEPTION_ILLEGAL_INSTRUCTION:      pSEHName = "SEH Exception: ILLEGAL INSTRUCTION"; break;
		case EXCEPTION_NONCONTINUABLE_EXCEPTION: pSEHName = "SEH Exception: NONCONTINUABLE EXCEPTION"; break;
		case EXCEPTION_STACK_OVERFLOW:           pSEHName = "SEH Exception: STACK OVERFLOW"; break;
		case EXCEPTION_INVALID_DISPOSITION:      pSEHName = "SEH Exception: INVALID DISPOSITION"; break;
		case EXCEPTION_GUARD_PAGE:               pSEHName = "SEH Exception: GUARD PAGE"; break;
		case EXCEPTION_INVALID_HANDLE:           pSEHName = "SEH Exception: INVALID HANDLE"; break;
//		case EXCEPTION_POSSIBLE_DEADLOCK:        pSEHName = "SEH Exception: POSSIBLE DEADLOCK"; break;
		default:                                 pSEHName = "Unknown SEH Exception"; break;
	}

	if( pSEHName)
		throw std::runtime_error( pSEHName);
}

sal_Bool SAL_CALL osl_setErrorReporting( sal_Bool bEnable )
{
	sal_Bool bOld = bErrorReportingEnabled;
	bErrorReportingEnabled = bEnable;

	if( !bEnable) // if the crash reporter is disabled
	{
		// fall back to handle Window's SEH events as C++ exceptions
		_set_se_translator( win_seh_translator);
	}

	return bOld;
}
