#define WINVER 0x0600
#define _WIN32_WINNT 0x0600
#include <Windows.h>
#include <Windowsx.h>
#include <Shlwapi.h>
#include <gl/GL.h>
#include <wglext.h>

#include "platform.h"
#include "main.h"
#include "ogl.h"
#include "sys_log.h"
//#include "14c/resource.h"
#define IDI_ICON1 101
#include <stdio.h>
#include <ShlObj.h>
#include <ShObjIdl.h>

#define GL(func, type) type func = NULL;
OPENGL_FUNC_LIST
#undef GL

#define REPORT_WINERROR do {yrLog(0, "The following windows error occured here:"); report_windows_lasterror();} while(0)

/************
* Static vars
*************/
static HINSTANCE	hInstance = NULL;
static uint64_t		perffreq = 0;
static HWND			anywindow = NULL;
static ATOM			wndclass_dummy = 0;
static ATOM			wndclass_main = 0;
static int			mwheel_accum = 0;

static PFNWGLCHOOSEPIXELFORMATARBPROC		wglChoosePixelFormatARB = NULL;
static PFNWGLCREATECONTEXTATTRIBSARBPROC	wglCreateContextAttribsARB = NULL;
static PFNWGLSWAPINTERVALEXTPROC			wglSwapInterval = NULL;

#define					emsg_buf_size 1024
static char				emsg_buf[emsg_buf_size] = {0};
static const wchar_t	w_oom[] = L"[Out of memory!]";

wchar_t*	widen(const char* str);
char*	narrow(const wchar_t* str);
static void		report_windows_lasterror(void);
LONG justkillmenow(EXCEPTION_POINTERS* exp);

/************************************
* OS-specific application entry point
* Calls the platform-independent main
*************************************/
int CALLBACK wWinMain(_In_ HINSTANCE hInst, _In_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
	SetUnhandledExceptionFilter(justkillmenow);
	hInstance = hInst;
	LARGE_INTEGER liPF;
	perffreq = QueryPerformanceFrequency(&liPF) ? liPF.QuadPart : 1000000;
	char* cmdline = narrow(lpCmdLine);
	if(!cmdline) return -2;

	HRESULT hr = CoInitializeEx(NULL, 0);
	if(hr != S_OK) return -1;

	timeBeginPeriod(1);
	int err = yr_appmain(cmdline);
	timeEndPeriod(1);
	
	free(cmdline);
	CoUninitialize();

	return (int)(size_t) err;
}

LONG justkillmenow(EXCEPTION_POINTERS* exp)
{
	//create params string
	wchar_t params[70] = {0};
	swprintf(params, 51, L"yrcrashhandler.exe %llx %llx %llx", (uint64_t)GetCurrentProcessId(), (uint64_t)GetCurrentThreadId(), (uint64_t)exp);
	//spawn crashhandler
	STARTUPINFOW sinfo = {0};
	sinfo.cb = sizeof(sinfo);
	PROCESS_INFORMATION pinfo = {0};
	CreateProcessW(NULL, params, NULL, NULL, TRUE, 0, NULL, NULL, &sinfo, &pinfo);
	//hang around for a minute, either the crashhandler will terminate us or the function should return
	Sleep(60000);
	return EXCEPTION_EXECUTE_HANDLER;
}

/******************
* Utility functions
*******************/
//Report an error to the user with printf syntax
void yr_msgbox(const char* msg, ...)
{
	va_list args;
	va_start(args, msg);
	int len = _vscprintf(msg, args) + 1;

	//alloc larger buffer if required and possible
	char* buffer = NULL;
	if(len >= emsg_buf_size) {
		buffer = malloc(len + 1);
	}
	if(!buffer) {
		buffer = emsg_buf;
		len = emsg_buf_size - 1;
	}

	//print to buffer
	vsnprintf(buffer, len + 1, msg, args);
	yrLog(0, "MSGBOX: %s", buffer);

	//show message
	wchar_t* wbuffer = widen(buffer);
	if(!wbuffer) wbuffer = (wchar_t*) w_oom;
	MessageBoxW(NULL, wbuffer, L"VIA", MB_OK | MB_ICONERROR | MB_TASKMODAL);

	//cleanup
	if(wbuffer != w_oom) free(wbuffer);
	if(buffer != emsg_buf) free(buffer);
	va_end(args);
}

//Platform-dependent main loop processing
int yr_platform_update(void)
{
	MSG msg;
	while(PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) {
		if(msg.message == WM_QUIT) return 1;
		TranslateMessage(&msg);
		DispatchMessageW(&msg);
	}
	return 0;
}

//Get the current execution time in microseconds (10e-6)
uint64_t yr_get_microtime(void)
{
	LARGE_INTEGER liTime;
	uint64_t now = QueryPerformanceCounter(&liTime) ? liTime.QuadPart : 0;
	uint64_t sec = now / perffreq;
	uint64_t part = now % perffreq;
	return sec * 1000000 + (part * 1000000)/perffreq;
}

//Get the current system time as timestamp
uint64_t yr_get_timestamp(void)
{
	SYSTEMTIME st = {0};
	GetSystemTime(&st);
	FILETIME ft = {0};
	SystemTimeToFileTime(&st, &ft);
	uint64_t out = ft.dwHighDateTime;
	out <<= 32;
	out |= ft.dwLowDateTime;
	return out;
}

char* yr_timestamp_to_timestring(uint64_t ts)
{
	FILETIME ft;
	ft.dwHighDateTime = (DWORD)(ts >> 32);
	ft.dwLowDateTime = (DWORD)(ts & 0xFFFFFFFFull);
	FileTimeToLocalFileTime(&ft, &ft);
	SYSTEMTIME st;
	FileTimeToSystemTime(&ft, &st);
	wchar_t szLocalTime[64];
	GetTimeFormatEx(LOCALE_NAME_USER_DEFAULT, TIME_NOSECONDS, &st, NULL, szLocalTime, 64);
	return narrow(szLocalTime);
}

char* yr_timestamp_to_datestring(uint64_t ts)
{
	FILETIME ft;
	ft.dwHighDateTime = (DWORD)(ts >> 32);
	ft.dwLowDateTime = (DWORD)(ts & 0xFFFFFFFFull);
	FileTimeToLocalFileTime(&ft, &ft);
	SYSTEMTIME st;
	FileTimeToSystemTime(&ft, &st);
	wchar_t szLocalDate[192];
	GetDateFormatEx(LOCALE_NAME_USER_DEFAULT, DATE_SHORTDATE, &st, NULL, szLocalDate, 192, NULL);
	return narrow(szLocalDate);
}

