#include <9pm/u.h>
#include <9pm/libc.h>
#include <9pm/draw.h>
#include <9pm/cursor.h>
#include <9pm/event.h>
#include <9pm/thread.h>

typedef struct	Slave Slave;
typedef struct	Ebuf Ebuf;

struct Slave
{
	int	pid;
	Ebuf	*head;		/* queue of messages for this descriptor */
	Ebuf	*tail;
	int	(*fn)(int, Event*, uchar*, int);
};

struct Ebuf
{
	Ebuf	*next;
	int	n;		/* number of bytes in buf */
	uchar	buf[EMAXMSG];
};

static	Slave	eslave[MAXSLAVE];
static	int	Skeyboard = -1;
static	int	Smouse = -1;
static	int	Stimer = -1;
static	int	logfid;

static	int	nslave;
static	int	parentpid;
static	int	epipe[2];
static	int	eforkslave(ulong);
static	void	extract(void);
static	void	ekill(void);
static	int	enote(void *, char *);
static	int	mousefd;
static	int	cursorfd;

static
Ebuf*
ebread(Slave *s)
{
	Ebuf *eb;
	Dir *d;
	ulong l;

	for(;;){
		d = dirfstat(epipe[0]);
		if(d == nil)
			drawerror(display, "events: eread stat error");
		l = d->length;
		free(d);
		if(s->head && l==0)
			break;
		extract();
	}
	eb = s->head;
	s->head = s->head->next;
	if(s->head == 0)
		s->tail = 0;
	return eb;
}

ulong
event(Event *e)
{
	return eread(~0UL, e);
}

ulong
eread(ulong keys, Event *e)
{
	Ebuf *eb;
	int i, id;

	if(keys == 0)
		return 0;
	for(;;){
		for(i=0; i<nslave; i++)
			if((keys & (1<<i)) && eslave[i].head){
				id = 1<<i;
				if(i == Smouse)
					e->mouse = emouse();
				else if(i == Skeyboard)
					e->kbdc = ekbd();
				else if(i == Stimer)
					eslave[i].head = 0;
				else{
					eb = ebread(&eslave[i]);
					e->n = eb->n;
					if(eslave[i].fn)
						id = (*eslave[i].fn)(id, e, eb->buf, eb->n);
					else
						memmove(e->data, eb->buf, eb->n);
					free(eb);
				}
				return id;
			}
		extract();
	}
	return 0;
}

int
ecanmouse(void)
{
	if(Smouse < 0)
		drawerror(display, "events: mouse not initialized");
	return ecanread(Emouse);
}

int
ecankbd(void)
{
	if(Skeyboard < 0)
		drawerror(display, "events: keyboard not initialzed");
	return ecanread(Ekeyboard);
}

int
ecanread(ulong keys)
{
	Dir *d;
	int i;
	ulong l;

	for(;;){
		for(i=0; i<nslave; i++)
			if((keys & (1<<i)) && eslave[i].head)
				return 1;
		d = dirfstat(epipe[0]);
		if(d == nil)
			drawerror(display, "events: ecanread stat error");
		l = d->length;
		free(d);
		if(l == 0)
			return 0;
		extract();
	}
	return -1;
}

ulong
estartfn(ulong key, int fd, int n, int (*fn)(int, Event*, uchar*, int))
{
	char buf[EMAXMSG+1];
	int i, r;

	if(fd < 0)
		drawerror(display, "events: bad file descriptor");
	if(n <= 0 || n > EMAXMSG)
		n = EMAXMSG;
	i = eforkslave(key);
	if(i < MAXSLAVE){
		eslave[i].fn = fn;
		return 1<<i;
	}
	buf[0] = i - MAXSLAVE;
	while((r = read(fd, buf+1, n))>0)
		if(write(epipe[1], buf, r+1)!=r+1)
			break;
	buf[0] = MAXSLAVE;
	write(epipe[1], buf, 1);
	_exits(0);
	return 0;
}

ulong
estart(ulong key, int fd, int n)
{
	return estartfn(key, fd, n, nil);
}

