/* Copyright (c) 2006 Russ Cox */

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

int chattydrawclient = 0;

static int	drawgettag(Mux *mux, void *vmsg);
static void*	drawrecv(Mux *mux);
static int	drawnbrecv(Mux *mux, void**);
static int	drawsend(Mux *mux, void *vmsg);
static int	drawsettag(Mux *mux, void *vmsg, uint tag);
static int canreadfd(int);

int
_displayconnect(Display *d)
{
	int pid, p[2];
	
	fmtinstall('W', drawfcallfmt);
	fmtinstall('H', encodefmt);
	
	if(pipe(p) < 0)
		return -1;
	if((pid=fork()) < 0){
		close(p[0]);
		close(p[1]);
		return -1;
	}
	if(pid == 0){
		char *devdraw;
		close(p[0]);
		dup(p[1], 0);
		dup(p[1], 1);
		/* execl("strace", "strace", "-o", "drawsrv.out", "drawsrv", nil); */
		/*
		 * The argv0 has no meaning to devdraw.
		 * Pass it along only so that the various
		 * devdraws in psu -a can be distinguished.
		 * The NOLIBTHREADDAEMONIZE keeps devdraw from
		 * forking before threadmain. OS X hates it when
		 * guis fork.
		 *
		 * If client didn't use ARGBEGIN, argv0 == nil.
		 * Can't send nil through because OS X expects
		 * argv[0] to be non-nil.  Also, OS X apparently
		 * expects argv[0] to be a valid executable name,
		 * so "(argv0)" is not okay.  Use "devdraw"
		 * instead.
		 */
		putenv("NOLIBTHREADDAEMONIZE", "1");
		devdraw = getenv("DEVDRAW");
		if(devdraw == nil)
			devdraw = "devdraw";
		if(argv0 == nil)
			argv0 = devdraw;
		execl(devdraw, argv0, argv0, "(devdraw)", nil);
		sysfatal("exec devdraw: %r");
	}
	close(p[1]);
	d->srvfd = p[0];
	return 0;
}

int
_displaymux(Display *d)
{
	if((d->mux = mallocz(sizeof(*d->mux), 1)) == nil)
		return -1;

	d->mux->mintag = 1;
	d->mux->maxtag = 255;
	d->mux->send = drawsend;
	d->mux->recv = drawrecv;
	d->mux->nbrecv = drawnbrecv;
	d->mux->gettag = drawgettag;
	d->mux->settag = drawsettag;
	d->mux->aux = d;
	muxinit(d->mux);
	
	return 0;
}

static int
drawsend(Mux *mux, void *vmsg)
{
	int n;
	uchar *msg;
	Display *d;
	
	msg = vmsg;
	GET(msg, n);
	d = mux->aux;
	return write(d->srvfd, msg, n);
}

static int
_drawrecv(Mux *mux, int canblock, void **vp)
{
	int n;
	uchar buf[4], *p;
	Display *d;

	d = mux->aux;
	*vp = nil;
	if(!canblock && !canreadfd(d->srvfd))
		return 0;
	if((n=readn(d->srvfd, buf, 4)) != 4)
		return 1;
	GET(buf, n);
	p = malloc(n);
	if(p == nil){
		fprint(2, "out of memory allocating %d in drawrecv\n", n);
		return 1;
	}
	memmove(p, buf, 4);
	if(readn(d->srvfd, p+4, n-4) != n-4){
		free(p);
		return 1;
	}
	*vp = p;
	return 1;
}

static void*
drawrecv(Mux *mux)
{
	void *p;
	_drawrecv(mux, 1, &p);
	return p;
}

static int
drawnbrecv(Mux *mux, void **vp)
{
	return _drawrecv(mux, 0, vp);
}

static int
drawgettag(Mux *mux, void *vmsg)
{
	uchar *msg;
	USED(mux);
	
	msg = vmsg;
	return msg[4];
}

static int
drawsettag(Mux *mux, void *vmsg, uint tag)
{
	uchar *msg;
	USED(mux);
	
	msg = vmsg;
	msg[4] = tag;
	return 0;
}

