/*
 * ExceptionHandler.cpp
 * --------------------
 * Purpose: Code for handling crashes (unhandled exceptions) in OpenMPT.
 * Notes  : (currently none)
 * Authors: OpenMPT Devs
 * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
 */


#include "stdafx.h"
#include "Mainfrm.h"
#include "Mptrack.h"
#include "AboutDialog.h"
#include "InputHandler.h"
#include "openmpt/sounddevice/SoundDevice.hpp"
#include "Moddoc.h"
#include <shlwapi.h>
#include "ExceptionHandler.h"
#include "../misc/WriteMemoryDump.h"
#include "../common/version.h"
#include "../common/mptFileIO.h"
#include "../soundlib/mod_specifications.h"

#include <atomic>


OPENMPT_NAMESPACE_BEGIN


// Write full memory dump instead of minidump.
bool ExceptionHandler::fullMemDump = false;

bool ExceptionHandler::stopSoundDeviceOnCrash = true;

bool ExceptionHandler::stopSoundDeviceBeforeDump = false;

// Delegate to system-specific crash processing once our own crash handler is
// finished. This is useful to allow attaching a debugger.
bool ExceptionHandler::delegateToWindowsHandler = false;

// Allow debugging the unhandled exception filter. Normally, if a debugger is
// attached, no exceptions are unhandled because the debugger handles them. If
// debugExceptionHandler is true, an additional __try/__catch is inserted around
// InitInstance(), ExitInstance() and the main message loop, which will call our
// filter, which then can be stepped through in a debugger.
bool ExceptionHandler::debugExceptionHandler = false;


bool ExceptionHandler::useAnyCrashHandler = false;
bool ExceptionHandler::useImplicitFallbackSEH = false;
bool ExceptionHandler::useExplicitSEH = false;
bool ExceptionHandler::handleStdTerminate = false;
bool ExceptionHandler::handleMfcExceptions = false;


static thread_local ExceptionHandler::Context *g_Context = nullptr;

static LPTOP_LEVEL_EXCEPTION_FILTER g_OriginalUnhandledExceptionFilter = nullptr;
static std::terminate_handler g_OriginalTerminateHandler = nullptr;

static UINT g_OriginalErrorMode = 0;


ExceptionHandler::Context *ExceptionHandler::SetContext(Context *newContext) noexcept
{
	Context *oldContext = g_Context;
	g_Context = newContext;
	return oldContext;
}


static std::atomic<int> & g_CrashCount()
{
	static std::atomic<int> s_CrashCount(0);
	return s_CrashCount;
}


static std::atomic<int> & g_TaintCountDriver()
{
	static std::atomic<int> s_TaintCountDriver(0);
	return s_TaintCountDriver;
}


static std::atomic<int> & g_TaintCountPlugin()
{
	static std::atomic<int> s_TaintCountPlugin(0);
	return s_TaintCountPlugin;
}


void ExceptionHandler::TaintProcess(ExceptionHandler::TaintReason reason)
{
	switch(reason)
	{
	case ExceptionHandler::TaintReason::Driver:
		g_TaintCountDriver().fetch_add(1);
		break;
	case ExceptionHandler::TaintReason::Plugin:
		g_TaintCountPlugin().fetch_add(1);
		break;
	default:
		MPT_ASSERT_NOTREACHED();
		break;
	}
}


enum DumpMode
{
	DumpModeCrash   = 0,  // crash
	DumpModeWarning = 1,  // assert
	DumpModeDebug   = 2,  // debug output (e.g. trace log)
};


struct CrashOutputDirectory
{
	bool valid;
	mpt::PathString path;
	CrashOutputDirectory()
		: valid(true)
	{
		const mpt::PathString timestampDir = mpt::PathString::FromCString((CTime::GetCurrentTime()).Format(_T("%Y-%m-%d %H.%M.%S\\")));
		// Create a crash directory
		path = mpt::GetTempDirectory() + P_("OpenMPT Crash Files\\");
		if(!path.IsDirectory())
		{
			CreateDirectory(path.AsNative().c_str(), nullptr);
		}
		// Set compressed attribute in order to save disk space.
		// Debugging information should clutter the users computer as little as possible.
		// Performance is not important here.
		// Ignore any errors.
		SetFilesystemCompression(path);
		// Compression will be inherited by children directories and files automatically.
		path += timestampDir;
		if(!path.IsDirectory())
		{
			if(!CreateDirectory(path.AsNative().c_str(), nullptr))
			{
				valid = false;
			}
		}
	}
};


