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

/*
 * So that the device can be mounted on /mnt to yield /mnt/wsys/wsys/n/files, 
 * we present all the files in #w/wsys/wsys.  Rio has the luxury of having a 
 * /mnt/wsys on which to mount, so it only presents a single wsys directory.
 *
 * Also unlike rio, we don't present any /mnt/wsys/cons etc. files.  Instead,
 * we expect that the code to create a new window will bind the appropriate
 * /mnt/wsys/wsys/n directory before /mnt/wsys.
 *
 * I'd like to be able to access /mnt/wsys/wsys/n/text from other windows.
 * One way to do so would be to have the device manage the window text like rio,
 * but that would require porting almost all the draw and frame libraries to memdraw,
 * which is likely too much pain.  Instead, I think we'll make the device text file
 * writable and have a user-level program that presents a single rio window.
 * (Perhaps just modify rio.)
 */
enum
{
	Qmnt	= 0,
	Qwsys,
	Qwsys1,
	Qn,
	Qnew,

	Qcons,
	Qconsctl,
	Qcursor,
	Qlabel,
	Qmouse,
	Qsnarf,
	Qtext,
	Qwctl,
	Qwdir,
	Qwindow,
	Qwinid,
	Qwinname,
};

/*
 * Qid path is:
 *	8 bits of file type (qids above),
 *	16 bits of window index
 */

#define	QSHIFT	8
#define	QID(q)	((((ulong)(q).path)&0x000000FF)>>0)
#define	WSYS(q)	((((ulong)(q).path)&0x00FFFF00)>>QSHIFT)
#define	PATH(t, n)	(((n)<<QSHIFT)|(t))

static struct {
	char *name;
	int type;
	int perm;
} wintab[] = {
	"cons",		Qcons,		0600,
	"consctl",		Qconsctl,		0200,
	"cursor",		Qcursor,		0600,
	"label",		Qlabel,		0600,
	"mouse",		Qmouse,		0600,
	"snarf",		Qsnarf,		0600,
	"text",		Qtext,		0600,
	"wctl",		Qwctl,		0600,
	"wdir",		Qwdir,		0600,
	"window",		Qwindow,		0400,
	"winid",		Qwinid,		0400,
	"winname",	Qinname,		0400,
};
	
static int
wsysgen(Chan *c, char *name, Dirtab *tab, int ntab, int s, Dir *dp)
{
	int i, n, t;
	Qid q;
	Window *w;

	USED(tab);
	USED(ntab);
	USED(name);

	q.path = 0;
	if(s == DEVDOTDOT){
		switch(QID(c->qid)){
		case Qtopdir:
		case Qwsys:
			mkqid(&q, Qtopdir, 0, QTDIR);
			devdir(c, q, "#w", 0, eve, 0500, dp);
			break;
		case Qwsys1:
			mkqid(&q, Qwsys, 0, QTDIR);
			devdir(c, q, "wsys", 0, eve, 0500, dp);
			break;
		case Qn:
			mkqid(&q, Qwsys1, 0, QTDIR);
			devdir(c, q, "wsys", 0, eve, 0500, dp);
			break;
		default:
			panic("wsyswalk %llux", c->qid.path);
		}
		return 1;
	}

	/*
	 * Top level directory contains wsys
	 */
	t = QID(c->qid);
	if(t == Qtopdir){
		switch(s){
		case 0:
			mkqid(&q, Qwsys, 0, QTDIR);
			devdir(c, q, "wsys", 0, eve, 0500, dp);
			break;
		default:
			return -1;
		}
		return 1;
	}

	/*
	 * Second level contains wsys
 	 */
	if(t == Qwsys){
		switch(s){
		case 0:
			mkqid(&q, Qwsys1, 0, QTDIR);
			devdir(c, q, "wsys", 0, eve, 0500, dp);
			break;
		default:
			return -1;
		}
		return 1;
	}

	/*
	 * Third level contains new and `n' directories.
	 */
	if(t == Qwsys1){
		if(s == 0){
			mkqid(&q, Qnew, 0, QTFILE);
			devdir(c, q, "new", 0, eve, 0444, dp);
		}else if(s-1 < nwin){
			s--;
			w = win[s];
			if(w == nil)
				return 0;
			snprint(up->genbuf, "%d", w->id);
			mkqid(&q, PATH(Qn, s), 0, QTDIR);
			devdir(c, q, up->genbuf, 0, eve, 0555, dp);
		}else
			return -1;
		return 1;
	}

	/*
	 * Fourth level contains window files.
	 */
	n = WSYS(c->qid);
	if(n >= nwin || (w = win[n]) == nil)
		return 0;
	if(s > nelem(wintab))
		return -1;

	mkqid(&q, PATH(wintab[s].type, n), 0, QTFILE);
	devdir(c, q, wintab[s].name, 0, eve, wintab[s].perm, dp);
	return 1;
}
			
