/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

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

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
/*****************************************************************************
*
*	$/Plasma20/Sources/Plasma/NucleusLib/pnCrashExe/pnCreError.cpp
*	
***/

#include "../Pch.h"
#pragma	hdrstop


namespace Crash	{
/*****************************************************************************
*
*	Private
*
***/

struct Module {
	LINK(Module)	link;
	
	unsigned_ptr	address;
	unsigned		buildId;
	unsigned		branchId;
	wchar *			name;
	wchar *			buildString;
	
	~Module	() { FREE(name); FREE(buildString); }
};

struct EmailParams : AtomicRef {
	char *			smtp;
	char *			sender;
	char *			recipients;
	char *			username;
	char *			password;
	char *			replyTo;
	
	~EmailParams () {
		FREE(smtp);
		FREE(sender);
		FREE(recipients);
		FREE(username);
		FREE(password);
		FREE(replyTo);
	}
};

struct ErrLog {
	unsigned	pos;
	wchar		name[MAX_PATH];
	char		buffer[512*1024];
	char		terminator;
};

struct DeadlockCheck {
	LINK(DeadlockCheck)	link;
	HANDLE				thread;
	unsigned			deadlockEmailMs;
	unsigned			deadlockTerminateMs;
	bool				deadlocked;
	bool				emailSent;
	wchar				debugStr[256];
};


/*****************************************************************************
*
*	Private	data
*
***/

// Assertion results from ProcessErrorLog()
const unsigned kErrFlagAssertionBreakpoint	= 0x01;
const unsigned kErrFlagAssertionExitProgram	= 0x02;

// Exception results from ProcessErrorLog()
const unsigned kErrFlagExceptionBreakpoint  = 0x04;
const unsigned kErrFlagExceptionExecHandler	= 0x08;

static const char	s_unknown[]			= "*unknown*";
static const char	s_sectionFmt_s[]	= "--------> %s <--------\r\n";

static const unsigned kStackDumpBytes	= 1024;
static const unsigned kCodeDumpBytes	= 64;

static unsigned							s_deadlockEmailMs;		// sends an email if a thread exceeds this time. If set to zero this is disabled		
static unsigned							s_deadlockTerminateMs;	// kills the process if a thread exceeds this time. If set to zero this is disabled
static CCritSect *						s_critsect;
static LISTDECL(Module,	link)			s_modules;
static EmailParams *					s_params;
static bool								s_running;

static bool								s_deadlockEnabled;
static unsigned							s_nextDeadlockCheck;
static LISTDECL(DeadlockCheck, link)	s_deadlockList;

static CCritSect						s_spareCrit;
static TSpareList<DeadlockCheck>		s_deadlockSpares;


#define SAFE_CRITSECT_ENTER()	if (s_critsect) s_critsect->Enter()
#define SAFE_CRITSECT_LEAVE()	if (s_critsect) s_critsect->Leave()


/*****************************************************************************
*
*	Internal functions
*
***/

//============================================================================
static void DelayDeadlockChecking () {
	// Delay deadlock checking for the next 2 minutes
	s_nextDeadlockCheck = 1 | (TimeGetMs() + 2 * 60 * 1000);
}

//============================================================================
static void	ReplaceEmailParams (EmailParams	* newParams) {

	if (newParams)
		newParams->IncRef();	
	
	SAFE_CRITSECT_ENTER();
	{
		SWAP(newParams,	s_params);
	}
	SAFE_CRITSECT_LEAVE();
	
	if (newParams)
		newParams->DecRef();
}

//============================================================================
static EmailParams * GetEmailParamsIncRef () {
	ref(GetEmailParamsIncRef);

	EmailParams	* params;
	
	SAFE_CRITSECT_ENTER();
	{
		if (nil	!= (params = s_params))
			params->IncRef();
	}
	SAFE_CRITSECT_LEAVE();
	
	return params;
};

//============================================================================
#ifdef _M_IX86
static void __declspec(naked) CrashFunc () {

	*(int *) 0 = 0;
}
#endif // def _M_IX86

//============================================================================
#ifdef _M_IX86
static void MakeThreadCrashOnResume (HANDLE handle) {
	
	// Point the thread's instruction pointer to CrashFunc 
	// so that it will crash once we resume it.
	CONTEXT context;
	context.ContextFlags = CONTEXT_CONTROL;
	GetThreadContext(handle, &context);
	context.Eip = (DWORD) CrashFunc;
	SetThreadContext(handle, &context);
}
#endif // def _M_IX86

//============================================================================
static inline const	char * GetExceptionString (unsigned	code) {
	#if	defined(HS_DEBUGGING) || defined(SERVER)
	switch (code) {
		#define	EXCEPTION(x) case EXCEPTION_##x: return	#x;
		EXCEPTION(ACCESS_VIOLATION)
		EXCEPTION(DATATYPE_MISALIGNMENT)
		EXCEPTION(BREAKPOINT)
		EXCEPTION(SINGLE_STEP)
		EXCEPTION(ARRAY_BOUNDS_EXCEEDED)
		EXCEPTION(FLT_DENORMAL_OPERAND)
		EXCEPTION(FLT_DIVIDE_BY_ZERO)
		EXCEPTION(FLT_INEXACT_RESULT)
		EXCEPTION(FLT_INVALID_OPERATION)
		EXCEPTION(FLT_OVERFLOW)
		EXCEPTION(FLT_STACK_CHECK)
		EXCEPTION(FLT_UNDERFLOW)
		EXCEPTION(INT_DIVIDE_BY_ZERO)
		EXCEPTION(INT_OVERFLOW)
		EXCEPTION(PRIV_INSTRUCTION)
		EXCEPTION(IN_PAGE_ERROR)
		EXCEPTION(ILLEGAL_INSTRUCTION)
		EXCEPTION(NONCONTINUABLE_EXCEPTION)
		EXCEPTION(STACK_OVERFLOW)
		EXCEPTION(INVALID_DISPOSITION)
		EXCEPTION(GUARD_PAGE)
		EXCEPTION(INVALID_HANDLE)
		#undef EXCEPTION

		default:
		return s_unknown;
	}
	#else	// if defined(HS_DEBUGGING)	|| defined(SERVER)
	{
		ref(code);
		return "";
	}
	#endif	// if defined(HS_DEBUGGING)	|| defined(SERVER)
}

//============================================================================
static void LogWriteToDisk (ErrLog * const log) {

	HANDLE file = CreateFileW(
		log->name,
		GENERIC_WRITE,
		FILE_SHARE_READ,
		(LPSECURITY_ATTRIBUTES) nil,
		OPEN_ALWAYS,
		FILE_ATTRIBUTE_NORMAL,
		(HANDLE) nil
	);
	if (file != INVALID_HANDLE_VALUE) {
		DWORD bytesWritten;
		SetFilePointer(file, 0, 0, FILE_END);
		WriteFile(file, log->buffer, StrLen(log->buffer), &bytesWritten, 0);
		CloseHandle(file);
	}
}

//============================================================================
static unsigned ProcessErrorLog (
    ErrLog * const	log,
    const char		programName[],
    const char		errorType[]
) {
	ref(programName);
	ref(errorType);
	
	LogWriteToDisk(log);
	
	// Servers email the error and continue running	
	#ifdef SERVER
	{
		// @@@ TODO: Write log to file here
		
		if (EmailParams * params = GetEmailParamsIncRef()) {
			CrashSendEmail(
				params->smtp,
				params->sender,
				params->recipients,
				params->username,
				params->password,
				params->replyTo,
				programName,
				errorType,
				log->buffer
			);
			params->DecRef();
		}
		return kErrFlagAssertionBreakpoint | kErrFlagExceptionExecHandler;
	}
	
	// Client programs display an error dialog giving the user a choice of
	// sending the error or not
	#else
	{
		// Todo: make a dialog box to handle this
		return kErrFlagAssertionExitProgram | kErrFlagExceptionExecHandler;
	}
	#endif
}

//============================================================================
static unsigned ProcessBreakException () {

	#ifdef SERVER

		// Servers running as daemons should attempt to keep running
		if (ErrorGetOption(kErrOptNonGuiAsserts))
			return kErrFlagExceptionExecHandler;
		else
			return kErrFlagExceptionBreakpoint;

	#else

		return kErrFlagExceptionBreakpoint;

	#endif
}

//============================================================================
static ErrLog *	CreateErrLog () {
	// Allocate	log	memory
	ErrLog * log = (ErrLog *) VirtualAlloc(nil, sizeof(*log), MEM_COMMIT, PAGE_READWRITE);
	log->pos		= 0;
	log->terminator	= 0;

	// Initialize log filename
	wchar srcName[MAX_PATH];
	SYSTEMTIME currTime;
	GetLocalTime(&currTime);
	StrPrintf(
		srcName,
		arrsize(srcName),
		L"Crash%02u%02u%02u.log",
		currTime.wYear % 100,
		currTime.wMonth,
		currTime.wDay
	);

	// Set log directory + filename
	AsyncLogGetDirectory(log->name,	arrsize(log->name));
	PathAddFilename(log->name, log->name, srcName, arrsize(log->name));

	return log;
}

//============================================================================
static void	DestroyErrLog (ErrLog *	errLog)	{
	VirtualFree(errLog,	0, MEM_RELEASE);
}

//============================================================================
static void	__cdecl	LogPrintf (
	ErrLog * const	log,
	const char		fmt[],
	...
) {
	va_list	args;
	va_start(args, fmt);
	char * pos = log->buffer + log->pos;
	unsigned len = StrPrintfV(
		pos,
		arrsize(log->buffer) - log->pos,
		fmt,
		args
	);
	va_end(args);
	log->pos +=	len;
}

//============================================================================
static inline void LogSectionHeader	(
	ErrLog * const	log,
	const char		section[]
) {
	LogPrintf(log, s_sectionFmt_s, section);
}

//============================================================================
#ifdef _M_IX86
static void	LogStackWalk (
	ErrLog * const		log,
	const CImageHelp &	ih,	
	HANDLE				hThread, 
	const CONTEXT &		ctx
) {
	STACKFRAME stk;
	ZeroMemory(&stk, sizeof(stk));
	stk.AddrPC.Offset		= ctx.Eip;
	stk.AddrPC.Mode			= AddrModeFlat;
	stk.AddrStack.Offset	= ctx.Esp;
	stk.AddrStack.Mode		= AddrModeFlat;
	stk.AddrFrame.Offset	= ctx.Ebp;
	stk.AddrFrame.Mode		= AddrModeFlat;

	LogSectionHeader(log, "Trace");
	for	(unsigned i	= 0; i < 100; i++) {
		const bool result =	ih.StackWalk(
			IMAGE_FILE_MACHINE_I386,
			ih.Process(),
			hThread,
			&stk,
			nil,	// context
			nil,	// read	memory routine
			ih.SymFunctionTableAccess,
			ih.SymGetModuleBase,
			nil	// translate address routine
		);
		if (!result)
			break;

		LogPrintf(
			log,
			"Pc:%08x Fr:%08x Rt:%08x Arg:%08x %08x %08x %08x ",
			stk.AddrPC.Offset,
			stk.AddrFrame.Offset,
			stk.AddrReturn.Offset,
			stk.Params[0],
			stk.Params[1],
			stk.Params[2],
			stk.Params[3]
		);

		LogPrintf(log, "\r\n");
	}

	LogPrintf(log, "\r\n");
}
#endif // _M_IX86

//============================================================================
static void	LogThread (
	ErrLog * const		log,
	const CImageHelp &	ih,
	HANDLE				hThread,
	const wchar			name[],
	const CONTEXT &		ctx
) {
	char threadName[256];
	StrPrintf(threadName, arrsize(threadName), "%SThread %#x", name, hThread);
	LogSectionHeader(log, threadName);
	
	LogPrintf(
		log,
		 "eax=%08lx ebx=%08lx ecx=%08lx edx=%08lx\r\n"
		 "esi=%08lx edi=%08lx\r\n"
		 "eip=%08lx esp=%08lx ebp=%08lx\r\n"
		 "cs=%04lx ss=%04lx ds=%04lx es=%04lx fs=%04lx gs=%04lx efl=%08lx\r\n\r\n",
		ctx.Eax, ctx.Ebx, ctx.Ecx, ctx.Edx,	ctx.Esi, ctx.Edi,
		ctx.Eip, ctx.Esp, ctx.Ebp, ctx.EFlags,
		ctx.SegCs, ctx.SegSs, ctx.SegDs, ctx.SegEs,	ctx.SegFs, ctx.SegGs, ctx.EFlags
	);

	#ifdef _M_IX86
	LogStackWalk(log, ih, hThread, ctx);
	#else
	ref(ih);
	ref(hThread);
	#endif
}

//============================================================================
static void	LogCrashInfo (
	const char			msg[],
	ErrLog * const		log,
	const CImageHelp &	ih
) {
	// Log application information
	{
		LogSectionHeader(log, "Program");
		
		wchar productIdStr[64];
		GuidToString(ProductId(), productIdStr, arrsize(productIdStr));
		
		wchar productBuildTag[128];
		ProductString(productBuildTag, arrsize(productBuildTag));
		
		SYSTEMTIME currtime;
		GetLocalTime(&currtime);
		LogPrintf(
			log,
			// beginning of	format string
			"App        : %s\r\n"
			"Build      : "
			#ifdef PLASMA_EXTERNAL_RELEASE
			"External "
			#else
			"Internal "
			#endif
			#ifdef HS_DEBUGGING
			"Debug"
			#else
			"Release"
			#endif
			"\r\n"
			"BuildMark  : %S\r\n"
			"ProductTag : %S\r\n"
			"ProductId  : %S\r\n"
			"Crashed    : %d/%d/%d %02d:%02d:%02d\r\n"
			"Msg        : %s\r\n"
			"\r\n",
			// end of format string
			ih.GetProgramName(),
			ProductBuildString(),
			productBuildTag,
			productIdStr,
			currtime.wMonth,
			currtime.wDay,
			currtime.wYear,
			currtime.wHour,
			currtime.wMinute,
			currtime.wSecond,
			msg
		);
	}

	// Log system information
	{
		LogSectionHeader(log, "System");

		char machineName[128];
		DWORD len =	arrsize(machineName);
		if (!GetComputerName(machineName, &len))
			StrCopy(machineName, s_unknown,	arrsize(machineName));
		LogPrintf(log, "Machine    : %s\r\n", machineName);

		for (;;) {
			WSADATA wsaData;
			if (WSAStartup(0x101, &wsaData) || (wsaData.wVersion != 0x101))
				break;

			wchar ipAddress[256];
			NetAddressNode addrNodes[16];
			unsigned addrCount = NetAddressGetLocal(16, addrNodes);
			LogPrintf(log, "IpAddrs    : ");
			for (unsigned i = 0; i < addrCount; ++i) {
				NetAddressNodeToString(addrNodes[i], ipAddress, arrsize(ipAddress));
				LogPrintf(log, "%S, ", ipAddress);
			}
			LogPrintf(log, "\r\n");

			WSACleanup();			
			break;
		}
		
		SYSTEM_INFO	si;
		GetSystemInfo(&si);
		DWORD ver =	GetVersion();

		LogPrintf(
			log,
			"OS Version : %u.%u\r\n"
			"CPU Count  : %u\r\n"
			"\r\n",
			LOBYTE(LOWORD(ver)), HIBYTE(LOWORD(ver)),
			si.dwNumberOfProcessors
	   );
	}

	// Log loaded modules
	{
		LogSectionHeader(log, "Modules");
		SAFE_CRITSECT_ENTER();
		for	(const Module *	p =	s_modules.Head(); p; p = p->link.Next()) {
			LogPrintf(
				log,
				"%p %S (%u.%u.%S)\r\n",
				p->address,
				p->name,
				p->buildId,
				p->branchId,
				p->buildString
			);
		}
		SAFE_CRITSECT_LEAVE();
		LogPrintf(log, "\r\n");
	}
}

//============================================================================
static LONG	ProcessException (const char occasion[], EXCEPTION_POINTERS * ep) {

	// log non-breakpont exceptions
	unsigned result;
	if (ep->ExceptionRecord->ExceptionCode != EXCEPTION_BREAKPOINT)	{

		// if this is a	stack fault, allocate a	new	stack so we	have
		// enough space	to log the error.
		static void	* newStack;
		#define	STACKBYTES (64 * 1024)
		if (ep->ExceptionRecord->ExceptionCode == STATUS_STACK_OVERFLOW) {
			newStack = VirtualAlloc(nil, STACKBYTES, MEM_COMMIT, PAGE_READWRITE);
			__asm {
				mov		eax, [newStack]			// get new memory block
				add		eax, STACKBYTES	- 4		// point to	end	of block
				mov		[eax], esp				// save	current	stack pointer
				mov		esp, eax				// set new stack pointer
			}
		}

		DelayDeadlockChecking();

		// Allocate	error log memory
		ErrLog * log = CreateErrLog();

		// get crash log data
		static const char s_exception[]	= "Exception";
		LogSectionHeader(log, "Crash");
		LogPrintf(
			log,
			"%s: %08x %s\r\n",
			s_exception,
			ep->ExceptionRecord->ExceptionCode,
			GetExceptionString(ep->ExceptionRecord->ExceptionCode)
		);
		if (ep->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
			LogPrintf(
				log,
				"Memory at address %08x could not be %s\r\n",
				ep->ExceptionRecord->ExceptionInformation[1],
				ep->ExceptionRecord->ExceptionInformation[0] ? "written" : "read"
			);
		}
		LogPrintf(log, "\r\n");
		
		CImageHelp ih((HINSTANCE)ModuleGetInstance());
		LogCrashInfo(occasion, log, ih);

		// log crashed thread
		LogThread(
			log,
			ih,
			GetCurrentThread(),
			L"",
			*ep->ContextRecord
		);

		// display the error
		result = ProcessErrorLog(
			log,
			ih.GetProgramName(),
			occasion
		);

		DestroyErrLog(log);
		ErrorSetOption(kErrOptDisableMemLeakChecking, true);

		// if this is a	stack overflow,	restore	original stack
		if (ep->ExceptionRecord->ExceptionCode == STATUS_STACK_OVERFLOW) {
			__asm {
				mov		eax, [newStack]			// get new memory block
				add		eax, STACKBYTES	- 4		// point to	end	of block
				mov		esp, [eax]				// restore old stack pointer
			}

			VirtualFree(newStack, 0, MEM_RELEASE);
		}
	}
	else {
		result = ProcessBreakException();
	}

	// If this is a	debug build	and	the	user pressed the "debug" button
	// then	we return EXCEPTION_CONTINUE_SEARCH	so the program will	
	// activate	just-in-time debugging,	or barring that, bring up the 
	// Microsoft error handler.
	if (result & kErrFlagExceptionBreakpoint)
		return EXCEPTION_CONTINUE_SEARCH;

	return EXCEPTION_EXECUTE_HANDLER;
}


//============================================================================
static LONG	WINAPI ExceptionFilter (EXCEPTION_POINTERS * ep) {
	ref(ExceptionFilter);
	
	LONG result	= ProcessException("Unhandled Exception", ep);

	// If the instruction pointer is inside	CrashFunc then this	exception
	// is a	deadlock that couldn't be resumed, so terminate	the	program.
	#ifdef _M_IX86
	if ((ep->ContextRecord->Eip	>= (DWORD) CrashFunc)
	&&	(ep->ContextRecord->Eip	<= (DWORD) CrashFunc + 5))
		TerminateProcess(GetCurrentProcess(), 1);
	#else
	# error	"ExceptionFilter not implemented for this CPU"
	#endif	// def _M_IX86

	return result;
}

//============================================================================
static void ProcessDeadlock_CS (const char occasion[], bool crashIt = true) {

	unsigned currTimeMs = TimeGetMs();
	unsigned crashCount = 0;

	// Suspend all threads so that we can dump their callstacks
	DeadlockCheck * next, * check = s_deadlockList.Head();
	for (; check; check = next) {
		next = s_deadlockList.Next(check);
		SuspendThread(check->thread);
	}
	
	// Allocate	error log memory
	ErrLog * log = CreateErrLog();

	// Log report header and system data
	CImageHelp ih((HINSTANCE)ModuleGetInstance());
	LogCrashInfo(occasion, log, ih);

	// Log all threads
	if (!s_deadlockList.Head())
		LogPrintf(log, "*** No threads queued for deadlock check ***\r\n\r\n");
		
	check = s_deadlockList.Head();
	for (; check; check = next) {
		next = s_deadlockList.Next(check);
        CONTEXT ctx;
        ctx.ContextFlags = CONTEXT_SEGMENTS | CONTEXT_INTEGER | CONTEXT_CONTROL;
        GetThreadContext(
            check->thread,
            &ctx
        );
		if (true == (check->deadlocked = (int)(check->deadlockEmailMs - currTimeMs) < 0) && !check->emailSent && s_deadlockEmailMs) {
			LogPrintf(log, "This thread has hit the min deadlock check time\r\n");
			check->emailSent = true;
		}
		else if (true == (check->deadlocked = (int)(check->deadlockTerminateMs - currTimeMs) < 0) && s_deadlockTerminateMs) {
			LogPrintf(log, "This thread has hit the max deadlock check time\r\n");
			if (crashIt) {
				MakeThreadCrashOnResume(check->thread);
				++crashCount;
			}
		}

		if(check->deadlocked) {
			LogPrintf(log, "Debug information: ");
			LogPrintf(log, "%S\r\n", check->debugStr);
		}

		LogThread(
			log,
			ih,
			check->thread,
			L"",
			ctx
		);
	}	

	(void) ProcessErrorLog(
		log,
		ih.GetProgramName(),
		occasion
	);
	
	DestroyErrLog(log);
	ErrorSetOption(kErrOptDisableMemLeakChecking, true);

	// Resume all threads
	unsigned elapsedMs = TimeGetMs() - currTimeMs;
	check = s_deadlockList.Head();
	for (; check; check = next) {
		next = s_deadlockList.Next(check);

		if (check->deadlocked && crashIt)
			// remove 'check' from list since we're crashing the thread on resume
			check->link.Unlink();
		else {
			// Update deadlock time to offset by the amount of time we had them suspended.
			check->deadlockEmailMs += elapsedMs;
			check->deadlockTerminateMs += elapsedMs;
		}

		ResumeThread(check->thread);
	}

	// Allow the resumed thread a bit of time to crash and
	// send the resulting email then terminate the process
	if (crashCount) {
		Sleep(60 * 1000);
		TerminateProcess(GetCurrentProcess(), 1);
	}	
}

//============================================================================
static void DeadlockCheckProc (void *) {
	ref(DeadlockCheckProc);

	while (s_running) {
		Sleep(5 * 1000);

		if (!s_deadlockEnabled)
			continue;

		unsigned currTimeMs = TimeGetMs();

		// Check for a forced delay in deadlock checking
		if (s_nextDeadlockCheck && (int)(s_nextDeadlockCheck - currTimeMs) > 0)
			continue;
		s_nextDeadlockCheck = 0;
		
		SAFE_CRITSECT_ENTER();
		for (;;) {
			DeadlockCheck * next, * check = s_deadlockList.Head();
			for (; check; check = next) {
				next = s_deadlockList.Next(check);
				if ((int)(check->deadlockEmailMs - currTimeMs) <= 0 && !check->emailSent && s_deadlockEmailMs) {
					// we have hit our minimum deadlock check time. Send and email but dont crash the process, yet.
					ProcessDeadlock_CS("DeadlockChecker", false);
					check->emailSent = true;
					break;
				}
				else if ((int)(check->deadlockTerminateMs - currTimeMs) <= 0 && s_deadlockTerminateMs){
					// we have hit out max deadlock check time. This will send an email and crash the process so the service can restart.
					ProcessDeadlock_CS("DeadlockChecker(Process Terminated)");
					break;
				}
			}
			break;
		}
		SAFE_CRITSECT_LEAVE(); 
	}
}

//============================================================================
#ifdef SERVER
static void StartDeadlockThread () {
	(void)_beginthread(DeadlockCheckProc, 0, nil);
}
#endif

//============================================================================
#ifdef SERVER
static void DeadlockCheckNowProc (void *) {
	SAFE_CRITSECT_ENTER();
	{
		ProcessDeadlock_CS("Deadlock Check");
	}
	SAFE_CRITSECT_LEAVE();
}
#endif

//============================================================================
#ifdef SERVER
static void ThreadReportProc (void *) {
	SAFE_CRITSECT_ENTER();
	{
		ProcessDeadlock_CS("Thread Report", false);
	}
	SAFE_CRITSECT_LEAVE();
}
#endif

//============================================================================
static void	pnCrashExeShutdown () {
	s_running = false;

	ReplaceEmailParams(nil);
	ASSERT(!s_deadlockList.Head());
	s_deadlockSpares.CleanUp();
}

//============================================================================
AUTO_INIT_FUNC(pnCrashExe) {
    // The critical section has to be initialized
    // before program startup and never freed
    static byte rawMemory[sizeof CCritSect];
    s_critsect = new(rawMemory) CCritSect;

	s_running = true;
	
	atexit(pnCrashExeShutdown);

	#ifdef SERVER
	SetUnhandledExceptionFilter(ExceptionFilter);
	StartDeadlockThread();
	#endif
}



/*****************************************************************************
*
*	Module functions
*
***/


} // namespace Crash


