#include "dat.h"
#include <draw.h>
#include <mouse.h>
#include <keyboard.h>
#include <control.h>

int ctldeletequits = 1;

typedef struct RequestType RequestType;
typedef struct Request Request;
typedef struct Memory Memory;

struct RequestType
{
	char		*file;			/* file to read requests from */
	void		(*f)(Request*);		/* request handler */
	void		(*r)(Controlset*);	/* resize handler */
	int		fd;			/* fd = open(file, ORDWR) */
	Channel		*rc;			/* channel requests are multiplexed to */
	Controlset	*cs;
};

struct Request
{
	RequestType	*rt;
	Attr		*a;
	Attr		*tag;
};

struct Memory
{
	Memory	*next;
	Attr	*a;
	Attr	*val;
};
Memory *mem;

static void	readreq(void*);
static void	hide(void);
static void	unhide(void);
static void	openkmr(void);
static void	closekmr(void);
static Memory*	searchmem(Attr*);
static void	addmem(Attr*, Attr*);

static void	confirm(Request*);
static void	resizeconfirm(Controlset*);
static void	needkey(Request*);
static void	resizeneedkey(Controlset*);

Control *b_remember;
Control *b_accept;
Control *b_refuse;

RequestType rt[] = 
{
	{ "/mnt/factotum/confirm",	confirm,	resizeconfirm, },
	{ "/mnt/factotum/needkey",	needkey,	resizeneedkey, },
	{ 0 },
};

enum
{
	ButtonDim=	15,
};

void
threadmain(int argc, char *argv[])
{
	Request r;
	Channel *rc;
	RequestType *p;
	Font *invis;

	ARGBEGIN{
	}ARGEND;

	if(newwindow("-hide") < 0)
		sysfatal("newwindow: %r");

	fmtinstall('A', _attrfmt);

	/* create the proc's that read */
	rc = chancreate(sizeof(Request), 0);
	for(p = rt;  p->file != 0; p++){
		p->fd = -1;
		p->rc = rc;
		proccreate(readreq, p, 16*1024);
	}

	/* gui initialization */
	if(initdraw(0, 0, "auth/fgui") < 0)
		sysfatal("initdraw failed: %r");
	initcontrols();
	hide();

	/* get an invisible font for passwords */
	invis = openfont(display, "/lib/font/bit/lucm/passwd.9.font");
	if (invis == nil)
		sysfatal("fgui: %s: %r", "/lib/font/bit/lucm/passwd.9.font");
	namectlfont(invis, "invisible");

	/* serialize all requests */
	for(;;){
		if(recv(rc, &r) < 0)
			break;
		(*r.rt->f)(&r);
		_freeattr(r.a);
		_freeattr(r.tag);
	}

	threadexitsall(nil);
}

/*
 *  read requests and pass them to the main loop
 */
enum
{
	Requestlen=4096,
};
static void
readreq(void *a)
{
	RequestType *rt = a;
	char *buf, *p;
	int n;
	Request r;
	Attr **l;

	rt->fd = open(rt->file, ORDWR);
	if(rt->fd < 0)
		sysfatal("opening %s: %r", rt->file);
	rt->cs = nil;

	buf = malloc(Requestlen);
	if(buf == nil)
		sysfatal("allocating read buffer: %r");
	r.rt = rt;

	for(;;){
		n = read(rt->fd, buf, Requestlen-1);
		if(n < 0)
			break;
		buf[n] = 0;

		/* skip verb, parse attributes, and remove tag */
		p = strchr(buf, ' ');
		if(p != nil)
			p++;
		else
			p = buf;
		r.a = _parseattr(p);

		/* separate out the tag */
		r.tag = nil;
		for(l = &r.a; *l != nil; l = &(*l)->next)
			if(strcmp((*l)->name, "tag") == 0){
				r.tag = *l;
				*l = r.tag->next;
				r.tag->next = nil;
				break;
			}

		/* if no tag, forget it */
		if(r.tag == nil){
			_freeattr(r.a);
			continue;
		}

		send(rt->rc, &r);
	}
}
#ifdef asdf
static void
readreq(void *a)
{
	RequestType *rt = a;
	char *buf, *p;
	int n;
	Request r;

	rt->fd = -1;
	rt->cs = nil;

	buf = malloc(Requestlen);
	if(buf == nil)
		sysfatal("allocating read buffer: %r");
	r.rt = rt;

	for(;;){
		strcpy(buf, "adfasdf=afdasdf asdfasdf=asdfasdf");
		r.a = _parseattr(buf);
		send(rt->rc, &r);
		sleep(5000);
	}
}
#endif asdf

