#include <u.h>
#include <libc.h>
#include <thread.h>
#include <9pclient.h>
#include <acme.h>

static CFsys *acmefs;
static Win *windows;
static Win *last;

static void
mountacme(void)
{
	if(acmefs == nil){
		acmefs = nsmount("acme", nil);
		if(acmefs == nil)
			sysfatal("cannot mount acme: %r");
	}
}

Win*
newwin(void)
{
	CFid *fid;
	char buf[100];
	int id, n;

	mountacme();
	fid = fsopen(acmefs, "new/ctl", ORDWR);
	if(fid == nil)
		sysfatal("open new/ctl: %r");
	n = fsread(fid, buf, sizeof buf-1);
	if(n <= 0)
		sysfatal("read new/ctl: %r");
	buf[n] = 0;
	id = atoi(buf);
	if(id == 0)
		sysfatal("read new/ctl: malformed message: %s", buf);

	return openwin(id, fid);
}

Win*
openwin(int id, CFid *ctl)
{
	char buf[100];
	Win *w;

	mountacme();
	if(ctl == nil){
		snprint(buf, sizeof buf, "%d/ctl", id);
		if((ctl = fsopen(acmefs, buf, ORDWR)) == nil)
			sysfatal("open %s: %r", buf);
	}
	w = emalloc(sizeof *w);
	w->id = id;
	w->ctl = ctl;
	w->next = nil;
	w->prev = last;
	if(last)
		last->next = w;
	else
		windows = w;
	last = w;
	return w;
}

void
winclosefiles(Win *w)
{
	if(w->ctl){
		fsclose(w->ctl);
		w->ctl = nil;
	}
	if(w->body){
		fsclose(w->body);
		w->body = nil;
	}
	if(w->addr){
		fsclose(w->addr);
		w->addr = nil;
	}
	if(w->tag){
		fsclose(w->tag);
		w->tag = nil;
	}
	if(w->event){
		fsclose(w->event);
		w->event = nil;
	}
	if(w->data){
		fsclose(w->data);
		w->data = nil;
	}
	if(w->xdata){
		fsclose(w->xdata);
		w->xdata = nil;
	}
}

void
winfree(Win *w)
{
	winclosefiles(w);
	if(w->c){
		chanfree(w->c);
		w->c = nil;
	}
	if(w->next)
		w->next->prev = w->prev;
	else
		last = w->prev;
	if(w->prev)
		w->prev->next = w->next;
	else
		windows = w->next;
	free(w);
}

void
windeleteall(void)
{
	Win *w, *next;

	for(w=windows; w; w=next){
		next = w->next;
		winctl(w, "delete");
	}
}

static CFid*
wfid(Win *w, char *name)
{
	char buf[100];
	CFid **fid;

	if(strcmp(name, "ctl") == 0)
		fid = &w->ctl;
	else if(strcmp(name, "body") == 0)
		fid = &w->body;
	else if(strcmp(name, "addr") == 0)
		fid = &w->addr;
	else if(strcmp(name, "tag") == 0)
		fid = &w->tag;
	else if(strcmp(name, "event") == 0)
		fid = &w->event;
	else if(strcmp(name, "data") == 0)
		fid = &w->data;
	else if(strcmp(name, "xdata") == 0)
		fid = &w->xdata;
	else{
		fid = 0;
		sysfatal("bad window file name %s", name);
	}

	if(*fid == nil){
		snprint(buf, sizeof buf, "acme/%d/%s", w->id, name);
		*fid = fsopen(acmefs, buf, ORDWR);
		if(*fid == nil)
			sysfatal("open %s: %r", buf);
	}
	return *fid;
}

int
winopenfd(Win *w, char *name, int mode)
{
	char buf[100];

	snprint(buf, sizeof buf, "%d/%s", w->id, name);
	return fsopenfd(acmefs, buf, mode);
}

int
winctl(Win *w, char *fmt, ...)
{
	char *s;
	va_list arg;
	CFid *fid;
	int n;

	va_start(arg, fmt);
	s = evsmprint(fmt, arg);
	va_end(arg);

	fid = wfid(w, "ctl");
	n = fspwrite(fid, s, strlen(s), 0);
	free(s);
	return n;
}

int
winname(Win *w, char *fmt, ...)
{
	char *s;
	va_list arg;
	int n;

	va_start(arg, fmt);
	s = evsmprint(fmt, arg);
	va_end(arg);

	n = winctl(w, "name %s\n", s);
	free(s);
	return n;
}