/*****************************************************************************
*
*	Exports
*
***/

//============================================================================
void CrashExceptionDump	(const char occasion[], void * info) {

	(void) ProcessException(occasion, (EXCEPTION_POINTERS *) info);
}

//============================================================================
void CrashSetEmailParameters (
	const char		smtp[],
	const char		sender[],
	const char		recipients[],
	const char		username[],
	const char		password[],
	const char		replyTo[]
) {
	ASSERT(smtp);
	ASSERT(sender);
	ASSERT(recipients);
	
	EmailParams	* params = NEWZERO(EmailParams);
	
	params->smtp		= StrDup(smtp);
	params->sender		= StrDup(sender);
	params->recipients	= StrDup(recipients);
	if (username)
		params->username = StrDup(username);
	if (password)
		params->password = StrDup(password);
	if (replyTo)
		params->replyTo	 = StrDup(replyTo);
		
	ReplaceEmailParams(params);
}

//============================================================================
void * CrashAddModule (
	unsigned_ptr	address,
	unsigned		buildId,
	unsigned		branchId,
	const wchar		name[],
	const wchar		buildString[]
) {
	ASSERT(name);
	Module * module	= NEWZERO(Module);
	
	module->address		= address;
	module->buildId		= buildId;
	module->branchId	= branchId;
	module->name		= StrDup(name);
	module->buildString	= StrDup(buildString);
	// trim trailing spaces from buildString
	for (unsigned i = StrLen(buildString) - 1; i > 0; --i) {
		if (module->buildString[i] != L' ')
			break;
		module->buildString[i] = 0;
	}
	
	SAFE_CRITSECT_ENTER();
	{
		s_modules.Link(module);
	}
	SAFE_CRITSECT_LEAVE();
	
	return module;
}

