#include <u.h>
#include <libc.h>
#include <draw.h>
#include <plumb.h>
#include <regexp.h>
#include <event.h>	/* for support routines only */
#include <bio.h>
#include "faces.h"

int	history = 0;	/* use old interface, showing history of mailbox rather than current state */
int	initload = 0;	/* initialize program with contents of mail box */
int	clickrm = 0;	/* allows removing mail faces by left clicking */

enum
{
	Facesep = 6,	/* must be even to avoid damaging background stipple */
	Infolines = 9,

	HhmmTime = 18*60*60,	/* max age of face to display hh:mm time */
};

enum
{
	Mainp,
	Timep,
	Mousep,
	NPROC
};

int pids[NPROC];
char *procnames[] = {
	"main",
	"time",
	"mouse"
};

Rectangle leftright = {0, 0, 20, 15};

uchar leftdata[] = {
	0x00, 0x80, 0x00, 0x01, 0x80, 0x00, 0x03, 0x80,
	0x00, 0x07, 0x80, 0x00, 0x0f, 0x00, 0x00, 0x1f,
	0xff, 0xf0, 0x3f, 0xff, 0xf0, 0xff, 0xff, 0xf0,
	0x3f, 0xff, 0xf0, 0x1f, 0xff, 0xf0, 0x0f, 0x00,
	0x00, 0x07, 0x80, 0x00, 0x03, 0x80, 0x00, 0x01,
	0x80, 0x00, 0x00, 0x80, 0x00
};

uchar rightdata[] = {
	0x00, 0x10, 0x00, 0x00, 0x18, 0x00, 0x00, 0x1c,
	0x00, 0x00, 0x1e, 0x00, 0x00, 0x0f, 0x00, 0xff,
	0xff, 0x80, 0xff, 0xff, 0xc0, 0xff, 0xff, 0xf0,
	0xff, 0xff, 0xc0, 0xff, 0xff, 0x80, 0x00, 0x0f,
	0x00, 0x00, 0x1e, 0x00, 0x00, 0x1c, 0x00, 0x00,
	0x18, 0x00, 0x00, 0x10, 0x00
};

Image	*blue;		/* full arrow */
Image	*bgrnd;		/* pale blue background color */
Image	*left;		/* left-pointing arrow mask */
Image	*right;		/* right-pointing arrow mask */
Font	*tinyfont;
Font	*mediumfont;
Font	*datefont;
int	first, last;	/* first and last visible face; last is first invisible */
int	nfaces;
int	mousefd;
int	nacross;
int	ndown;

char	date[64];
Face	**faces;
char	*maildir = "/mail/fs/mbox";
ulong	now;

Point	datep = { 8, 6 };
Point	facep = { 8, 6+0+4 };	/* 0 updated to datefont->height in init() */
Point	enddate;			/* where date ends on display; used to place arrows */
Rectangle	leftr;			/* location of left arrow on display */
Rectangle	rightr;		/* location of right arrow on display */
void updatetimes(void);

void
setdate(void)
{
	now = time(nil);
	strcpy(date, ctime(now));
	date[4+4+3+5] = '\0';	/* change from Thu Jul 22 14:28:43 EDT 1999\n to Thu Jul 22 14:28 */
}

void
init(void)
{
	mousefd = open("/dev/mouse", OREAD);
	if(mousefd < 0){
		fprint(2, "faces: can't open mouse: %r\n");
		exits("mouse");
	}
	initplumb();

	/* make background color */
	bgrnd = allocimagemix(display, DPalebluegreen, DWhite);
	blue = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x008888FF);	/* blue-green */
	left = allocimage(display, leftright, GREY1, 0, DWhite);
	right = allocimage(display, leftright, GREY1, 0, DWhite);
	if(bgrnd==nil || blue==nil || left==nil || right==nil){
		fprint(2, "faces: can't create images: %r\n");
		exits("image");
	}

	loadimage(left, leftright, leftdata, sizeof leftdata);
	loadimage(right, leftright, rightdata, sizeof rightdata);

	/* initialize little fonts */
	tinyfont = openfont(display, "/lib/font/bit/misc/ascii.5x7.font");
	if(tinyfont == nil)
		tinyfont = font;
 	mediumfont = openfont(display, "/lib/font/bit/misc/unicode.6x13.font");
 	if(mediumfont == nil)
 		mediumfont = font;
	datefont = font;

	facep.y += datefont->height;
	if(datefont->height & 1)	/* stipple parity */
		facep.y++;
	faces = nil;
}