//Send a media key event
void yr_media_action(media_action ma)
{
	LONG key = 0;
	switch(ma) {
	case maPlayPause:	key = MAKELONG(0, FAPPCOMMAND_KEY | APPCOMMAND_MEDIA_PLAY_PAUSE); break;
	case maStop:		key = MAKELONG(0, FAPPCOMMAND_KEY | APPCOMMAND_MEDIA_STOP); break;
	case maNext:		key = MAKELONG(0, FAPPCOMMAND_KEY | APPCOMMAND_MEDIA_NEXTTRACK); break;
	case maPrev:		key = MAKELONG(0, FAPPCOMMAND_KEY | APPCOMMAND_MEDIA_PREVIOUSTRACK); break;
	case maVolumeUp:	key = MAKELONG(0, FAPPCOMMAND_KEY | APPCOMMAND_VOLUME_UP); break;
	case maVolumeDown:	key = MAKELONG(0, FAPPCOMMAND_KEY | APPCOMMAND_VOLUME_DOWN); break;
	case maMute:		key = MAKELONG(0, FAPPCOMMAND_KEY | APPCOMMAND_VOLUME_MUTE); break;
	}
	if(key && anywindow) {
		SendMessageW(anywindow, WM_APPCOMMAND, (WPARAM) anywindow, key);
	}
}

//Sleep this thread, note that even though time is specified as microseconds, accuracy is not even 1 ms
void yr_sleep(uint64_t time_us)
{
	DWORD time_ms = (DWORD) (time_us / 1000);
	Sleep(time_ms);
}

void yr_get_real_time(unsigned* year, unsigned* month, unsigned* day, unsigned* h, unsigned* m, unsigned* s, unsigned* ms)
{
	SYSTEMTIME st = {0};
	GetSystemTime(&st);
	*year = st.wYear;
	*month = st.wMonth;
	*day = st.wDay;
	*h = st.wHour;
	*m = st.wMinute;
	*s = st.wSecond;
	*ms = st.wMilliseconds;
}

/***************
* File functions
****************/
//Check path existence
int yr_path_exists(const char* path)
{
	wchar_t* wpath = widen(path);
	if(wpath == NULL) { yrLog(0, "Out of memory for filepath"); return 0; }
	int found = (PathFileExistsW(wpath) != FALSE);
	free(wpath);
	return found;
}

//Delete the file
int yr_delete_file(const char* filepath)
{
	wchar_t* wpath = widen(filepath);
	if(wpath == NULL) { yrLog(0, "Out of memory for filepath"); return -1; }
	BOOL done = DeleteFileW(wpath);
	free(wpath);
	if(!done) {
		REPORT_WINERROR;
		yrLog(0, "Failed to delete file: %s", filepath);
		return -1;
	}
	return 0;
}

//Create the directory and its parent directories
int yr_create_directory(const char* path)
{
	char* p = _strdup(path);
	char* ptr = p;									if(!p) { yrLog(0, "Out of memory for directory path"); goto onerror; }
	while(ptr = strchr(ptr, '/')) *ptr++ = '\\';
	ptr = p;
	while(ptr == strchr(ptr, '\\')) ++ptr; //skip leading for unc paths
	while(ptr = strchr(ptr, '\\')) {
		*ptr = 0;
		if(!yr_path_exists(p)) {
			*ptr = L'\\';
			break;
		}
		*ptr++ = '\\';
	}
	if(!ptr) { free(p); return 0; }
	while(ptr = strchr(ptr, '\\')) {
		*ptr = 0;
		wchar_t* wpath = widen(p);					if(!wpath) { free(p); yrLog(0, "Out of memory for directory path"); goto onerror; }
		if(CreateDirectoryW(wpath, NULL) != FALSE);	else { free(p); free(wpath); REPORT_WINERROR; goto onerror; }
		free(wpath);
		*ptr++ = '\\';
	}
	free(p);
	return 0;
onerror:
	yrLog(0, "Failed to create directory: %s", path);
	return -1;
}

/******************
* Window management
*******************/

typedef struct {
	HWND wnd;
	HDC dc;
	HGLRC rc;
	POINT minsize;
	HIMC ime;
	WINDOWPLACEMENT placement;
	int placementsaved;
	void* id;
	yr_windowfunc_mouse func_mouse;
	yr_windowfunc_keyevent func_keyevent;
	yr_windowfunc_resize func_resize;
	yr_windowfunc_char func_char;
	struct {
		int x;
		int y;
		int w;
		int h;
		int enabled;
	} caret;
}
_yr_window;

static int bind_wgl(void);
static int bind_opengl(void);
#ifdef _DEBUG
static void APIENTRY yrRender_gllogdebug(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam)
{
	yrLog((severity == GL_DEBUG_SEVERITY_HIGH) ? 0 : 1, "OpenGL %s 0x%x: %s", (type == GL_DEBUG_TYPE_ERROR) ? "error" : "message", id, message);
}
#endif

