#include <9pm/windows.h>
#include <9pm/u.h>
#include <9pm/libc.h>
#include <9pm/thread.h>
#include <9pm/ns.h>
#include <9pm/syscall.h>

static HWND window;
static LRESULT CALLBACK SyscallProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
static HANDLE me;

void
threadmain(int argc, char **argv)
{
	WNDCLASS wc;
	HANDLE inst;
	MSG msg;

	threadprint(1, "hello world\n");

	inst = GetModuleHandle(nil);
	wc.style = 0;
	wc.lpfnWndProc = SyscallProc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = inst;
	wc.hIcon = LoadIcon(inst, nil);
	wc.hCursor = LoadCursor(nil, IDC_ARROW);
	wc.hbrBackground = GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName = 0;
	wc.lpszClassName = L"Plan 9 Kernel";
	RegisterClass(&wc);
	me = GetCurrentProcess();

	window = CreateWindowEx(
		WS_EX_TOOLWINDOW,
		L"Plan 9 Kernel",
		L"Plan 9 Kernel",
		WS_OVERLAPPEDWINDOW|
		WS_MINIMIZE|WS_DISABLED,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		100,
		100,
		NULL,
		NULL,
		inst,
		NULL
		);
	if(window == nil){
		oserror();
		sysfatal("can't allocate window: %r");
	}
	dprint(~0, "allocated window\n");
	while(GetMessage(&msg, NULL, 0, 0)) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	threadexits(nil);
}

DWORD WINAPI
SyscallThread(void *arg)
{
	int i;
	HANDLE harr[2];
	Handles *h;
	int status;
	ulong *call;

	h = arg;
	harr[0] = h->hserversig;
	harr[1] = h->hclientproc;
	call = (ulong*)&h[2];
	threadprint(1, "waiting\n");
	for(;;){
		i = WaitForMultipleObjects(2, harr, 0, INFINITE);
		switch(i){
		case WAIT_OBJECT_0:
			threadprint(1, "Got call %d\n", *call);
			*(call+1) = 67890;
			Sleep(10000);
			threadprint(1, "Releasing semaphore\n");
			ReleaseSemaphore(h->hclientsig, 1, nil);
			break;
		case WAIT_OBJECT_0+1:
			if(!GetExitCodeProcess(h->hclientproc, &status)){
				threadprint(1, "GECP failed\n");
				goto end;
			}
			if(status == STILL_ACTIVE){
				threadprint(1, "got client proc\n");
				break;
			}
			goto end;
		default:
			threadprint(1, "Confused\n");
			goto end;
		}
	}
		
end:
	CloseHandle(h->hserversig);
	CloseHandle(h->hclientsig);
	CloseHandle(h->hclientproc);
	CloseHandle(h->hmem);
	UnmapViewOfFile(h);
	ExitThread(0);
	return 0;
}

LRESULT CALLBACK
SyscallProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
	Handles *h, *x;
	void *mem;
	HANDLE hproc, hmem, hthr;

	threadprint(1, "msg %d\n", msg);
	switch(msg){
	case WM_USER:
		hproc = OpenProcess(PROCESS_ALL_ACCESS, 0, lparam);
		if(hproc == nil){
			oserror();
			threadprint(1, "OpenProcess %d: %r\n", lparam);
		Err0:
			return 0;
		}
		if(0){
		Err1:
			CloseHandle(hproc);
			goto Err0;
		}

		hmem = CreateFileMapping(nil, nil, PAGE_READWRITE, 0, CallMem, nil);
		if(hmem == nil){
			oserror();
			threadprint(1, "CreateFileMapping for %d: %r\n", lparam);
			goto Err1;
		}
		if(0){
		Err2:
			CloseHandle(hmem);
			goto Err1;
		}

		mem = MapViewOfFile(hmem, FILE_MAP_WRITE, 0, 0, 0);
		if(mem == nil){
			oserror();
			threadprint(1, "MapViewOfFile for %d: %r\n", lparam);
			goto Err2;
		}
		if(0){
		Err3:
			UnmapViewOfFile(mem);
			goto Err2;
		}

		h = mem;
		memset(h, 0, 2*sizeof(*h));
		h->hmem = hmem;
		h->hclientproc = hproc;

		if((h->hserversig = CreateSemaphore(nil, 0, 1, nil)) == nil){
			oserror();
			threadprint(1, "CreateSemaphore: %r\n");
			goto Err3;
		}
		if(0){
		Err4:
			CloseHandle(h->hserversig);
			goto Err3;
		}

		if((h->hclientsig = CreateSemaphore(nil, 0, 1, nil)) == nil){
			oserror();
			threadprint(1, "CreateSemaphore: %r\n");
			goto Err4;
		}
		if(0){
		Err5:
			CloseHandle(h->hclientsig);
			goto Err4;
		}

		x = &h[1];
		if(!DuplicateHandle(me, h->hserversig, hproc, &x->hserversig, 0, 0, DUPLICATE_SAME_ACCESS)){
			oserror();
			threadprint(1, "DuplicateHandle 0 %p for %d: %r\n", hmem, lparam);
			goto Err5;
		}
		if(!DuplicateHandle(me, h->hclientsig, hproc, &x->hclientsig, 0, 0, DUPLICATE_SAME_ACCESS)){
			oserror();
			threadprint(1, "DuplicateHandle 1 %p for %d: %r\n", hmem, lparam);
			goto Err5;
		}
		if(!DuplicateHandle(me, h->hmem, hproc, &x->hmem, 0, 0, DUPLICATE_SAME_ACCESS)){
			oserror();
			threadprint(1, "DuplicateHandle 2 %p for %d: %r\n", hmem, lparam);
			goto Err5;
		}

		/*
		 * We can't close the other guy's handles, so if an error occurs
		 * we have to just leave them and only clean up after ourselves.
		 */
		hthr = CreateThread(nil, 16384, SyscallThread, h, 0, nil);
		if(hthr == nil){
			oserror();
			threadprint(1, "CreateThread: %r\n");
			goto Err5;
		}
		CloseHandle(hthr);

		return (LRESULT)x->hmem;

	default:
		return DefWindowProc(hwnd, msg, wparam, lparam);
	}
}
