module std::os::win32 @if(env::WIN32);
import std::thread, std::os::backtrace;

const Win32_DWORD STARTF_USESTDHANDLES = 0x00000100;
const Win32_DWORD CREATE_NO_WINDOW = 0x08000000;
const Win32_DWORD CREATE_PROTECTED_PROCESS = 0x00040000;
const Win32_DWORD CREATE_UNICODE_ENVIRONMENT = 0x00000400;
const uint WAIT_OBJECT_0 = 0;
const uint WAIT_ABANDONED = 128;
const uint WAIT_IO_COMPLETION = 192;
const uint WAIT_FAILED = (uint)-1;
const Win32_DWORD HANDLE_FLAG_INHERIT = 1;
const Win32_DWORD HANDLE_FLAG_PROTECT_FROM_CLOSE = 2;
const uint INFINITE = (uint)-1;
const Win32_DWORD PIPE_ACCESS_DUPLEX = 0x00000003;
const Win32_DWORD PIPE_ACCESS_INBOUND = 0x00000001;
const Win32_DWORD PIPE_ACCESS_OUTBOUND = 0x00000002;
const Win32_DWORD FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000;
const Win32_DWORD FILE_FLAG_WRITE_THROUGH = 0x80000000;
const Win32_DWORD FILE_FLAG_OVERLAPPED = 0x40000000;
const Win32_DWORD WRITE_DAC = 0x00040000;
const Win32_DWORD WRITE_OWNER = 0x00080000;
const Win32_DWORD ACCESS_SYSTEM_SECURITY = 0x01000000;
const Win32_DWORD PIPE_TYPE_BYTE = 0;
const Win32_DWORD PIPE_TYPE_MESSAGE = 4;
const Win32_DWORD PIPE_READMODE_BYTE = 0;
const Win32_DWORD PIPE_READMODE_MESSAGE = 2;
const Win32_DWORD PIPE_WAIT = 0;
const Win32_DWORD PIPE_NOWAIT = 1;
const Win32_DWORD PIPE_ACCEPT_REMOTE_CLIENTS = 0;
const Win32_DWORD PIPE_REJECT_REMOTE_CLIENTS = 8;
const SYMOPT_CASE_INSENSITIVE = 0x01;
const SYMOPT_UNDNAME = 0x02;
const SYMOPT_DEFERRED_LOADS = 0x04;
const SYMOPT_NO_CPP = 0x08;
const SYMOPT_LOAD_LINES = 0x10;
const SYMOPT_OMAP_FIND_NEAREST = 0x20;
const SYMOPT_LOAD_ANYTHING = 0x40;
const SYMOPT_IGNORE_CVREC = 0x80;

const IMAGE_FILE_MACHINE_UNKNOWN = 0;
const IMAGE_FILE_MACHINE_TARGET_HOST = 1;
const IMAGE_FILE_MACHINE_I386 = 0x014c;
const IMAGE_FILE_MACHINE_IA64 = 0x0200;
const IMAGE_FILE_MACHINE_ARM64 = 0xAA64;
const IMAGE_FILE_MACHINE_AMD64 = 0x8664;
const UNDNAME_COMPLETE = 0x0000;

alias Win32_INIT_ONCE_FN = fn Win32_BOOL(Win32_INIT_ONCE* initOnce, void* parameter, void** context);