/*
 *  open/close the keyboard, mouse and resize channels
 */
static Channel *kbdc;
static Channel *mousec;
static Channel *resizec;
static Keyboardctl *kctl;
static Mousectl *mctl;

static void
openkmr(void)
{
	/* get channels for subsequent newcontrolset calls  */
	kctl = initkeyboard(nil);
	if(kctl == nil)
		sysfatal("can't initialize keyboard: %r");
	kbdc = kctl->c;
	mctl = initmouse(nil, screen);
	if(mctl == nil)
		sysfatal("can't initialize mouse: %r");
	mousec = mctl->c;
	resizec = mctl->resizec;
}
static void
closekmr(void)
{
	Mouse m;

	while(nbrecv(kbdc, &m) > 0)
		;
	closekeyboard(kctl);
	while(nbrecv(mousec, &m) > 0)
		;
	closemouse(mctl);
}


/*
 *  called when the window is resized
 */
void
resizecontrolset(Controlset *cs)
{
	RequestType *p;

	for(p = rt;  p->file != 0; p++){
		if(p->cs == cs){
			(*p->r)(cs);
			break;
		}
	}
}

/*
 *  hide window when not in use
 */
static void
unhide(void)
{
	int wctl;

	wctl = open("/dev/wctl", OWRITE);
	if(wctl < 0)
		return;
	fprint(wctl, "unhide");
	close(wctl);
}
static void
hide(void)
{
	int wctl;
	int tries;

	wctl = open("/dev/wctl", OWRITE);
	if(wctl < 0)
		return;
	for(tries = 0; tries < 10; tries++){
		if(fprint(wctl, "hide") >= 0)
			break;
		sleep(100);
	}
	close(wctl);
}

/*
 *  set up the controls for the confirmation window
 */
static Channel*
setupconfirm(Request *r)
{
	Controlset *cs;
	Channel *c;
	Attr *a;

	/* create a new control set for the confirmation */
	openkmr();
	cs = newcontrolset(screen, kbdc, mousec, resizec);

	createtext(cs, "msg");
	chanprint(cs->ctl, "msg image paleyellow");
	chanprint(cs->ctl, "msg border 1");
	chanprint(cs->ctl, "msg add 'The following key is being used:'");
	for(a = r->a; a != nil; a = a->next)
		chanprint(cs->ctl, "msg add '    %s = %s'", a->name,
				a->val);

	namectlimage(display->white, "i_white");
	namectlimage(display->black, "i_black");

	b_remember = createbutton(cs, "b_remember");
	chanprint(cs->ctl, "b_remember border 1");
	chanprint(cs->ctl, "b_remember mask i_white");
	chanprint(cs->ctl, "b_remember image i_white");
	chanprint(cs->ctl, "b_remember light i_black");

	createtext(cs, "t_remember");
	chanprint(cs->ctl, "t_remember image white");
	chanprint(cs->ctl, "t_remember bordercolor white");
	chanprint(cs->ctl, "t_remember add 'Remember this answer for future confirmations'");

	b_accept = createtextbutton(cs, "b_accept");
	chanprint(cs->ctl, "b_accept border 1");
	chanprint(cs->ctl, "b_accept align center");
	chanprint(cs->ctl, "b_accept text Accept");
	chanprint(cs->ctl, "b_accept image i_white");
	chanprint(cs->ctl, "b_accept light i_black");

	b_refuse = createtextbutton(cs, "b_refuse");
	chanprint(cs->ctl, "b_refuse border 1");
	chanprint(cs->ctl, "b_refuse align center");
	chanprint(cs->ctl, "b_refuse text Refuse");
	chanprint(cs->ctl, "b_refuse image i_white");
	chanprint(cs->ctl, "b_refuse light i_black");

	c = chancreate(sizeof(char*), 0);
	controlwire(b_remember, "event", c);
	controlwire(b_accept, "event", c);
	controlwire(b_refuse, "event", c);

	/* make the controls interactive */
	activate(b_remember);
	activate(b_accept);
	activate(b_refuse);
	r->rt->cs = cs;
	unhide();
	resizecontrolset(cs);

	return c;
}

/*
 *  resize the controls for the confirmation window
 */