class DebugReporter
{
private:
	int crashCount;
	int taintCountDriver;
	int taintCountPlugin;
	bool stateFrozen;
	const DumpMode mode;
	const CrashOutputDirectory crashDirectory;
	bool writtenMiniDump;
	bool writtenTraceLog;
	int rescuedFiles;
private:
	static bool FreezeState(DumpMode mode);
	static bool Cleanup(DumpMode mode);
	bool GenerateDump(_EXCEPTION_POINTERS *pExceptionInfo);
	bool GenerateTraceLog();
	int RescueFiles();
	bool HasWrittenDebug() const { return writtenMiniDump || writtenTraceLog; }
	static void StopSoundDevice();
public:
	DebugReporter(DumpMode mode, _EXCEPTION_POINTERS *pExceptionInfo);
	~DebugReporter();
	void ReportError(mpt::ustring errorMessage);
};


DebugReporter::DebugReporter(DumpMode mode, _EXCEPTION_POINTERS *pExceptionInfo)
	: crashCount(g_CrashCount().fetch_add(1) + 1)
	, taintCountDriver(g_TaintCountDriver().load())
	, taintCountPlugin(g_TaintCountPlugin().load())
	, stateFrozen(FreezeState(mode))
	, mode(mode)
	, writtenMiniDump(false)
	, writtenTraceLog(false)
	, rescuedFiles(0)
{
	if(mode == DumpModeCrash || mode == DumpModeWarning)
	{
		writtenMiniDump = GenerateDump(pExceptionInfo);
	}
	if(mode == DumpModeCrash || mode == DumpModeWarning || mode == DumpModeDebug)
	{
		writtenTraceLog = GenerateTraceLog();
	}
	if(mode == DumpModeCrash || mode == DumpModeWarning)
	{
		rescuedFiles = RescueFiles();
	}
}


DebugReporter::~DebugReporter()
{
	Cleanup(mode);
}


bool DebugReporter::GenerateDump(_EXCEPTION_POINTERS *pExceptionInfo)
{
	return WriteMemoryDump(pExceptionInfo, (crashDirectory.path + P_("crash.dmp")).AsNative().c_str(), ExceptionHandler::fullMemDump);
}


bool DebugReporter::GenerateTraceLog()
{
	return mpt::log::Trace::Dump(crashDirectory.path + P_("trace.log"));
}


static void SaveDocumentSafe(CModDoc *pModDoc, const mpt::PathString &filename)
{
	__try
	{
		pModDoc->OnSaveDocument(filename);
	} __except(EXCEPTION_EXECUTE_HANDLER)
	{
		// nothing
	}
}


// Rescue modified files...
int DebugReporter::RescueFiles()
{
	int numFiles = 0;
	auto documents = theApp.GetOpenDocuments();
	for(auto modDoc : documents)
	{
		if(modDoc->IsModified())
		{
			if(numFiles == 0)
			{
				// Show the rescue directory in Explorer...
				CTrackApp::OpenDirectory(crashDirectory.path);
			}

			mpt::PathString filename;
			filename += crashDirectory.path;
			filename += mpt::PathString::FromUnicode(mpt::ufmt::val(++numFiles));
			filename += P_("_");
			filename += mpt::PathString::FromCString(modDoc->GetTitle()).SanitizeComponent();
			filename += P_(".");
			filename += mpt::PathString::FromUTF8(modDoc->GetSoundFile().GetModSpecifications().fileExtension);

			try
			{
				SaveDocumentSafe(modDoc, filename);
			} catch(...)
			{
				continue;
			}
		}
	}
	return numFiles;
}


