#include "vnc.h"
#include "vncv.h"
#include <cursor.h>

typedef struct Cursor Cursor;

typedef struct Mouse Mouse;
struct Mouse {
	int buttons;
	Point xy;
};

void
adjustwin(Vnc *v, int force)
{
	int fd;
	Point d;

	if(force)
		d = v->dim.max;
	else {
		/*
		 * limit the window to at most the vnc server's size
		 */
		d = subpt(screen->r.max, screen->r.min);
		if(d.x > v->dim.max.x){
			d.x = v->dim.max.x;
			force = 1;
		}
		if(d.y > v->dim.max.y){
			d.y = v->dim.max.y;
			force = 1;
		}
	}
	if(force) {
		fd = open("/dev/wctl", OWRITE);
		if(fd >= 0){
			fprint(fd, "resize -dx %d -dy %d", d.x+2*Borderwidth, d.y+2*Borderwidth);
			close(fd);
		}
	}
}

static void
resized(int first)
{
	lockdisplay(display);
	if(getwindow(display, Refnone) < 0)
		sysfatal("internal error: can't get the window image");
	if((vnc->canresize&2) == 0)
		adjustwin(vnc, !autoscale && first);
	unlockdisplay(display);
	requestupdate(vnc, 0);
}

static Cursor dotcursor = {
	{-7, -7},
	{0x00, 0x00,
	 0x00, 0x00,
	 0x00, 0x00,
	 0x00, 0x00, 
	 0x03, 0xc0,
	 0x07, 0xe0,
	 0x0f, 0xf0, 
	 0x0f, 0xf0,
	 0x0f, 0xf0,
	 0x07, 0xe0,
	 0x03, 0xc0,
	 0x00, 0x00, 
	 0x00, 0x00,
	 0x00, 0x00,
	 0x00, 0x00,
	 0x00, 0x00, },
	{0x00, 0x00,
	 0x00, 0x00,
	 0x00, 0x00,
	 0x00, 0x00, 
	 0x00, 0x00,
	 0x03, 0xc0,
	 0x07, 0xe0, 
	 0x07, 0xe0,
	 0x07, 0xe0,
	 0x03, 0xc0,
	 0x00, 0x00,
	 0x00, 0x00, 
	 0x00, 0x00,
	 0x00, 0x00,
	 0x00, 0x00,
	 0x00, 0x00, }
};

static void
mouseevent(Vnc *v, Mouse m)
{
	vnclock(v);
	vncwrchar(v, MMouse);
	vncwrchar(v, m.buttons);
	vncwrpoint(v, m.xy);
	vncflush(v);
	vncunlock(v);
}

void
mousewarp(Point pt)
{
	pt = addpt(pt, screen->r.min);
	if(fprint(mousefd, "m%d %d", pt.x, pt.y) < 0)
		fprint(2, "mousefd write: %r\n");
}

void
initmouse(void)
{
	char buf[1024];

	snprint(buf, sizeof buf, "%s/mouse", display->devdir);
	if((mousefd = open(buf, ORDWR)) < 0)
		sysfatal("open %s: %r", buf);
}

enum {
	EventSize = 1+4*12
};
void
readmouse(Vnc *v)
{
	int cursorfd, len, n;
	char buf[10*EventSize], *start, *end;
	uchar curs[2*4+2*2*16];
	Cursor *cs;
	Mouse m;

	cs = &dotcursor;

	snprint(buf, sizeof buf, "%s/cursor", display->devdir);
	if((cursorfd = open(buf, OWRITE)) < 0)
		sysfatal("open %s: %r", buf);

	BPLONG(curs+0*4, cs->offset.x);
	BPLONG(curs+1*4, cs->offset.y);
	memmove(curs+2*4, cs->clr, 2*2*16);
	write(cursorfd, curs, sizeof curs);

	resized(1);
	start = end = buf;
	len = 0;
	for(;;){
		if((n = read(mousefd, end, sizeof(buf) - (end - buf))) < 0)
			sysfatal("read mouse failed");

		len += n;
		end += n;
		while(len >= EventSize){
			if(*start == 'm'){
				m.xy.x = atoi(start+1);
				m.xy.y = atoi(start+1+12);
				m.xy = subpt(m.xy, screen->r.min);
				if(autoscale){
					m.xy.x *= (v->dim.max.x - v->dim.min.x) / (double)(screen->r.max.x - screen->r.min.x);
					m.xy.y *= (v->dim.max.y - v->dim.min.y) / (double)(screen->r.max.y - screen->r.min.y);
				}
				m.buttons = atoi(start+1+2*12) & 0x1F;
				if(ptinrect(m.xy, v->dim)){
					mouseevent(v, m);
					/* send wheel button *release* */ 
					if ((m.buttons & 0x7) != m.buttons) {
						m.buttons &= 0x7;
						mouseevent(v, m);
					}
				}
			} else
				resized(0);
			start += EventSize;
			len -= EventSize;
		}
		if(start - buf > sizeof(buf) - EventSize){
			memmove(buf, start, len);
			start = buf;
			end = start+len;
		}
	}
}