static void
resizeconfirm(Controlset *cs)
{
	Rectangle r, mr, nr, ntr, ar, rr;
	int fontwidth;

	fontwidth = font->height;

	/* get usable window rectangle */
	if(getwindow(display, Refnone) < 0)
		ctlerror("resize failed: %r");
	r = insetrect(screen->r, 10);

	/* message box fills everything not needed for buttons */
	mr = r;
	mr.max.y = mr.min.y + font->height*((Dy(mr)-3*ButtonDim-font->height-4)/font->height);

	/* remember button */
	nr.min = Pt(mr.min.x, mr.max.y+ButtonDim);
	nr.max = Pt(mr.max.x, r.max.y);
	if(Dx(nr) > ButtonDim)
		nr.max.x = nr.min.x+ButtonDim;
	if(Dy(nr) > ButtonDim)
		nr.max.y = nr.min.y+ButtonDim;
	ntr.min = Pt(nr.max.x+ButtonDim, nr.min.y);
	ntr.max = Pt(r.max.x, nr.min.y+font->height);

	/* accept/refuse buttons */
	ar.min = Pt(r.min.x+Dx(r)/2-ButtonDim-6*fontwidth, nr.max.y+ButtonDim);
	ar.max = Pt(ar.min.x+6*fontwidth, ar.min.y+font->height+4);
	rr.min = Pt(r.min.x+Dx(r)/2+ButtonDim, nr.max.y+ButtonDim);
	rr.max = Pt(rr.min.x+6*fontwidth, rr.min.y+font->height+4);

	/* make the controls visible */
	chanprint(cs->ctl, "msg rect %R\nmsg show", mr);
	chanprint(cs->ctl, "b_remember rect %R\nb_remember show", nr);
	chanprint(cs->ctl, "t_remember rect %R\nt_remember show", ntr);
	chanprint(cs->ctl, "b_accept rect %R\nb_accept show", ar);
	chanprint(cs->ctl, "b_refuse rect %R\nb_refuse show", rr);
}

/*
 *  free the controls for the confirmation window
 */
static void
teardownconfirm(Request *r)
{
	Controlset *cs;

	cs = r->rt->cs;
	r->rt->cs = nil;
	hide();
	closecontrolset(cs);
	closekmr();
}

/*
 *  get user confirmation of a key
 */
static void
confirm(Request *r)
{
	Channel *c;
	char *s;
	int n;
	char *args[3];
	int remember;
	Attr *val;
	Memory *m;

	/* if it's something that the user wanted us not to ask again about */
	m = searchmem(r->a);
	if(m != nil){
		fprint(r->rt->fd, "%A %A", r->tag, m->val);
		return;
	}

	/* set up the controls */
	c = setupconfirm(r);

	/* wait for user to reply */
	remember = 0;
	for(;;){
		s = recvp(c);
		n = tokenize(s, args, nelem(args));
		if(n==3 && strcmp(args[1], "value")==0){
			if(strcmp(args[0], "b_remember:") == 0){
				remember = atoi(args[2]);
			}
			if(strcmp(args[0], "b_accept:") == 0){
				val = _mkattr(AttrNameval, "answer", "yes", nil);
				free(s);
				break;
			}
			if(strcmp(args[0], "b_refuse:") == 0){
				val = _mkattr(AttrNameval, "answer", "no", nil);
				free(s);
				break;
			}
		}
		free(s);
	}
	teardownconfirm(r);
	fprint(r->rt->fd, "%A %A", r->tag, val);
	if(remember)
		addmem(_copyattr(r->a), val);
	else
		_freeattr(val);
}

/*
 *  confirmations that are remembered
 */
static int
match(Attr *a, Attr *b)
{
	Attr *x;

	for(; a != nil; a = a->next){
		x = _findattr(b, a->name);
		if(x == nil || strcmp(a->val, x->val) != 0)
			return 0;
	}
	return 1;
}
static Memory*
searchmem(Attr *a)
{
	Memory *m;

	for(m = mem; m != nil; m = m->next){
		if(match(a, m->a))
			break;
	}
	return m;
}
static void
addmem(Attr *a, Attr *val)
{
	Memory *m;

	m = malloc(sizeof *m);
	if(m == nil)
		return;
	m->a = a;
	m->val = val;
	m->next = mem;
	mem = m;
}

/* controls for needkey */
Control *msg;
Control *b_done;
Control *b_cancel;
enum {
	Pprivate=	1<<0,
	Pneed=		1<<1,
};
typedef struct Entry Entry;
struct Entry {
	Control *name;
	Control *val;
	Control *eq;
	Attr *a;
};
static Entry *entry;
static int entries;