//Window procedure
static LRESULT WINAPI yr_windowproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
	_yr_window* w = (_yr_window*) GetWindowLongPtrW(hwnd, GWLP_USERDATA);
	if(!w) return DefWindowProcW(hwnd, msg, wparam, lparam);

	switch(msg) {
	//mouse move
	case WM_MOUSEMOVE: {
		if(w->func_mouse) {
			int x = GET_X_LPARAM(lparam);
			int y = GET_Y_LPARAM(lparam);
			w->func_mouse(w->id, x, y);
		}
		break;
	}
	//mouse buttons
	case WM_LBUTTONDOWN:
	case WM_LBUTTONUP:
	case WM_RBUTTONDOWN:
	case WM_RBUTTONUP:
	case WM_MBUTTONDOWN:
	case WM_MBUTTONUP:
	case WM_XBUTTONDOWN:
	case WM_XBUTTONUP:
	{
		if(w->func_keyevent) {
			unsigned but;
			int down;
			switch(msg) {
				case WM_LBUTTONDOWN:	but = VK_LBUTTON; down = 1; break;
				case WM_LBUTTONUP:		but = VK_LBUTTON; down = 0; break;
				case WM_RBUTTONDOWN:	but = VK_RBUTTON; down = 1; break;
				case WM_RBUTTONUP:		but = VK_RBUTTON; down = 0; break;
				case WM_MBUTTONDOWN:	but = VK_MBUTTON; down = 1; break;
				case WM_MBUTTONUP:		but = VK_MBUTTON; down = 0; break;
				case WM_XBUTTONDOWN:	but = (wparam & MK_XBUTTON1) ? XBUTTON1 : XBUTTON2; down = 1; break;
				case WM_XBUTTONUP:		but = (wparam & MK_XBUTTON1) ? XBUTTON1 : XBUTTON2; down = 0; break;
			}
			w->func_keyevent(w->id, but, down);
		}
		break;
	}
	//mouse scroll
	case WM_MOUSEWHEEL: {
		if(w->func_keyevent) {
			unsigned but = 0x1400; //TODO: hacky
			mwheel_accum += GET_WHEEL_DELTA_WPARAM(wparam);
			while(mwheel_accum >= WHEEL_DELTA) {
				mwheel_accum -= WHEEL_DELTA;
				w->func_keyevent(w->id, but, 1);
			}
			while(mwheel_accum <= -WHEEL_DELTA) {
				mwheel_accum += WHEEL_DELTA;
				w->func_keyevent(w->id, but, -1);
			}
		};
		break;
	}
	//keyboard
	case WM_KEYDOWN:
	case WM_KEYUP:
	{
		if(w->func_keyevent) {
			w->func_keyevent(w->id, (unsigned) wparam, (msg==WM_KEYDOWN)?1:0);
		}
		break;
	}
	//text input
	case WM_CHAR:
	{
		if(w->func_char) {
			w->func_char(w->id, (unsigned) wparam);
		}
		break;
	}
	//window resize
	case WM_SIZE: {
		if(w->func_resize) {
			w->func_resize(w->id, (unsigned) LOWORD(lparam), (unsigned) HIWORD(lparam));
		}
		break;
	}
	//cursor
	case WM_SETCURSOR: {
		SetCursor(LoadCursorW(NULL,MAKEINTRESOURCEW(IDC_ARROW)));
		break;
	}
	//minimal window size
	case WM_GETMINMAXINFO: {
		LPMINMAXINFO mmi = (LPMINMAXINFO) lparam;
		mmi->ptMinTrackSize = w->minsize;
		return 0;
	}
	//session end (windows logout)
	case WM_QUERYENDSESSION: {
		PostQuitMessage(0); //shut down immediately when requested to do so
		return TRUE;
	}
	case WM_ENDSESSION: {
		return 0; //this should be happening during window destruction, so autosave should have finished already
	}
	//window close
	case WM_CLOSE: {
		size_t foo = 0;
		void* bar = NULL;
		yrWindow_get_placement(w, &foo, &bar);
		if(bar) {
			w->placementsaved = 1;
			memcpy(&w->placement, bar, sizeof(WINDOWPLACEMENT));
			free(bar);
		}
	}
	case WM_DESTROY: {
		PostQuitMessage(0);
		break;
	}
	}
	return DefWindowProcW(hwnd, msg, wparam, lparam);
}

//Create a new window, bind OpenGL functions if required
yrWindow	yrWindow_create(unsigned client_w, unsigned client_h,
							unsigned min_w, unsigned min_h,
							void* id,
							yr_windowfunc_mouse wf_m,
							yr_windowfunc_keyevent wf_ke,
							yr_windowfunc_resize wf_r,
							yr_windowfunc_char wf_c)
{
	int err = bind_wgl();
	if(err) return NULL;

	_yr_window* out = malloc(sizeof(_yr_window));
	if(!out) { yrLog(0, "Out of memory for window."); return NULL; }
	out->wnd = NULL;
	out->dc = NULL;
	out->rc = NULL;
	out->id = id;
	out->func_mouse = wf_m;
	out->func_keyevent = wf_ke;
	out->func_resize = wf_r;
	out->func_char = wf_c;
	out->caret.x = -1;
	out->caret.y = -1;
	out->caret.w = 1;
	out->caret.h = 1;
	out->placementsaved = 0;

	POINT topleft = {0, 0};
	RECT wpos = {0, 0, (LONG) client_w, (LONG) client_h};
	PIXELFORMATDESCRIPTOR pfd = {0};

	//register window class
	if(!wndclass_main) {
		WNDCLASSEXW wc = {0};
		wc.cbSize = sizeof(WNDCLASSEXW);
		wc.lpfnWndProc = (WNDPROC) yr_windowproc;
		wc.hInstance = hInstance;
		wc.lpszClassName = L"VIA Desktop Window";
		wc.style = CS_CLASSDC;
		wc.hIcon = (HICON) LoadImageW(hInstance, MAKEINTRESOURCEW(IDI_ICON1), IMAGE_ICON,
									  GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), 0);
		wndclass_main = RegisterClassExW(&wc);
		if(!wndclass_main) {
			REPORT_WINERROR;
			goto onerror;
		}
	}

	//create actual window
	DWORD exstyle = 0;
	DWORD style = WS_OVERLAPPEDWINDOW;
	AdjustWindowRect(&wpos, style, 0);
	out->wnd = CreateWindowExW(
		exstyle, //extended style
		(LPCWSTR)MAKEINTATOM(wndclass_main), //window class
		L"YR",  //title
		style, //style
		CW_USEDEFAULT, //x
		CW_USEDEFAULT, //y
		wpos.right - wpos.left,
		wpos.bottom - wpos.top,
		NULL, //parent
		NULL, //menu
		hInstance, //instance
		NULL); //pointer to extra data
	if(!out->wnd) {
		REPORT_WINERROR;
		goto onerror;
	}
	SetWindowLongPtrW(out->wnd, GWLP_USERDATA, (LONG_PTR)out);

	//calculate minsize
	if(min_w == 0 && min_h == 0) {
		min_w = client_w;
		min_h = client_h;
	}
	out->minsize.x = min_w + wpos.right - wpos.left - client_w;
	out->minsize.y = min_h + wpos.bottom - wpos.top - client_h;

	//create ime context
	out->ime = ImmGetContext(out->wnd);
	if(!out->ime) {
		REPORT_WINERROR;
		goto onerror;
	}

	//create render context
	out->dc = GetDC(out->wnd);
	if(!out->dc) {
		REPORT_WINERROR;
		goto onerror;
	}

	pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
	pfd.nVersion = 1;
	pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
	pfd.iPixelType = PFD_TYPE_RGBA;
	pfd.cColorBits = 32;
	pfd.cDepthBits = 24;
	pfd.cStencilBits = 8;
	const int pfAttribList[] = {
		WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
		WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
		WGL_DOUBLE_BUFFER_ARB, GL_TRUE,
		WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB,
		WGL_COLOR_BITS_ARB, 32,
		WGL_DEPTH_BITS_ARB, 24,
		WGL_STENCIL_BITS_ARB, 8,
		0};
	int pixelFormat;
	UINT numFormats;
	if(wglChoosePixelFormatARB(out->dc, pfAttribList, NULL, 1, &pixelFormat, &numFormats));
	else pixelFormat = 0;
	BOOL ok = SetPixelFormat(out->dc, pixelFormat, &pfd);
	if(!pixelFormat || !ok) {
		REPORT_WINERROR;
		goto onerror;
	}
	const int rcAttribList[] = {
		WGL_CONTEXT_MAJOR_VERSION_ARB, 4,
		WGL_CONTEXT_MINOR_VERSION_ARB, 4,
		0};
	out->rc = wglCreateContextAttribsARB(out->dc, 0, rcAttribList);
	if(!out->rc) {
		REPORT_WINERROR;
		goto onerror;
	}
	wglMakeCurrent(out->dc, out->rc);
	wglSwapInterval(0);

	//bind the opengl functions
	#define GL(func, type) funcok = funcok && func != NULL;
	int funcok = 1;
	OPENGL_FUNC_LIST;
	#undef GL
	if(!funcok) err = bind_opengl();
	else err = 0;
	if(err) goto onerror;

