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

void	del(Text *, Text *, int, int, Rune *, int);
void	delcol(Text *, Text *, int, int, Rune *, int);
void	cut(Text *, Text *, int, int, Rune *, int);
void	exit(Text *, Text *, int, int, Rune *, int);
void	get(Text *, Text *, int, int, Rune *, int);
void	go(Text *,Text *,  int, int, Rune *, int);
void	duckduckgo(Text *,Text *,  int, int, Rune *, int);
void	new(Text*, Text *, int, int, Rune *, int);
void	newcol(Text*, Text *, int, int, Rune *, int);
void	paste(Text *, Text *, int, int, Rune *, int);
void	sort(Text *, Text *, int, int, Rune *, int);
void	stop(Text *, Text *, int, int, Rune *, int);
void	debug(Text *, Text *, int, int, Rune *, int);

typedef struct Exectab Exectab;
struct Exectab
{
	Rune	*name;
	void	(*fn)(Text *, Text *, int, int, Rune *, int);
	int		flag1;
	int		flag2;
};

Exectab exectab[] = {
	{ L"Back",		go,		FALSE,	XXX		},
	{ L"Cut",		cut,		TRUE,	TRUE	},
	{ L"Debug",	debug,	XXX,		XXX		},
	{ L"Del",		del,		XXX,		XXX		},
	{ L"Delcol",	delcol,	FALSE,	TRUE	},
	{ L"Exit",		exit,		XXX,		XXX		},
	{ L"Get",		get,		XXX,		XXX		},
	{ L"DDGo",	duckduckgo,	XXX,		XXX		},
	{ L"New",		new,		XXX,		XXX		},
	{ L"Newcol",	newcol,	XXX,		XXX		},
	{ L"Next",		go,		TRUE,	XXX		},
	{ L"Paste",		paste,	TRUE,	XXX		},
	{ L"Snarf",		cut,		TRUE,	FALSE	},
	{ L"Stop",		stop,		XXX,		XXX		},
	{ L"Sort",		sort,		XXX,		XXX		},
	{ nil, 			nil,		0,		0		},
};

static
Exectab*
lookup(Rune *r, int n)
{
	Exectab *e;
	int nr;

	r = skipbl(r, n, &n);
	if(n == 0)
		return nil;
	findbl(r, n, &nr);
	nr = n-nr;
	for(e=exectab; e->name; e++)
		if(runeeq(r, nr, e->name, runestrlen(e->name)) == TRUE)
			return e;
	return nil;
}

int
isexecc(int c)
{
	if(isalnum(c))
		return 1;
	return c=='<' || c=='|' || c=='>';
}

void
execute(Text *t, uint aq0, uint aq1, Text *)
{
	uint q0, q1;
	Rune *r, *s;
	Exectab *e;
	int c, n;

	q0 = aq0;
	q1 = aq1;
	if(q1 == q0){	/* expand to find word (actually file name) */
		/* if in selection, choose selection */
		if(t->q1>t->q0 && t->q0<=q0 && q0<=t->q1){
			q0 = t->q0;
			q1 = t->q1;
		}else{
			while(q1<t->rs.nr && isexecc(c=t->rs.r[q1]) && c!=':')
				q1++;
			while(q0>0 && isexecc(c=t->rs.r[q0-1]) && c!=':')
				q0--;
			if(q1 == q0)
				return;
		}
	}
	r = runemalloc(q1-q0);
	runemove(r, t->rs.r+q0, q1-q0);
	e = lookup(r, q1-q0);
	if(e){
		s = skipbl(r, q1-q0, &n);
		s = findbl(s, n, &n);
		s = skipbl(s, n, &n);
		(*e->fn)(t, seltext, e->flag1, e->flag2, s, n);
	}
	free(r);
}

void
newcol(Text *et, Text *, int, int, Rune *, int)
{
	Column *c;

	c = rowadd(et->row, nil, -1);
	if(c)
		winsettag(coladd(c, nil, nil, -1));
}

void
delcol(Text *t, Text *, int, int, Rune *, int)
{
	Column *c;

	c = t->col;
	if(c==nil || colclean(c)==0)
		return;

	rowclose(c->row, c, TRUE);
}

void
del(Text *et, Text *, int flag1, int, Rune *, int)
{
	if(et->w==nil)
		return;

	if(flag1 || winclean(et->w, FALSE))
		colclose(et->col, et->w, TRUE);
}

void
sort(Text *et, Text *, int, int, Rune *, int)
{
	if(et->col)
		colsort(et->col);
}

void
exit(Text *, Text *, int, int, Rune *, int)
{
	sendul(cexit, 0);
	threadexits(nil);
}