/*
 *  set up the controls for the confirmation window
 */
static Channel*
setupneedkey(Request *r)
{
	Controlset *cs;
	Channel *c;
	Attr *a;
	Attr **l;
	char cn[10];
	int i;

	/* create a new control set for the confirmation */
	openkmr();
	cs = newcontrolset(screen, kbdc, mousec, resizec);

	/* count attributes and allocate entry controls */
	entries = 0;
	for(l = &r->a; *l; l = &(*l)->next)
		entries++;
	if(entries == 0){
		closecontrolset(cs);
		closekmr();
		return nil;
	}
	*l = a = mallocz(sizeof *a, 1);
	a->type = AttrQuery;
	entries++;
	l = &(*l)->next;
	*l = a = mallocz(sizeof *a, 1);
	a->type = AttrQuery;
	entries++;
	entry = malloc(entries*sizeof(Entry));
	if(entry == nil){
		closecontrolset(cs);
		closekmr();
		return nil;
	}

	namectlimage(display->white, "i_white");
	namectlimage(display->black, "i_black");

	/* create controls */
	msg = createtext(cs, "msg");
	chanprint(cs->ctl, "msg image white");
	chanprint(cs->ctl, "msg bordercolor white");
	chanprint(cs->ctl, "msg add 'You need the following key.  Fill in the blanks'");
	chanprint(cs->ctl, "msg add 'and click on the DONE button.'");

	for(i = 0, a = r->a; a != nil; i++, a = a->next){
		entry[i].a = a;
		snprint(cn, sizeof cn, "name_%d", i);
		if(entry[i].a->name == nil){
			entry[i].name = createentry(cs, cn);
			chanprint(cs->ctl, "%s image paleyellow", cn);
			chanprint(cs->ctl, "%s border 1", cn);
		} else {
			entry[i].name = createtext(cs, cn);
			chanprint(cs->ctl, "%s image white", cn);
			chanprint(cs->ctl, "%s bordercolor white", cn);
			chanprint(cs->ctl, "%s add '%s'", cn, a->name);
		}

		snprint(cn, sizeof cn, "val_%d", i);
		if(a->type == AttrQuery){
			entry[i].val = createentry(cs, cn);
			chanprint(cs->ctl, "%s image paleyellow", cn);
			chanprint(cs->ctl, "%s border 1", cn);
			if(a->name != nil){
				if(strcmp(a->name, "user") == 0)
					chanprint(cs->ctl, "%s value %q", cn, getuser());
				if(*a->name == '!')
					chanprint(cs->ctl, "%s font invisible", cn);
			}
		} else {
			entry[i].val = createtext(cs, cn);
			chanprint(cs->ctl, "%s image white", cn);
			chanprint(cs->ctl, "%s add %q", cn, a->val);
		}

		snprint(cn, sizeof cn, "eq_%d", i);
		entry[i].eq = createtext(cs, cn);
		chanprint(cs->ctl, "%s image white", cn);
		chanprint(cs->ctl, "%s add ' = '", cn);
	}

	b_done = createtextbutton(cs, "b_done");
	chanprint(cs->ctl, "b_done border 1");
	chanprint(cs->ctl, "b_done align center");
	chanprint(cs->ctl, "b_done text Done");
	chanprint(cs->ctl, "b_done image palegreen");
	chanprint(cs->ctl, "b_done light palegreen");

	b_cancel = createtextbutton(cs, "b_cancel");
	chanprint(cs->ctl, "b_cancel border 1");
	chanprint(cs->ctl, "b_cancel align center");
	chanprint(cs->ctl, "b_cancel text Cancel");
	chanprint(cs->ctl, "b_cancel image i_white");
	chanprint(cs->ctl, "b_cancel light i_black");

	/* wire controls for input */
	c = chancreate(sizeof(char*), 0);
	controlwire(b_done, "event", c);
	controlwire(b_cancel, "event", c);
	for(i = 0; i < entries; i++)
		if(entry[i].a->type == AttrQuery)
			controlwire(entry[i].val, "event", c);

	/* make the controls interactive */
	activate(msg);
	activate(b_done);
	activate(b_cancel);
	for(i = 0; i < entries; i++){
		if(entry[i].a->type != AttrQuery)
			continue;
		if(entry[i].a->name == nil)
			activate(entry[i].name);
		activate(entry[i].val);
	}

	/* change the display */
	r->rt->cs = cs;
	unhide();
	resizecontrolset(cs);

	return c;
}