#ifdef _DEBUG
	glDebugMessageCallback(yrRender_gllogdebug, NULL);
#endif

	ShowWindow(out->wnd, SW_SHOWNORMAL);
	anywindow = out->wnd;
	return out;

onerror:
	yrLog(0, "OS failed to create window.");
	yrWindow_destroy(out);
	return NULL;
}

//Close and destroy the given window and its associated graphics API context
void yrWindow_destroy(yrWindow wnd)
{
	YR_ASSERT(wnd);
	_yr_window* w = (_yr_window*) wnd;
	if(anywindow == w->wnd) anywindow = NULL;
	MSG msg;
	while(PeekMessageW(&msg, w->wnd, 0, 0, PM_REMOVE)) DispatchMessageW(&msg); //clear messages
	if(w->dc) wglMakeCurrent(w->dc, NULL);
	if(w->rc) wglDeleteContext(w->rc);
	if(w->dc) ReleaseDC(w->wnd, w->dc);
	if(w->ime) ImmReleaseContext(w->wnd, w->ime);
	if(w->wnd) DestroyWindow(w->wnd);
	free(w);
}

//Flip the back and front buffers on the window
void yrWindow_swapbuffers(yrWindow wnd)
{
	YR_ASSERT(wnd);
	_yr_window* w = (_yr_window*) wnd;
	SwapBuffers(w->dc);
}

//Set the titlebar text
void yrWindow_set_title(yrWindow wnd, const char* txt)
{
	YR_ASSERT(wnd);
	_yr_window* w = (_yr_window*) wnd;
	wchar_t* wtxt = widen(txt);
	if(wtxt) {
		SetWindowTextW(w->wnd, wtxt);
		free(wtxt);
	}
	else yrLog(1, "Out of memory for window title");
}

//create or destroy the caret
void yrWindow_caret_enable(yrWindow wnd, int enabled)
{
	YR_ASSERT(wnd);
	_yr_window* win = (_yr_window*) wnd;
	if(enabled) {
		if(!win->caret.enabled) {
			win->caret.enabled = 1;
			LOGFONTW imefont = {0};
			imefont.lfHeight = win->caret.h;
			imefont.lfCharSet = DEFAULT_CHARSET;
			ImmSetCompositionFontW(win->ime, &imefont);

			CreateCaret(win->wnd, NULL, win->caret.w, win->caret.h);
			yrWindow_caret_move(wnd, win->caret.x, win->caret.y);
		}
	} else {
		if(win->caret.enabled) {
			win->caret.enabled = 0;
			DestroyCaret();
		}
	}
}

//move the caret
void yrWindow_caret_move(yrWindow wnd, int x, int y)
{
	YR_ASSERT(wnd);
	_yr_window* win = (_yr_window*) wnd;
	win->caret.x = x;
	win->caret.y = y;
	if(win->caret.enabled) {
		SetCaretPos(win->caret.x, win->caret.y);
		COMPOSITIONFORM cf;
		cf.dwStyle = CFS_POINT;
		cf.ptCurrentPos.x = win->caret.x;
		cf.ptCurrentPos.y = win->caret.y;// + win->caret.h;
		ImmSetCompositionWindow(win->ime, &cf);
	}
}

//recreate the caret with a new size
void yrWindow_caret_resize(yrWindow wnd, int w, int h)
{
	YR_ASSERT(wnd);
	_yr_window* win = (_yr_window*) wnd;
	win->caret.w = w;
	win->caret.h = h;
	if(win->caret.enabled) {
		yrWindow_caret_enable(wnd, 0);
		yrWindow_caret_enable(wnd, 1);
	}
}

//retrieve window placement data in an os specific format
void yrWindow_get_placement(yrWindow wnd, size_t* len, void** data)
{
	YR_ASSERT(wnd);
	_yr_window* win = (_yr_window*) wnd;
	*data = calloc(1, sizeof(WINDOWPLACEMENT));
	if(!*data) {yrLog(0, "Out of memory!"); *len = 0; return;}
	*len = sizeof(WINDOWPLACEMENT);
	((WINDOWPLACEMENT*)*data)->length = sizeof(WINDOWPLACEMENT);
	BOOL ok = GetWindowPlacement(win->wnd, (WINDOWPLACEMENT*)*data);
	if(!ok) {
		if(win->placementsaved) {
			memcpy(*data, &win->placement, *len);
		} else {
			REPORT_WINERROR;
			free(*data);
			*data = NULL;
			*len = 0;
			return;
		}
	}
	UINT cmd = ((WINDOWPLACEMENT*)*data)->showCmd;
	if(cmd == SW_MINIMIZE || cmd == SW_SHOWMINIMIZED || cmd == SW_SHOWMINNOACTIVE) {
		((WINDOWPLACEMENT*)*data)->showCmd = SW_SHOW;
	}
}