static Chan*
wsysattach(char *spec)
{
	return devattach('i', spec);
}

static Walkqid*
wsyswalk(Chan *c, Chan *nc, char **name, int nname)
{
	return devwalk(c, nc, name, nname, 0, 0, wsysgen);
}

static int
wsysstat(Chan *c, uchar *db, int n)
{
	return devstat(c, db, n, 0, 0, wsysgen);
}

static Chan*
wsysopen(Chan *c, int omode)
{
	int n;
	Window *w;

	c = devopen(c, omode, 0, 0, wsysgen);
	if(waserror()){
		c->flag &= ~COPEN;
		nexterror();
	}
	switch(QID(c->qid)){
	case Qnew:
		n = mkwsys();
		if(n < 0)
			error(Enodev);
		c->qid.path = PATH(Qwinid, n);
		break;
	}
	w = windowbyqid(c->qid);
	switch(QID(c->qid)){
	case Qconsctl:
		if(!canlock(&w->consctlopen))
			error(Einuse);
		break;
	case Qmouse:
		if(!canlock(&w->mouseopen))
			error(Einuse);
		break;
	case Qwctl:
		if((omode&3) != OWRITE){
			if(!canlock(&w->wctlopenread))
				error(Einuse);
		}
		break;
	}
	incref(&w->ref);
	return c;
}

static void
wsysclose(Chan *c)
{
	Window *w;

	if(c->qid.type&QTDIR)
		return;
	if(!(c->flag & COPEN))
		return;
	/* might error, that's ok */
	w = windowbyqid(c->qid);
	switch(QID(c->qid)){
	case Qconsctl:
		if(w->holding && --w->holding==0)
			wnohold(w);
		if(w->rawing && --w->rawing==0)
			wnoraw(w);
		unlock(&w->consctlopen);
		break;
	case Qcursor:
		qlock(&w->lk);
		w->cursor = arrow;
		wsetcursor(w);
		qunlock(&w->lk);
		break;
	case Qmouse:
		unlock(&w->mouseopen);
		break;
	case Qsnarf:
		qlock(&w->lk);
		wsetsnarf(w);
		qunlock(&w->lk);
		break;
	case Qwctl:
		if(c->omode != OWRITE)
			unlock(&w->wctlopen);
		break;
	}
	closewsys(w, WSYS(c->qid));
}

