/*
 * Mouse device.  Also provides /dev/snarf, because it was convenient.
 * In a perfect world, this would be autogenerated from a
 * Plan 9 devmouse.
 */
#include	"u.h"
#include	"lib.h"
#include	"mem.h"
#include	"dat.h"
#include	"fns.h"
#include	"error.h"

#define	Image	IMAGE
#include	<draw.h>
#include	<memdraw.h>
#include	<cursor.h>
#include	"screen.h"

typedef struct Cursor Cursor;

Cursor	arrow = {
	{ -1, -1 },
	{ 0xFF, 0xFF, 0x80, 0x01, 0x80, 0x02, 0x80, 0x0C, 
	  0x80, 0x10, 0x80, 0x10, 0x80, 0x08, 0x80, 0x04, 
	  0x80, 0x02, 0x80, 0x01, 0x80, 0x02, 0x8C, 0x04, 
	  0x92, 0x08, 0x91, 0x10, 0xA0, 0xA0, 0xC0, 0x40, 
	},
	{ 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFC, 0x7F, 0xF0, 
	  0x7F, 0xE0, 0x7F, 0xE0, 0x7F, 0xF0, 0x7F, 0xF8, 
	  0x7F, 0xFC, 0x7F, 0xFE, 0x7F, 0xFC, 0x73, 0xF8, 
	  0x61, 0xF0, 0x60, 0xE0, 0x40, 0x40, 0x00, 0x00, 
	},
};

enum {
	ScrollUp = 0x08,
	ScrollDown = 0x10,
	ScrollLeft = 0x20,
	ScrollRight = 0x40,
};

enum
{
	CMbuttonmap,
	CMscrollswap,
	CMswap,
};

static Cmdtab mousectlmsg[] =
{
	CMbuttonmap,	"buttonmap",	0,
	CMscrollswap,	"scrollswap",	0,
	CMswap,		"swap",		1,
};

Mouseinfo	mouse;
Cursorinfo	cursor;
int		mouseshifted;
Cursor		curs;

void	Cursortocursor(Cursor*);
int	mousechanged(void*);

enum{
	Qdir,
	Qcursor,
	Qmouse,
	Qmousectl,
	Qsnarf,
};

static Dirtab mousedir[]={
	".",	{Qdir, 0, QTDIR},	0,			DMDIR|0555,
	"cursor",	{Qcursor},	0,			0666,
	"mouse",	{Qmouse},	0,			0666,
	"mousectl",	{Qmousectl},	0,			0220,
	"snarf",	{Qsnarf},	0,	0666,
};

static uchar buttonmap[8] = {
	0, 1, 2, 3, 4, 5, 6, 7,
};
static int mouseswap;
static int scrollswap;
static ulong mousetime;

Rectangle mouserect;	/* maintained by x11 for us */

static void
mousereset(void)
{
	if(!conf.monitor)
		return;
}

static int
mousedevgen(Chan *c, char *name, Dirtab *tab, int ntab, int i, Dir *dp)
{
	int rc;

	rc = devgen(c, name, tab, ntab, i, dp);
	if(rc != -1)
		dp->atime = mousetime;
	return rc;
}

static void
mouseinit(void)
{
	if(!conf.monitor)
		return;
	mousetime = seconds();
}

static Chan*
mouseattach(char *spec)
{
	if(!conf.monitor)
		error(Egreg);
	curs = arrow;
	Cursortocursor(&arrow);
	return devattach('m', spec);
}

static Walkqid*
mousewalk(Chan *c, Chan *nc, char **name, int nname)
{
	Walkqid *wq;

	/*
	 * We use devgen() and not mousedevgen() here
	 * see "Ugly problem" in dev.c/devwalk()
	 */
	wq = devwalk(c, nc, name, nname, mousedir, nelem(mousedir), devgen);
	if(wq != nil && wq->clone != c && wq->clone != nil && (wq->clone->qid.type&QTDIR)==0)
		incref(&mouse.ref);
	return wq;
}

static int
mousestat(Chan *c, uchar *db, int n)
{
	return devstat(c, db, n, mousedir, nelem(mousedir), mousedevgen);
}

static Chan*
mouseopen(Chan *c, int omode)
{
	switch((ulong)c->qid.path){
	case Qdir:
		if(omode != OREAD)
			error(Eperm);
		break;
	case Qmouse:
		lock(&mouse.ref.lk);
		if(mouse.open){
			unlock(&mouse.ref.lk);
			error(Einuse);
		}
		mouse.open = 1;
		mouse.ref.ref++;
		mouse.lastresize = mouse.resize;
		unlock(&mouse.ref.lk);
		break;
	case Qsnarf:
		if(omode == ORDWR)
			error(Eperm);	/* one at a time please */
		c->aux = nil;
		incref(&mouse.ref);
		break;
	default:
		incref(&mouse.ref);
	}
	c->mode = openmode(omode);
	c->flag |= COPEN;
	c->offset = 0;
	return c;
}