//set window placement data in an os specific format
void yrWindow_set_placement(yrWindow wnd, size_t len, void* data, unsigned* w, unsigned* h)
{
	YR_ASSERT(wnd);
	_yr_window* win = (_yr_window*) wnd;
	if(len != sizeof(WINDOWPLACEMENT)) return;
	BOOL ok = SetWindowPlacement(win->wnd, (WINDOWPLACEMENT*) data);
	if(!ok) REPORT_WINERROR;
	else {
		//manually post a WM_SIZE to update the client area
		RECT crect;
		ok = GetClientRect(win->wnd, &crect);
		if(!ok) REPORT_WINERROR;
		else {
			LPARAM lprm = MAKELPARAM(crect.right - crect.left, crect.bottom - crect.top);
			if(w) *w = (unsigned) crect.right - crect.left;
			if(h) *h = (unsigned) crect.bottom - crect.top;
			SendMessageW(win->wnd, WM_SIZE, 0, lprm);
		}
	}
}

/****************************
* Threads and synchronization
*****************************/

typedef struct {
	yr_threadfunc func;
	void* param;
}
threadparam;

static DWORD WINAPI threadrunner(LPVOID p)
{
	threadparam tp;
	tp.func = ((threadparam*) p)->func;
	tp.param = ((threadparam*) p)->param;
	free(p);

	tp.func(tp.param);

	return 0;
}

yrThread yrThread_create(yr_threadfunc func, void* param)
{
	threadparam* p = malloc(sizeof(threadparam));
	if(!p) return NULL;
	p->func = func;
	p->param = param;
	HANDLE h = CreateThread(NULL, 0, threadrunner, (LPVOID) p, 0, NULL);
	if(h == NULL) {
		free(p);
		REPORT_WINERROR;
		return NULL;
	}
	return (yrThread) h;
}

//Wait for the thread to finish, this deletes the thread
int yrThread_join(yrThread t, uint64_t time_us)
{
	uint64_t time_ms = time_us / 1000;
	if(time_ms > INFINITE) time_ms = INFINITE;
	WaitForSingleObject((HANDLE) t, (DWORD) time_ms);
	DWORD ret = 0;
	BOOL ok = GetExitCodeThread((HANDLE) t, &ret);
	if(!ok || ret != STILL_ACTIVE) {
		CloseHandle(t);
		if(!ok) REPORT_WINERROR;
		return ok ? 0 : -1;
	}
	return 1;
}

//Create a synchronization event
yrEvent yrEvent_create(void)
{
	yrEvent out = (yrEvent) CreateEventW(NULL, TRUE, FALSE, NULL);
	if(!out) REPORT_WINERROR;
	return out;
}

//Destroy the event
void yrEvent_destroy(yrEvent e)
{
	if(e) CloseHandle((HANDLE) e);
}

//Set or reset the event
void yrEvent_set(yrEvent e, int state)
{
	BOOL ok = FALSE;
	if(state) ok = SetEvent((HANDLE) e);
	else ok = ResetEvent((HANDLE) e);
	if(!ok) REPORT_WINERROR;
}

//Wait for the event to be set
int yrEvent_wait(yrEvent e, uint64_t time_us)
{
	uint64_t time_ms = time_us / 1000;
	if(time_ms > INFINITE) time_ms = INFINITE;
	DWORD ret = WaitForSingleObject((HANDLE) e, (DWORD) time_ms);
	if(ret == WAIT_FAILED) {REPORT_WINERROR; }
	return (ret == WAIT_TIMEOUT) ? 1 : 0;
}

/****************
* Extra functions
*****************/
//convert UTF-8 to UTF-16
wchar_t* widen(const char* str)
{
	//alloc wide string
	int size = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
	if(size == 0) size = 1;
	wchar_t* wstr = malloc(size * sizeof(wchar_t));
	if(!wstr) return NULL;
	MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, size);
	wstr[size - 1] = 0;
	return wstr;
}

//convert UTF-16 to UTF-8
char* narrow(const wchar_t* wstr)
{
	//alloc wide string
	int size = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, FALSE);
	if(size == 0) size = 1;
	char* str = malloc(size * sizeof(char));
	if(!str) return NULL;
	WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, size, NULL, FALSE);
	str[size - 1] = 0;
	return str;
}

//return the last windows err as yr_ret
static void report_windows_lasterror(void)
{
	DWORD winerr = GetLastError();
	LPSTR buf = NULL;
	DWORD ok = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 0, winerr, 0, (LPSTR)&buf, 1, NULL);
	if(ok) {
		buf[strlen(buf)-2] = 0;//drop the newline
		buf[strlen(buf)-1] = 0;//drop the newline
		yrLog(0, "%s", buf);
	}
	else yrLog(0, "Windows errorcode %x occured and while reporting it FormatMessage failed with errorcode %x.", winerr, GetLastError());
}

//Utility function to check the validness of a function pointer
static int invalidPFN(void* function)
{
	return (function == 0) || (function == ((void*) -1));
}