void DebugReporter::ReportError(mpt::ustring errorMessage)
{

	if(!crashDirectory.valid)
	{
		errorMessage += UL_("\n\n");
		errorMessage += UL_("Could not create the following directory for saving debug information and modified files to:\n");
		errorMessage += crashDirectory.path.ToUnicode();
	}

	if(HasWrittenDebug())
	{
		errorMessage += UL_("\n\n");
		errorMessage += UL_("Debug information has been saved to\n");
		errorMessage += crashDirectory.path.ToUnicode();
	}

	if(rescuedFiles > 0)
	{
		errorMessage += UL_("\n\n");
		if(rescuedFiles == 1)
		{
			errorMessage += UL_("1 modified file has been rescued, but it cannot be guaranteed that it is still intact.");
		} else
		{
			errorMessage += MPT_UFORMAT("{} modified files have been rescued, but it cannot be guaranteed that they are still intact.")(rescuedFiles);
		}
	}

	errorMessage += UL_("\n\n");
	errorMessage += MPT_UFORMAT("OpenMPT {} {} ({} ({}))")
		( Build::GetVersionStringExtended()
		, mpt::OS::Windows::Name(mpt::OS::Windows::GetProcessArchitecture())
		, SourceInfo::Current().GetUrlWithRevision()
		, SourceInfo::Current().GetStateString()
		);

	errorMessage += UL_("\n\n");
	errorMessage += MPT_UFORMAT("Session error count: {}\n")(crashCount);
	if(taintCountDriver > 0 || taintCountPlugin > 0)
	{
		errorMessage += UL_("Process is in tainted state!\n");
		errorMessage += MPT_UFORMAT("Previously masked driver crashes: {}\n")(taintCountDriver);
		errorMessage += MPT_UFORMAT("Previously masked plugin crashes: {}\n")(taintCountPlugin);
	}

	errorMessage += UL_("\n");

	{
		mpt::SafeOutputFile sf(crashDirectory.path + P_("error.txt"), std::ios::binary, mpt::FlushMode::Full);
		mpt::ofstream& f = sf;
		f.imbue(std::locale::classic());
		f << mpt::replace(mpt::ToCharset(mpt::Charset::UTF8, errorMessage), std::string("\n"), std::string("\r\n"));
	}

	if(auto ih = CMainFrame::GetInputHandler(); ih != nullptr)
	{
		mpt::SafeOutputFile sf(crashDirectory.path + P_("last-commands.txt"), std::ios::binary, mpt::FlushMode::Full);
		mpt::ofstream &f = sf;
		f.imbue(std::locale::classic());

		const auto commandSet = ih->m_activeCommandSet.get();
		f << "Last commands:\n";
		for(size_t i = 0; i < ih->m_lastCommands.size(); i++)
		{
			CommandID id = ih->m_lastCommands[(ih->m_lastCommandPos + i) % ih->m_lastCommands.size()];
			if(id == kcNull)
				continue;
			f << mpt::afmt::val(id);
			if(commandSet)
				f << " (" << mpt::ToCharset(mpt::Charset::UTF8, commandSet->GetCommandText(id)) << ")";
			f << "\n";
		}
	}

	{
		mpt::SafeOutputFile sf(crashDirectory.path + P_("threads.txt"), std::ios::binary, mpt::FlushMode::Full);
		mpt::ofstream& f = sf;
		f.imbue(std::locale::classic());
		f << MPT_AFORMAT("current : {}")(mpt::afmt::hex0<8>(GetCurrentThreadId())) << "\r\n";
		f << MPT_AFORMAT("GUI     : {}")(mpt::afmt::hex0<8>(mpt::log::Trace::GetThreadId(mpt::log::Trace::ThreadKindGUI))) << "\r\n";
		f << MPT_AFORMAT("Audio   : {}")(mpt::afmt::hex0<8>(mpt::log::Trace::GetThreadId(mpt::log::Trace::ThreadKindAudio))) << "\r\n";
		f << MPT_AFORMAT("Notify  : {}")(mpt::afmt::hex0<8>(mpt::log::Trace::GetThreadId(mpt::log::Trace::ThreadKindNotify))) << "\r\n";
		f << MPT_AFORMAT("WatchDir: {}")(mpt::afmt::hex0<8>(mpt::log::Trace::GetThreadId(mpt::log::Trace::ThreadKindWatchdir))) << "\r\n";
	}

	static constexpr struct { const mpt::uchar * section; const mpt::uchar * key; } configAnonymize[] =
	{
		{ UL_("Version"), UL_("InstallGUID") },
		{ UL_("Recent File List"), nullptr },
	};

	{
		mpt::SafeOutputFile sf(crashDirectory.path + P_("active-settings.txt"), std::ios::binary, mpt::FlushMode::Full);
		mpt::ofstream& f = sf;
		f.imbue(std::locale::classic());
		if(theApp.GetpSettings())
		{
			SettingsContainer &settings = theApp.GetSettings();
			for(const auto &it : settings)
			{
				bool skipPath = false;
				for(const auto &path : configAnonymize)
				{
					if((path.key == nullptr && path.section == it.first.GetRefSection()) // Omit entire section
						|| (path.key != nullptr && it.first == SettingPath(path.section, path.key))) // Omit specific key
					{
						skipPath = true;
					}
				}
				if(skipPath)
				{
					continue;
				}
				f
					<< mpt::ToCharset(mpt::Charset::UTF8, it.first.FormatAsString() + U_(" = ") + it.second.GetRefValue().FormatValueAsString())
					<< std::endl;
			}
		}
	}

	{
		const mpt::PathString crashStoredSettingsFilename = crashDirectory.path + P_("stored-mptrack.ini");
		CopyFile
			( theApp.GetConfigFileName().AsNative().c_str()
			, crashStoredSettingsFilename.AsNative().c_str()
			, FALSE
			);
		IniFileSettingsContainer crashStoredSettings{crashStoredSettingsFilename};
		for(const auto &path : configAnonymize)
		{
			if(path.key)
			{
				crashStoredSettings.Write(SettingPath(path.section, path.key), SettingValue(mpt::ustring()));
			} else
			{
				crashStoredSettings.Remove(path.section);
			}
		}
		crashStoredSettings.Flush();
	}

	/*
	// This is very slow, we instead write active-settings.txt above.
	{
		IniFileSettingsBackend f(crashDirectory.path + P_("active-mptrack.ini"));
		if(theApp.GetpSettings())
		{
			SettingsContainer & settings = theApp.GetSettings();
			for(const auto &it : settings)
			{
				f.WriteSetting(it.first, it.second.GetRefValue());
			}
		}
	}
	*/

	{
		mpt::SafeOutputFile sf(crashDirectory.path + P_("about-openmpt.txt"), std::ios::binary, mpt::FlushMode::Full);
		mpt::ofstream& f = sf;
		f.imbue(std::locale::classic());
		f << mpt::ToCharset(mpt::Charset::UTF8, CAboutDlg::GetTabText(0));
	}

	{
		mpt::SafeOutputFile sf(crashDirectory.path + P_("about-components.txt"), std::ios::binary, mpt::FlushMode::Full);
		mpt::ofstream& f = sf;
		f.imbue(std::locale::classic());
		f << mpt::ToCharset(mpt::Charset::UTF8, CAboutDlg::GetTabText(1));
	}

	Reporting::Error(errorMessage, (mode == DumpModeWarning) ? "OpenMPT Warning" : "OpenMPT Crash", CMainFrame::GetMainFrame());

}