ulong
etimer(ulong key, int n)
{
	char t[2];

	if(Stimer != -1)
		drawerror(display, "events: timer started twice");
	Stimer = eforkslave(key);
	if(Stimer < MAXSLAVE)
		return 1<<Stimer;
	if(n <= 0)
		n = 1000;
	t[0] = t[1] = Stimer - MAXSLAVE;
	do
		sleep(n);
	while(write(epipe[1], t, 2) == 2);
	t[0] = MAXSLAVE;
	write(epipe[1], t, 1);
	_exits(0);
	return 0;
}

static void
ekeyslave(int fd)
{
	Rune r;
	char t[3], k[10];
	int kr, kn, w;

	if(eforkslave(Ekeyboard) < MAXSLAVE)
		return;
	kn = 0;
	t[0] = Skeyboard;
	for(;;){
		while(!fullrune(k, kn)){
			kr = read(fd, k+kn, sizeof k - kn);
			if(kr <= 0)
				goto breakout;
			kn += kr;
		}
		w = chartorune(&r, k);
		kn -= w;
		memmove(k, &k[w], kn);
		t[1] = r;
		t[2] = r>>8;
		if(write(epipe[1], t, 3) != 3)
			break;
	}
breakout:;
	t[0] = MAXSLAVE;
	write(epipe[1], t, 1);
	_exits(0);
}

void
einit(ulong keys)
{
	int ctl, fd;
	char buf[256];

	parentpid = getpid();
	if(pipe(epipe) < 0)
		drawerror(display, "events: einit pipe");
	atexit(ekill);
	atnotify(enote, 1);
	snprint(buf, sizeof buf, "%s/mouse", display->devdir);
	mousefd = open(buf, ORDWR|OCEXEC);
	if(mousefd < 0)
		drawerror(display, "einit: can't open mouse\n");
	snprint(buf, sizeof buf, "%s/cursor", display->devdir);
	cursorfd = open(buf, ORDWR|OCEXEC);
	if(cursorfd < 0)
		drawerror(display, "einit: can't open cursor\n");
	if(keys&Ekeyboard){
		snprint(buf, sizeof buf, "%s/cons", display->devdir);
		fd = open(buf, OREAD);
		if(fd < 0)
			drawerror(display, "events: can't open console");
		snprint(buf, sizeof buf, "%s/consctl", display->devdir);
		ctl = open("/dev/consctl", OWRITE|OCEXEC);
		if(ctl < 0)
			drawerror(display, "events: can't open consctl");
		write(ctl, "rawon", 5);
		for(Skeyboard=0; Ekeyboard & ~(1<<Skeyboard); Skeyboard++)
			;
		ekeyslave(fd);
	}
	if(keys&Emouse){
		estart(Emouse, mousefd, 1+4*12);
		for(Smouse=0; Emouse & ~(1<<Smouse); Smouse++)
			;
	}
}

static void
extract(void)
{
	Slave *s;
	Ebuf *eb;
	int i, n;
	uchar ebuf[EMAXMSG+1];

	/* avoid generating a message if there's nothing to show. */
	/* this test isn't perfect, though; could do flushimage(display, 0) then call extract */
	/* also: make sure we don't interfere if we're multiprocessing the display */
	if(display->locking){
		/* if locking is being done by program, this means it can't depend on automatic flush in emouse() etc. */
		if(canqlock(&display->qlock)){
			if(display->bufp > display->buf)
				flushimage(display, 1);
			unlockdisplay(display);
		}
	}else
		if(display->bufp > display->buf)
			flushimage(display, 1);
loop:
	if((n=read(epipe[0], ebuf, EMAXMSG+1)) < 0
	|| ebuf[0] >= MAXSLAVE)
		drawerror(display, "eof on event pipe");
	if(n == 0)
		goto loop;
	i = ebuf[0];
	if(i >= nslave || n <= 1)
		drawerror(display, "events: protocol error: short read");
	s = &eslave[i];
	if(i == Stimer){
		s->head = (Ebuf *)1;
		return;
	}
	if(i == Skeyboard && n != 3)
		drawerror(display, "events: protocol error: keyboard");
	if(i == Smouse){
		if(n < 1+1+2*12)
			drawerror(display, "events: protocol error: mouse");
		if(ebuf[1] == 'r')
			eresized(1);
		/* squash extraneous mouse events */
		if((eb=s->tail) && memcmp(eb->buf+1+2*12, ebuf+1+1+2*12, 12)==0){
			memmove(eb->buf, &ebuf[1], n - 1);
			return;
		}
	}
	/* try to save space by only allocating as much buffer as we need */
	eb = malloc(sizeof(*eb) - sizeof(eb->buf) + n - 1);
	if(eb == 0)
		drawerror(display, "events: protocol error 4");
	eb->n = n - 1;
	memmove(eb->buf, &ebuf[1], n - 1);
	eb->next = 0;
	if(s->head)
		s->tail = s->tail->next = eb;
	else
		s->head = s->tail = eb;
}