//Bind OpenGL functions using the current context
static int bind_opengl(void)
{
	#define GL(func, type) \
	do { \
		func = (type) wglGetProcAddress(#func); \
		if(invalidPFN(func)) { \
			yrLog(0, "Failed to bind OpenGL function: %s", #func); \
			return -1; \
		} \
	} while(0)
	OPENGL_FUNC_LIST
	#undef GL
	return 0;
};

//Makes wglChoosePixelFormatARB and wglCreateContextAttribsARB available for use
static int bind_wgl(void)
{
	if(wglChoosePixelFormatARB &&
	   wglCreateContextAttribsARB &&
	   wglSwapInterval)
		return 0;

	//register dummy window class
	if(!wndclass_dummy) {
		WNDCLASSEXW wc = {0};
		wc.cbSize = sizeof(WNDCLASSEXW);
		wc.lpfnWndProc = DefWindowProcW;
		wc.hInstance = hInstance;
		wc.lpszClassName = L"VIA Dummy Window";
		wc.style = CS_CLASSDC;
		wndclass_dummy = RegisterClassExW(&wc);
		if(!wndclass_dummy) {
			REPORT_WINERROR;
			return -1;
		}
	}

	//create dummy window
	HWND dummy_handle = CreateWindowExW(
		0, //extended style
		(LPCWSTR)MAKEINTATOM(wndclass_dummy), //window class
		L"Dummy Window",  //title
		WS_DISABLED | WS_ICONIC, //style
		CW_USEDEFAULT, CW_USEDEFAULT, //x,y
		CW_USEDEFAULT, CW_USEDEFAULT, //w,h
		NULL, NULL, //parent, menu
		hInstance, NULL); //instance, pointer to extra data
	if(!dummy_handle) {
		REPORT_WINERROR;
		return -1;
	}

	//get basic OpenGL context
	HDC dummy_dc = GetDC(dummy_handle);
	PIXELFORMATDESCRIPTOR pfd = {0};
	pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
	pfd.nVersion = 1;
	pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
	pfd.iPixelType = PFD_TYPE_RGBA;
	pfd.cColorBits = 32;
	pfd.cDepthBits = 24;
	pfd.cStencilBits = 8;
	int dummy_pf = ChoosePixelFormat(dummy_dc, &pfd);
	BOOL ok = SetPixelFormat(dummy_dc, dummy_pf, &pfd);
	HGLRC dummy_rc = wglCreateContext(dummy_dc);
	if(!ok || !dummy_rc) {
		ReleaseDC(dummy_handle, dummy_dc);
		DestroyWindow(dummy_handle);
		REPORT_WINERROR;
		return -1;
	}
	wglMakeCurrent(dummy_dc, dummy_rc);

	//bind functions
	wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC) wglGetProcAddress("wglCreateContextAttribsARB");
	wglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC) wglGetProcAddress("wglChoosePixelFormatARB");
	wglSwapInterval = (PFNWGLSWAPINTERVALEXTPROC) wglGetProcAddress("wglSwapIntervalEXT");

	//cleanup
	wglMakeCurrent(dummy_dc, NULL);
	wglDeleteContext(dummy_rc);
	ReleaseDC(dummy_handle, dummy_dc);
	DestroyWindow(dummy_handle);

	//check function pointers
	if(invalidPFN(wglChoosePixelFormatARB) ||
	   invalidPFN(wglCreateContextAttribsARB) ||
	   invalidPFN(wglSwapInterval))
	{
		wglChoosePixelFormatARB = NULL;
		wglCreateContextAttribsARB = NULL;
		wglSwapInterval = NULL;
		yrLog(0, "Failed to bind a WGL function (wglChoosePixelFormatARB, wglCreateContextAttribsARB or wglSwapInterval).");
		return -1;
	}
	return 0;
}

yrLock yrLock_create(void)
{
	CRITICAL_SECTION* out = malloc(sizeof(CRITICAL_SECTION));
	if(!out) { yrLog(0, "Out of memory for lock"); return NULL; }
	InitializeCriticalSection(out);
	return out;
}

void yrLock_destroy(yrLock l)
{
	if(!l) return;
	DeleteCriticalSection(l);
}

void yrLock_aqcuire(yrLock l)
{
	if(!l) return;
	EnterCriticalSection(l);
}
void yrLock_release(yrLock l)
{
	if(!l) return;
	LeaveCriticalSection(l);
}

/***************
* File functions
****************/
struct yr_file {
	HANDLE f;
};

yrFile yrFile_open(const char* name, unsigned flags)
{
	struct yr_file* out = malloc(sizeof(struct yr_file));
	if(!out) { yrLog(0, "Out of memory"); return NULL; }

	//access & creation disposition
	DWORD access = 0;
	if(flags & yrF_read) access |= GENERIC_READ;
	if(flags & yrF_write) access |= GENERIC_WRITE;
	if(!access) { free(out); yrLog(0, "No access specified to file %s", name); return NULL; }

	DWORD creation = 0;
	if(flags & yrF_create) {
		creation = CREATE_ALWAYS;
	}
	else creation = OPEN_EXISTING;

	//unicode conversion of path
	wchar_t* wpath = widen(name);
	if(!wpath) { free(out); yrLog(0, "Out of memory"); return NULL; }

	//open file
	out->f = CreateFileW(wpath, access, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, creation, FILE_FLAG_RANDOM_ACCESS, NULL);
	free(wpath);
	if(out->f == INVALID_HANDLE_VALUE) {
		free(out);
		REPORT_WINERROR;
		yrLog(0, "Failed to open file %s", name);
		return NULL;
	}

	return out;
}

