#include <u.h>
#include <sys/select.h>
#include <libc.h>
#include <draw.h>
#include <cursor.h>
#include <event.h>
#include <mux.h>
#include <drawfcall.h>

typedef struct	Slave Slave;
typedef struct	Ebuf Ebuf;

struct Slave
{
	int	inuse;
	Ebuf	*head;		/* queue of messages for this descriptor */
	Ebuf	*tail;
	int	(*fn)(int, Event*, uchar*, int);
	Muxrpc *rpc;
	vlong nexttick;
	int fd;
	int n;
};

struct Ebuf
{
	Ebuf	*next;
	int	n;		/* number of bytes in buf */
	union {
		uchar	buf[EMAXMSG];
		Rune	rune;
		Mouse	mouse;
	} u;
};

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

static	int	nslave;
static	int	newkey(ulong);
static	int	extract(int canblock);

static
Ebuf*
ebread(Slave *s)
{
	Ebuf *eb;

	while(!s->head)
		extract(1);
	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->u.buf, eb->n);
					else
						memmove(e->data, eb->u.buf, eb->n);
					free(eb);
				}
				return id;
			}
		extract(1);
	}
	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)
{
	int i;

	for(;;){
		for(i=0; i<nslave; i++)
			if((keys & (1<<i)) && eslave[i].head)
				return 1;
		if(!extract(0))
			return 0;
	}
	return -1;
}

ulong
estartfn(ulong key, int fd, int n, int (*fn)(int, Event*, uchar*, int))
{
	int i;

	if(fd < 0)
		drawerror(display, "events: bad file descriptor");
	if(n <= 0 || n > EMAXMSG)
		n = EMAXMSG;
	i = newkey(key);
	eslave[i].fn = fn;
	eslave[i].fd = fd;
	eslave[i].n = n;
	return 1<<i;
}

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

ulong
etimer(ulong key, int n)
{
	if(Stimer != -1)
		drawerror(display, "events: timer started twice");
	Stimer = newkey(key);
	if(n <= 0)
		n = 1000;
	eslave[Stimer].n = n;
	eslave[Stimer].nexttick = nsec()+n*1000000LL;
	return 1<<Stimer;
}

void
einit(ulong keys)
{
	if(keys&Ekeyboard){
		for(Skeyboard=0; Ekeyboard & ~(1<<Skeyboard); Skeyboard++)
			;
		eslave[Skeyboard].inuse = 1;
		if(nslave <= Skeyboard)
			nslave = Skeyboard+1;
	}
	if(keys&Emouse){
		for(Smouse=0; Emouse & ~(1<<Smouse); Smouse++)
			;
		eslave[Smouse].inuse = 1;
		if(nslave <= Smouse)
			nslave = Smouse+1;
	}
}

static Ebuf*
newebuf(Slave *s, int n)
{
	Ebuf *eb;
	
	eb = malloc(sizeof(*eb) - sizeof(eb->u.buf) + n);
	if(eb == nil)
		drawerror(display, "events: out of memory");
	eb->n = n;
	eb->next = 0;
	if(s->head)
		s->tail = s->tail->next = eb;
	else
		s->head = s->tail = eb;
	return eb;
}

static Muxrpc*
startrpc(int type)
{
	uchar buf[100];
	Wsysmsg w;
	
	w.type = type;
	convW2M(&w, buf, sizeof buf);
	return muxrpcstart(display->mux, buf);
}

static int
finishrpc(Muxrpc *r, Wsysmsg *w)
{
	uchar *p;
	void *v;
	int n;
	
	if(!muxrpccanfinish(r, &v))
		return 0;
	p = v;
	if(p == nil)	/* eof on connection */
		exit(0);
	GET(p, n);
	convM2W(p, n, w);
	free(p);
	return 1;
}

