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

static long _passfd(int, HANDLE);
static void _initenv(void);
static Rune srv[] = L"Plan 9 Kernel";
static int fdid[3] = {
	STD_INPUT_HANDLE,
	STD_OUTPUT_HANDLE,
	STD_ERROR_HANDLE
};
static HANDLE hsrv;
static HWND hwin;
static ulong _tlssyscall;

Syscallmem*
_getsyscallmem(void)
{
	return TlsGetValue(_tlssyscall);
}

static void
console(void *a)
{
	int *fd, i;
	char *arg[10];
	char buf[3][20];

	fd = a;
	arg[0] = "console";
	arg[1] = "--";
	for(i=0; i<3; i++){
		close(fd[2*i+1]);
		sprint(buf[i], "%d", fd[2*i]);
		arg[2+i] = buf[i];
	}
	arg[5] = nil;
	sprint(buf[0], "%d", fd[0]);
	sprint(buf[1], "%d", fd[2]);
	sprint(buf[2], "%d", fd[4]);
	exec("/9pm/bin/aux/console", arg);
}

void
pm_syscallinit(void)
{
	char *what, err[ERRMAX], buf[256];
	int needconsole, fd[6], i, pid, p[2];
	DWORD m;
	HANDLE std, hh;

	_tlssyscall = TlsAlloc();
	if(_tlssyscall == TLS_OUT_OF_INDEXES){
		what = "TlsAlloc";
	Err:
		osrerrstr(err, sizeof err);
		snprint(buf, sizeof buf, "syscallinit: %s: %s", what, err);
		hh = GetStdHandle(STD_ERROR_HANDLE);
		WriteFile(hh, buf, strlen(buf), &m, nil);
		ExitProcess(1);
	}

	hwin = FindWindow(srv, srv);
	if(hwin == 0){
		what = "FindWindow";
		goto Err;
	}
	if(!GetWindowThreadProcessId(hwin, &pid)){
		what = "GetWindowThreadProcessId";
		goto Err;
	}
	hsrv = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);
	if(hsrv == nil){
		what = "OpenProcess";
		goto Err;
	}
	_syscallinit(_plan9id);
	if(_plan9id==0){	/* are starting from scratch; initialize state */
		needconsole = 0;
		for(i=0; i<3; i++){
			fd[2*i] = fd[2*i+1] = -1;
			std = GetStdHandle(fdid[i]);
			if(std==nil || std==INVALID_HANDLE_VALUE)
				continue;
			if(_passfd(i, std) < 0){
				pipe(fd+2*i);
				needconsole = 1;
		}
		_initenv();
		if(needconsole){
			ffork(RFNOWAIT|RFFDG, console, fd, 8192);
			close(fd[0]);
			close(fd[2]);
			close(fd[4]);
		}
		for(i=0; i<3; i++){
			if(fd[2*i+1] != i && fd[2*i+1] >= 0){
				dup(fd[2*i+1], i);
				close(fd[2*i+1]);
			}
		}
	}
}

void
_syscallinit(int pid)
{
	char *what, err[ERRMAX], buf[256];
	DWORD m;
	HANDLE hh, hmem;
	Syscallmem *c;

	hmem = (void*)SendMessage(hwin, WM_USER, pid, GetCurrentProcessId());
	if(hmem == nil){
		what = "SendMessage";
	Err:
		osrerrstr(err, sizeof err);
		snprint(buf, sizeof buf, "syscallinit: %s: %s", what, err);
		hh = GetStdHandle(STD_ERROR_HANDLE);
		WriteFile(hh, buf, strlen(buf), &m, nil);
		ExitProcess(1);
	}
	if(hmem==(HANDLE)1){
		pm_dprint(~0, "Could not attach to kernel as process %d\n", pid);
		ExitProcess(1);
	}
	c = MapViewOfFile(hmem, FILE_MAP_WRITE, 0, 0, 0);
	if(c == nil){
		what = "MapViewOfFile";
		goto Err;
	}
	TlsSetValue(_tlssyscall, c);
}


long
_dosyscall(Syscallmem *c)
{
	char status;
	Rune *p, *q;
	int ret;

	if(!ReleaseSemaphore(c->hc.hserversig, 1, nil))
		abort();
	if(WaitForSingleObject(c->hc.hclientsig, INFINITE) != WAIT_OBJECT_0)
		abort();
	/* BUG: check for notify */
	switch(c->state){
	case Exit:
		CloseHandle(c->hc.hserversig);
		CloseHandle(c->hc.hclientsig);
		CloseHandle(c->hc.hmem);
		CloseHandle(c->hc.hkillsig);
		status = c->exitstatus[0];
		UnmapViewOfFile(c);
		ExitThread(status);
	/*
	 * Exec leaves the command name and command line for us;
	 * we prepare to execute, do Sexec once more to commit,
	 * and drop our handles, run CreateProcess, and ExitThread.
	 */
	case Exec:
		p = (Rune*)&c[1];
		q = p+runestrlen(p)+1;
		p = runestrcpy(p);
		q = runestrcpy(q);
		if(p==nil || q==nil){
			free(p);
			free(q);
			werrstr("out of memory");
			return -1;
		}
		c->nr = Sexec;
		ReleaseSemaphore(c->hc.hserversig, 1, nil);
		CloseHandle(c->hc.hserversig);
		CloseHandle(c->hc.hclientsig);
		CloseHandle(c->hc.hmem);
		CloseHandle(c->hc.hkillsig);
		UnmapViewOfFile(c);
		CreateProcess(p, q, nil, nil, 0, nil, nil, nil, nil);
		ExitThread(0);
	}
	return c->ret;
}

static char*
wenv2env(Rune *r)
{
	char *s;
	Rune *rr;
	int len, n;

	len = 0;
	for(rr=r; *rr; rr+=runestrlen(rr)+1)
		len += runenlen(rr, runestrlen(rr))+1;
	len += 10;
	s = malloc(len);
	if(s == nil)
		return nil;
	n = 0;
	for(rr=r; *rr; rr+=runestrlen(rr)+1){
		win_wstr2utfn(s+n, len-n, rr);
		pm_dprint(PmDebugNs, "'%s'\n", s+n);
		n += strlen(s+n)+1;
	}
	return s; 
}

static void
_initenv(void)
{
	char buf[256];
	int fd;
	Rune *wenv;
	char *env, *p, *q;

	wenv = GetEnvironmentStrings();
	if(wenv == nil)
		return;
	env = wenv2env(wenv);
	FreeEnvironmentStrings(wenv);
	if(env == nil)
		return;

	for(p=env; *p; p+=strlen(p)+1){
		q = strchr(p+1, '=');
		if(q == nil)
			continue;
		*q++ = '\0';
		if(strlen(p) > 250)
			continue;
		snprint(buf, sizeof buf, "#e/%s", p);
		if((fd = create(buf, OWRITE, 0666)) < 0)
			continue;
		write(fd, q, strlen(q));
		close(fd);
	}
}

/*
 * Could do this with a '#H' equivalent to #d.
 */
static long
_passfd(int fd, HANDLE h)
{
	Syscallmem *c;

	c = _getsyscallmem();
	c->arg[0] = fd;
	c->arg[1] = (ulong)h;
	c->nr = Spassfd;
	return _dosyscall(c);
}

