#include <u.h>
#include <libc.h>
#include <draw.h>
#include <memdraw.h>
#include <thread.h>
#include <mouse.h>
#include <keyboard.h>
#include <cursor.h>
#include <frame.h>
#include <regexp.h>
#include <plumb.h>
#include <html.h>
#include "dat.h"
#include "fns.h"

enum {
	WPERCOL = 8,
};
void	mousethread(void *);
void	keyboardthread(void *);
void	iconinit(void);
void	plumbproc(void*);

Channel	*cexit;
Channel	*cplumb;
Mousectl *mousectl;

char *fontnames[2] = {
	"/lib/font/bit/lucidasans/unicode.8.font",
	"/lib/font/bit/lucidasans/passwd.6.font",
};

int	snarffd = -1;
int	mainpid;
int	plumbwebfd;
int	plumbsendfd ;
char	*webmountpt = "/mnt/web";
char	*charset = "iso-8859-1";
int	mainstacksize = STACK;

void	readpage(Column *, char *);
int	shutdown(void *, char *);

void
derror(Display *, char *s)
{
	error(s);
}

static void
usage(void)
{
	fprint(2, "usage: %s [-c ncol] [-m mtpt] [-t charset] [-f font] [url...]\n",
		argv0);
	exits("usage");
}

void
threadmain(int argc, char *argv[])
{
	Column *c;
	char buf[256];
	int i, ncol;
	char *tfnt = nil;

	rfork(RFENVG|RFNAMEG);

	ncol = 1;
	ARGBEGIN{
	case 'c':
		ncol = atoi(EARGF(usage()));
		if(ncol <= 0)
			usage();
		break;
	case 'm':
		webmountpt = EARGF(usage());
		break;
	case 'p':
		procstderr++;
		break;
	case 't':
		charset = EARGF(usage());
		break;
	case 'f':
		tfnt = EARGF(usage());
	default:
		usage();
		break;
	}ARGEND

	snprint(buf, sizeof(buf), "%s/ctl", webmountpt);
	webctlfd = open(buf, ORDWR);
	if(webctlfd < 0)
		sysfatal("can't initialize webfs: %r");

	snarffd = open("/dev/snarf", OREAD|OCEXEC);

	if(tfnt == nil){
		tfnt = getenv("font");
		if(tfnt != nil)
			fontnames[0] = tfnt;
	}

	if(initdraw(derror, fontnames[0], "abaco") < 0)
		sysfatal("can't open display: %r");
	memimageinit();
	iconinit();
	timerinit();
	initfontpaths();

	cexit = chancreate(sizeof(int), 0);
	crefresh = chancreate(sizeof(Page *), 0);
	if(cexit==nil || crefresh==nil)
		sysfatal("can't create initial channels: %r");

	mousectl = initmouse(nil, screen);
	if(mousectl == nil)
		sysfatal("can't initialize mouse: %r");
	mouse = mousectl;
	keyboardctl = initkeyboard(nil);
	if(keyboardctl == nil)
		sysfatal("can't initialize keyboard: %r");
	mainpid = getpid();
	plumbwebfd = plumbopen("web", OREAD|OCEXEC);
	if(plumbwebfd >= 0){
		cplumb = chancreate(sizeof(Plumbmsg*), 0);
		proccreate(plumbproc, nil, STACK);
	}
	plumbsendfd = plumbopen("send", OWRITE|OCEXEC);

	rowinit(&row, screen->clipr);
	for(i=0; i<ncol; i++){
		c = rowadd(&row, nil, -1);
		if(c==nil && i==0)
			error("initializing columns");
	}
	c = row.col[row.ncol-1];
	for(i=0; i<argc; i++)
		if(i/WPERCOL >= row.ncol)
			readpage(c, argv[i]);
		else
			readpage(row.col[i/WPERCOL], argv[i]);
	flushimage(display, 1);
	threadcreate(keyboardthread, nil, STACK);
	threadcreate(mousethread, nil, STACK);

	threadnotify(shutdown, 1);
	recvul(cexit);
	threadexitsall(nil);
}