// Freezes the state as much as possible in order to avoid further confusion by
// other (possibly still running) threads
bool DebugReporter::FreezeState(DumpMode mode)
{
	MPT_TRACE();

	// seal the trace log as early as possible
	mpt::log::Trace::Seal();

	if(mode == DumpModeCrash || mode == DumpModeWarning)
	{
		if(CMainFrame::GetMainFrame() && CMainFrame::GetMainFrame()->gpSoundDevice && CMainFrame::GetMainFrame()->gpSoundDevice->DebugIsFragileDevice())
		{
			// For fragile devices, always stop the device. Stop before the dumping if not in realtime context.
			if(!CMainFrame::GetMainFrame()->gpSoundDevice->DebugInRealtimeCallback())
			{
				StopSoundDevice();
			}
		} else
		{
			if(ExceptionHandler::stopSoundDeviceOnCrash && ExceptionHandler::stopSoundDeviceBeforeDump)
			{
				StopSoundDevice();
			}
		}
	}

	return true;
}


static void StopSoundDeviceSafe(CMainFrame *pMainFrame)
{
	__try
	{
		if(pMainFrame->gpSoundDevice)
		{
			pMainFrame->gpSoundDevice->Close();
		}
		if(pMainFrame->m_NotifyTimer)
		{
			pMainFrame->KillTimer(pMainFrame->m_NotifyTimer);
			pMainFrame->m_NotifyTimer = 0;
		}
	} __except(EXCEPTION_EXECUTE_HANDLER)
	{
		// nothing
	}
}