void
drawtime(void)
{
	Rectangle r;

	r.min = addpt(screen->r.min, datep);
	if(eqpt(enddate, ZP)){
		enddate = r.min;
		enddate.x += stringwidth(datefont, "Wed May 30 22:54");	/* nice wide string */
		enddate.x += Facesep;	/* for safety */
	}
	r.max.x = enddate.x;
	r.max.y = enddate.y+datefont->height;
	draw(screen, r, bgrnd, nil, ZP);
	string(screen, r.min, display->black, ZP, datefont, date);
}

void
timeproc(void)
{
	for(;;){
		lockdisplay(display);
		drawtime();
		updatetimes();
		flushimage(display, 1);
		unlockdisplay(display);
		now = time(nil);
		sleep(((60 - now%60) + 1)*1000); /* wait for minute to change */
		setdate();
	}
}

int
alreadyseen(char *digest)
{
	int i;
	Face *f;

	if(!digest)
		return 0;

	/* can do accurate check */
	for(i=0; i<nfaces; i++){
		f = faces[i];
		if(f->str[Sdigest]!=nil && strcmp(digest, f->str[Sdigest])==0)
			return 1;
	}
	return 0;
}

int
torune(Rune *r, char *s, int nr)
{
	int i;

	for(i=0; i<nr-1 && *s!='\0'; i++)
		s += chartorune(r+i, s);
	r[i] = L'\0';
	return i;
}

void
center(Font *f, Point p, char *s, Image *color)
{
	int i, n, dx;
	Rune rbuf[32];
	char sbuf[32*UTFmax+1];

	dx = stringwidth(f, s);
	if(dx > Facesize){
		n = torune(rbuf, s, nelem(rbuf));
		for(i=0; i<n; i++){
			dx = runestringnwidth(f, rbuf, i+1);
			if(dx > Facesize)
				break;
		}
		sprint(sbuf, "%.*S", i, rbuf);
		s = sbuf;
		dx = stringwidth(f, s);
	}
	p.x += (Facesize-dx)/2;
	string(screen, p, color, ZP, f, s);
}

Rectangle
facerect(int index)	/* index is geometric; 0 is always upper left face */
{
	Rectangle r;
	int x, y;

	x = index % nacross;
	y = index / nacross;
	r.min = addpt(screen->r.min, facep);
	r.min.x += x*(Facesize+Facesep);
	r.min.y += y*(Facesize+Facesep+2*mediumfont->height);
	r.max = addpt(r.min, Pt(Facesize, Facesize));
	r.max.y += 2*mediumfont->height;
	/* simple fix to avoid drawing off screen, allowing customers to use position */
	if(index<0 || index>=nacross*ndown)
		r.max.x = r.min.x;
	return r;
}

static char *mon = "JanFebMarAprMayJunJulAugSepOctNovDec";
char*
facetime(Face *f, int *recent)
{
	static char buf[30];

	if((long)(now - f->time) > HhmmTime){
		*recent = 0;
		sprint(buf, "%.3s %2d", mon+3*f->tm.mon, f->tm.mday);
		return buf;
	}else{
		*recent = 1;
		sprint(buf, "%02d:%02d", f->tm.hour, f->tm.min);
		return buf;
	}
}

void
drawface(Face *f, int i)
{
	char *tstr;
	Rectangle r;
	Point p;

	if(f == nil)
		return;
	if(i<first || i>=last)
		return;
	r = facerect(i-first);
	draw(screen, r, bgrnd, nil, ZP);
	draw(screen, r, f->bit, f->mask, ZP);
	r.min.y += Facesize;
	center(mediumfont, r.min, f->str[Suser], display->black);
	r.min.y += mediumfont->height;
	tstr = facetime(f, &f->recent);
	center(mediumfont, r.min, tstr, display->black);
	if(f->unknown){
		r.min.y -= mediumfont->height + tinyfont->height + 2;
		for(p.x=-1; p.x<=1; p.x++)
			for(p.y=-1; p.y<=1; p.y++)
				center(tinyfont, addpt(r.min, p), f->str[Sdomain], display->white);
		center(tinyfont, r.min, f->str[Sdomain], display->black);
	}
}

void
updatetimes(void)
{
	int i;
	Face *f;

	for(i=0; i<nfaces; i++){
		f = faces[i];
		if(f == nil)
			continue;
		if(((long)(now - f->time) <= HhmmTime) != f->recent)
			drawface(f, i);
	}	
}

void
setlast(void)
{
	last = first+nacross*ndown;
	if(last > nfaces)
		last = nfaces;
}

void
drawarrows(void)
{
	Point p;

	p = enddate;
	p.x += Facesep;
	if(p.x & 1)
		p.x++;	/* align background texture */
	leftr = rectaddpt(leftright, p);
	p.x += Dx(leftright) + Facesep;
	rightr = rectaddpt(leftright, p);
	draw(screen, leftr, first>0? blue : bgrnd, left, leftright.min);
	draw(screen, rightr, last<nfaces? blue : bgrnd, right, leftright.min);
}