void
readpage(Column *c, char *s)
{
	Window *w;
	Runestr rs;

	w = coladd(c, nil, nil, -1);
	bytetorunestr(s, &rs);
	pageget(&w->page, &rs, nil, HGet, TRUE);
	closerunestr(&rs);
}

char *oknotes[] = {
	"delete",
	"hangup",
	"kill",
	"exit",
	nil
};

int
shutdown(void*, char *msg)
{
	int i;

	for(i=0; oknotes[i]; i++)
		if(strncmp(oknotes[i], msg, strlen(oknotes[i])) == 0)
			threadexitsall(msg);
	print("abaco: %s\n", msg);
//	abort();
	return 0;
}

void
plumbproc(void *)
{
	Plumbmsg *m;

	threadsetname("plumbproc");
	for(;;){
		m = plumbrecv(plumbwebfd);
		if(m == nil)
			threadexits(nil);
		sendp(cplumb, m);
	}
}

enum { KTimer, KKey, NKALT, };

void
keyboardthread(void *)
{
	Timer *timer;
	Text *t;
	Rune r;

	static Alt alts[NKALT+1];

	alts[KTimer].c = nil;
	alts[KTimer].v = nil;
	alts[KTimer].op = CHANNOP;
	alts[KKey].c = keyboardctl->c;
	alts[KKey].v = &r;
	alts[KKey].op = CHANRCV;
	alts[NKALT].op = CHANEND;

	timer = nil;
	threadsetname("keyboardthread");
	for(;;){
		switch(alt(alts)){
		case KTimer:
			timerstop(timer);
			alts[KTimer].c = nil;
			alts[KTimer].op = CHANNOP;
			break;
		case KKey:
		casekeyboard:
			typetext = rowwhich(&row, mouse->xy, r, TRUE);
			t = typetext;
			if(t!=nil && t->col!=nil &&
			    !(r==Kdown || r==Kleft || r==Kright))
				/* scrolling doesn't change activecol */
				activecol = t->col;
			if(timer != nil)
				timercancel(timer);
			if(t!=nil){
				texttype(t, r);
				timer = timerstart(500);
				alts[KTimer].c = timer->c;
				alts[KTimer].op = CHANRCV;
			}else{
				timer = nil;
				alts[KTimer].c = nil;
				alts[KTimer].op = CHANNOP;
			}
			if(nbrecv(keyboardctl->c, &r) > 0)
				goto casekeyboard;
			flushimage(display, 1);
			break;
		}
	}
}

void
mousethread(void *)
{
	Plumbmsg *pm;
	Mouse m;
	Text *t;
	int but;
	enum { MResize, MMouse, MPlumb, MRefresh, NMALT };
	static Alt alts[NMALT+1];

	threadsetname("mousethread");
	alts[MResize].c = mousectl->resizec;
	alts[MResize].v = nil;
	alts[MResize].op = CHANRCV;
	alts[MMouse].c = mousectl->c;
	alts[MMouse].v = &mousectl->Mouse;
	alts[MMouse].op = CHANRCV;
	alts[MPlumb].c = cplumb;
	alts[MPlumb].v = &pm;
	alts[MPlumb].op = CHANRCV;
	alts[MRefresh].c = crefresh;
	alts[MRefresh].v = nil;
	alts[MRefresh].op = CHANRCV;
	if(cplumb == nil)
		alts[MPlumb].op = CHANNOP;
	alts[NMALT].op = CHANEND;

	for(;;){
		qlock(&row);
		flushrefresh();
		qunlock(&row);
		flushimage(display, 1);
		switch(alt(alts)){
		case MResize:
			if(getwindow(display, Refnone) < 0)
				error("resized");
			scrlresize();
			tmpresize();
			rowresize(&row, screen->clipr);
			break;
		case MPlumb:
			plumblook(pm);
			plumbfree(pm);
			break;
		case MRefresh:
			break;
		case MMouse:
			m = mousectl->Mouse;
			if(m.buttons == 0)
				continue;

			qlock(&row);
			but = 0;
			if(m.buttons == 1)
				but = 1;
			else if(m.buttons == 2)
				but = 2;
			else if(m.buttons == 4)
				but = 3;

			if(m.buttons & (8|16)){
				if(m.buttons & 8)
					but = Kscrolloneup;
				else
					but = Kscrollonedown;
				rowwhich(&row, m.xy, but, TRUE);
			}else	if(but){
				t = rowwhich(&row, m.xy, but, FALSE);
				if(t)
					textmouse(t, m.xy, but);
			}
			qunlock(&row);
			break;
		}
	}
}

