#include <u.h>
#include <libc.h>
#include "plumb.h"

int
plumbopen(char *name, int omode)
{
	int fd, f;
	char *s, *plumber;
	char buf[128], err[ERRMAX];

	if(name[0] == '/')
		return open(name, omode);
		
	/* find elusive plumber */
	if(access("/mnt/plumb/send", AWRITE) >= 0)
		plumber = "/mnt/plumb";
	else if(access("/mnt/term/mnt/plumb/send", AWRITE) >= 0)
		plumber = "/mnt/term/mnt/plumb";
	else{
		/* last resort: try mounting service */
		plumber = "/mnt/plumb";
		s = getenv("plumbsrv");
		if(s == nil)
			return -1;
		f = open(s, ORDWR);
		free(s);
		if(f < 0)
			return -1;
		if(mount(f, -1, "/mnt/plumb", MREPL, "") < 0){
			close(f);
			return -1;
		}
		if(access("/mnt/plumb/send", AWRITE) < 0)
			return -1;
	}

	snprint(buf, sizeof buf, "%s/%s", plumber, name);
	fd = open(buf, omode);
	if(fd >= 0)
		return fd;

	/* try creating port; used by non-standard plumb implementations */
	rerrstr(err, sizeof err);
	fd = create(buf, omode, 0600);
	if(fd >= 0)
		return fd;
	errstr(err, sizeof err);

	return -1;
}

static int
Strlen(char *s)
{
	if(s == nil)
		return 0;
	return strlen(s);
}

static char*
Strcpy(char *s, char *t)
{
	if(t == nil)
		return s;
	return strcpy(s, t) + strlen(t);
}

/* quote attribute value, if necessary */
static char*
quote(char *s, char *buf, char *bufe)
{
	char *t;
	int c;

	if(s == nil){
		buf[0] = '\0';
		return buf;
	}
	if(strpbrk(s, " '=\t") == nil)
		return s;
	t = buf;
	*t++ = '\'';
	while(t < bufe-2){
		c = *s++;
		if(c == '\0')
			break;
		*t++ = c;
		if(c == '\'')
			*t++ = c;
	}
	*t++ = '\'';
	*t = '\0';
	return buf;
}

char*
plumbpackattr(Plumbattr *attr)
{
	int n;
	Plumbattr *a;
	char *s, *t, *buf, *bufe;

	if(attr == nil)
		return nil;
	if((buf = malloc(4096)) == nil)
		return nil;
	bufe = buf + 4096;
	n = 0;
	for(a=attr; a!=nil; a=a->next)
		n += Strlen(a->name) + 1 + Strlen(quote(a->value, buf, bufe)) + 1;
	s = malloc(n);
	if(s == nil) {
		free(buf);
		return nil;
	}
	t = s;
	*t = '\0';
	for(a=attr; a!=nil; a=a->next){
		if(t != s)
			*t++ = ' ';
		strcpy(t, a->name);
		strcat(t, "=");
		strcat(t, quote(a->value, buf, bufe));
		t += strlen(t);
	}
	if(t > s+n)
		abort();
	free(buf);
	return s;
}

char*
plumblookup(Plumbattr *attr, char *name)
{
	while(attr){
		if(strcmp(attr->name, name) == 0)
			return attr->value;
		attr = attr->next;
	}
	return nil;
}

char*
plumbpack(Plumbmsg *m, int *np)
{
	int n, ndata;
	char *buf, *p, *attr;

	ndata = m->ndata;
	if(ndata < 0)
		ndata = Strlen(m->data);
	attr = plumbpackattr(m->attr);
	n = Strlen(m->src)+1 + Strlen(m->dst)+1 + Strlen(m->wdir)+1 +
		Strlen(m->type)+1 + Strlen(attr)+1 + 16 + ndata;
	buf = malloc(n+1);	/* +1 for '\0' */
	if(buf == nil){
		free(attr);
		return nil;
	}
	p = Strcpy(buf, m->src);
	*p++ = '\n';
	p = Strcpy(p, m->dst);
	*p++ = '\n';
	p = Strcpy(p, m->wdir);
	*p++ = '\n';
	p = Strcpy(p, m->type);
	*p++ = '\n';
	p = Strcpy(p, attr);
	*p++ = '\n';
	p += sprint(p, "%d\n", ndata);
	memmove(p, m->data, ndata);
	*np = (p-buf)+ndata;
	buf[*np] = '\0';	/* null terminate just in case */
	if(*np >= n+1)
		abort();
	free(attr);
	return buf;
}

int
plumbsend(int fd, Plumbmsg *m)
{
	char *buf;
	int n;

	buf = plumbpack(m, &n);
	if(buf == nil)
		return -1;
	n = write(fd, buf, n);
	free(buf);
	return n;
}

static int
plumbline(char **linep, char *buf, int i, int n, int *bad)
{
	int starti;
	char *p;

	starti = i;
	while(i<n && buf[i]!='\n')
		i++;
	if(i == n)
		*bad = 1;
	else{
		p = malloc((i-starti) + 1);
		if(p == nil)
			*bad = 1;
		else{
			memmove(p, buf+starti, i-starti);
			p[i-starti] = '\0';
		}
		*linep = p;
		i++;
	}
	return i;
}