int yrFile_get_filemeta(const char* name, int64_t* size, char** time_created, char** time_modified)
{
	//unicode conversion of path
	wchar_t* wpath = widen(name);
	if(!wpath) { yrLog(0, "Out of memory"); return -1; }
	
	//open file
	HANDLE f = CreateFileW(wpath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
	free(wpath);
	if(f == INVALID_HANDLE_VALUE) {
		REPORT_WINERROR;
		yrLog(0, "Failed to open file %s", name);
		return -1;
	}

	//get size
	LARGE_INTEGER liSize;
	BOOL ok = GetFileSizeEx(f, &liSize);
	*size = ok ? liSize.QuadPart : 0;

	//get file times
	FILETIME create;
	FILETIME modify;
	ok = GetFileTime(f, &create, NULL, &modify);
	CloseHandle(f);
	if(!ok) {
		REPORT_WINERROR;
		return -1;
	}

	//file times to strings
	SYSTEMTIME st;
	wchar_t szLocalDate[192], szLocalTime[64], szTotal[256];
	FileTimeToLocalFileTime(&create, &create);
	FileTimeToLocalFileTime(&modify, &modify);

	FileTimeToSystemTime(&create, &st);
	GetDateFormatEx(LOCALE_NAME_USER_DEFAULT, DATE_LONGDATE, &st, NULL, szLocalDate, 192, NULL);
	GetTimeFormatEx(LOCALE_NAME_USER_DEFAULT, TIME_NOSECONDS, &st, NULL, szLocalTime, 64);
	swprintf(szTotal, 256, L"%s - %s", szLocalTime, szLocalDate);
	*time_created = narrow(szTotal);

	FileTimeToSystemTime(&modify, &st);
	GetDateFormatEx(LOCALE_NAME_USER_DEFAULT, DATE_LONGDATE, &st, NULL, szLocalDate, 192, NULL);
	GetTimeFormatEx(LOCALE_NAME_USER_DEFAULT, TIME_NOSECONDS, &st, NULL, szLocalTime, 64);
	swprintf(szTotal, 256, L"%s - %s", szLocalTime, szLocalDate);
	*time_modified = narrow(szTotal);

	if(!*time_created) *time_created = _strdup("");
	if(!*time_modified) *time_modified = _strdup("");

	return 0;
}

int yrFile_close(yrFile f)
{
	if(!f) return -1;
	BOOL ok = CloseHandle(((struct yr_file*)f)->f);
	if(!ok) REPORT_WINERROR;
	free(f);
	return ok ? 0 : -1;
}

int64_t	yrFile_read(yrFile f, int64_t len, void* buf)
{
	if(!f) return 0;
	if(len <= 0) return 0;
	if(len > 0xFFFFFFFFull) return 0;
	HANDLE file = ((struct yr_file*)f)->f;
	DWORD rlen = (DWORD) len;
	DWORD rdone;

	BOOL ok = ReadFile(file, buf, rlen, &rdone, NULL);
	if(!ok) REPORT_WINERROR;
	return ok ? rdone : 0;
}

int64_t	yrFile_write(yrFile f, int64_t len, void* buf)
{
	if(!f) return 0;
	if(len <= 0) return 0;
	if(len > 0xFFFFFFFFull) return 0;
	HANDLE file = ((struct yr_file*)f)->f;
	DWORD rlen = (DWORD) len;
	DWORD rdone;

	BOOL ok = WriteFile(file, buf, rlen, &rdone, NULL);
	if(!ok) REPORT_WINERROR;
	return ok ? rdone : 0;
}

int64_t	yrFile_seek(yrFile f, int64_t pos, unsigned mode)
{
	if(!f) return 0;
	HANDLE file = ((struct yr_file*)f)->f;
	DWORD lo = (DWORD) (pos & 0xFFFFFFFFull);
	DWORD hi = (DWORD) (pos >> 32);
	DWORD md;
	switch(mode) {
	case yrF_seekset: md = FILE_BEGIN; break;
	case yrF_seekcur: md = FILE_CURRENT; break;
	case yrF_seekend: md = FILE_END; break;
	default: md = FILE_BEGIN; break;
	}
	lo = SetFilePointer(file, lo, &hi, md);
	if(lo == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) {
		REPORT_WINERROR;
		return 0;
	}
	return (((int64_t)hi)<<32) + ((int64_t)lo);
}

/*************************
* Common file dialog stuff
**************************/

#define PGUID_EQ(a,b) (((uint64_t*)a)[0] == ((uint64_t*)b)[0] && ((uint64_t*)a)[1] == ((uint64_t*)b)[1])

static HRESULT STDMETHODCALLTYPE yrFDE_QueryInterface(__RPC__in IFileDialogEvents * This, __RPC__in REFIID riid, _COM_Outptr_  void **ppvObject)
{
	if(!ppvObject) return E_POINTER;
	//IFileDialog
	if(PGUID_EQ(riid, &IID_IFileDialog)) {
		 *ppvObject = This;
		 return S_OK;
	}
	//unsupported interface
	return E_NOINTERFACE;
}
ULONG STDMETHODCALLTYPE yrFDE_AddRef(__RPC__in IFileDialogEvents * This) { return 1; } //"bad" implementation but this is only ever used in the body of yrFile_pathdialog
ULONG STDMETHODCALLTYPE yrFDE_Release(__RPC__in IFileDialogEvents * This) { return 0; }
static HRESULT STDMETHODCALLTYPE yrFDE_OnFileOk(__RPC__in IFileDialogEvents * This, __RPC__in_opt IFileDialog *pfd)
{ return E_NOTIMPL; }
static HRESULT STDMETHODCALLTYPE yrFDE_OnFolderChanging(__RPC__in IFileDialogEvents * This, __RPC__in_opt IFileDialog *pfd, __RPC__in_opt IShellItem *psiFolder)
{ return E_NOTIMPL; }
static HRESULT STDMETHODCALLTYPE yrFDE_OnFolderChange(__RPC__in IFileDialogEvents * This, __RPC__in_opt IFileDialog *pfd)
{ return E_NOTIMPL; }
static HRESULT STDMETHODCALLTYPE yrFDE_OnSelectionChange(__RPC__in IFileDialogEvents * This, __RPC__in_opt IFileDialog *pfd)
{ return E_NOTIMPL; }
static HRESULT STDMETHODCALLTYPE yrFDE_OnShareViolation(__RPC__in IFileDialogEvents * This, __RPC__in_opt IFileDialog *pfd, __RPC__in_opt IShellItem *psi, __RPC__out FDE_SHAREVIOLATION_RESPONSE *pResponse)
{ return E_NOTIMPL; }
static HRESULT STDMETHODCALLTYPE yrFDE_OnTypeChange(__RPC__in IFileDialogEvents * This, __RPC__in_opt IFileDialog *pfd)
{ return E_NOTIMPL; }
static HRESULT STDMETHODCALLTYPE yrFDE_OnOverwrite(__RPC__in IFileDialogEvents * This, __RPC__in_opt IFileDialog *pfd, __RPC__in_opt IShellItem *psi, __RPC__out FDE_OVERWRITE_RESPONSE *pResponse)
{ return E_NOTIMPL; }
static struct IFileDialogEventsVtbl yrFDE_vtable = {
	yrFDE_QueryInterface,
	yrFDE_AddRef,
	yrFDE_Release,
	yrFDE_OnFileOk,
	yrFDE_OnFolderChanging,
	yrFDE_OnFolderChange,
	yrFDE_OnSelectionChange,
	yrFDE_OnShareViolation,
	yrFDE_OnTypeChange,
	yrFDE_OnOverwrite,
};

static void destroy_filterspec(unsigned filter_count, COMDLG_FILTERSPEC* filters)
{
	for(unsigned i = 0; i < filter_count; ++i) {
		free((void*)filters[i].pszName);
		free((void*)filters[i].pszSpec);
	}
	free(filters);
}

static COMDLG_FILTERSPEC* create_filterspec(unsigned filter_count, const char** filter_names, const char** filter_specs)
{
	COMDLG_FILTERSPEC* out = malloc(filter_count * sizeof(COMDLG_FILTERSPEC));
	if(!out) {yrLog(0, "Out of memory"); return NULL;}

	unsigned i;
	for(i = 0; i < filter_count; ++i)
	{
		out[i].pszName = widen(filter_names[i]);
		if(!out[i].pszName) {
			destroy_filterspec(i, out);
			return NULL;
		}
		out[i].pszSpec = widen(filter_specs[i]);
		if(!out[i].pszSpec) {
			free((void*)out[i].pszName);
			destroy_filterspec(i, out);
			return NULL;
		}
	}
	return out;
}


static const GUID dialog_guids[] = {
	{ 0xa3525f3f, 0x84bf, 0x4b5c, { 0x98, 0x5d, 0xe0, 0x9a, 0x8c, 0xdd, 0x54, 0x0d } }, // {A3525F3F-84BF-4B5C-985D-E09A8CDD540D}
	{ 0xa3525f3f, 0x84bf, 0x4b5c, { 0x98, 0x5d, 0xe0, 0x9a, 0x8c, 0xdd, 0x54, 0x0d } }, // {A3525F3F-84BF-4B5C-985D-E09A8CDD540D}
	{ 0xdc1df494, 0x30a2, 0x4844, { 0x9a, 0x19, 0xaa, 0xa7, 0xe2, 0xf1, 0xcb, 0x77 } }, // {DC1DF494-30A2-4844-9A19-AAA7E2F1CB77}
	{ 0xea96f301, 0x286b, 0x4cc9, { 0xb6, 0xe6, 0xc7, 0xa7, 0xd0, 0xc6, 0xec, 0xea } }, // {EA96F301-286B-4CC9-B6E6-C7A7D0C6ECEA}
	{ 0x12351d8b, 0xd614, 0x498f, { 0x82, 0x3d, 0x20, 0x3c, 0xd7, 0x64, 0x1c, 0xd8 } }, // {12351D8B-D614-498F-823D-203CD7641CD8}
};

char* yrFile_path_dialog(int dialogid, int issavedlg, int isfolderdlg, unsigned filter_count, const char** filter_names, const char** filter_specs, const char* default_ext)
{
	if(dialogid < 0 || dialogid >= (sizeof(dialog_guids)/sizeof(GUID))) dialogid = 0;
	char* out = NULL;
	HRESULT hr;
	IFileDialog* pfd = NULL;
	IFileDialogEvents fde_handler = { &yrFDE_vtable };
	DWORD cookie = 0;
	DWORD flags;
	int pfd_advised = 0;
	COMDLG_FILTERSPEC* filters = NULL;
	wchar_t* wdef_ext = NULL;
	IShellItem* result = NULL;
	wchar_t* wide_out = NULL;

	//create dialog
	const CLSID clsid = issavedlg ? CLSID_FileSaveDialog : CLSID_FileOpenDialog;
	const IID iid = IID_IFileDialog;
	hr = CoCreateInstance(&clsid, NULL, CLSCTX_INPROC_SERVER, &iid, &pfd);		if(FAILED(hr)) {yrLog(0, "HRESULT error: %x", hr); goto cleanup;}
	//event handler plumbing
	//hr = pfd->lpVtbl->Advise(pfd, &fde_handler, &cookie);						if(FAILED(hr)) {yrLog(0, "HRESULT error: %x", hr); goto cleanup;}
	pfd_advised = 1;
	//set dialog options
	hr = pfd->lpVtbl->GetOptions(pfd, & flags);									if(FAILED(hr)) {yrLog(0, "HRESULT error: %x", hr); goto cleanup;}
	flags |= FOS_FORCEFILESYSTEM;
	if(isfolderdlg) flags |= FOS_PICKFOLDERS;
	if(issavedlg) flags |= FOS_OVERWRITEPROMPT;
	hr = pfd->lpVtbl->SetOptions(pfd, flags);									if(FAILED(hr)) {yrLog(0, "HRESULT error: %x", hr); goto cleanup;}
	//file type filters
	if(filter_count) {
		filters = create_filterspec(filter_count, filter_names, filter_specs);	if(!filters) goto cleanup;
		hr = pfd->lpVtbl->SetFileTypes(pfd, filter_count, filters);				if(FAILED(hr)) {yrLog(0, "HRESULT error: %x", hr); goto cleanup;}
	}
	//default extension
	if(default_ext) {
		wdef_ext = widen(default_ext);											if(!wdef_ext) goto cleanup;
		hr = pfd->lpVtbl->SetDefaultExtension(pfd, wdef_ext);					if(FAILED(hr)) {yrLog(0, "HRESULT error: %x", hr); goto cleanup;}
	}
	//show dialog
	pfd->lpVtbl->SetClientGuid(pfd, &dialog_guids[dialogid]);
	hr = pfd->lpVtbl->Show(pfd, NULL);
	if(hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) goto cleanup;					if(FAILED(hr)) {yrLog(0, "HRESULT error: %x", hr); goto cleanup;}
	//get path
	hr = pfd->lpVtbl->GetResult(pfd, &result);									if(FAILED(hr)) {yrLog(0, "HRESULT error: %x", hr); goto cleanup;}
	hr = result->lpVtbl->GetDisplayName(result, SIGDN_FILESYSPATH, &wide_out);	if(FAILED(hr)) {yrLog(0, "HRESULT error: %x", hr); goto cleanup;}
	out = narrow(wide_out);														if(!out) goto cleanup;

	//done, just cleanup and error logging left
cleanup:
	if(wide_out) CoTaskMemFree(wide_out);
	if(result) result->lpVtbl->Release(result);
	if(wdef_ext) free(wdef_ext);
	if(filters) destroy_filterspec(filter_count, filters);
	//if(pfd_advised) pfd->lpVtbl->Unadvise(pfd, cookie);
	//fde needs no cleanup because it doesn't really exist
	if(pfd) pfd->lpVtbl->Release(pfd);
	return out;
}

char* yrFile_path_localdata(void)
{
	wchar_t* w_lappdata = NULL;
	HRESULT hr = SHGetKnownFolderPath(&FOLDERID_LocalAppData, 0, NULL, &w_lappdata);
	if(FAILED(hr) || !w_lappdata) {yrLog(0, "Could not retrieve AppData/Local path. (%x)", hr); return NULL;}

	char* lappdata = narrow(w_lappdata);
	CoTaskMemFree(w_lappdata);
	if(!lappdata) {yrLog(0, "Out of memory"); return NULL;}

	const char subdir[] = "/Racaedym/VIA/";
	char* out = realloc(lappdata, strlen(lappdata) + sizeof(subdir));
	if(!out) {yrLog(0, "Out of memory"); free(lappdata); return NULL;}
	strcat(out, subdir);

	return out;
}