void DebugReporter::StopSoundDevice()
{
	CMainFrame* pMainFrame = CMainFrame::GetMainFrame();
	if(pMainFrame)
	{
		try
		{
			StopSoundDeviceSafe(pMainFrame);
		} catch(...)
		{
			// nothing
		}
	}
}


bool DebugReporter::Cleanup(DumpMode mode)
{
	MPT_TRACE();

	if(mode == DumpModeCrash || mode == DumpModeWarning)
	{
		if(CMainFrame::GetMainFrame() && CMainFrame::GetMainFrame()->gpSoundDevice && CMainFrame::GetMainFrame()->gpSoundDevice->DebugIsFragileDevice())
		{
			// For fragile devices, always stop the device. Stop after the dumping if in realtime context.
			if(CMainFrame::GetMainFrame()->gpSoundDevice->DebugInRealtimeCallback())
			{
				StopSoundDevice();
			}
		} else
		{
			if(ExceptionHandler::stopSoundDeviceOnCrash && !ExceptionHandler::stopSoundDeviceBeforeDump)
			{
				StopSoundDevice();
			}
		}
	}

	return true;
}


// Different entry points for different situations in which we want to dump some information


static bool IsCxxException(_EXCEPTION_POINTERS *pExceptionInfo)
{
	if (!pExceptionInfo)
		return false;
	if (!pExceptionInfo->ExceptionRecord)
		return false;
	if (pExceptionInfo->ExceptionRecord->ExceptionCode != 0xE06D7363u)
		return false;
	return true;
}


template <typename E>
static const E * GetCxxException(_EXCEPTION_POINTERS *pExceptionInfo)
{
	// https://blogs.msdn.microsoft.com/oldnewthing/20100730-00/?p=13273
	struct info_a_t
	{
		DWORD  bitmask;              // Probably: 1=Const, 2=Volatile
		DWORD  destructor;           // RVA (Relative Virtual Address) of destructor for that exception object
		DWORD  unknown;
		DWORD  catchableTypesPtr;    // RVA of instance of type "B"
	};
	struct info_c_t
	{
		DWORD  someBitmask;
		DWORD  typeInfo;             // RVA of std::type_info for that type
		DWORD  memberDisplacement;   // Add to ExceptionInformation[1] in EXCEPTION_RECORD to obtain 'this' pointer.
		DWORD  virtBaseRelated1;     // -1 if no virtual base
		DWORD  virtBaseRelated2;     // ?
		DWORD  objectSize;           // Size of the object in bytes
		DWORD  probablyCopyCtr;      // RVA of copy constructor (?)
	};
	if(!pExceptionInfo)
		return nullptr;
	if (!pExceptionInfo->ExceptionRecord)
		return nullptr;
	if(pExceptionInfo->ExceptionRecord->ExceptionCode != 0xE06D7363u)
		return nullptr;
	#ifdef _WIN64
		if(pExceptionInfo->ExceptionRecord->NumberParameters != 4)
			return nullptr;
	#else
		if(pExceptionInfo->ExceptionRecord->NumberParameters != 3)
			return nullptr;
	#endif
	if(pExceptionInfo->ExceptionRecord->ExceptionInformation[0] != 0x19930520u)
		return nullptr;
	std::uintptr_t base_address = 0;
	#ifdef _WIN64
		base_address = pExceptionInfo->ExceptionRecord->ExceptionInformation[3];
	#else
		base_address = 0;
	#endif
	std::uintptr_t obj_address = pExceptionInfo->ExceptionRecord->ExceptionInformation[1];
	if(!obj_address)
		return nullptr;
	std::uintptr_t info_a_address = pExceptionInfo->ExceptionRecord->ExceptionInformation[2];
	if(!info_a_address)
		return nullptr;
	const info_a_t * info_a = reinterpret_cast<const info_a_t *>(info_a_address);
	std::uintptr_t info_b_offset = info_a->catchableTypesPtr;
	if(!info_b_offset)
		return nullptr;
	const DWORD * info_b = reinterpret_cast<const DWORD *>(base_address + info_b_offset);
	for(DWORD type = 1; type <= info_b[0]; ++type)
	{
		std::uintptr_t info_c_offset = info_b[type];
		if(!info_c_offset)
			continue;
		const info_c_t * info_c = reinterpret_cast<const info_c_t *>(base_address + info_c_offset);
		if(!info_c->typeInfo)
			continue;
		const std::type_info * ti = reinterpret_cast<const std::type_info *>(base_address + info_c->typeInfo);
		if(*ti != typeid(E))
			continue;
		const E * e = reinterpret_cast<const E *>(obj_address + info_c->memberDisplacement);
		return e;
	}
	return nullptr;
}