static void
mousecreate(Chan *c, char *name, int perm, ulong mode)
{
	if(!conf.monitor)
		error(Egreg);
	error(Eperm);
}

static void
mouseclose(Chan *c)
{
	if((c->qid.type&QTDIR)==0 && (c->flag&COPEN)){
		if(c->qid.path == Qsnarf){
			if(c->mode == OWRITE){
				if(c->aux)
					putsnarf(c->aux);
				else
					putsnarf("");
			}
			free(c->aux);
		}
		lock(&mouse.ref.lk);
		if(c->qid.path == Qmouse)
			mouse.open = 0;
		if(--mouse.ref.ref == 0){
			curs = arrow;
			Cursortocursor(&arrow);
		}
		unlock(&mouse.ref.lk);
		if(c->qid.path == Qmouse)
			termredraw();
	}
}

static long
mouseread(Chan *c, void *va, long n, vlong off)
{
	char buf[1+4*12+1], *s;
	uchar *p;
	ulong offset = off;
	Mousestate m;
	int b;

	p = va;
	switch((ulong)c->qid.path){
	case Qdir:
		return devdirread(c, va, n, mousedir, nelem(mousedir), mousedevgen);

	case Qcursor:
		if(offset != 0)
			return 0;
		if(n < 2*4+2*2*16)
			error(Eshort);
		n = 2*4+2*2*16;
		lock(&cursor.lk);
		BPLONG(p+0, curs.offset.x);
		BPLONG(p+4, curs.offset.y);
		memmove(p+8, curs.clr, 2*16);
		memmove(p+40, curs.set, 2*16);
		unlock(&cursor.lk);
		return n;

	case Qmouse:
		while(mousechanged(0) == 0)
			sleep(&mouse.r, mousechanged, 0);

		mouse.qfull = 0;
		mousetime = seconds();

		/*
		 * No lock of the indices is necessary here, because ri is only
		 * updated by us, and there is only one mouse reader
		 * at a time.  I suppose that more than one process
		 * could try to read the fd at one time, but such behavior
		 * is degenerate and already violates the calling
		 * conventions for sleep above.
		 */
		if(mouse.ri != mouse.wi) {
			m = mouse.queue[mouse.ri];
			if(++mouse.ri == nelem(mouse.queue))
				mouse.ri = 0;
		} else {
			while(!canlock(&cursor.lk))
				tsleep(&up->sleep, return0, 0, TK2MS(1));

			m = mouse.mstate;
			unlock(&cursor.lk);
		}

		b = buttonmap[m.buttons&7];
		/* put buttons 4 and 5 back in */
		b |= m.buttons & (3<<3);
		if (scrollswap){
			if (b == 8)
				b = 16;
			else if (b == 16)
				b = 8;
		}
		sprint(buf, "m%11d %11d %11d %11lud ",
			m.xy.x, m.xy.y,
			b,
			m.msec);
		mouse.lastcounter = m.counter;
		if(n > 1+4*12)
			n = 1+4*12;
		if(mouse.lastresize != mouse.resize){
			mouse.lastresize = mouse.resize;
			buf[0] = 'r';
		}
		memmove(va, buf, n);
		return n;
	
	case Qsnarf:
		if(offset == 0){
			s = getsnarf();
			if(c->aux)
				free(c->aux);
			c->aux = s;
		}
		if(c->aux == nil)
			return 0;
		return readstr(offset, va, n, c->aux);
	}
	return 0;
}

static void
setbuttonmap(char* map)
{
	int i, x, one, two, three;

	one = two = three = 0;
	for(i = 0; i < 3; i++){
		if(map[i] == 0)
			error(Ebadarg);
		if(map[i] == '1'){
			if(one)
				error(Ebadarg);
			one = 1<<i;
		}
		else if(map[i] == '2'){
			if(two)
				error(Ebadarg);
			two = 1<<i;
		}
		else if(map[i] == '3'){
			if(three)
				error(Ebadarg);
			three = 1<<i;
		}
		else
			error(Ebadarg);
	}
	if(map[i])
		error(Ebadarg);

	memset(buttonmap, 0, 8);
	for(i = 0; i < 8; i++){
		x = 0;
		if(i & 1)
			x |= one;
		if(i & 2)
			x |= two;
		if(i & 4)
			x |= three;
		buttonmap[x] = i;
	}
}

