#include	<9pm/u.h>
#include	<9pm/libc.h>
#include	<9pm/draw.h>
#include	<9pm/memdraw.h>
#include	<9pm/cursor.h>
#include	<9pm/mouse.h>
#include	"dat.h"
#include	"fns.h"
#include	"screen.h"

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

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

static void
mousereset(void)
{
	curs = arrow;
	Cursortocursor(&arrow);
}

static Chan*
mouseattach(char *spec)
{
	int i;
	char *p;
	Chan *c;
	Drawgrp *dg;

	i = strtol(spec, &p, 10);
	if(i == 0 || *p != '\0' || (dg = lookupdrawgrp(i)) == nil)
		error(Ebadspec);
	
	c = devattach('m', spec);
	c->aux = dg;
	return c;
}

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

	wq = devwalk(c, nc, name, nname, mousedir, nelem(mousedir), devgen);
	if(wq != nil && wq->clone != c && (wq->clone->qid.type&QTDIR)==0){
		dg = c->aux;
		incref(&dg->ref);
		wq->aux = dg;
	}
	return wq;
}

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

static Chan*
mouseopen(Chan *c, int omode)
{
	switch((ulong)c->qid.path){
	case Qdir:
		if(omode != OREAD)
			error(Eperm);
		break;
	case Qmouse:
		lock(&mouse.lk);
		if(mouse.open){
			unlock(&mouse.lk);
			error(Einuse);
		}
		mouse.open = 1;
		incref(&mouse.ref);
		unlock(&mouse.lk);
		break;
	default:
		incref(&mouse.ref);
	}
	c->mode = openmode(omode);
	c->flag |= COPEN;
	c->offset = 0;
	return c;
}

static void
mouseclose(Chan *c)
{
	Drawgrp *dg;

	dg = c->aux;
	if((c->qid.type&QTDIR)==0 && (c->flag&COPEN)){
		lock(&dg->mouse.lk);
		if(c->qid.path == Qmouse)
			dg->mouse.open = 0;
		if(decref(&dg->mouse.ref) == 0){
			curs = arrow;
			Cursortocursor(&arrow);
		}
		unlock(&dg->mouse.lk);
	}
	c->aux = nil;
	closedrawgrp(dg);
}

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

	p = va;
	dg = c->aux;
	switch((ulong)c->qid.path){
	default:
		error(Egreg);

	case Qdir:
		return devdirread(c, va, n, mousedir, nelem(mousedir), devgen);

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

	case Qmouse:
		while(mousechanged(dg) == 0)
			rendsleep(&dg->mouse.r, mousechanged, dg);

		dg->mouse.qfull = 0;
		/*
		 * No lock of the indicies is necessary here, because ri is only
		 * updated by us, and there is only one mouse fd
		 * 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(dg->mouse.ri != dg->mouse.wi) {
			m = dg->mouse.queue[dg->mouse.ri];
			if(++dg->mouse.ri == nelem(dg->mouse.queue))
				dg->mouse.ri = 0;
		} else {
			lock(&dg->mouse.lk);
			m = dg->mouse.m;
			unlock(&dg->mouse.lk);
		}
		sprint(buf, "m%11d %11d %11d %11lud",
			m.m.xy.x, m.m.xy.y,
			m.m.buttons,
			m.m.msec);
		nresize = dg->mouse.nresize;
		if(dg->mouse.mresize < nresize){
			dg->mouse.mresize = nresize;
			buf[0] = 'r';
		}
		dg->mouse.lastcounter = m.counter;
		if(n > 1+4*12)
			n = 1+4*12;
		memmove(va, buf, n);
		return n;
	}
}

static long
mousewrite(Chan *c, void *va, long n, vlong offset)
{
	char *p;
	Point pt;
	char buf[64];
	Drawgrp *dg;

	USED(offset);

	p = va;
	dg = c->aux;
	switch((ulong)c->qid.path){
	default:
		error(Egreg);

	case Qdir:
		error(Eisdir);

	case Qcursor:
		lock(&dg->cursor.lk);
		if(n < 2*4+2*2*16)
			dg->cursor.c = arrow;
		else{
			n = 2*4+2*2*16;
			dg->cursor.c.offset.x = BGLONG(p+0);
			dg->cursor.c.offset.y = BGLONG(p+4);
			memmove(dg->cursor.c.clr, p+8, 2*16);
			memmove(dg->cursor.c.set, p+40, 2*16);
		}
		unlock(&dg->cursor.lk);
		Cursortocursor(&curs);
		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);
		if(ptinrect(pt, dg->screenimage->r))
			movecursor(dg, pt);
		return n;
	}
}

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

	mousereset,
	mouseattach,
	mousewalk,
	mousestat,
	mouseopen,
	devcreate,
	mouseclose,
	mouseread,
	devbread,
	mousewrite,
	devbwrite,
	devremove,
	devwstat,
};

void
Cursortocursor(Cursor *c)
{
	lock(&cursor.lk);
	memmove(&cursor.c, c, sizeof(Cursor));
	cursorload(c);
	unlock(&cursor.lk);
}

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

	lastb = mouse.m.buttons;
	mouse.m.xy = xy;
	mouse.m.buttons = b;
	mouse.m.counter++;
	if(msec == 0)
		msec = nsec()/1000000;
	mouse.m.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.m;
		if(++mouse.wi == nelem(mouse.queue))
			mouse.wi = 0;
		if(mouse.wi == mouse.ri)
			mouse.qfull = 1;
	}
	rendwakeup(&mouse.r);
}

int
mousechanged(void *a)
{
	USED(a);
	return mouse.lastcounter != mouse.m.counter;
}

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