void
debug(Text *, Text *, int, int, Rune *, int)
{
	Column *c;
	int i, j;

	for(j=0; j<row.ncol; j++){
		c = row.col[j];
		for(i=0; i<c->nw; i++){
			fprint(2, "Col: %d; Win: %d\n", j, i);
			windebug(c->w[i]);
		}
	}
}

void
stop(Text *t, Text *, int, int, Rune *, int)
{
	if(t==nil || t->w==nil)
		return;

	pageabort(&t->w->page);
}

void
get(Text *t, Text *, int, int, Rune *, int)
{
	Window *w;
	int dohist;

	if(t==nil || t->w==nil)
		return;
	w = t->w;
	if(w->url.rs.nr == 0)
		return;

	dohist = FALSE;
	if(w->page.url==nil || runestreq(w->page.url->act, w->url.rs)==FALSE)
		dohist = TRUE;

	pageget(&w->page, &w->url.rs, nil, HGet, dohist);
}

void
go(Text *et, Text *t, int isnext, int, Rune *, int)
{
	if(et!=nil && et->w!=nil)
		t = et;
	if(t==nil || t->w==nil)
		return;

	wingohist(t->w, isnext);
}

void
cut(Text *, Text *t, int dosnarf, int docut, Rune *, int)
{
	Runestr rs;
	uint u;

	if(selpage){
		if(dosnarf && !docut && !eqpt(selpage->top, selpage->bot))
			pagesnarf(selpage);
		return;
	}
	if(t==nil){
		/* can only happen if seltext == nil */
		return;
	}
	if(t->q0 > t->q1){
		u = t->q0;
		t->q0 = t->q1;
		t->q1 =u;
	}

	if(t->q0 == t->q1)
		return;

	if(dosnarf){
		rs.nr = t->q1-t->q0;
		rs.r = runemalloc(rs.nr);
		runemove(rs.r, t->rs.r+t->q0, rs.nr);
		putsnarf(&rs);
		closerunestr(&rs);
	}
	if(docut){
		textdelete(t, t->q0, t->q1);
		textsetselect(t, t->q0, t->q0);
		if(t->w)
			textscrdraw(t);
	}else if(dosnarf)	/* Snarf command */
		argtext = t;
}

void
paste(Text *, Text *t, int selectall, int, Rune *, int)
{
	Runestr rs;
	uint q1;

	if(t == nil)
		return;

	getsnarf(&rs);
	if(rs.nr == 0)
		return;

	cut(t, t, FALSE, TRUE, nil, 0);
	textinsert(t, t->q0, rs.r, rs.nr);
	q1 = t->q0+rs.nr;
	if(selectall)
		textsetselect(t, t->q0, q1);
	else
		textsetselect(t, q1, q1);
	if(t->w)
		textscrdraw(t);

	closerunestr(&rs);
}

typedef	struct	Expand Expand;

struct Expand
{
	uint	q0;
	uint	q1;
	Rune	*name;
	int	nname;
	int	jump;
	union{
		Text	*at;
		Rune	*ar;
	};
	int	(*agetc)(void*, uint);
	int	a0;
	int	a1;
};

int
expand(Text *t, uint q0, uint q1, Expand *e)
{
	memset(e, 0, sizeof *e);

	/* if in selection, choose selection */
	e->jump = TRUE;
	if(q1==q0 && t->q1>t->q0 && t->q0<=q0 && q0<=t->q1){
		q0 = t->q0;
		q1 = t->q1;
		if(t->what == Tag)
			e->jump = FALSE;
	}
	if(q0 == q1){
		while(q1<t->rs.nr && isalnum(t->rs.r[q1]))
			q1++;
		while(q0>0 && isalnum(t->rs.r[q0-1]))
			q0--;
	}
	e->q0 = q0;
	e->q1 = q1;
	return q1 > q0;
}