static long
mousewrite(Chan *c, void *va, long n, vlong offset)
{
	char *p;
	Point pt;
	Cmdbuf *cb;
	Cmdtab *ct;
	char buf[64];
	int nn;

	p = va;
	switch((ulong)c->qid.path){
	case Qdir:
		error(Eisdir);

	case Qcursor:
		if(n < 2*4+2*2*16){
			curs = arrow;
			Cursortocursor(&arrow);
		}else{
			n = 2*4+2*2*16;
			curs.offset.x = BGLONG(p+0);
			curs.offset.y = BGLONG(p+4);
			memmove(curs.clr, p+8, 2*16);
			memmove(curs.set, p+40, 2*16);
			Cursortocursor(&curs);
		}
		qlock(&mouse.qlk);
		mouse.redraw = 1;
		qunlock(&mouse.qlk);
		return n;

	case Qmousectl:
		cb = parsecmd(va, n);
		if(waserror()){
			free(cb);
			nexterror();
		}

		ct = lookupcmd(cb, mousectlmsg, nelem(mousectlmsg));

		switch(ct->index){
		case CMswap:
			if(mouseswap)
				setbuttonmap("123");
			else
				setbuttonmap("321");
			mouseswap ^= 1;
			break;

		case CMscrollswap:
			scrollswap ^= 1;
			break;

		case CMbuttonmap:
			if(cb->nf == 1)
				setbuttonmap("123");
			else
				setbuttonmap(cb->f[1]);
			break;
		}

		free(cb);
		poperror();
		return n;

	case Qmouse:
		if(n > sizeof buf-1)
			n = sizeof buf -1;
		memmove(buf, va, n);
		buf[n] = 0;
		p = 0;
		pt.x = strtoul(buf+1, &p, 0);
		if(p == 0)
			error(Eshort);
		pt.y = strtoul(p, 0, 0);
		qlock(&mouse.qlk);
		if(ptinrect(pt, mouserect)){
			mouse.mstate.xy = pt;
			mouse.redraw = 1;
			mouse.track = 1;
		}
		qunlock(&mouse.qlk);
		setmouse(pt);
		return n;
	
	case Qsnarf:
		if(offset+n >= SnarfSize)
			error("too much snarf");
		if(n == 0)
			return 0;
		assert(mousedir[Qsnarf].qid.path == Qsnarf);
		mousedir[Qsnarf].qid.vers++;
		if(c->aux == nil)
			nn = 0;
		else
			nn = strlen(c->aux);
		if(offset+n > nn){
			nn = offset+n;
			p = smalloc(nn+1);
			if(c->aux){
				strcpy(p, c->aux);
				free(c->aux);
			}
			c->aux = p;
		}
		memmove(c->aux+offset, va, n);
		return n;
	}

	error(Egreg);
	return -1;
}

Dev mousedevtab = {
	'm',
	"mouse",

	mousereset,
	mouseinit,
	devshutdown,
	mouseattach,
	mousewalk,
	mousestat,
	mouseopen,
	mousecreate,
	mouseclose,
	mouseread,
	devbread,
	mousewrite,
	devbwrite,
	devremove,
	devwstat,
};

void
Cursortocursor(Cursor *c)
{
	lock(&cursor.lk);
	cursor.cursor = *c;
	unlock(&cursor.lk);
	setcursor(c);
}

int
mousechanged(void *v)
{
	return mouse.lastcounter != mouse.mstate.counter ||
		mouse.lastresize != mouse.resize;
}

Point
mousexy(void)
{
	return mouse.mstate.xy;
}

/*
 * notify reader that screen has been resized
 */
void
mouseresize(void)
{
	mouse.resize++;
	wakeup(&mouse.r);
}

/*
 *  called at interrupt level to update the structure and
 *  awaken any waiting procs.
 */
void
mousetrack(int x, int y, int b, int msec)
{
	int lastb;

	if(x < mouserect.min.x)
		x = mouserect.min.x;
	if(x >= mouserect.max.x)
		x = mouserect.max.x;
	if(y < mouserect.min.y)
		y = mouserect.min.y;
	if(y >= mouserect.max.y)
		y = mouserect.max.y;

	lastb = mouse.mstate.buttons;
	mouse.mstate.xy = Pt(x, y);
	mouse.mstate.buttons = b;
	mouse.redraw = 1;
	mouse.mstate.counter++;
	mouse.mstate.msec = msec;

	/*
	 * if the queue fills, we discard the entire queue and don't
	 * queue any more events until a reader polls the mouse.
	 */
	if(!mouse.qfull && lastb != b) {	/* add to ring */
		mouse.queue[mouse.wi] = mouse.mstate;
		if(++mouse.wi == nelem(mouse.queue))
			mouse.wi = 0;
		if(mouse.wi == mouse.ri)
			mouse.qfull = 1;
	}
	wakeup(&mouse.r);
	drawactive(1);
}