static int
tcs(int fd0, int fd1)
{
	int pfd[2];

	if(strcmp(charset, "utf-8") == 0)
		goto Dup;
	if(pipe(pfd) < 0)
		goto Dup;
	switch(rfork(RFPROC|RFFDG|RFMEM)){
	case -1:
		close(pfd[0]);
		close(pfd[1]);
		goto Dup;
	case 0:
		if(fd0 < 0){
			dup(pfd[0], 0);
			dup(fd1, 1);
			close(fd1);	
		} else {
			dup(pfd[0], 1);
			dup(fd0, 0);
			close(fd0);	
		}
		close(pfd[0]);
		close(pfd[1]);
		execl("/bin/tcs", "tcs", fd0 < 0 ? "-f" : "-t", charset, nil);
		execl("/bin/cat", "cat", nil);
		_exits(0);
	}
	close(pfd[0]);
	return pfd[1];
Dup:
	return dup(fd0 < 0 ? fd1 : fd0, -1);
}

static int snarffd = -1;
static ulong snarfvers;

static int
gotsnarf(void)
{
	Dir *dir;
	int ret;

	if(snarffd < 0 || (dir = dirfstat(snarffd)) == nil)
		return 0;

	ret = dir->qid.vers != snarfvers;
	snarfvers = dir->qid.vers;
	free(dir);

	return ret;
}

void 
writesnarf(Vnc *v, long n)
{
	uchar buf[8192];
	int fd, sfd;
	long m;

	vnclock(v);
	fd = -1;
	if((sfd = create("/dev/snarf", OWRITE, 0666)) >= 0){
		fd = tcs(-1, sfd);
		close(sfd);
	}
	if(fd < 0)
		vncgobble(v, n);
	else {
		while(n > 0){
			m = n;
			if(m > sizeof(buf))
				m = sizeof(buf);
			vncrdbytes(v, buf, m);
			n -= m;
			write(fd, buf, m);
		}
		close(fd);
		waitpid();
	}
	gotsnarf();
	vncunlock(v);
}

char *
getsnarf(int *sz)
{
	char *snarf, *p;
	int fd, n, c;

	*sz =0;
	n = 8192;
	p = snarf = malloc(n);

	seek(snarffd, 0, 0);
	if((fd = tcs(snarffd, -1)) >= 0){
		while((c = read(fd, p, n)) > 0){
			p += c;
			n -= c;
			*sz += c;
			if (n == 0){
				snarf = realloc(snarf, *sz + 8192);
				p = snarf + *sz;
				n = 8192;
			}
		}
		close(fd);
		waitpid();
	}
	return snarf;
}

void
checksnarf(Vnc *v)
{
	char *snarf;
	int len;

	if(snarffd < 0){
		snarffd = open("/dev/snarf", OREAD);
		if(snarffd < 0)
			sysfatal("can't open /dev/snarf: %r");
	}

	for(;;){
		sleep(1000);

		vnclock(v);
		if(gotsnarf()){
			snarf = getsnarf(&len);

			vncwrchar(v, MCCut);
			vncwrbytes(v, "pad", 3);
			vncwrlong(v, len);
			vncwrbytes(v, snarf, len);
			vncflush(v);

			free(snarf);
		}
		vncunlock(v);
	}
}