extern fn void initializeCriticalSection(Win32_CRITICAL_SECTION* section) @cname("InitializeCriticalSection");
extern fn void deleteCriticalSection(Win32_CRITICAL_SECTION* section) @cname("DeleteCriticalSection");
extern fn Win32_HANDLE createMutex(void*, Win32_BOOL, void*) @cname("CreateMutexA");
extern fn Win32_BOOL releaseMutex(Win32_HANDLE) @cname("ReleaseMutex");
extern fn void enterCriticalSection(Win32_CRITICAL_SECTION* section) @cname("EnterCriticalSection");
extern fn void leaveCriticalSection(Win32_CRITICAL_SECTION* section) @cname("LeaveCriticalSection");
extern fn Win32_BOOL tryEnterCriticalSection(Win32_CRITICAL_SECTION* section) @cname("TryEnterCriticalSection");
extern fn void initializeSRWLock(Win32_SRWLOCK* lock) @cname("InitializeSRWLock");
extern fn void acquireSRWLockExclusive(Win32_SRWLOCK* lock) @cname("AcquireSRWLockExclusive");
extern fn void acquireSRWLockShared(Win32_SRWLOCK* lock) @cname("AcquireSRWLockShared");
extern fn void releaseSRWLockExclusive(Win32_SRWLOCK* lock) @cname("ReleaseSRWLockExclusive");
extern fn void releaseSRWLockShared(Win32_SRWLOCK* lock) @cname("ReleaseSRWLockShared");
extern fn Win32_BOOL tryAcquireSRWLockExclusive(Win32_SRWLOCK* lock) @cname("TryAcquireSRWLockExclusive");
extern fn Win32_BOOL tryAcquireSRWLockShared(Win32_SRWLOCK* lock) @cname("TryAcquireSRWLockShared");
extern fn void initializeConditionVariable(Win32_CONDITION_VARIABLE* conditionVariable) @cname("InitializeConditionVariable");
extern fn void wakeConditionVariable(Win32_CONDITION_VARIABLE* conditionVariable) @cname("WakeConditionVariable");
extern fn void wakeAllConditionVariable(Win32_CONDITION_VARIABLE* conditionVariable) @cname("WakeAllConditionVariable");
extern fn Win32_BOOL sleepConditionVariableCS(Win32_CONDITION_VARIABLE* conditionVariable, Win32_CRITICAL_SECTION* section, Win32_DWORD dwMilliseconds) @cname("SleepConditionVariableCS");
extern fn Win32_BOOL sleepConditionVariableSRW(Win32_CONDITION_VARIABLE* conditionVariable, Win32_SRWLOCK* lock, Win32_DWORD dwMilliseconds, Win32_ULONG flags) @cname("SleepConditionVariableSRW");
extern fn Win32_BOOL initOnceExecuteOnce(Win32_INIT_ONCE* initOnce, Win32_INIT_ONCE_FN initFn, void* parameter, void** context) @cname("InitOnceExecuteOnce");
extern fn Win32_DWORD waitForSingleObject(Win32_HANDLE hHandle, Win32_DWORD dwMilliseconds) @cname("WaitForSingleObject");
extern fn Win32_DWORD waitForSingleObjectEx(Win32_HANDLE hHandle, Win32_DWORD dwMilliseconds, Win32_BOOL bAlertable) @cname("WaitForSingleObjectEx");
extern fn Win32_DWORD waitForMultipleObjects(Win32_DWORD nCount, Win32_HANDLE* lpHandles, Win32_BOOL bWaitAll, Win32_DWORD dwMilliseconds) @cname("WaitForMultipleObjects");
extern fn Win32_DWORD waitForMultipleObjectsEx(Win32_DWORD nCount, Win32_HANDLE* lpHandles, Win32_BOOL bWaitAll, Win32_DWORD dwMilliseconds, Win32_BOOL bAlertable) @cname("WaitForMultipleObjectsEx");
extern fn void sleep(uint ms) @cname("Sleep");
extern fn Win32_BOOL resetEvent(Win32_HANDLE event) @cname("ResetEvent");
extern fn Win32_BOOL setEvent(Win32_HANDLE handle) @cname("SetEvent");
extern fn long interlockedCompareExchange(int* dest, int exchange, int comperand) @cname("InterlockedCompareExchange");
extern fn Win32_DWORD sleepEx(Win32_DWORD ms, Win32_BOOL alertable) @cname("SleepEx");
extern fn Win32_HANDLE createThread(void* attributes, usz stack, ThreadFn func, Win32_LPVOID arg, Win32_DWORD flags, Win32_LPDWORD thread_id) @cname("CreateThread");
extern fn Win32_BOOL getExitCodeThread(Win32_HANDLE handle, Win32_LPDWORD exit_code) @cname("GetExitCodeThread");
extern fn Win32_BOOL getExitCodeProcess(Win32_HANDLE hProcess, Win32_LPDWORD lpExitCode) @cname("GetExitCodeProcess");
extern fn Win32_DWORD getThreadId(Win32_HANDLE) @cname("GetThreadId");
extern fn void exitThread(Win32_DWORD dwExitCode) @noreturn @cname("ExitThread");
extern fn Win32_HANDLE getCurrentThread() @cname("GetCurrentThread");
extern fn Win32_BOOL terminateProcess(Win32_HANDLE hProcess, Win32_UINT uExitCode) @cname("TerminateProcess");
extern fn Win32_DWORD getCurrentProcessId() @cname("GetCurrentProcessId");
extern fn Win32_HANDLE getCurrentProcess() @cname("GetCurrentProcess");
extern fn Win32_DWORD getCurrentThreadId() @cname("GetCurrentThreadId");
extern fn Win32_BOOL setHandleInformation(Win32_HANDLE hObject, Win32_DWORD dwMask, Win32_DWORD dwFlags) @cname("SetHandleInformation");
extern fn Win32_HANDLE createEventA(Win32_LPSECURITY_ATTRIBUTES lpEventAttributes, Win32_BOOL bManualReset, Win32_BOOL bInitialState, Win32_LPCSTR lpName) @cname("CreateEventA");
extern fn Win32_BOOL createProcessW(Win32_LPCWSTR lpApplicationName, Win32_LPWSTR lpCommandLine, Win32_LPSECURITY_ATTRIBUTES lpProcessAttributes, Win32_LPSECURITY_ATTRIBUTES lpThreadAttributes, Win32_BOOL bInheritHandles, Win32_DWORD dwCreationFlags, Win32_LPVOID lpEnvironment, Win32_LPCWSTR lpCurrentDirectory, Win32_LPSTARTUPINFOW lpStartupInfo, Win32_LPPROCESS_INFORMATION lpProcessInformation) @cname("CreateProcessW");
extern fn Win32_HANDLE createNamedPipeA(Win32_LPCSTR lpName, Win32_DWORD dwOpenMode, Win32_DWORD dwPipeMode, Win32_DWORD nMaxInstances, Win32_DWORD nOutBufferSize, Win32_DWORD nInBufferSize, Win32_DWORD nDefaultTimeOut, Win32_LPSECURITY_ATTRIBUTES lpSecurityAttributes) @cname("CreateNamedPipeA");
extern fn Win32_BOOL getOverlappedResult(Win32_HANDLE hFile, Win32_LPOVERLAPPED lpOverlapped, Win32_LPDWORD lpNumberOfBytesTransferred, Win32_BOOL bWait) @cname("GetOverlappedResult");
extern fn Win32_DWORD getEnvironmentVariableW(Win32_LPCWSTR lpName, Win32_LPWSTR  lpBuffer, Win32_DWORD nSize) @cname("GetEnvironmentVariableW");
extern fn Win32_BOOL setEnvironmentVariableW(Win32_LPCWSTR lpName, Win32_LPCWSTR lpValue) @cname("SetEnvironmentVariableW");
extern fn void getSystemInfo(Win32_LPSYSTEM_INFO lpSystemInfo) @cname("GetSystemInfo");
extern fn Win32_BOOL enumProcessModules(Win32_HANDLE hProcess, Win32_HMODULE* lphModule, Win32_DWORD cb, Win32_LPDWORD lpcbNeeded) @cname("K32EnumProcessModules");
extern fn Win32_BOOL getModuleInformation(Win32_HANDLE hProcess, Win32_HMODULE hModule, Win32_LPMODULEINFO lpmodinfo, Win32_DWORD cb) @cname("K32GetModuleInformation");
extern fn Win32_DWORD symAddrIncludeInlineTrace(Win32_HANDLE hProcess, Win32_DWORD64 address) @cname("SymAddrIncludeInlineTrace");
extern fn Win32_BOOL symQueryInlineTrace(Win32_HANDLE hProcess, Win32_DWORD64 startAddress, Win32_DWORD startContext, Win32_DWORD64 startRetAddress, Win32_DWORD64 curAddress, Win32_LPDWORD curContext, Win32_LPDWORD curFrameIndex) @cname("SymQueryInlineTrace");
extern fn Win32_BOOL symFromInlineContext(Win32_HANDLE hProcess, Win32_DWORD64 address, Win32_ULONG inlineContext, Win32_PDWORD64 displacement, Win32_PSYMBOL_INFO symbol) @cname("SymFromInlineContext");
extern fn Win32_BOOL symGetLineFromInlineContext(Win32_HANDLE hProcess, Win32_DWORD64 qwAddr, Win32_ULONG inlineContext, Win32_DWORD64 qwModuleBaseAddress, Win32_PDWORD pdwDisplacement, Win32_PIMAGEHLP_LINE64 line64) @cname("SymGetLineFromInlineContext");
extern fn Win32_ULONG rtlWalkFrameChain(Win32_PVOID*, Win32_ULONG, Win32_ULONG) @cname("RtlWalkFrameChain");
extern fn Win32_BOOL symInitialize(Win32_HANDLE hProcess, Win32_PCSTR userSearchPath, Win32_BOOL fInvadeProcess) @cname("SymInitialize");
extern fn Win32_BOOL symCleanup(Win32_HANDLE hProcess) @cname("SymCleanup");
extern fn Win32_DWORD64 symLoadModuleEx(Win32_HANDLE hProcess, Win32_HANDLE hFile, Win32_PCSTR imageName, Win32_PCSTR moduleName, Win32_DWORD64 baseOfDll, Win32_DWORD dllSize, Win32_PMODLOAD_DATA data, Win32_DWORD flags) @cname("SymLoadModule");
extern fn Win32_BOOL stackWalk64(Win32_DWORD machineType, Win32_HANDLE hProcess, Win32_HANDLE hThread, Win32_LPSTACKFRAME64 stackFrame, Win32_PVOID contextRecord, Win32_PREAD_PROCESS_MEMORY_ROUTINE64   readMemoryRoutine, Win32_PFUNCTION_TABLE_ACCESS_ROUTINE64 functionTableAccessRoutine, Win32_PGET_MODULE_BASE_ROUTINE64 getModuleBaseRoutine, Win32_PTRANSLATE_ADDRESS_ROUTINE64 translateAddress) @cname("StackWalk64");
extern fn void rtlCaptureContext(Win32_PCONTEXT contextRecord) @cname("RtlCaptureContext");
extern fn void* symFunctionTableAccess64(Win32_HANDLE  hProcess, Win32_DWORD64 addrBase) @cname("SymFunctionTableAccess64");
extern fn Win32_DWORD64 symGetModuleBase64(Win32_HANDLE  hProcess, Win32_DWORD64 qwAddr) @cname("SymGetModuleBase64");
extern fn Win32_DWORD getModuleBaseNameA(Win32_HANDLE  hProcess, Win32_HMODULE hModule, Win32_LPSTR lpBaseName, Win32_DWORD nSize) @cname("K32GetModuleBaseNameA");
extern fn Win32_DWORD symGetOptions() @cname("SymGetOptions");
extern fn Win32_DWORD symSetOptions(Win32_DWORD symOptions) @cname("SymSetOptions");
extern fn Win32_PIMAGE_NT_HEADERS imageNtHeader(Win32_PVOID base) @cname("ImageNtHeader");
extern fn Win32_DWORD unDecorateSymbolName(Win32_PCSTR name, Win32_PSTR outputString, Win32_DWORD maxStringLength, Win32_DWORD flags) @cname("UnDecorateSymbolName");
extern fn Win32_BOOL symFromAddr(Win32_HANDLE hProcess, Win32_DWORD64 address, Win32_PDWORD64 displacement, Win32_PSYMBOL_INFO symbol) @cname("SymFromAddr");
extern fn Win32_BOOL symGetLineFromAddr64(Win32_HANDLE hProcess, Win32_DWORD64 dwAddr, Win32_PDWORD pdwDisplacement, Win32_PIMAGEHLP_LINE64 line) @cname("SymGetLineFromAddr64");
extern fn Win32_WORD rtlCaptureStackBackTrace(Win32_DWORD framesToSkip, Win32_DWORD framesToCapture, Win32_PVOID *backTrace, Win32_PDWORD backTraceHash) @cname("RtlCaptureStackBackTrace");
extern fn Win32_BOOL symGetModuleInfo64(Win32_HANDLE hProcess, Win32_DWORD64 qwAddr, Win32_PIMAGEHLP_MODULE64 moduleInfo) @cname("SymGetModuleInfo64");

