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

static void forkpasser(void);
static void procsched(Tproc*);
int mainstacksize;
static int passerpid;
static int notefd;	/* for use only by passer */
static ulong tlsproc = ~0;
static Proc thesysproc;
static Rune* proccmd(char**);
extern Rune* _fetchegrp(Egrp*);
extern HANDLE _exporthandle(Chan*);
extern Rune* _execpath(char*);

typedef struct Mainarg Mainarg;
struct Mainarg
{
	int argc;
	char **argv;
};

static void
mainlauncher(void *arg)
{
	Mainarg *a;

	a = arg;
	threadmain(a->argc, a->argv);
}

void
pm_main(int argc, char *argv[])
{
	Mainarg *a;
	Tproc *p;
	extern void (*pm__sysfatal)(char *fmt, va_list arg);

	pm__qlockinit(_threadrendezvous);
	pm__sysfatal = _threadsysfatal;
	pm_quotefmtinstall();	/* for chanprint */
	if(mainstacksize == 0)
		mainstacksize = 512*1024;

	/* Fork off the passer, set up the process groups. */
	// forkpasser();

	a = smalloc(sizeof(*a));
	a->argc = argc;
	a->argv = argv;

	p = _newproc(mainlauncher, a, mainstacksize, "threadmain", 0, 0);
	tlsproc = TlsAlloc();
	TlsSetValue(tlsproc, p);
	p->sysproc = thesysproc;
	_threaddebug(DBGPROC, "calling procsched");
	procsched(p);
	/* not reached */
}

static DWORD WINAPI
proclauncher(LPVOID p)
{
	TlsSetValue(tlsproc, p);
	_threaddebug(DBGPROC, "proclauncher %p\n", p);
	procsched(p);
	return 0;
}

/*
 * The general structure of procsched is system-independent,
 * but the system-dependent parts are special enough that it's
 * very hard to tease them out into separate functions.
 */
static void
procsched(Tproc *self)
{
	char exitstr[ERRMAX];
	int action, flag, i, r;
	Fgrp *f;
	Execproc *e;
	HANDLE h, he[3];
	PROCESS_INFORMATION pi;
	Rune *cmd, *eb, *file;
	STARTUPINFO si;
	Tproc *p;

	_threaddebug(DBGPROC, "procsched self=%p", self);

	self->pid = getpid();
	self->sysproc.pid = self->pid;
	r = ~0;
	for(;;){
		_threaddebug(DBGPROC, "procsched run", self->pid, self->curthread->id);
		switch(action=_threadswtch(self->oldenv, self->curthread->env, r)){

		default:		/* can't happen */
			threadprint(2, "runproc, can't happen: %d\n", action);
			threadassert(0);

		case DOEXEC:	/* fork off a program, return pid */
			self = _threadgetproc();
			_threaddebug(DBGPROC, "doexec");
			e = &self->execproc;
			file = _execpath(e->file);
			if(file == nil){
				r = -1;
				continue;
			}
			f = pm_getproc()->fgrp;
			for(i=0; i<3; i++)
				he[i] = INVALID_HANDLE_VALUE;
			if(f->maxfd > 0){
				for(i=0; i<3; i++){
					he[i] = _exporthandle(f->fd[i]);
					_threaddebug(DBGPROC, "_exporthandle %p = %p", f->fd[i], he[i]);
				}
			}

			memset(&si, 0, sizeof(si));
			si.cb = sizeof(si);
			si.dwFlags = STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
			si.wShowWindow = SW_SHOW;
			si.hStdInput = he[0];
			si.hStdOutput = he[1];
			si.hStdError = he[2];
			_threaddebug(DBGPROC, "si handles %p %p %p", he[0], he[1], he[2]);

			flag = CREATE_UNICODE_ENVIRONMENT|CREATE_SEPARATE_WOW_VDM;
		//	if(!hasconsole && type == Econsole) {
		//		/* keep the console window hidden */
		//		si.wShowWindow = SW_HIDE;
		//	}

			eb = _fetchegrp(self->sysproc.egrp);
			cmd = proccmd(e->arg);
			_threaddebug(DBGPROC, "CreateProcess...");
			r = CreateProcess(file, cmd, 0, 0, 1, flag, eb, 0, &si, &pi);

			/* allow child to run */
			Sleep(0);

			free(file);
			free(cmd);
			free(eb);

			CloseHandle(he[0]);
			CloseHandle(he[1]);
			CloseHandle(he[2]);

			if(!r){
				oserror();
				_threaddebug(DBGPROC, "CreateProcess: %r");
				r = -1;
			}else{
				CloseHandle(pi.hThread);
				CloseHandle(pi.hProcess);
				r = pi.dwProcessId;
				_threaddebug(DBGPROC, "CreateProcess => %d", r);
			}
			continue;

		case DOPROC:	/* start a new Tproc running */
			self = _threadgetproc();
			_threaddebug(DBGPROC, "doproc");
			p = self->arg;
			r = p->curthread->id;
			h = CreateThread(NULL, 8192, proclauncher, p, 0, NULL);
			CloseHandle(h);
			continue;

		case DOEXIT:	/* destroy this proc */
			self = _threadgetproc();
			_threaddebug(DBGPROC, "doexit");

			/* remove ourself from the list of active procs */
			lock(&_threadpq.lk);
			if(_threadpq.head == self){
				_threadpq.head = self->next;
				if(_threadpq.tail == self){
					_threadpq.tail = nil;
					/* no procs left, signal the passer to exit */
					_threadsignalpasser();
				}
			}else{
				for(p=_threadpq.head; p->next; p=p->next){
					if(p->next == self){
						p->next = self->next;
						if(_threadpq.tail == self)
							_threadpq.tail = p;
						break;
					}
				}
			}
			unlock(&_threadpq.lk);

			/*
			 * Clean up and exit.  Remember that we're on the	
			 * system stack segment (not a malloced stack),
			 * thus it is okay to free self and all associated data.
			 */
			utfecpy(exitstr, exitstr+sizeof exitstr, self->exitstr);
			_freeproc(self);
			_exits(exitstr);
		}
		/* not reached */
		abort();
	}
}