void
addface(Face *f)	/* always adds at 0 */
{
	Face **ofaces;
	Rectangle r0, r1, r;
	int y, nx, ny;

	if(f == nil)
		return;
	lockdisplay(display);
	if(first != 0){
		first = 0;
		resized();
	}
	findbit(f);

	nx = nacross;
	ny = (nfaces+(nx-1)) / nx;

	for(y=ny; y>=0; y--){
		/* move them along */
		r0 = facerect(y*nx+0);
		r1 = facerect(y*nx+1);
		r = r1;
		r.max.x = r.min.x + (nx - 1)*(Facesize+Facesep);
		draw(screen, r, screen, nil, r0.min);
		/* copy one down from row above */
		if(y != 0){
			r = facerect((y-1)*nx+nx-1);
			draw(screen, r0, screen, nil, r.min);
		}
	}

	ofaces = faces;
	faces = emalloc((nfaces+1)*sizeof(Face*));
	memmove(faces+1, ofaces, nfaces*(sizeof(Face*)));
	free(ofaces);
	nfaces++;
	setlast();
	drawarrows();
	faces[0] = f;
	drawface(f, 0);
	flushimage(display, 1);
	unlockdisplay(display);
}

void
loadmboxfaces(char *maildir)
{
	int dirfd;
	Dir *d;
	int i, n;

	dirfd = open(maildir, OREAD);
	if(dirfd >= 0){
		chdir(maildir);
		while((n = dirread(dirfd, &d)) > 0){
			for(i=0; i<n; i++)
				addface(dirface(maildir, d[i].name));
			free(d);
		}
		close(dirfd);
	}
}

void
freeface(Face *f)
{
	int i;

	if(f->file==nil || f->bit!=f->file->image)
		freeimage(f->bit);
	freefacefile(f->file);
	for(i=0; i<Nstring; i++)
		free(f->str[i]);
	free(f);
}

void
delface(int j)
{
	Rectangle r0, r1, r;
	int nx, ny, x, y;

	if(j < first)
		first--;
	else if(j < last){
		nx = nacross;
		ny = (nfaces+(nx-1)) / nx;
		x = (j-first)%nx;
		for(y=(j-first)/nx; y<ny; y++){
			if(x != nx-1){
				/* move them along */
				r0 = facerect(y*nx+x);
				r1 = facerect(y*nx+x+1);
				r = r0;
				r.max.x = r.min.x + (nx - x - 1)*(Facesize+Facesep);
				draw(screen, r, screen, nil, r1.min);
			}
			if(y != ny-1){
				/* copy one up from row below */
				r = facerect((y+1)*nx);
				draw(screen, facerect(y*nx+nx-1), screen, nil, r.min);
			}
			x = 0;
		}
		if(last < nfaces)	/* first off-screen becomes visible */
			drawface(faces[last], last-1);
		else{
			/* clear final spot */
			r = facerect(last-first-1);
			draw(screen, r, bgrnd, nil, r.min);
		}
	}
	freeface(faces[j]);
	memmove(faces+j, faces+j+1, (nfaces-(j+1))*sizeof(Face*));
	nfaces--;
	setlast();
	drawarrows();
}

void
dodelete(int i)
{
	Face *f;

	f = faces[i];
	if(history){
		free(f->str[Sshow]);
		f->str[Sshow] = estrdup("");
	}else{
		delface(i);
		flushimage(display, 1);
	}
}

void
delete(char *s, char *digest)
{
	int i;
	Face *f;

	lockdisplay(display);
	for(i=0; i<nfaces; i++){
		f = faces[i];
		if(digest != nil){
			if(f->str[Sdigest]!=nil && strcmp(digest, f->str[Sdigest]) == 0){
				dodelete(i);
				break;
			}
		}else{
			if(f->str[Sshow] && strcmp(s, f->str[Sshow]) == 0){
				dodelete(i);
				break;
			}
		}
	}
	unlockdisplay(display);
}

void
faceproc(void)
{
	for(;;)
		addface(nextface());
}

void
resized(void)
{
	int i;

	nacross = (Dx(screen->r)-2*facep.x+Facesep)/(Facesize+Facesep);
	for(ndown=1; rectinrect(facerect(ndown*nacross), screen->r); ndown++)
		;
	setlast();
	draw(screen, screen->r, bgrnd, nil, ZP);
	enddate = ZP;
	drawtime();
	for(i=0; i<nfaces; i++)
		drawface(faces[i], i);
	drawarrows();
	flushimage(display, 1);
}

void
eresized(int new)
{
	lockdisplay(display);
	if(new && getwindow(display, Refnone) < 0) {
		fprint(2, "can't reattach to window\n");
		killall("reattach");
	}
	resized();
	unlockdisplay(display);
}