fn Win32_DWORD? load_modules()
{
	Win32_HANDLE process = getCurrentProcess();
	Win32_DWORD needed;
	symInitialize(getCurrentProcess(), null, 1);
	Win32_DWORD symOptions = symGetOptions();
	symOptions |= SYMOPT_LOAD_LINES | SYMOPT_UNDNAME;
	symSetOptions(symOptions);
	Win32_HMODULE[1024] mod_buffer;
	if (!enumProcessModules(process, &mod_buffer, mod_buffer.len, &needed))
	{
		return backtrace::RESOLUTION_FAILED~;
	}
	if (needed > mod_buffer.len) return backtrace::RESOLUTION_FAILED~;
	Win32_HMODULE[] modules = mod_buffer[:needed];
	void* base = null;
	foreach (mod : modules)
	{
		Win32_MODULEINFO info;
		if (!getModuleInformation(process, mod, &info, $sizeof(info)))
		{
			return backtrace::RESOLUTION_FAILED~;
		}
		if (!base) base = info.lpBaseOfDll;
		Win32_DWORD load_size = info.sizeOfImage;
		char[1024] char_buf;
		Win32_DWORD len = getModuleFileNameA(mod, (Win32_LPSTR)&char_buf, char_buf.len - 1);
		if (len < 1) continue;
		char[1024] module_name;
		Win32_DWORD len2 = getModuleBaseNameA(process, mod, (Win32_LPSTR)&module_name, 1021);
		if (len2 < 1) continue;
		Win32_DWORD64 base_addr = symLoadModuleEx(process, null, (Win32_PCSTR)&char_buf, (Win32_PCSTR)&module_name, (Win32_DWORD64)info.lpBaseOfDll, load_size, null, 0);
	}
	if (!base) return backtrace::IMAGE_NOT_FOUND~;
	Win32_IMAGE_NT_HEADERS* h = imageNtHeader(base);
	return h.fileHeader.machine;
}

