#include <u.h>
#include <libc.h>
#include <bio.h>
#include <regexp.h>
#include <thread.h>
#include <plumb.h>
#include "plumber.h"

static char*
nonnil(char *s)
{
	if(s == nil)
		return "";
	return s;
}

int
verbis(int obj, Plumbmsg *m, Rule *r)
{
	switch(obj){
	default:
		fprint(2, "unimplemented 'is' object %d\n", obj);
		break;
	case OData:
		return strcmp(m->data, r->qarg) == 0;
	case ODst:
		return strcmp(m->dst, r->qarg) == 0;
	case OType:
		return strcmp(m->type, r->qarg) == 0;
	case OWdir:
		return strcmp(m->wdir, r->qarg) == 0;
	case OSrc:
		return strcmp(m->src, r->qarg) == 0;
	}
	return 0;
}

static void
setvar(Resub rs[10], char *match[10])
{
	int i, n;

	for(i=0; i<10; i++){
		free(match[i]);
		match[i] = nil;
	}
	for(i=0; i<10 && rs[i].sp!=nil; i++){
		n = rs[i].ep-rs[i].sp;
		match[i] = emalloc(n+1);
		memmove(match[i], rs[i].sp, n);
		match[i][n] = '\0';
	}
}

int
clickmatch(Reprog *re, char *text, Resub rs[10], int click)
{
	char *clickp;
	int i, w;
	Rune r;

	/* click is in characters, not bytes */
	for(i=0; i<click && text[i]!='\0'; i+=w)
		w = chartorune(&r, text+i);
	clickp = text+i;
	for(i=0; i<=click; i++){
		memset(rs, 0, 10*sizeof(Resub));
		if(regexec(re, text+i, rs, 10))
			if(rs[0].sp<=clickp && clickp<=rs[0].ep)
				return 1;
	}
	return 0;
}

int
verbmatches(int obj, Plumbmsg *m, Rule *r, Exec *e)
{
	Resub rs[10];
	char *clickval, *alltext;
	int p0, p1, ntext;

	memset(rs, 0, sizeof rs);
	ntext = -1;
	switch(obj){
	default:
		fprint(2, "unimplemented 'matches' object %d\n", obj);
		break;
	case OData:
		clickval = plumblookup(m->attr, "click");
		if(clickval == nil){
			alltext = m->data;
			ntext = m->ndata;
			goto caseAlltext;
		}
		if(!clickmatch(r->regex, m->data, rs, atoi(clickval)))
			break;
		p0 = rs[0].sp - m->data;
		p1 = rs[0].ep - m->data;
		if(e->p0 >=0 && !(p0==e->p0 && p1==e->p1))
			break;
		e->clearclick = 1;
		e->setdata = 1;
		e->p0 = p0;
		e->p1 = p1;
		setvar(rs, e->match);
		return 1;
	case ODst:
		alltext = m->dst;
		goto caseAlltext;
	case OType:
		alltext = m->type;
		goto caseAlltext;
	case OWdir:
		alltext = m->wdir;
		goto caseAlltext;
	case OSrc:
		alltext = m->src;
		/* fall through */
	caseAlltext:
		/* must match full text */
		if(ntext < 0)
			ntext = strlen(alltext);
		if(!regexec(r->regex, alltext, rs, 10) || rs[0].sp!=alltext || rs[0].ep!=alltext+ntext)
			break;
		setvar(rs, e->match);
		return 1;
	}
	return 0;
}

int
isfile(char *file, ulong maskon, ulong maskoff)
{
	Dir *d;
	int mode;

	d = dirstat(file);
	if(d == nil)
		return 0;
	mode = d->mode;
	free(d);
	if((mode & maskon) == 0)
		return 0;
	if(mode & maskoff)
		return 0;
	return 1;
}

char*
absolute(char *dir, char *file)
{
	char *p;

	if(file[0] == '/')
		return estrdup(file);
	p = emalloc(strlen(dir)+1+strlen(file)+1);
	sprint(p, "%s/%s", dir, file);
	return cleanname(p);
}

int
verbisfile(int obj, Plumbmsg *m, Rule *r, Exec *e, ulong maskon, ulong maskoff, char **var)
{
	char *file;

	switch(obj){
	default:
		fprint(2, "unimplemented 'isfile' object %d\n", obj);
		break;
	case OArg:
		file = absolute(m->wdir, expand(e, r->arg, nil));
		if(isfile(file, maskon, maskoff)){
			*var = file;
			return 1;
		}
		free(file);
		break;
	case OData:
	case OWdir:
		file = absolute(m->wdir, obj==OData? m->data : m->wdir);
		if(isfile(file, maskon, maskoff)){
			*var = file;
			return 1;
		}
		free(file);
		break;
	}
	return 0;
}

int
verbset(int obj, Plumbmsg *m, Rule *r, Exec *e)
{
	char *new;

	switch(obj){
	default:
		fprint(2, "unimplemented 'is' object %d\n", obj);
		break;
	case OData:
		new = estrdup(expand(e, r->arg, nil));
		m->ndata = strlen(new);
		free(m->data);
		m->data = new;
		e->p0 = -1;
		e->p1 = -1;
		e->setdata = 0;
		return 1;
	case ODst:
		new = estrdup(expand(e, r->arg, nil));
		free(m->dst);
		m->dst = new;
		return 1;
	case OType:
		new = estrdup(expand(e, r->arg, nil));
		free(m->type);
		m->type = new;
		return 1;
	case OWdir:
		new = estrdup(expand(e, r->arg, nil));
		free(m->wdir);
		m->wdir = new;
		return 1;
	case OSrc:
		new = estrdup(expand(e, r->arg, nil));
		free(m->src);
		m->src = new;
		return 1;
	}
	return 0;
}