static long
wsysread(Chan *c, void *a, long n, vlong off)
{
	char *ca, buf[256];
	int n, nresize;
	Kmouse m;
	Window *w;

	if(c->qid.type & QTDIR)
		return devdirread(c, a, n, 0, 0, wsysgen);

	w = windowbyqid(c->qid);
	switch(QID(c->qid)){
	default:
		error(Egreg);
	case Qcons:
	case Qcursor:
		if(off != 0)
			return 0;
		if(n < 2*4+2*2*16)
			error(Eshort);
		n = 2*4+2*2*16;
		qlock(&w->lk);
		BPLONG(p+0, w->cursor.offset.x);
		BPLONG(p+4, w->cursor.offset.y);
		memmove(p+8, w->cursor.clr, 2*16);
		memmove(p+40, w->cursor.set, 2*16);
		qunlock(&w->lk);
		return n;

	case Qlabel:
		qlock(&w->lk);
		if(w->label == nil)
			n = 0;
		else
			n = readstr(off, a, n, w->label);
		qunlock(&w->lk);
		return n;
		
	case Qmouse:
		qlock(&w->mouse.read);
		while(mousechanged(w) == 0)
			rendsleep(&w->mouse.r, mousechanged, w);
		w->mouse.qfull = 0;
		if(w->mouse.ri != w->mouse.wi) {
			m = w->mouse.queue[w->mouse.ri];
			if(++w->mouse.ri == nelem(w->mouse.queue))
				w->mouse.ri = 0;
		} else {
			lock(&w->mouse.lk);
			m = w->mouse.m;
			unlock(&w->mouse.lk);
		}
		sprint(buf, "m%11d %11d %11d %11lud",
			m.xy.x, m.xy.y,
			m.buttons,
			m.msec);
		nresize = w->mouse.nresize;
		if(w->mouse.mresize < nresize){
			w->mouse.mresize = nresize;
			buf[0] = 'r';
		}
		w->mouse.lastcounter = m.counter;
		if(n > 1+4*12)
			n = 1+4*12;
		memmove(va, buf, n);
		qunlock(&w->mouse.read);
		return n;

	case Qsnarf:
		return snarfread(c, a, n, off);

	case Qtext:
		error(Egreg);

	case Qwctl:
		error(Egreg);

	case Qwdir:
		qlock(&w->lk);
		if(w->wdir == nil)
			n = 0;
		else
			n = readstr(off, a, n, w->wdir);
		qunlock(&w->lk);
		return n;

	case Qwindow:
		error(Egreg);

	case Qwinid:
		snprint(up->genbuf, sizeof up->genbuf, "%d", w->id);
		return readstr(off, a, n, up->genbuf);

	case Qwinname:
		return readstr(off, a, n, w->name);
	}
}

static long
wsyswrite(Chan *c, void *a, long n, vlong off)
{
	char buf[256], *p;
	int n, nresize;
	Kmouse m;
	Point pt;
	Window *w;

	if(c->qid.type & QTDIR)
		return devdirread(c, a, n, 0, 0, wsysgen);

	w = windowbyqid(c->qid);
	switch(QID(c->qid)){
	default:
		error(Egreg);
	case Qcons:
	case Qcursor:
		qlock(&w->lk);
		if(n < 2*4+2*2*16)
			w->cursor = arrow;
			n = 0;
		else{
			n = 2*4+2*2*16;
			w->cursor.offset.x = BGLONG(p+0);
			w->cursor.offset.y = BGLONG(p+4);
			memmove(w->cursor.clr, p+8, 2*16);
			memmove(w->cursor.set, p+40, 2*16);
		}
		winsetcursor(w);
		qunlock(&w->lk);
		return n;

	case Qlabel:
		if(off != 0)
			error("non-zero offset writing label");
		qlock(&w->lk);
		free(w->label);
		w->label = smalloc(n+1);
		memmove(w->label, a, n);
		w->label[n] = 0;
		winsetlabel(w);
		qunlock(&w->lk);
		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, &p, 0);
		if(p == 0)
			error(Eshort);
		if(ptinrect(pt, w->image->r))
			movecursor(w, pt);
		return n;

	case Qsnarf:
		return snarfwrite(c, a, n, off);

	case Qtext:
		error(Egreg);

	case Qwctl:
		error(Egreg);

	case Qwdir:
		qlock(&w->lk);
		p = a;
		if(n>0 && p[n-1] == '\n')
			n--;
		if(n == 0)
			return 0;
		/*
		  * Problem: programs like dossrv, ftp produce illegal UTF;
		  * we must cope by converting it first.
		  */
		snprint(buf, sizeof buf, "%.*s", n, p);
		if(buf[0] == '/'){
			free(w->dir);
			w->dir = smalloc(strlen(buf) + 1);
			strcpy(w->dir, buf);
		}else{
			p = smalloc(strlen(w->dir) + 1 + strlen(buf) + 1);
			sprint(p, "%s/%s", w->dir, buf);
			free(w->dir);
			w->dir = cleanname(p);
		}
		qunlock(&w->lk);
		return n;
	}
}

Dev devwsys = {
	'w',
	"wsys",

	devreset,
	wsysattach,
	wsyswalk,
	wsysstat,
	wsysopen,
	devcreate,
	wsysclose,
	wsysread,
	devbread,
	wsyswrite,
	devbwrite,
	devremove,
	devwstat,
};