static int
eforkslave(ulong key)
{
	int i, pid;

	for(i=0; i<MAXSLAVE; i++)
		if((key & ~(1<<i)) == 0 && eslave[i].pid == 0){
			if(nslave <= i)
				nslave = i + 1;
			/*
			 * share the file descriptors so the last child
			 * out closes all connections to the window server.
			 */
			switch(pid = rfork(RFPROC)){
			case 0:
				return MAXSLAVE+i;
			case -1:
				fprint(2, "events: fork error\n");
				exits("fork");
			}
			eslave[i].pid = pid;
			eslave[i].head = eslave[i].tail = 0;
			return i;
		}
	drawerror(display, "events: bad slave assignment");
	return 0;
}

static int
enote(void *v, char *s)
{
	char t[1];
	int i, pid;

	USED(v);
	USED(s);
	pid = getpid();
	if(pid != parentpid){
		for(i=0; i<nslave; i++){
			if(pid == eslave[i].pid){
				t[0] = MAXSLAVE;
				write(epipe[1], t, 1);
				break;
			}
		}
		return 0;
	}
	close(epipe[0]);
	epipe[0] = -1;
	close(epipe[1]);
	epipe[1] = -1;
	for(i=0; i<nslave; i++){
		if(pid == eslave[i].pid)
			continue;	/* don't kill myself */
		postnote(PNPROC, eslave[i].pid, "die");
	}
	return 0;
}

static void
ekill(void)
{
	enote(0, 0);
}

Mouse
emouse(void)
{
	Mouse m;
	Ebuf *eb;
	static but[2];
	int b;

	if(Smouse < 0)
		drawerror(display, "events: mouse not initialized");
	eb = ebread(&eslave[Smouse]);
	m.xy.x = atoi((char*)eb->buf+1+0*12);
	m.xy.y = atoi((char*)eb->buf+1+1*12);
	b = atoi((char*)eb->buf+1+2*12);
	m.buttons = b&7;
	m.msec = atoi((char*)eb->buf+1+3*12);
	if (logfid)
		fprint(logfid, "b: %d xy: %P\n", m.buttons, m.xy);
	free(eb);
	return m;
}

int
ekbd(void)
{
	Ebuf *eb;
	int c;

	if(Skeyboard < 0)
		drawerror(display, "events: keyboard not initialzed");
	eb = ebread(&eslave[Skeyboard]);
	c = eb->buf[0] + (eb->buf[1]<<8);
	free(eb);
	return c;
}

void
emoveto(Point pt)
{
	char buf[2*12+2];
	int n;

	n = sprint(buf, "m%d %d", pt.x, pt.y);
	write(mousefd, buf, n);
}

void
esetcursor(Cursor *c)
{
	uchar curs[2*4+2*2*16];

	if(c == 0)
		write(cursorfd, curs, 0);
	else{
		BPLONG(curs+0*4, c->offset.x);
		BPLONG(curs+1*4, c->offset.y);
		memmove(curs+2*4, c->clr, 2*2*16);
		write(cursorfd, curs, sizeof curs);
	}
}

int
ereadmouse(Mouse *m)
{
	int n;
	char buf[128];

	do{
		n = read(mousefd, buf, sizeof(buf));
		if(n < 0)	/* probably interrupted */
			return -1;
		n = eatomouse(m, buf, n);
	}while(n == 0);
	return n;
}

int
eatomouse(Mouse *m, char *buf, int n)
{
	if(n != 1+4*12){
		werrstr("atomouse: bad count");
		return -1;
	}

	if(buf[0] == 'r')
		eresized(1);
	m->xy.x = atoi(buf+1+0*12);
	m->xy.y = atoi(buf+1+1*12);
	m->buttons = atoi(buf+1+2*12);
	m->msec = atoi(buf+1+3*12);
	return n;
}