static int
extract(int canblock)
{
	Ebuf *eb;
	int i, n, max;
	fd_set rset, wset, xset;
	struct timeval tv, *timeout;
	Wsysmsg w;
	vlong t0;

	/*
	 * Flush draw buffer before waiting for responses.
	 * Avoid doing so if buffer is empty.
	 * Also make sure that we don't interfere with app-specific locking.
	 */
	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);

	/*
	 * Set up for select.
	 */
	FD_ZERO(&rset);
	FD_ZERO(&wset);
	FD_ZERO(&xset);
	max = -1;
	timeout = nil;
	for(i=0; i<nslave; i++){
		if(!eslave[i].inuse)
			continue;
		if(i == Smouse){
			if(eslave[i].rpc == nil)
				eslave[i].rpc = startrpc(Trdmouse);
			if(eslave[i].rpc){
				/* if ready, don't block in select */
				if(eslave[i].rpc->p)
					canblock = 0;
				FD_SET(display->srvfd, &rset);
				FD_SET(display->srvfd, &xset);
				if(display->srvfd > max)
					max = display->srvfd;
			}
		}else if(i == Skeyboard){
			if(eslave[i].rpc == nil)
				eslave[i].rpc = startrpc(Trdkbd);
			if(eslave[i].rpc){
				/* if ready, don't block in select */
				if(eslave[i].rpc->p)
					canblock = 0;
				FD_SET(display->srvfd, &rset);
				FD_SET(display->srvfd, &xset);
				if(display->srvfd > max)
					max = display->srvfd;
			}
		}else if(i == Stimer){
			t0 = nsec();
			if(t0 >= eslave[i].nexttick){
				tv.tv_sec = 0;
				tv.tv_usec = 0;
			}else{
				tv.tv_sec = (eslave[i].nexttick-t0)/1000000000;
				tv.tv_usec = (eslave[i].nexttick-t0)%1000000000 / 1000;
			}
			timeout = &tv;
		}else{
			FD_SET(eslave[i].fd, &rset);
			FD_SET(eslave[i].fd, &xset);
			if(eslave[i].fd > max)
				max = eslave[i].fd;
		}
	}
	
	if(!canblock){
		tv.tv_sec = 0;
		tv.tv_usec = 0;
		timeout = &tv;
	}

	if(select(max+1, &rset, &wset, &xset, timeout) < 0)
		drawerror(display, "select failure");

	/*
	 * Look to see what can proceed.
	 */
	n = 0;
	for(i=0; i<nslave; i++){
		if(!eslave[i].inuse)
			continue;
		if(i == Smouse){
			if(finishrpc(eslave[i].rpc, &w)){
				eslave[i].rpc = nil;
				eb = newebuf(&eslave[i], sizeof(Mouse));
				eb->u.mouse = w.mouse;
				if(w.resized)
					eresized(1);
				n++;
			}
		}else if(i == Skeyboard){
			if(finishrpc(eslave[i].rpc, &w)){
				eslave[i].rpc = nil;
				eb = newebuf(&eslave[i], sizeof(Rune)+2);	/* +8: alignment */
				eb->u.rune = w.rune;
				n++;
			}
		}else if(i == Stimer){
			t0 = nsec();
			while(t0 > eslave[i].nexttick){
				eslave[i].nexttick += eslave[i].n*1000000LL;
				eslave[i].head = (Ebuf*)1;
				n++;
			}
		}else{
			if(FD_ISSET(eslave[i].fd, &rset)){
				eb = newebuf(&eslave[i], eslave[i].n);
				eb->n = read(eslave[i].fd, eb->u.buf, eslave[i].n);
				n++;
			}
		}
	}
	return n;
}

static int
newkey(ulong key)
{
	int i;

	for(i=0; i<MAXSLAVE; i++)
		if((key & ~(1<<i)) == 0 && eslave[i].inuse == 0){
			if(nslave <= i)
				nslave = i + 1;
			eslave[i].inuse = 1;
			return i;
		}
	drawerror(display, "events: bad slave assignment");
	return 0;
}

Mouse
emouse(void)
{
	Mouse m;
	Ebuf *eb;

	if(Smouse < 0)
		drawerror(display, "events: mouse not initialized");
	eb = ebread(&eslave[Smouse]);
	m = eb->u.mouse;
	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->u.rune;
	free(eb);
	return c;
}

void
emoveto(Point pt)
{
	_displaymoveto(display, pt);
}

void
esetcursor(Cursor *c)
{
	_displaycursor(display, c);
}

int
ereadmouse(Mouse *m)
{
	int resized;

	resized = 0;
	if(_displayrdmouse(display, m, &resized) < 0)
		return -1;
	if(resized)
		eresized(1);
	return 1;
}