struct Symbol
{
	inline Win32_SYMBOL_INFO sym;
	char[256] buffer;
}

Win32_DWORD64 displacement;

fn BacktraceList? symbolize_backtrace(Allocator allocator, void*[] backtrace)
{
	BacktraceList list;
	list.init(allocator, backtrace.len);
	Win32_HANDLE process = getCurrentProcess();
	symInitialize(process, null, 1);
	defer symCleanup(process);
	foreach (addr : backtrace)
	{
		list.push(resolve_backtrace(allocator, addr, process) ?? backtrace::BACKTRACE_UNKNOWN);
	}
	return list;
}

fn Backtrace? resolve_backtrace(Allocator allocator, void* addr, Win32_HANDLE process)
{
	Symbol symbol;
	//Win32_DWORD image_type = load_modules()!;
	symbol.sizeOfStruct = Win32_SYMBOL_INFO.sizeof;
	symbol.maxNameLen = 255;
	if (!symFromAddr(process, (Win32_DWORD64)addr - 1, &displacement, &symbol))
	{
		return backtrace::NO_BACKTRACE_SYMBOLS~;
	}
	Win32_IMAGEHLP_MODULE64 module_info;
	module_info.sizeOfStruct = Win32_IMAGEHLP_MODULE64.sizeof;
	if (!symGetModuleInfo64(process, (Win32_DWORD64)addr - 1, &module_info))
	{
		return backtrace::NO_BACKTRACE_SYMBOLS~;
	}
	ZString module_name = (ZString)&module_info.imageName;
	char[256] name;
	unDecorateSymbolName(&symbol.name, (Win32_PSTR)&name, 256, UNDNAME_COMPLETE);
	Win32_DWORD offset = 0;
	Win32_IMAGEHLP_LINE64 line;
	Backtrace backtrace;
	ZString zname = (ZString)&name;
	if (!symGetLineFromAddr64(process, (Win32_ULONG64)addr - 1, &offset, &line))
	{
		backtrace.init(allocator, (uptr)addr, zname.str_view(), module_name.str_view());
		return backtrace;
	}
	String filename = ((ZString)line.fileName).str_view();
	backtrace.init(allocator, (uptr)addr, zname.str_view(), module_name.str_view(), file: filename, line: line.lineNumber);
	return backtrace;
}