void
_threadsignal(void)
{
//	pm_postnote(PNGROUP, getpid(), "threadsignal");
}

void
_threadsignalpasser(void)
{
//	pm_postnote(PNPROC, passerpid, "threadsignal");
}

void
_osnewproc(Tproc *p)
{
	p->impl = CreateSemaphore(NULL, 0, 1, NULL);
	if(p->impl == NULL)
		panic("CreateSemaphore");
}

void
_osfreeproc(Tproc *p)
{
	CloseHandle(p->impl);
}

void
_osexitsall(char *status)
{
	if(status && *status)
		ExitProcess(1);
	ExitProcess(0);
}

void
pm_nap(void)
{
	Tproc *tp;

	tp = _threadgetproc();
	_threaddebug(DBGPROC, "Proc %d sleeping", tp->pid);
	WaitForSingleObject(tp->impl, INFINITE);
	_threaddebug(DBGPROC, "Proc %d awake", tp->pid);
}

void
pm_wake(Proc *p)
{
	Tproc *tp;

	tp = (Tproc*)((ulong)p-(ulong)&((Tproc*)0)->sysproc);
	_threaddebug(DBGPROC, "Waking %d", tp->pid);
	ReleaseSemaphore(tp->impl, 1, NULL);
}

void
_procexecwait(int pid)
{
	Channel *c;
	Waitmsg *w;
	HANDLE h;
	DWORD status;

	if(pid == -1)
		return;

	_threaddebug(DBGCHLD, "Proc %d waiting for exec-ed child %d", getpid(), pid);
	h = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
	if(h == INVALID_HANDLE_VALUE || h == NULL){
		_threaddebug(DBGCHLD, "Pid %d already dead\n");
		if((c = _threadwaitchan) != nil){
			w = _threadmalloc(sizeof(*w)+20, 1);
			w->pid = pid;
			w->msg = "missing";
			sendp(c, w);
		}
		return;
	}

	for(;;){
		if(!GetExitCodeProcess(h, &status)){
			oserror();
			_threaddebug(DBGCHLD, "GetExitCodeProcess: %r");
			threadassert(0);
		}
		if(status != STILL_ACTIVE)
			break;
		WaitForSingleObject(h, INFINITE);
	}
	CloseHandle(h);
	if ((c = _threadwaitchan) != nil) {	/* global is exposed */
		_threaddebug(DBGCHLD, "Sending exit status for exec: pid %d, waiter pid %d, status %d", pid, getpid(), status);
		w = _threadmalloc(sizeof(*w)+20, 1);
		w->pid = pid;
		w->msg = (char*)&w[1];
		snprint(w->msg, 15, "%d", status);
		sendp(c, w);
	}
}

Tproc*
_threadgetproc(void)
{
	if(tlsproc == ~0)
		return nil;
	return TlsGetValue(tlsproc);
}


Proc*
pm_getproc(void)
{
	Tproc *p;

	if(p = _threadgetproc())
		return &p->sysproc;
	return &thesysproc;
}

long
_threadtimes(long *t)
{
	t[0] = t[1] = t[2] = t[3] = 0;
	return 0;
}

/*
 * windows quoting rules - I think
 * Words are seperated by space or tab
 * Words containing a space or tab can be quoted using "
 * 2N backslashes + " ==> N backslashes and end quote
 * 2N+1 backslashes + " ==> N backslashes + literal "
 * N backslashes not followed by " ==> N backslashes
 */
static Rune *
dblquote(Rune *cmd, char *s)
{
	int nb, i;
	char *p;

	for(p=s; *p; p++)
		if(*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r' || *p == '"')
			break;

	if(*p == 0) {
		/* easy case */
		while(*s)
			s += chartorune(cmd++, s);
		*cmd = 0;
		return cmd;
	}

	*cmd++ = '"';
	
	for(;;) {
		for(nb=0; *s=='\\'; s++)
			nb++;

		if(*s == 0) {
			for(i=0; i<nb; i++){
				*cmd++ = '\\';
				*cmd++ = '\\';
			}
			break;
		}

		if(*s == '"') {
			for(i=0; i<nb; i++){
				*cmd++ = '\\';
				*cmd++ = '\\';
			}
			*cmd++ = '\\';
			*cmd++ = '"';
			s++;
		} else {
			for(i=0; i<nb; i++)
				*cmd++ = '\\';
			s += chartorune(cmd++, s);
		}
	}

	*cmd++ = '"';
	*cmd = 0;

	return cmd;
}

static Rune*
proccmd(char **argv)
{
	int i, n;
	Rune *cmd, *p;

	/* conservative guess at size of cmd line */
	for(i=0,n=0; argv[i]; i++) {
		n += 3 + 2*strlen(argv[i]);
	}
	n += 2;
	
	cmd = malloc(n*sizeof(Rune));
	for(i=0,p=cmd; argv[i]; i++) {
		p = dblquote(p, argv[i]);
		*p++ = ' ';
	}
	*--p = 0;

	return cmd;
}