void ExceptionHandler::UnhandledMFCException(CException * e, const MSG * pMsg)
{
	DebugReporter report(DumpModeCrash, nullptr);
	mpt::ustring errorMessage;
	if(e && dynamic_cast<CSimpleException*>(e))
	{
		TCHAR tmp[1024 + 1];
		MemsetZero(tmp);
		if(dynamic_cast<CSimpleException*>(e)->GetErrorMessage(tmp, static_cast<UINT>(std::size(tmp) - 1)) != 0)
		{
			tmp[1024] = 0;
			errorMessage = MPT_UFORMAT("Unhandled MFC exception occurred while processming window message '{}': {}.")
				(mpt::ufmt::dec(pMsg ? pMsg->message : 0)
				, mpt::ToUnicode(CString(tmp))
				);
		} else
		{
			errorMessage = MPT_UFORMAT("Unhandled MFC exception occurred while processming window message '{}': {}.")
				(mpt::ufmt::dec(pMsg ? pMsg->message : 0)
				, mpt::ToUnicode(CString(tmp))
				);
		}
	}
	else
	{
		errorMessage = MPT_UFORMAT("Unhandled MFC exception occurred while processming window message '{}'.")
			( mpt::ufmt::dec(pMsg ? pMsg->message : 0)
			);
	}
	report.ReportError(errorMessage);
}


static void UnhandledExceptionFilterImpl(_EXCEPTION_POINTERS *pExceptionInfo)
{
	DebugReporter report(DumpModeCrash, pExceptionInfo);

	mpt::ustring errorMessage;
	const std::exception * pE = GetCxxException<std::exception>(pExceptionInfo);
	if(g_Context)
	{
		if(!g_Context->description.empty())
		{
			errorMessage += MPT_UFORMAT("OpenMPT detected a crash in '{}'.\nThis is very likely not an OpenMPT bug. Please report the problem to the respective software author.\n")(g_Context->description);
		} else
		{
			errorMessage += MPT_UFORMAT("OpenMPT detected a crash in unknown foreign code.\nThis is likely not an OpenMPT bug.\n")();
		}
	}
	if(pE)
	{
		const std::exception & e = *pE;
		errorMessage += MPT_UFORMAT("Unhandled C++ exception '{}' occurred at address 0x{}: '{}'.")
			( mpt::ToUnicode(mpt::Charset::ASCII, typeid(e).name())
			, mpt::ufmt::hex0<mpt::pointer_size*2>(reinterpret_cast<std::uintptr_t>(pExceptionInfo->ExceptionRecord->ExceptionAddress))
			, mpt::get_exception_text<mpt::ustring>(e)
			);
	} else
	{
		errorMessage += MPT_UFORMAT("Unhandled exception 0x{} at address 0x{} occurred.")
			( mpt::ufmt::HEX0<8>(pExceptionInfo->ExceptionRecord->ExceptionCode)
			, mpt::ufmt::hex0<mpt::pointer_size*2>(reinterpret_cast<std::uintptr_t>(pExceptionInfo->ExceptionRecord->ExceptionAddress))
			);
	}
	report.ReportError(errorMessage);

}


LONG ExceptionHandler::UnhandledExceptionFilterContinue(_EXCEPTION_POINTERS *pExceptionInfo)
{

	UnhandledExceptionFilterImpl(pExceptionInfo);

	// Disable the call to std::terminate() as that would re-renter the crash
	// handler another time, but with less information available.
#if 0
	// MSVC implements calling std::terminate by its own UnhandledExeptionFilter.
	// However, we do overwrite it here, thus we have to call std::terminate
	// ourselves.
	if (IsCxxException(pExceptionInfo))
	{
		std::terminate();
	}
#endif

	// Let a potential debugger handle the exception...
	return EXCEPTION_CONTINUE_SEARCH;

}