/*
 *  resize the controls for the confirmation window
 */
static void
resizeneedkey(Controlset *cs)
{
	Rectangle r, mr;
	int mid, i, n, lasty;

	/* get usable window rectangle */
	if(getwindow(display, Refnone) < 0)
		ctlerror("resize failed: %r");
	r = insetrect(screen->r, 10);

	/* find largest name */
	mid = 0;
	for(i = 0; i < entries; i++){
		if(entry[i].a->name == nil)
			continue;
		n = strlen(entry[i].a->name);
		if(n > mid)
			mid = n;
	}
	mid = (mid+2) * font->height;

	/* top line is the message */
	mr = r;
	mr.max.y = mr.min.y + 2*font->height + 2;
	chanprint(cs->ctl, "msg rect %R\nmsg show", mr);

	/* one line per attribute */
	mr.min.x += 2*font->height;
	lasty = mr.max.y;
	for(i = 0; i < entries; i++){
		r.min.x = mr.min.x;
		r.min.y = lasty+2;
		r.max.x = r.min.x + mid - 3*stringwidth(font, "=");
		r.max.y = r.min.y + font->height;
		chanprint(cs->ctl, "name_%d rect %R\nname_%d show", i, r, i);

		r.min.x = r.max.x;
		r.max.x = r.min.x + 3*stringwidth(font, "=");
		chanprint(cs->ctl, "eq_%d rect %R\neq_%d show", i, r, i);

		r.min.x = r.max.x;
		r.max.x = mr.max.x;
		if(Dx(r) > 32*font->height)
			r.max.x = r.min.x + 32*font->height;
		chanprint(cs->ctl, "val_%d rect %R\nval_%d show", i, r, i);
		lasty = r.max.y;
	}

	mr.min.x -= 2*font->height;

	/* done button */
	r.min.x = mr.min.x + mid - 9*font->height;
	r.min.y = lasty+10;
	r.max.x = r.min.x + 6*font->height;
	r.max.y = r.min.y + font->height + 2;
	chanprint(cs->ctl, "b_done rect %R\nb_done show", r);

	/* cancel button */
	r.min.x = mr.min.x + mid + 3*font->height;
	r.min.y = lasty+10;
	r.max.x = r.min.x + 6*font->height;
	r.max.y = r.min.y + font->height + 2;
	chanprint(cs->ctl, "b_cancel rect %R\nb_cancel show", r);
}

/*
 *  free the controls for the confirmation window
 */
static void
teardownneedkey(Request *r)
{
	Controlset *cs;

	cs = r->rt->cs;
	r->rt->cs = nil;
	hide();
	closecontrolset(cs);
	closekmr();

	if(entry != nil)
		free(entry);
	entry = nil;
}

static void
needkey(Request *r)
{
	Channel *c;
	char *nam, *val;
	int i, n;
	int fd;
	char *args[3];

	/* set up the controls */
	c = setupneedkey(r);
	if(c == nil)
		goto out;

	/* wait for user to reply */
	for(;;){
		val = recvp(c);
		n = tokenize(val, args, nelem(args));
		if(n==3 && strcmp(args[1], "value")==0)	/* user hit 'enter' */
			break;
		free(val);
	}
	if(strcmp(args[0], "b_cancel:") == 0){
		free(val);
		teardownneedkey(r);
		fprint(r->rt->fd, "%A error='canceled by user'", r->tag);
		return;
	}
	free(val);

	/* get entry values */
	for(i = 0; i < entries; i++){
		if(entry[i].a->type != AttrQuery)
			continue;

		chanprint(r->rt->cs->ctl, "val_%d data", i);
		val = recvp(entry[i].val->data);
		if(entry[i].a->name == nil){
			chanprint(r->rt->cs->ctl, "name_%d data", i);
			nam = recvp(entry[i].name->data);
			if(nam == nil || *nam == 0){
				free(val);
				continue;
			}
			entry[i].a->val = estrdup(val);
			free(val);
			entry[i].a->name = estrdup(nam);
			free(nam);
		} else {
			if(val != nil){
				entry[i].a->val = estrdup(val);
				free(val);
			}
		}
		entry[i].a->type = AttrNameval;
	}

	/* enter the new key */
	fd = open("/mnt/factotum/ctl", OWRITE);
	if(fd >= 0){
		fprint(fd, "key %A", r->a);
		close(fd);
	}

	teardownneedkey(r);
out:
	fprint(r->rt->fd, "%A", r->tag);
}