void
plumbfree(Plumbmsg *m)
{
	Plumbattr *a, *next;

	free(m->src);
	free(m->dst);
	free(m->wdir);
	free(m->type);
	for(a=m->attr; a!=nil; a=next){
		next = a->next;
		free(a->name);
		free(a->value);
		free(a);
	}
	free(m->data);
	free(m);
}

Plumbattr*
plumbunpackattr(char *p)
{
	Plumbattr *attr, *prev, *a;
	char *q, *v, *buf, *bufe;
	int c, quoting;

	buf = malloc(4096);
	if(buf == nil)
		return nil;
	bufe = buf + 4096;
	attr = prev = nil;
	while(*p!='\0' && *p!='\n'){
		while(*p==' ' || *p=='\t')
			p++;
		if(*p == '\0')
			break;
		for(q=p; *q!='\0' && *q!='\n' && *q!=' ' && *q!='\t'; q++)
			if(*q == '=')
				break;
		if(*q != '=')
			break;	/* malformed attribute */
		a = malloc(sizeof(Plumbattr));
		if(a == nil)
			break;
		a->name = malloc(q-p+1);
		if(a->name == nil){
			free(a);
			break;
		}
		memmove(a->name, p, q-p);
		a->name[q-p] = '\0';
		/* process quotes in value */
		q++;	/* skip '=' */
		v = buf;
		quoting = 0;
		while(*q!='\0' && *q!='\n'){
			if(v >= bufe)
				break;
			c = *q++;
			if(quoting){
				if(c == '\''){
					if(*q == '\'')
						q++;
					else{
						quoting = 0;
						continue;
					}
				}
			}else{
				if(c==' ' || c=='\t')
					break;
				if(c == '\''){
					quoting = 1;
					continue;
				}
			}
			*v++ = c;
		}
		a->value = malloc(v-buf+1);
		if(a->value == nil){
			free(a->name);
			free(a);
			break;
		}
		memmove(a->value, buf, v-buf);
		a->value[v-buf] = '\0';
		a->next = nil;
		if(prev == nil)
			attr = a;
		else
			prev->next = a;
		prev = a;
		p = q;
	}
	free(buf);
	return attr;
}

Plumbattr*
plumbaddattr(Plumbattr *attr, Plumbattr *new)
{
	Plumbattr *l;

	l = attr;
	if(l == nil)
		return new;
	while(l->next != nil)
		l = l->next;
	l->next = new;
	return attr;
}

Plumbattr*
plumbdelattr(Plumbattr *attr, char *name)
{
	Plumbattr *l, *prev;

	prev = nil;
	for(l=attr; l!=nil; l=l->next){
		if(strcmp(name, l->name) == 0)
			break;
		prev = l;
	}
	if(l == nil)
		return nil;
	if(prev)
		prev->next = l->next;
	else
		attr = l->next;
	free(l->name);
	free(l->value);
	free(l);
	return attr;
}

Plumbmsg*
plumbunpackpartial(char *buf, int n, int *morep)
{
	Plumbmsg *m;
	int i, bad;
	char *ntext, *attr;

	m = malloc(sizeof(Plumbmsg));
	if(m == nil)
		return nil;
	memset(m, 0, sizeof(Plumbmsg));
	if(morep != nil)
		*morep = 0;
	bad = 0;
	i = plumbline(&m->src, buf, 0, n, &bad);
	i = plumbline(&m->dst, buf, i, n, &bad);
	i = plumbline(&m->wdir, buf, i, n, &bad);
	i = plumbline(&m->type, buf, i, n, &bad);
	i = plumbline(&attr, buf, i, n, &bad);
	i = plumbline(&ntext, buf, i, n, &bad);
	if(bad){
		plumbfree(m);
		return nil;
	}
	m->attr = plumbunpackattr(attr);
	free(attr);
	m->ndata = atoi(ntext);
	if(m->ndata != n-i){
		bad = 1;
		if(morep!=nil && m->ndata>n-i)
			*morep = m->ndata - (n-i);
	}
	free(ntext);
	if(!bad){
		m->data = malloc(n-i+1);	/* +1 for '\0' */
		if(m->data == nil)
			bad = 1;
		else{
			memmove(m->data, buf+i, m->ndata);
			m->ndata = n-i;
			/* null-terminate in case it's text */
			m->data[m->ndata] = '\0';
		}
	}
	if(bad){
		plumbfree(m);
		m = nil;
	}
	return m;
}

Plumbmsg*
plumbunpack(char *buf, int n)
{
	return plumbunpackpartial(buf, n, nil);
}

Plumbmsg*
plumbrecv(int fd)
{
	char *buf;
	Plumbmsg *m;
	int n, more;

	buf = malloc(8192);
	if(buf == nil)
		return nil;
	n = read(fd, buf, 8192);
	m = nil;
	if(n > 0){
		m = plumbunpackpartial(buf, n, &more);
		if(m==nil && more>0){
			/* we now know how many more bytes to read for complete message */
			buf = realloc(buf, n+more);
			if(buf == nil)
				return nil;
			if(readn(fd, buf+n, more) == more)
				m = plumbunpackpartial(buf, n+more, nil);
		}
	}
	free(buf);
	return m;
}