static int
displayrpc(Display *d, Wsysmsg *tx, Wsysmsg *rx, void **freep)
{
	int n, nn;
	void *tpkt, *rpkt;
	
	n = sizeW2M(tx);
	tpkt = malloc(n);
	if(freep)
		*freep = nil;
	if(tpkt == nil)
		return -1;
	tx->tag = 0;
	if(chattydrawclient)
		fprint(2, "<- %W\n", tx);
	nn = convW2M(tx, tpkt, n);
	if(nn != n){
		free(tpkt);
		werrstr("drawclient: sizeW2M convW2M mismatch");
		fprint(2, "%r\n");
		return -1;
	}
	/*
	 * This is the only point where we might reschedule.
	 * Muxrpc might need to acquire d->mux->lk, which could
	 * be held by some other proc (e.g., the one reading from
	 * the keyboard via Trdkbd messages).  If we need to wait
	 * for the lock, don't let other threads from this proc
	 * run.  This keeps up the appearance that writes to /dev/draw
	 * don't cause rescheduling.  If you *do* allow rescheduling
	 * here, then flushimage(display, 1) happening in two different
	 * threads in the same proc can cause a buffer of commands
	 * to be written out twice, leading to interesting results
	 * on the screen.
	 *
	 * Threadpin and threadunpin were added to the thread library
	 * to solve exactly this problem.  Be careful!  They are dangerous.
	 *
	 * _pin and _unpin are aliases for threadpin and threadunpin
	 * in a threaded program and are no-ops in unthreaded programs.
	 */
	_pin();
	rpkt = muxrpc(d->mux, tpkt);
	_unpin();
	free(tpkt);
	if(rpkt == nil){
		werrstr("muxrpc: %r");
		return -1;
	}
	GET((uchar*)rpkt, n);
	nn = convM2W(rpkt, n, rx);
	if(nn != n){
		free(rpkt);
		werrstr("drawclient: convM2W packet size mismatch %d %d %.*H", n, nn, n, rpkt);
		fprint(2, "%r\n");
		return -1;
	}
	if(chattydrawclient)
		fprint(2, "-> %W\n", rx);
	if(rx->type == Rerror){
		werrstr("%s", rx->error);
		free(rpkt);
		return -1;
	}
	if(rx->type != tx->type+1){
		werrstr("packet type mismatch -- tx %d rx %d",
			tx->type, rx->type);
		free(rpkt);
		return -1;
	}
	if(freep)
		*freep = rpkt;
	else
		free(rpkt);
	return 0;
}

int
_displayinit(Display *d, char *label, char *winsize)
{
	Wsysmsg tx, rx;

	tx.type = Tinit;
	tx.label = label;
	tx.winsize = winsize;
	return displayrpc(d, &tx, &rx, nil);
}

int
_displayrdmouse(Display *d, Mouse *m, int *resized)
{
	Wsysmsg tx, rx;

	tx.type = Trdmouse;
	if(displayrpc(d, &tx, &rx, nil) < 0)
		return -1;
	*m = rx.mouse;
	*resized = rx.resized;
	return 0;
}

int
_displayrdkbd(Display *d, Rune *r)
{
	Wsysmsg tx, rx;

	tx.type = Trdkbd;
	if(displayrpc(d, &tx, &rx, nil) < 0)
		return -1;
	*r = rx.rune;
	return 0;
}

int
_displaymoveto(Display *d, Point p)
{
	Wsysmsg tx, rx;

	tx.type = Tmoveto;
	tx.mouse.xy = p;
	return displayrpc(d, &tx, &rx, nil);
}

int
_displaycursor(Display *d, Cursor *c)
{
	Wsysmsg tx, rx;
	
	tx.type = Tcursor;
	if(c == nil){
		memset(&tx.cursor, 0, sizeof tx.cursor);
		tx.arrowcursor = 1;
	}else{
		tx.arrowcursor = 0;
		tx.cursor = *c;
	}
	return displayrpc(d, &tx, &rx, nil);
}

int
_displaybouncemouse(Display *d, Mouse *m)
{
	Wsysmsg tx, rx;
	
	tx.type = Tbouncemouse;
	tx.mouse = *m;
	return displayrpc(d, &tx, &rx, nil);
}

int
_displaylabel(Display *d, char *label)
{
	Wsysmsg tx, rx;
	
	tx.type = Tlabel;
	tx.label = label;
	return displayrpc(d, &tx, &rx, nil);
}

char*
_displayrdsnarf(Display *d)
{
	void *p;
	char *s;
	Wsysmsg tx, rx;
	
	tx.type = Trdsnarf;
	if(displayrpc(d, &tx, &rx, &p) < 0)
		return nil;
	s = strdup(rx.snarf);
	free(p);
	return s;
}

int
_displaywrsnarf(Display *d, char *snarf)
{
	Wsysmsg tx, rx;
	
	tx.type = Twrsnarf;
	tx.snarf = snarf;
	return displayrpc(d, &tx, &rx, nil);
}

int
_displayrddraw(Display *d, void *v, int n)
{
	void *p;
	Wsysmsg tx, rx;
	
	tx.type = Trddraw;
	tx.count = n;
	if(displayrpc(d, &tx, &rx, &p) < 0)
		return -1;
	memmove(v, rx.data, rx.count);
	free(p);
	return rx.count;
}

int
_displaywrdraw(Display *d, void *v, int n)
{
	Wsysmsg tx, rx;
	
	tx.type = Twrdraw;
	tx.count = n;
	tx.data = v;
	if(displayrpc(d, &tx, &rx, nil) < 0)
		return -1;
	return rx.count;
}

int
_displaytop(Display *d)
{
	Wsysmsg tx, rx;

	tx.type = Ttop;
	return displayrpc(d, &tx, &rx, nil);
}

int
_displayresize(Display *d, Rectangle r)
{
	Wsysmsg tx, rx;
	
	tx.type = Tresize;
	tx.rect = r;
	return displayrpc(d, &tx, &rx, nil);
}

static int
canreadfd(int fd)
{
	fd_set rs, ws, xs;
	struct timeval tv;
	
	FD_ZERO(&rs);
	FD_ZERO(&ws);
	FD_ZERO(&xs);
	FD_SET(fd, &rs);
	FD_SET(fd, &xs);
	tv.tv_sec = 0;
	tv.tv_usec = 0;
	if(select(fd+1, &rs, &ws, &xs, &tv) < 0)
		return 0;
	if(FD_ISSET(fd, &rs) || FD_ISSET(fd, &xs))
		return 1;
	return 0;
}