void
look3(Text *t, uint q0, uint q1)
{
	Expand e;
	Text *ct;
	Runestr rs;
	char buf[32];
	Rune *r, c;
	uint p;
	int n;

	ct = seltext;
	if(ct == nil)
		ct = seltext = t;
	if(expand(t, q0, q1, &e) == FALSE)
		return;
	if(plumbsendfd >= 0){
		/* send whitespace-delimited word to plumber */
		buf[0] = '\0';
		if(q1 == q0){
			if(t->q1>t->q0 && t->q0<=q0 && q0<=t->q1){
				q0 = t->q0;
				q1 = t->q1;
			}else{
				p = q0;
				while(q0>0 && (c=t->rs.r[q0-1])!=L' ' && c!=L'\t' && c!=L'\n')
					q0--;
				while(q1<t->rs.nr && (c=t->rs.r[q1])!=L' ' && c!=L'\t' && c!=L'\n')
					q1++;
				if(q1 == q0)
					return;
				sprint(buf, "click=%d", p-q0);
			}
		}
		rs.r = runemalloc(q1-q0);
		runemove(rs.r, t->rs.r+q0, q1-q0);
		rs.nr = q1-q0;
		if(plumbrunestr(&rs, buf) >= 0){
			closerunestr(&rs);
			return;
		}
		/* plumber failed to match; fall through */
	}
	if(t == ct)
		textsetselect(ct, e.q1, e.q1);
	n = e.q1 - e.q0;
	r = runemalloc(n);
	runemove(r, t->rs.r+e.q0, n);
	if(search(ct, r, n) && e.jump)
		moveto(mousectl, addpt(frptofchar(ct, ct->p0), Pt(4, ct->font->height-4)));

	free(r);
}

int
search(Text *ct, Rune *r, uint n)
{
	uint q, nb, maxn;
	int around;
	Rune *s, *b, *c;

	if(n==0 || n>ct->rs.nr || 2*n>RBUFSIZE)
		return FALSE;

	maxn = max(n*2, RBUFSIZE);
	s = runemalloc(RBUFSIZE);
	b = s;
	nb = 0;
	b[nb] = 0;
	around = 0;
	q = ct->q1;
	for(;;){
		if(q >= ct->rs.nr){
			q = 0;
			around = 1;
			nb = 0;
			b[nb] = 0;
		}
		if(nb > 0){
			c = runestrchr(b, r[0]);
			if(c == nil){
				q += nb;
				nb = 0;
				b[nb] = 0;
				if(around && q>=ct->q1)
					break;
				continue;
			}
			q += (c-b);
			nb -= (c-b);
			b = c;
		}
		/* reload if buffer covers neither string nor rest of file */
		if(nb<n && nb!=ct->rs.nr-q){
			nb = ct->rs.nr-q;
			if(nb >= maxn)
				nb = maxn-1;
			runemove(s, ct->rs.r+q, nb);
			b = s;
			b[nb] = '\0';
		}
		/* this runeeq is fishy but the null at b[nb] makes it safe */
		if(runeeq(b, n, r, n) == TRUE){
			if(ct->w)
				textshow(ct, q, q+n, 1);
			else{
				ct->q0 = q;
				ct->q1 = q+n;
			}
			seltext = ct;
			free(s);
			return TRUE;
		}
		if(around && q>=ct->q1)
			break;
		--nb;
		b++;
		q++;
	}
	free(s);
	return FALSE;
}

Window*
lookpage(Rune *s, int n)
{
	int i, j;
	Window *w;
	Column *c;
	Page *p;

	/* avoid terminal slash on directories */
	if(n>1 && s[n-1] == '/')
		--n;
	for(j=0; j<row.ncol; j++){
		c = row.col[j];
		for(i=0; i<c->nw; i++){
			w = c->w[i];
			p = &w->page;
			if(p->url && runeeq(p->url->src.r, p->url->src.nr, s, n))
				if(w->col != nil)
					return w;
		}
	}
	return nil;
}

Window *
openpage(Page *p, Runestr *rs)
{
	Window *w;

	if(!validurl(rs->r))
		return nil;

	w = lookpage(rs->r, rs->nr);
	if(w){
		p = &w->page;
		if(!p->col->safe && Dy(p->r)==0) /* window is obscured by full-column window */
			colgrow(p->col, p->col->w[0], 1);
	}else{
		w = makenewwindow(p);
		winsettag(w);
		pageget(&w->page, rs, nil, HGet, TRUE);
	}
	return w;
}

void
plumblook(Plumbmsg *m)
{
	Runestr rs;

	if(m->ndata >= BUFSIZE){
		fprint(2, "insanely long file name (%d bytes) in plumb message (%.32s...)\n", m->ndata, m->data);
		return;
	}
	if(m->data[0] == '\0')
		return;

	bytetorunestr(m->data, &rs);
	openpage(nil, &rs);
	closerunestr(&rs);
}

void
new(Text *et, Text *, int, int, Rune *, int)
{
	if(et->col != nil)
		winsettag(coladd(et->col, nil, nil, -1));
}

void
duckduckgo(Text *, Text *, int, int, Rune *arg, int narg)
{
	Runestr rs;
	Rune *s;

	s = ucvt(arg);
	rs.r = runesmprint("http://lite.duckduckgo.com/lite/?q=%.*S", narg, s);
	rs.nr = runestrlen(rs.r);
	openpage(nil, &rs);
	free(s);
	closerunestr(&rs);
}