int
winprint(Win *w, char *name, char *fmt, ...)
{
	char *s;
	va_list arg;
	int n;

	va_start(arg, fmt);
	s = evsmprint(fmt, arg);
	va_end(arg);

	n = fswrite(wfid(w, name), s, strlen(s));
	free(s);
	return n;
}

int
winaddr(Win *w, char *fmt, ...)
{
	char *s;
	va_list arg;
	int n;

	va_start(arg, fmt);
	s = evsmprint(fmt, arg);
	va_end(arg);

	n = fswrite(wfid(w, "addr"), s, strlen(s));
	free(s);
	return n;
}

int
winreadaddr(Win *w, uint *q1)
{
	char buf[40], *p;
	uint q0;
	int n;

	n = fspread(wfid(w, "addr"), buf, sizeof buf-1, 0);
	if(n <= 0)
		return -1;
	buf[n] = 0;
	q0 = strtoul(buf, &p, 10);
	if(q1)
		*q1 = strtoul(p, nil, 10);
	return q0;
}

int
winread(Win *w, char *file, void *a, int n)
{
	return fsread(wfid(w, file), a, n);
}

int
winwrite(Win *w, char *file, void *a, int n)
{
	return fswrite(wfid(w, file), a, n);
}

char*
winmread(Win *w, char *file)
{
	char *buf;
	int n, tot, m;

	m = 128;
	buf = emalloc(m+1);
	tot = 0;
	while((n = fsread(wfid(w, file), buf+tot, m-tot)) > 0){
		tot += n;
		if(tot >= m){
			m += 128;
			buf = erealloc(buf, m+1);
		}
	}
	if(n < 0){
		free(buf);
		return nil;
	}
	buf[tot] = 0;
	return buf;
}

int
winseek(Win *w, char *file, int n, int off)
{
	return fsseek(wfid(w, file), n, off);
}

int
winwriteevent(Win *w, Event *e)
{
	char buf[100];

	snprint(buf, sizeof buf, "%c%c%d %d \n", e->c1, e->c2, e->q0, e->q1);
	return fswrite(wfid(w, "event"), buf, strlen(buf));
}

int
windel(Win *w, int sure)
{
	return winctl(w, sure ? "delete" : "del");
}

int
winfd(Win *w, char *name, int mode)
{
	char buf[100];

	snprint(buf, sizeof buf, "acme/%d/%s", w->id, name);
	return fsopenfd(acmefs, buf, mode);
}

static void
error(Win *w, char *msg)
{
	if(msg == nil)
		longjmp(w->jmp, 1);
	fprint(2, "%s: win%d: %s\n", argv0, w->id, msg);
	longjmp(w->jmp, 2);
}

static int
getec(Win *w, CFid *efd)
{
	if(w->nbuf <= 0){
		w->nbuf = fsread(efd, w->buf, sizeof w->buf);
		if(w->nbuf <= 0)
			error(w, nil);
		w->bufp = w->buf;
	}
	--w->nbuf;
	return *w->bufp++;
}

static int
geten(Win *w, CFid *efd)
{
	int n, c;

	n = 0;
	while('0'<=(c=getec(w,efd)) && c<='9')
		n = n*10+(c-'0');
	if(c != ' ')
		error(w, "event number syntax");
	return n;
}

static int
geter(Win *w, CFid *efd, char *buf, int *nb)
{
	Rune r;
	int n;

	r = getec(w, efd);
	buf[0] = r;
	n = 1;
	if(r < Runeself)
		goto Return;
	while(!fullrune(buf, n))
		buf[n++] = getec(w, efd);
	chartorune(&r, buf);
    Return:
	*nb = n;
	return r;
}

static void
gete(Win *w, CFid *efd, Event *e)
{
	int i, nb;

	e->c1 = getec(w, efd);
	e->c2 = getec(w, efd);
	e->q0 = geten(w, efd);
	e->q1 = geten(w, efd);
	e->flag = geten(w, efd);
	e->nr = geten(w, efd);
	if(e->nr > EVENTSIZE)
		error(w, "event string too long");
	e->nb = 0;
	for(i=0; i<e->nr; i++){
		/* e->r[i] = */ geter(w, efd, e->text+e->nb, &nb);
		e->nb += nb;
	}
/* 	e->r[e->nr] = 0; */
	e->text[e->nb] = 0;
	if(getec(w, efd) != '\n')
		error(w, "event syntax 2");
}

