#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*);

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;

	fmtinstall('A', attrconv);

	/* 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 */
	initdraw(0, 0, "factotum_gui");
	initcontrols();
	hide();

	/* get an invisible font for passwords */
	invis = openfont(display, "/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(s_to_c((*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);
}

/* controls for confirm */
Control *msg;
Control *b_remember;
Control *t_remember;
Control *b_accept;
Control *b_refuse;

/*
 *  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);

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

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

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

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

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

	b_refuse = createtextbutton(cs, "b_refuse");
	chanprint(b_refuse->ctl, "border 1");
	chanprint(b_refuse->ctl, "align center");
	chanprint(b_refuse->ctl, "text Refuse");
	chanprint(b_refuse->ctl, "image i_white");
	chanprint(b_refuse->ctl, "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 *)
{
	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(msg->ctl, "rect %R\nshow", mr);
	chanprint(b_remember->ctl, "rect %R\nshow", nr);
	chanprint(t_remember->ctl, "rect %R\nshow", ntr);
	chanprint(b_accept->ctl, "rect %R\nshow", ar);
	chanprint(b_refuse->ctl, "rect %R\nshow", 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, s_to_c(a->name));
		if(x == nil || strcmp(s_to_c(a->val), s_to_c(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;
enum {
	Pprivate=	1<<0,
	Pneed=		1<<1,
};
typedef struct Entry Entry;
struct Entry {
	Control *name;
	Control *val;
	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;
	char cn[10];
	int i;
	Control *ctl;

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

	/* count attributes and allocate entry controls */
	entries = 0;
	for(a = r->a; a != nil; a = a->next)
		entries++;
	if(entries == 0)
		return nil;
	entry = malloc(entries*sizeof(Entry));
	if(entry == nil)
		return nil;

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

	/* create controls */
	msg = createtext(cs, "msg");
	chanprint(msg->ctl, "image white");
	chanprint(msg->ctl, "bordercolor white");
	chanprint(msg->ctl, "add 'You need the following key.  Fill in the blanks'");
	chanprint(msg->ctl, "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);
		ctl = entry[i].name = createtext(cs, cn);
		chanprint(ctl->ctl, "image white");
		chanprint(ctl->ctl, "bordercolor white");
		chanprint(ctl->ctl, "add '%s ='", s_to_c(a->name));

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

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

	/* wire controls for input */
	c = chancreate(sizeof(char*), 0);
	controlwire(b_done, "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);
	for(i = 0; i < entries; i++)
		if(entry[i].a->type == AttrQuery)
			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 *)
{
	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++){
		n = s_len(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(msg->ctl, "rect %R\nshow", 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;
		r.max.y = r.min.y + font->height;
		chanprint(entry[i].name->ctl, "rect %R\nshow", r);

		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(entry[i].val->ctl, "rect %R\nshow", r);
		lasty = r.max.y;
	}

	/* done button */
	mr.min.x -= 2*font->height;
	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(b_done->ctl, "rect %R\nshow", 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);
}

static void
needkey(Request *r)
{
	Channel *c;
	char *s;
	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(;;){
		s = recvp(c);
		n = tokenize(s, args, nelem(args));
		if(n==3 && strcmp(args[1], "value")==0){
			if(strcmp(args[0], "b_done:") == 0){
				free(s);
				break;
			}
		}
		free(s);
	}

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

		chanprint(entry[i].val->ctl, "data");
		s = recvp(entry[i].val->data);
		if(s != nil){
			entry[i].a->val = s_copy(s);
			free(s);
		}
		entry[i].a->type = AttrNameval;
	}

	/* enter the new key !!!!need to do something in case of error!!!! */
	fd = open("/mnt/factotum/ctl", OWRITE);
	fprint(fd, "key %A", r->a);
	close(fd);

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