LONG ExceptionHandler::ExceptionFilter(_EXCEPTION_POINTERS *pExceptionInfo)
{
	UnhandledExceptionFilterImpl(pExceptionInfo);
	// Let a potential debugger handle the exception...
	return EXCEPTION_EXECUTE_HANDLER;
}


static void mpt_unexpected_handler();
static void mpt_terminate_handler();


void ExceptionHandler::Register()
{
	if(useImplicitFallbackSEH)
	{
		g_OriginalUnhandledExceptionFilter = ::SetUnhandledExceptionFilter(&UnhandledExceptionFilterContinue);
	}
	if(handleStdTerminate)
	{
		g_OriginalTerminateHandler = std::set_terminate(&mpt_terminate_handler);
	}
}


void ExceptionHandler::ConfigureSystemHandler()
{
#if (_WIN32_WINNT >= 0x0600)
	if(delegateToWindowsHandler)
	{
		//SetErrorMode(0);
		g_OriginalErrorMode = ::GetErrorMode();
	}	else
	{
		g_OriginalErrorMode = ::SetErrorMode(::GetErrorMode() | SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
	}
#else // _WIN32_WINNT < 0x0600
	if(delegateToWindowsHandler)
	{
		g_OriginalErrorMode = ::SetErrorMode(0);
	} else
	{
		g_OriginalErrorMode = ::SetErrorMode(::SetErrorMode(0) | SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
	}
#endif // _WIN32_WINNT
}


void ExceptionHandler::UnconfigureSystemHandler()
{
	::SetErrorMode(g_OriginalErrorMode);
	g_OriginalErrorMode = 0;
}


void ExceptionHandler::Unregister()
{
	if(handleStdTerminate)
	{
		std::set_terminate(g_OriginalTerminateHandler);
		g_OriginalTerminateHandler = nullptr;
	}
	if(useImplicitFallbackSEH)
	{
		::SetUnhandledExceptionFilter(g_OriginalUnhandledExceptionFilter);
		g_OriginalUnhandledExceptionFilter = nullptr;
	}
}


static void mpt_terminate_handler()
{
	DebugReporter(DumpModeCrash, nullptr).ReportError(U_("A C++ runtime crash occurred: std::terminate() called."));
#if 1
	std::abort();
#else
	if(g_OriginalTerminateHandler)
	{
		g_OriginalTerminateHandler();
	} else
	{
		std::abort();
	}
#endif
}


#if defined(MPT_ASSERT_HANDLER_NEEDED)

MPT_NOINLINE void AssertHandler(const mpt::source_location &loc, const char *expr, const char *msg)
{
	DebugReporter report(msg ? DumpModeWarning : DumpModeCrash, nullptr);
	if(IsDebuggerPresent())
	{
		OutputDebugString(_T("ASSERT("));
		OutputDebugString(mpt::ToWin(mpt::Charset::ASCII, expr).c_str());
		OutputDebugString(_T(") failed\n"));
		DebugBreak();
	} else
	{
		mpt::ustring errorMessage;
		if(msg)
		{
			errorMessage = MPT_UFORMAT("Internal state inconsistency detected at {}({}). This is just a warning that could potentially lead to a crash later on: {} [{}].")
				( mpt::ToUnicode(mpt::Charset::ASCII, loc.file_name() ? loc.file_name() : "")
				, loc.line()
				, mpt::ToUnicode(mpt::Charset::ASCII, msg)
				, mpt::ToUnicode(mpt::Charset::ASCII, loc.function_name() ? loc.function_name() : "")
				);
		} else
		{
			errorMessage = MPT_UFORMAT("Internal error occurred at {}({}): ASSERT({}) failed in [{}].")
				( mpt::ToUnicode(mpt::Charset::ASCII, loc.file_name() ? loc.file_name() : "")
				, loc.line()
				, mpt::ToUnicode(mpt::Charset::ASCII, expr)
				, mpt::ToUnicode(mpt::Charset::ASCII, loc.function_name() ? loc.function_name() : "")
				);
		}
		report.ReportError(errorMessage);
	}
}

#endif // MPT_ASSERT_HANDLER_NEEDED


void DebugInjectCrash()
{
	DebugReporter(DumpModeCrash, nullptr).ReportError(U_("Injected crash."));
}


void DebugTraceDump()
{
	DebugReporter report(DumpModeDebug, nullptr);
}


OPENMPT_NAMESPACE_END