int
winreadevent(Win *w, Event *e)
{
	CFid *efd;
	int r;

	if((r = setjmp(w->jmp)) != 0){
		if(r == 1)
			return 0;
		return -1;
	}
	efd = wfid(w, "event");
	gete(w, efd, e);
	e->oq0 = e->q0;
	e->oq1 = e->q1;

	/* expansion */
	if(e->flag&2){
		gete(w, efd, &w->e2);
		if(e->q0==e->q1){
			w->e2.oq0 = e->q0;
			w->e2.oq1 = e->q1;
			w->e2.flag = e->flag;
			*e = w->e2;
		}
	}

	/* chorded argument */
	if(e->flag&8){
		gete(w, efd, &w->e3);	/* arg */
		gete(w, efd, &w->e4);	/* location */
		strcpy(e->arg, w->e3.text);
		strcpy(e->loc, w->e4.text);
	}

	return 1;
}

int
eventfmt(Fmt *fmt)
{
	Event *e;

	e = va_arg(fmt->args, Event*);
	return fmtprint(fmt, "%c%c %d %d %d %d %q", e->c1, e->c2, e->q0, e->q1, e->flag, e->nr, e->text);
}

void*
emalloc(uint n)
{
	void *v;

	v = mallocz(n, 1);
	if(v == nil)
		sysfatal("out of memory");
	return v;
}

void*
erealloc(void *v, uint n)
{
	v = realloc(v, n);
	if(v == nil)
		sysfatal("out of memory");
	return v;
}

char*
estrdup(char *s)
{
	s = strdup(s);
	if(s == nil)
		sysfatal("out of memory");
	return s;
}

char*
evsmprint(char *s, va_list v)
{
	s = vsmprint(s, v);
	if(s == nil)
		sysfatal("out of memory");
	return s;
}

int
pipewinto(Win *w, char *name, int errto, char *cmd, ...)
{
	va_list arg;
	char *p;
	int fd[3], pid;

	va_start(arg, cmd);
	p = evsmprint(cmd, arg);
	va_end(arg);
	fd[0] = winfd(w, name, OREAD);
	fd[1] = dup(errto, -1);
	fd[2] = dup(errto, -1);
	pid = threadspawnl(fd, "rc", "rc", "-c", p, 0);
	free(p);
	return pid;
}

int
pipetowin(Win *w, char *name, int errto, char *cmd, ...)
{
	va_list arg;
	char *p;
	int fd[3], pid;

	va_start(arg, cmd);
	p = evsmprint(cmd, arg);
	va_end(arg);
	fd[0] = open("/dev/null", OREAD);
	fd[1] = winfd(w, name, OWRITE);
	if(errto == 0)
		fd[2] = dup(fd[1], -1);
	else
		fd[2] = dup(errto, -1);
	pid = threadspawnl(fd, "rc", "rc", "-c", p, 0);
	free(p);
	return pid;
}

char*
sysrun(char *fmt, ...)
{
	static char buf[1025];
	char *cmd;
	va_list arg;
	int n, fd[3], p[2], tot;

#undef pipe
	if(pipe(p) < 0)
		sysfatal("pipe: %r");
	fd[0] = open("/dev/null", OREAD);
	fd[1] = p[1];
	fd[2] = dup(p[1], -1);

	va_start(arg, fmt);
	cmd = evsmprint(fmt, arg);
	va_end(arg);
	threadspawnl(fd, "rc", "rc", "-Ic", cmd, 0);

	tot = 0;
	while((n = read(p[0], buf+tot, sizeof buf-tot)) > 0)
		tot += n;
	close(p[0]);
	if(n < 0)
		return nil;
	free(cmd);
	if(tot == sizeof buf)
		tot--;
	buf[tot] = 0;
	while(tot > 0 && isspace(buf[tot-1]))
		tot--;
	buf[tot] = 0;
	if(tot == 0){
		werrstr("no output");
		return nil;
	}
	return buf;
}

static void
eventreader(void *v)
{
	Event e[2];
	Win *w;
	int i;

	w = v;
	i = 0;
	for(;;){
		if(winreadevent(w, &e[i]) <= 0)
			break;
		sendp(w->c, &e[i]);
		i = 1-i;	/* toggle */
	}
	sendp(w->c, nil);
	threadexits(nil);
}

Channel*
wineventchan(Win *w)
{
	if(w->c == nil){
		w->c = chancreate(sizeof(Event*), 0);
		threadcreate(eventreader, w, 32*1024);
	}
	return w->c;
}
