#include "System.h"

#ifdef _WIN32

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <shellapi.h>
#include <DbgHelp.h>

#include <stdio.h>
#include <stdlib.h>

#pragma comment(lib, "dbghelp.lib")

#include "Log.h"

void PrintWin32Error(const char *place, const char *msg, DWORD dwErrorCode)
{
	LPSTR lpErrorMsg = NULL;
	FormatMessageA(
		FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwErrorCode,
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&lpErrorMsg, 0, NULL);
	if (place == NULL)
		Msg_Warning("%s: %s\n", msg, lpErrorMsg);
	else
		Msg_Warning("%s at %s: %s\n", msg, place, lpErrorMsg);
	LocalFree(lpErrorMsg);
}

void *OS_MapFile(const char *path)
{
	HANDLE hFile, hMapping;
	void *result;

	hFile = CreateFileA(path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
	{
		PrintWin32Error(path, "Unable to open file for mapping", GetLastError());
		return NULL;
	}

	hMapping = CreateFileMappingA(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
	if (hMapping == NULL)
	{
		PrintWin32Error(path, "Unable to create file mapping", GetLastError());
		return NULL;
	}

	result = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0);
	if (result == NULL)
	{
		PrintWin32Error(path, "Unable to map file view", GetLastError());
	}

	CloseHandle(hFile);
	CloseHandle(hMapping);
	return result;
}

void OS_UnmapFile(void *data)
{
	UnmapViewOfFile(data);
}

bool OS_DeleteFile(const char *filename)
{
	if (DeleteFileA(filename) == 0)
	{
		PrintWin32Error(filename, "Unable to delete file", GetLastError());
		return false;
	}
	return true;
}

void OS_DeleteAllInDir(const char *path)
{
	WIN32_FIND_DATAA ffd;
	DWORD dwError;
	HANDLE hFile;

	char dirPath[MAX_PATH], filePath[MAX_PATH];

	strcpy(dirPath, path);
	strcat(dirPath, "\\*");
	strcpy(filePath, path);
	strcat(filePath, "\\");

	hFile = FindFirstFileA(dirPath, &ffd);
	if (hFile == INVALID_HANDLE_VALUE)
	{
		Msg_Warning("Unable to find any files in dir %s", path);
		return;
	}

	do
	{
		if (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0
			|| ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
			continue;
		strcpy(filePath, path);
		strcat(filePath, "\\");
		strcat(filePath, ffd.cFileName);
		DeleteFileA(filePath);
	} while (FindNextFileA(hFile, &ffd) != 0);

	dwError = GetLastError();
	if (dwError != ERROR_NO_MORE_FILES)
	{
		PrintWin32Error(path, "Error deleting files", dwError);
	}

	FindClose(hFile);
}

void OS_MkdirIfNotExists(const char *path)
{
	DWORD dwAttribs = GetFileAttributesA(path);
	if (dwAttribs == INVALID_FILE_ATTRIBUTES || !(dwAttribs & FILE_ATTRIBUTE_DIRECTORY))
	{
		CreateDirectoryA(path, NULL);
	}
}

const char *ExceptionName(DWORD code)
{
	switch (code)
	{
	case EXCEPTION_ACCESS_VIOLATION: return "Access violation";
	case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return "Array index out of bounds";
	case EXCEPTION_BREAKPOINT: return "Hit breakpoint";
	case EXCEPTION_FLT_DIVIDE_BY_ZERO: return "Float divide by zero";
	case EXCEPTION_INT_DIVIDE_BY_ZERO: return "Integer divide by zero";
	case EXCEPTION_ILLEGAL_INSTRUCTION: return "Illegal instruction";
	case EXCEPTION_STACK_OVERFLOW: return "Stack overflow";
	default: return "Unknown exception";
	}
}

#define MAX_STACKFRAMES 64
#define MAX_NAMELEN 128

LONG HandleException(EXCEPTION_POINTERS *info)
{
	char msgbuf[1024];
	char fmtbuf[64];
	const char *exceptionStr;
	FILE *crashlog;

	PSYMBOL_INFO symbol;
	unsigned short numStackFrames, i;
	void *stackFrames[MAX_STACKFRAMES];
	
	HANDLE hProcess = GetCurrentProcess();
	DWORD64 displacement = 0;
	DWORD64 originAddress = (DWORD64)info->ExceptionRecord->ExceptionAddress;

	SymInitialize(hProcess, NULL, TRUE);

	numStackFrames = CaptureStackBackTrace(0, MAX_STACKFRAMES, stackFrames, NULL);

	symbol = malloc(sizeof(SYMBOL_INFO) + MAX_NAMELEN);
	symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
	symbol->MaxNameLen = MAX_NAMELEN;

	crashlog = fopen("crash.log", "a");
	if (crashlog)
	{
		SYSTEMTIME systime;
		int ntimestamp;
		GetLocalTime(&systime);
		ntimestamp = snprintf(fmtbuf, 63, "\n\n==== %d-%02d-%02d %02d:%02d:%02d WINDOWS ====\n",
			systime.wYear, systime.wMonth, systime.wDay, systime.wHour, systime.wMinute, systime.wSecond);
		fwrite(fmtbuf, 1, ntimestamp, crashlog);
	}

	// Print information about the origin of the crash and show error box
	exceptionStr = ExceptionName(info->ExceptionRecord->ExceptionCode);
	if (SymFromAddr(hProcess, originAddress, &displacement, symbol))
	{
		int msglen = snprintf(msgbuf, 1023, "%s at %s+%lld\n", exceptionStr, symbol->Name, displacement);
		if (crashlog)
			fwrite(msgbuf, 1, msglen, crashlog);
	}
	else
	{
		PrintWin32Error(NULL, "Could not get function for address", GetLastError());
		int msglen = snprintf(msgbuf, 1023, "%s at 0x%p\n", exceptionStr, info->ExceptionRecord->ExceptionAddress);
		if (crashlog)
			fwrite(msgbuf, 1, msglen, crashlog);
	}

	MessageBoxA(NULL, msgbuf, "Rovgend's Labyrinth crashed!", MB_ICONERROR | MB_OK);

	// Write backtrace to log file
	if (crashlog)
	{
		for (i = 0; i < numStackFrames; i++)
		{
			if (SymFromAddr(hProcess, (DWORD64)stackFrames[i], &displacement, symbol))
			{
				int msglen = snprintf(fmtbuf, 63, "%i:\t0x%llx %s+%lld\n", i, symbol->Address, symbol->Name, displacement);
				fwrite(fmtbuf, 1, msglen, crashlog);
			}
			else
			{
				int msglen = snprintf(fmtbuf, 63, "%i:\t0x%p\n", i, stackFrames[i]);
				if (crashlog)
					fwrite(msgbuf, 1, msglen, crashlog);
			}
		}
	}

	if (crashlog) fclose(crashlog);
	free(symbol);

	return EXCEPTION_CONTINUE_SEARCH;
}

void OS_SetupCrashHandler(void)
{
	SetUnhandledExceptionFilter(HandleException);
}

#endif // _WIN32