int
verbadd(int obj, Plumbmsg *m, Rule *r, Exec *e)
{
	switch(obj){
	default:
		fprint(2, "unimplemented 'add' object %d\n", obj);
		break;
	case OAttr:
		m->attr = plumbaddattr(m->attr, plumbunpackattr(expand(e, r->arg, nil)));
		return 1;
	}
	return 0;
}

int
verbdelete(int obj, Plumbmsg *m, Rule *r, Exec *e)
{
	char *a;

	switch(obj){
	default:
		fprint(2, "unimplemented 'delete' object %d\n", obj);
		break;
	case OAttr:
		a = expand(e, r->arg, nil);
		if(plumblookup(m->attr, a) == nil)
			break;
		m->attr = plumbdelattr(m->attr, a);
		return 1;
	}
	return 0;
}

int
matchpat(Plumbmsg *m, Exec *e, Rule *r)
{
	switch(r->verb){
	default:
		fprint(2, "unimplemented verb %d\n", r->verb);
		break;
	case VAdd:
		return verbadd(r->obj, m, r, e);
	case VDelete:
		return verbdelete(r->obj, m, r, e);
	case VIs:
		return verbis(r->obj, m, r);
	case VIsdir:
		return verbisfile(r->obj, m, r, e, DMDIR, 0, &e->dir);
	case VIsfile:
		return verbisfile(r->obj, m, r, e, ~DMDIR, DMDIR, &e->file);
	case VMatches:
		return verbmatches(r->obj, m, r, e);
	case VSet:
		verbset(r->obj, m, r, e);
		return 1;
	}
	return 0;
}

void
freeexec(Exec *exec)
{
	int i;

	if(exec == nil)
		return;
	free(exec->dir);
	free(exec->file);
	for(i=0; i<10; i++)
		free(exec->match[i]);
	free(exec);
}

Exec*
newexec(Plumbmsg *m)
{
	Exec *exec;
	
	exec = emalloc(sizeof(Exec));
	exec->msg = m;
	exec->p0 = -1;
	exec->p1 = -1;
	return exec;
}

void
rewrite(Plumbmsg *m, Exec *e)
{
	Plumbattr *a, *prev;

	if(e->clearclick){
		prev = nil;
		for(a=m->attr; a!=nil; a=a->next){
			if(strcmp(a->name, "click") == 0){
				if(prev == nil)
					m->attr = a->next;
				else
					prev->next = a->next;
				free(a->name);
				free(a->value);	
				free(a);
				break;
			}
			prev = a;
		}
		if(e->setdata){
			free(m->data);
			m->data = estrdup(expand(e, "$0", nil));
			m->ndata = strlen(m->data);
		}
	}
}

char**
buildargv(char *s, Exec *e)
{
	char **av;
	int ac;

	ac = 0;
	av = nil;
	for(;;){
		av = erealloc(av, (ac+1) * sizeof(char*));
		av[ac] = nil;
		while(*s==' ' || *s=='\t')
			s++;
		if(*s == '\0')
			break;
		av[ac++] = estrdup(expand(e, s, &s));
	}
	return av;
}

Exec*
matchruleset(Plumbmsg *m, Ruleset *rs)
{
	int i;
	Exec *exec;

	if(m->dst!=nil && m->dst[0]!='\0' && rs->port!=nil && strcmp(m->dst, rs->port)!=0)
		return nil;
	exec = newexec(m);
	for(i=0; i<rs->npat; i++)
		if(!matchpat(m, exec, rs->pat[i])){
			freeexec(exec);
			return nil;
		}
	if(rs->port!=nil && (m->dst==nil || m->dst[0]=='\0')){
		free(m->dst);
		m->dst = estrdup(rs->port);
	}
	rewrite(m, exec);
	return exec;
}

enum
{
	NARGS		= 100,
	NARGCHAR	= 8*1024,
	EXECSTACK 	= 8192+(NARGS+1)*sizeof(char*)+NARGCHAR
};

/* copy argv to stack and free the incoming strings, so we don't leak argument vectors */
void
stackargv(char **inargv, char *argv[NARGS+1], char args[NARGCHAR])
{
	int i, n;
	char *s, *a;

	s = args;
	for(i=0; i<NARGS; i++){
		a = inargv[i];
		if(a == nil)
			break;
		n = strlen(a)+1;
		if((s-args)+n >= NARGCHAR)	/* too many characters */
			break;
		argv[i] = s;
		memmove(s, a, n);
		s += n;
		free(a);
	}
	argv[i] = nil;
}


void
execproc(void *v)
{
	char **av;
	char buf[1024], *args[NARGS+1], argc[NARGCHAR];

	rfork(RFFDG);
	close(0);
	open("/dev/null", OREAD);
	av = v;
	stackargv(av, args, argc);
	free(av);
	procexec(nil, args[0], args);
	if(args[0][0]!='/' && strncmp(args[0], "./", 2)!=0 && strncmp(args[0], "../", 3)!=0)
		snprint(buf, sizeof buf, "/bin/%s", args[0]);
	procexec(nil, buf, args);
	threadexits("can't exec");
}

char*
startup(Ruleset *rs, Exec *e)
{
	char **argv;
	int i;

	if(rs != nil)
		for(i=0; i<rs->nact; i++){
			if(rs->act[i]->verb == VStart)
				goto Found;
			if(rs->act[i]->verb == VClient){
				if(e->msg->dst==nil || e->msg->dst[0]=='\0')
					return "no port for \"client\" rule";
				e->holdforclient = 1;
				goto Found;
			}
		}
	return "no start action for plumb message";

Found:
	argv = buildargv(rs->act[i]->arg, e);
	if(argv[0] == nil)
		return "empty argument list";
	proccreate(execproc, argv, EXECSTACK);
	return nil;
}