int
getmouse(Mouse *m)
{
	int n;
	static int eof;
	char buf[128];

	if(eof)
		return 0;
	for(;;){
		n = read(mousefd, buf, sizeof(buf));
		if(n <= 0){
			/* so callers needn't check return value every time */
			eof = 1;
			m->buttons = 0;
			return 0;
		}
		n = eatomouse(m, buf, n);
		if(n > 0)
			return 1;
	}
}

enum
{
	Clicksize	= 3,		/* pixels */
};

int
scroll(int but, Point p)
{
	int delta;

	delta = 0;
	lockdisplay(display);
	if(ptinrect(p, leftr) && first>0){
		if(but == 2)
			delta = -first;
		else{
			delta = nacross;
			if(delta > first)
				delta = first;
			delta = -delta;
		}
	}else if(ptinrect(p, rightr) && last<nfaces){
		if(but == 2)
			delta = (nfaces-nacross*ndown) - first;
		else{
			delta = nacross;
			if(delta > nfaces-last)
				delta = nfaces-last;
		}
	}
	first += delta;
	last += delta;
	unlockdisplay(display);
	if(delta)
		eresized(0);
	return delta;
}

void
click(int button, Mouse *m)
{
	Point p;
	int i;

	p = m->xy;
	while(m->buttons == (1<<(button-1)))
		getmouse(m);
	if(m->buttons)
		return;
	if(abs(p.x-m->xy.x)>Clicksize || abs(p.y-m->xy.y)>Clicksize)
		return;
	switch(button){
	case 1:
		if(scroll(1, p))
			break;
		if(history){
			/* click clears display */
			lockdisplay(display);
			for(i=0; i<nfaces; i++)
				freeface(faces[i]);
			free(faces);
			faces=nil;
			nfaces = 0;
			unlockdisplay(display);
			eresized(0);
			return;
		}else{
			for(i=first; i<last; i++)	/* clear vwhois faces */
				if(ptinrect(p, facerect(i-first)) 
				&& (clickrm || strstr(faces[i]->str[Sshow], "/XXXvwhois"))){
					delface(i);
					flushimage(display, 1);
				}
		}
		break;
	case 2:
		scroll(2, p);
		break;
	case 3:
		scroll(3, p);
		lockdisplay(display);
		for(i=first; i<last; i++)
			if(ptinrect(p, facerect(i-first))){
				showmail(faces[i]);
				break;
			}
		unlockdisplay(display);
		break;
	}
}

void
mouseproc(void)
{
	Mouse mouse;

	while(getmouse(&mouse)){
		if(mouse.buttons == 1)
			click(1, &mouse);
		else if(mouse.buttons == 2)
			click(2, &mouse);
		else if(mouse.buttons == 4)
			click(3, &mouse);

		while(mouse.buttons)
			getmouse(&mouse);
	}
}

void
killall(char *s)
{
	int i, pid;

	pid = getpid();
	for(i=0; i<NPROC; i++)
		if(pids[i] && pids[i]!=pid)
			postnote(PNPROC, pids[i], "kill");
	exits(s);
}

void
startproc(void (*f)(void), int index)
{
	int pid;

	switch(pid = rfork(RFPROC|RFMEM|RFNOWAIT)){
	case -1:
		fprint(2, "faces: fork failed: %r\n");
		killall("fork failed");
	case 0:
		f();
		fprint(2, "faces: %s process exits\n", procnames[index]);
		if(index >= 0)
			killall("process died");
		exits(nil);
	}
	if(index >= 0)
		pids[index] = pid;
}

void
usage(void)
{
	fprint(2, "usage: faces [-chi] [-m maildir]\n");
	exits("usage");
}

void
main(int argc, char *argv[])
{
	int i;

	ARGBEGIN{
	case 'h':
		history++;
		break;
	case 'i':
		initload++;
		break;
	case 'm':
		addmaildir(EARGF(usage()));
		maildir = nil;
		break;
	case 'c':
		clickrm++;
		break;
	default:
		usage();
	}ARGEND

	if(initdraw(nil, nil, "faces") < 0){
		fprint(2, "faces: initdraw failed: %r\n");
		exits("initdraw");
	}
	if(maildir)
		addmaildir(maildir);
	init();
	unlockdisplay(display);	/* initdraw leaves it locked */
	display->locking = 1;	/* tell library we're using the display lock */
	setdate();
	eresized(0);

	pids[Mainp] = getpid();
	startproc(timeproc, Timep);
	startproc(mouseproc, Mousep);
	if(initload)
		for(i = 0; i < nmaildirs; i++)
		 loadmboxfaces(maildirs[i]);
	faceproc();
	fprint(2, "faces: %s process exits\n", procnames[Mainp]);
	killall(nil);
}