//============================================================================
void CrashRemoveModule (
	void *			param
) {
	Module * module	= (Module *) param;
	SAFE_CRITSECT_ENTER();
	{
		DEL(module);
	}
	SAFE_CRITSECT_LEAVE();
}


/*****************************************************************************
*
*   Deadlock detection (server only)
*
***/

//============================================================================
#ifdef SERVER
void * CrashAddDeadlockCheck (
	void *		thread,
	const wchar debugStr[]
) {
	s_spareCrit.Enter();
	DeadlockCheck * check = (DeadlockCheck *)s_deadlockSpares.Alloc();
	s_spareCrit.Leave();
	
	(void) new(check) DeadlockCheck;

	check->deadlockEmailMs = TimeGetMs() + s_deadlockEmailMs;
	check->deadlockTerminateMs = TimeGetMs() + s_deadlockTerminateMs;
	check->thread = (HANDLE) thread;
	check->emailSent = false;
	StrCopy(check->debugStr, debugStr, arrsize(check->debugStr));
	
	SAFE_CRITSECT_ENTER();
	{
		s_deadlockList.Link(check);
	}
	SAFE_CRITSECT_LEAVE();
	
	return check;
}
#endif

//============================================================================
#ifdef SERVER
void CrashRemoveDeadlockCheck (
	void *		ptr
) {
	ASSERT(ptr);
	DeadlockCheck * check = (DeadlockCheck *)ptr;
	SAFE_CRITSECT_ENTER();
	{
		s_deadlockList.Unlink(check);
	}
	SAFE_CRITSECT_LEAVE();
	
	check->~DeadlockCheck();
	s_spareCrit.Enter();
	s_deadlockSpares.Free(check);
	s_spareCrit.Leave();
}
#endif

//============================================================================
#ifdef SERVER
bool CrashEnableDeadlockChecking (
	bool			enable
) {
	SWAP(s_deadlockEnabled, enable);
	return enable;
}
#endif

//============================================================================
#ifdef SERVER
void CrashSetDeadlockCheckTimes(unsigned emailSec, unsigned terminateSec) {
	if(!emailSec && !terminateSec)
		CrashEnableDeadlockChecking(false);
	else {
		CrashEnableDeadlockChecking(true);
		s_deadlockEmailMs = emailSec * 1000;
		s_deadlockTerminateMs = terminateSec * 1000;
	}
}
#endif

//============================================================================
#ifdef SERVER
void CrashDeadlockCheckNow () {
	// Perform in a thread not queued for deadlock checking
	(void)_beginthread(DeadlockCheckNowProc, 0, nil);
}
#endif

//============================================================================
#ifdef SERVER
void CrashSendThreadReport () {
	// Perform in a thread not queued for deadlock checking
	(void)_beginthread(ThreadReportProc, 0, nil);
}
#endif