Cursor boxcursor = {
	{-7, -7},
	{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
	 0xFF, 0xFF, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F,
	 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFF, 0xFF,
	 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
	{0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE,
	 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
	 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
	 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x00}
};

void
iconinit(void)
{
	Rectangle r;

	/* Green */
	tagcols[BACK] = allocimagemix(display, DPalegreen, DWhite);
	if(tagcols[BACK] == nil)
		error("allocimagemix");
	tagcols[HIGH] = eallocimage(display, Rect(0,0,1,1), screen->chan, 1, DDarkgreen);
	tagcols[BORD] = eallocimage(display, Rect(0,0,1,1), screen->chan, 1, DMedgreen);
	tagcols[TEXT] = display->black;
	tagcols[HTEXT] = display->black;

	/* Grey */
	textcols[BACK] = display->white;
	textcols[HIGH] = eallocimage(display, Rect(0,0,1,1), CMAP8,1, 0xCCCCCCFF);
	textcols[BORD] = display->black;
	textcols[TEXT] = display->black;
	textcols[HTEXT] = display->black;

	r = Rect(0, 0, Scrollsize+2, font->height+1);
	button = eallocimage(display, r, screen->chan, 0, DNofill);
	draw(button, r, tagcols[BACK], nil, r.min);
	r.max.x -= 2;
	border(button, r, 2, tagcols[BORD], ZP);

	r = button->r;
	colbutton = eallocimage(display, r, screen->chan, 0, 0x00994CFF);

	but2col = eallocimage(display, Rect(0,0,1,2), screen->chan, 1, 0xAA0000FF);
	but3col = eallocimage(display, Rect(0,0,1,2), screen->chan, 1, 0x444488FF);

	passfont = openfont(display, fontnames[1]);
	if(passfont == nil)
		error("openfont");
}

/*
 * /dev/snarf updates when the file is closed, so we must open our own
 * fd here rather than use snarffd
 */

/*
 * rio truncates large snarf buffers, so this avoids using the
 * service if the string is huge
 */

enum
{
	NSnarf = 1000,
	MAXSNARF = 100*1024,
};

void
putsnarf(Runestr *rs)
{
	int fd, i, n;

	if(snarffd<0 || rs->nr==0)
		return;
	if(rs->nr > MAXSNARF)
		return;
	fd = open("/dev/snarf", OWRITE);
	if(fd < 0)
		return;
	for(i=0; i<rs->nr; i+=n){
		n = rs->nr-i;
		if(n > NSnarf)
			n =NSnarf;
		if(fprint(fd, "%.*S", n, rs->r) < 0)
			break;
	}
	close(fd);
}

void
getsnarf(Runestr *rs)
{
	int i, n, nb, nulls;
	char *sn, buf[BUFSIZE];

	if(snarffd < 0)
		return;
	sn = nil;
	i = 0;
	seek(snarffd, 0, 0);
	while((n=read(snarffd, buf, sizeof(buf))) > 0){
		sn = erealloc(sn, i+n+1);
		memmove(sn+i, buf, n);
		i += n;
		sn[i] = 0;
	}
	if(i > 0){
		rs->r = runemalloc(i+1);
		cvttorunes(sn, i, rs->r, &nb, &rs->nr, &nulls);
		free(sn);
	}
}
