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

int debug;
int _dontforkconsole = 1;
int fd[3];
HANDLE h[3];

int tab[] = {
	STD_INPUT_HANDLE,
	STD_OUTPUT_HANDLE,
	STD_ERROR_HANDLE,
};

/*
 * It appears that win NT 4.0 has a bug in ReadConsole
 * If ReadConsole is called with a buffer that is smaller than
 * the input that is buffered, it appears that readconsoles buffer
 * can get corrupted by WriteConsole
 * i.e
 *      char buf[3];
 *	int n2;
 *
 *	for(;;) {
 *		if(!ReadConsole(h, buf, sizeof(buf), &n2, 0))
 *			exit(0);
 *		if(!WriteConsole(h2, buf, n2, &n2, 0))
 *			exit(0);
 *
 *		Sleep(100);
 *	}
 *
 */
static void
consoleread(HANDLE h, int fd)
{
	int n2, i;
	Rune r, rbuf[200];
	char err[ERRMAX];
	char *p;
	uchar buf[200*UTFmax];

	for(;;){
		if(!ReadConsole(h, rbuf, nelem(rbuf), &n2, 0)){
			if(debug){
				osrerrstr(err, sizeof err);
				pm_dprint(~0, "ReadConsole: %s\n", err);
			}
			break;
		}
		if(n2 == 0)
			continue;

		for(i=0,p=buf; i<n2; i++) {
			r = rbuf[i];
			if(r == '\r')
				continue;
			if(r == 0x4) {
				if(write(fd, buf, p-buf) != p-buf){
					if(debug)
						pm_dprint(~0, "consoleread write: %r\n");
					break;
				}
				p = buf;
				continue;
			}
			p += runetochar(p, &r);
		}
		if(write(fd, buf, p-buf) != p-buf){
			if(debug)
				pm_dprint(~0, "consoleread write: %r\n");
			break;
		}
	}
	close(fd);
	CloseHandle(h);
}

static void
consolewrite(int fd, HANDLE h)
{
	char err[ERRMAX], buf[1000], *p;
	Rune buf2[1000], *q;
	int i, n, n2, nbuf, on;

	for(;;){
		n = read(fd, buf+nbuf, sizeof buf);
		if(n < 0){
			if(debug)
				pm_dprint(~0, "consolewrite read: %r\n");
			return;
		}
		p = buf;
		on = n;
		/* handle partial runes */
		if(nbuf) {
			i = nbuf;
			assert(i < UTFmax);
			while(i < UTFmax && n>0) {
				buf[i] = *p;
				i++;
				p++;
				n--;
				if(fullrune(buf, i)) {
					nbuf = 0;
					chartorune(buf2, buf);
					if(!WriteConsole(h, buf2, 1, &n, 0)){
						if(debug){
							osrerrstr(err, sizeof err);
							pm_dprint(~0, "WriteConsole1: %s\n", err);
						}
						close(fd);
						return;
					}
					break;
				}
			}
		}
		while(n >= UTFmax || fullrune(p, n)) {
			n2 = nelem(buf2);
			q = buf2;

			while(n2) {
				if(n < UTFmax && !fullrune(p, n))
					break;
				i = chartorune(q, p);
				p += i;
				n -= i;
				n2--;
				q++;
			}
		
			if(!WriteConsole(h, buf2, q-buf2, &n2, 0)) {
				if(debug){
					osrerrstr(err, sizeof err);
					pm_dprint(~0, "WriteConsole2: %s\n", err);
				}
				close(fd);
				return;
			}
		}
		if(n != 0) {
			assert(n+nbuf < UTFmax);
			memmove(buf+nbuf, p, n);
			nbuf += n;
		}
	}
	close(fd);
	CloseHandle(h);
}

void
dispatch(void *a)
{
	switch((int)a){
	case 0:
		consoleread(h[0], fd[0]);
		break;
	case 1:
		consolewrite(fd[1], h[1]);
		break;
	case 2:
		consolewrite(fd[2], h[2]);
		break;
	default:
		pm_dprint(~0, "dispatch %d?\n", (int)a);
	}
}

void
watchproc(void *a)
{
	WaitForSingleObject(a, INFINITE);
	ExitProcess(0);
}

void
main(int argc, char **argv)
{
	char buf[ERRMAX];
	int i, pid, p[2], test;
	HANDLE hproc;

	test = 0;
	ARGBEGIN{
	case 'd':
		debug = 1;
		break;
	case 't':
		test = 1;
		pipe(p);
		break;
	}ARGEND

	if(argc != 3 && argc != 4){
		pm_dwrite("usage: console [-t] fd0 fd1 fd2 [processid]\n");
		exits("usage");
	}
		
	if(argc==4){
		pid = atoi(argv[3]);
		hproc = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);
		if(hproc == nil){
			osrerrstr(buf, sizeof buf);
			pm_dprint(~0, "OpenProcess: %s\n", buf);
			exits("OpenProcess");
		}
		ffork(0, watchproc, hproc, 8192);
	}
	for(i=0; i<3; i++){
		if(test && i<2)
			fd[i] = p[i];
		else
			fd[i] = atoi(argv[i]);
		if(fd[i] != -1){
			h[i] = GetStdHandle(tab[i]);
			if(h[i]!=INVALID_HANDLE_VALUE && h[i]!=nil)
				ffork(0, dispatch, (void*)i, 8192);
		}
	}
	_exits(0);
}
