/*
 * Acme jukebox player.
 * Only talks to acme - external programs do all the hard work.
 *
 * The main window, named Juke/, is a play list.  Lines beginning with song/number
 * are treated as play list entries.  Juke runs {jukesongfile number} to find the song file name.
 * Then it runs {jukeplay file} to start playing.  Jukeplay is expected to write
 * 44.1kHz 16-bit stereo big-endian PCM to standard output.
 *
 * Right-clicking in any window creates a new window, containing either a song list
 * or a search.  Song lists and the main window are initialized with the output of 
 * {jukeget id window-name}.  Search windows are initialized with
 * {jukesearch id search-string}.  The id is the window id being operated on.
 * Put calls jukeput instead of jukeget.
 *
 * Middle-clicking in the play list window starts playing the clicked song.
 * Middle-clicking in the list and search windows queues the selected lines.
 */

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

extern int chatty9pclient;
int debug;
#define dprint if(debug)print
QLock audiolock;

enum {
	STACK = 128*1024,
	AudioBuf = 16384,	/* 100 ms */
	Vdelta = 3,
};

enum {
	Play,
	Next,
	Stop,
	Pause,
	Get,
	Put,
	Del,
	Delete,
	Debug,
	Vplus,
	Vminus,
	Mute,
	Dev,
	XXX
};

char *cmds[] = {
	"Play",
	"Next",
	"Stop",
	"Pause",
	"Get",
	"Put",
	"Del",
	"Delete",
	"Debug",
	"V+",
	"V-",
	"Mute",
	"Dev",
	nil
};

int		playwinchanged;
int		playerpid;			/* pid of current player (decoder) */
int		paused;			/* is output paused? */
Channel	*unpausechan;		/* chan[1](void*) - send nil here to unpause */
Win		*playwin;			/* playlist window */
Audio	*va;		/* owned by playlistthread */
Audio	*aa;		/* owned by audio proc */
char		*adev;	/* owned by playlistthread */

void		audioproc(void*);
void		donext(Win *w);
void		dopause(Win *w);
void		doplay(Win *w);
void		doresume(Win *w);
void		stopaudio(void);
char*	expandarg(Win *w, Event *e);
uint		findmarker(Win *w);
int		isjukepage(char *name);
int		listexec(Win *w, Event *e);
int		lookup(char *s, char **list);
void		navthread(void *v);
uint		nomarkers(Win *w, uint q);
int		playlistexec(Win *w, Event *e);
void		playlistthread(void *v);
void		playnext(Win *w, int isclick);
int		startsong(int n);

/*
 * look for s in list
 */
int
lookup(char *s, char **list)
{
	int i;

	for(i=0; list[i]; i++)
		if(strcmp(list[i], s) == 0)
			return i;
	return -1;
}

/*
 * watch the exiting children
 */
typedef struct Waitreq Waitreq;
struct Waitreq
{
	int pid;
	Channel *c;
};

Channel *twaitchan;	/* chan(Waitreq) */
Channel *playerwaitchan;	/* chan(Waitmsg*) */
void
waitthread(void *v)
{
	Alt a[3];
	Waitmsg *w, **wq;
	Waitreq *rq, r;
	int i, nrq, nwq;

	threadsetname("waitthread");
	a[0].c = threadwaitchan();
	a[0].v = &w;
	a[0].op = CHANRCV;
	a[1].c = twaitchan;
	a[1].v = &r;
	a[1].op = CHANRCV;
	a[2].op = CHANEND;

	nrq = 0;
	nwq = 0;
	rq = nil;
	wq = nil;
	dprint("wait: start\n");
	for(;;){
	cont2:;
		dprint("wait: alt\n");
		switch(alt(a)){
		case 0:
			dprint("wait: pid %d exited\n", w->pid);
			for(i=0; i<nrq; i++){
				if(rq[i].pid == w->pid){
					dprint("wait: match with rq chan %p\n", rq[i].c);
					sendp(rq[i].c, w);
					rq[i] = rq[--nrq];
					goto cont2;
				}
			}
			/*
			 * standing request for playerpid.
			 */
			if(playerpid == w->pid){
				sendp(playerwaitchan, w);
				goto cont2;
			}
			
			if(i == nrq){
				dprint("wait: queueing waitmsg\n");
				wq = erealloc(wq, (nwq+1)*sizeof(wq[0]));
				wq[nwq++] = w;
			}
			break;
		
		case 1:
			dprint("wait: req for pid %d chan %p\n", r.pid, r.c);
			for(i=0; i<nwq; i++){
				if(w->pid == r.pid){
					dprint("wait: match with waitmsg\n");
					sendp(r.c, w);
					wq[i] = wq[--nwq];
					goto cont2;
				}
			}
			if(i == nwq){
				dprint("wait: queueing req\n");
				rq = erealloc(rq, (nrq+1)*sizeof(rq[0]));
				rq[nrq] = r;
				dprint("wait: queueing req pid %d chan %p\n", rq[nrq].pid, rq[nrq].c);
				nrq++;
			}
			break;
		}
	}
}

Waitmsg*
twaitfor(int pid)
{
	Waitreq r;
	Waitmsg *w;
	
	r.pid = pid;
	r.c = chancreate(sizeof(Waitmsg*), 1);
	send(twaitchan, &r);
	w = recvp(r.c);
	chanfree(r.c);
	return w;
}

void
twait(int pid)
{
	free(twaitfor(pid));
}

void
wintop(Win *w)
{
	winaddr(w, "#0");
	winctl(w, "dot=addr");
	winctl(w, "show");
}
	
/*
 * manage the Juke/ playlist window.
 */
void
playlistthread(void *v)
{
	char *arg, buf[20], *ndev, *p;
	Alt a[3];
	Audio *nva;
	Win *w;
	Waitmsg *wm;
	Event *e;
	uint q, nq;
	int fd, lvol, rvol, muted;

	a[0].c = 0;
	a[0].v = &e;
	a[0].op = CHANRCV;
	
	a[1].c = playerwaitchan;
	a[1].v = &wm;
	a[1].op = CHANRCV;
	
	a[2].op = CHANEND;

	threadsetname("playlistthread");
	va = openaudio(adev, VOLUME);
	muted = 0;
	lvol = rvol = 50;
	playwinchanged = 1;
	w = newwin();

	snprint(buf, sizeof buf, "%d", w->id);
	if((fd = winopenfd(w, "errors", OWRITE)) >= 0){
		dup(fd, 1);
		dup(fd, 2);
	}
	playwin = w;
	winname(w, "Juke/");
	winctl(w, "dumpdir /");
	winctl(w, "dump Juke");
	winctl(w, "noscroll");
	winprint(w, "tag", "Put Play Pause Stop Next V- V+ ");
	winaddr(w, ",");
	twait(pipetowin(w, "data", 2, "jukeget '' | jukefmt %d", w->id));
	dprint("pl clean\n");
	winctl(w, "clean");

	a[0].c = wineventchan(w);

	/* if list has > in it, pick up there */
	if((q = findmarker(w)) != ~0){
		dprint("pl findmarker %d\n", a[0].op);
		winaddr(w, "#%ud,#%ud", q, q+1);
		dprint("pl findmarker %d\n", a[0].op);
		winwrite(w, "data", "", 0);
		dprint("pl findmarker %d\n", a[0].op);
		playnext(w, 1);
	}

	for(;;){
		dprint("pl alt %d\n", a[0].op);
		switch(alt(a)){
		case 0:	/* event */
			dprint("pl event %E\n", e);
			if(e == nil)
				goto out;
			if(e->c1=='M' || e->c1=='K' || e->c1=='E')
			if(e->c2=='D' || e->c2=='I')
				playwinchanged = 1;
			if(e->c1 != 'M')
				continue;
			switch(e->c2){
			case 'x':
			case 'X':
				dprint("x %s\n", e->text);
				if((p = strchr(e->text, ' ')) != nil){
					*p++ = 0;
					while(*p && *p == ' ')
						p++;
					strcpy(e->arg, p);
				}
				switch(lookup(e->text, cmds)){
				case Play:
					if(paused){
						paused = 0;
						nbsendp(unpausechan, 0);
						break;
					}
					/* start at dot and move back to beginning of line */
					nomarkers(w, 0);
					winctl(w, "addr=dot");
					q = winreadaddr(w, nil);
					winaddr(w, "#%ud-/^/");
					nq = winreadaddr(w, nil);
					if(nq > q){
						dprint("wrapped around\n");
						winaddr(w, "#0");
					}
					dprint("Play q=%ud nq=%ud now=%ud\n", q, nq, winreadaddr(w, nil));
					playnext(w, 0);
					break;
				case Next:
				case_Next:
					stopaudio();
					q = findmarker(w);
					q = nomarkers(w, q);
					if(q == ~0)
						break;
					if(winaddr(w, "#%ud+1", q) >= 0)
						playnext(w, 0);
					break;
				case Stop:
					stopaudio();
					nomarkers(w, 0);
					break;
				case Pause:
					paused = 1;	/* audioproc will notice */
					break;
				case Get:
					winaddr(w, ",");
					winwrite(w, "data", "", 0);
					twait(pipetowin(w, "body", 2, "jukeget '' | jukefmt %d", w->id));
					wintop(w);
					winctl(w, "clean");
					break;
				case Put:
					twait(pipewinto(w, "body", 2, "jukeput '' | jukefmt %d", w->id));
					winctl(w, "clean");
					break;
				case Del:
				case Delete:
					goto out;
				case Debug:
					debug = !debug;
					break;
				case Mute:
					if(va == nil)
						break;
					va->getvolume(va, "pcm", &lvol, &rvol);
					va->setvolume(va, "pcm", 0, 0);
					muted = 1;
					break;
				case Vplus:
					if(va == nil)
						break;
					if(muted){
						va->setvolume(va, "pcm", lvol, rvol);
						muted = 0;
						break;
					}
					if(va->getvolume(va, "pcm", &lvol, &rvol) < 0)
						break;
					lvol += Vdelta;
					rvol += Vdelta;
					if(lvol > 100)
						lvol = 100;
					if(rvol > 100)
						rvol = 100;
					va->setvolume(va, "pcm", lvol, rvol);
					break;
				case Vminus:
					if(va == nil)
						break;
					if(muted){
						va->setvolume(va, "pcm", lvol, rvol);
						muted = 0;
						break;
					}
					if(va->getvolume(va, "pcm", &lvol, &rvol) < 0)
						break;
					lvol -= Vdelta;
					rvol -= Vdelta;
					if(lvol < 0)
						lvol = 0;
					if(rvol < 0)
						rvol = 0;
					va->setvolume(va, "pcm", lvol, rvol);
					break;
				case Dev:
					if(e->arg[0] == 0){
						fprint(2, "Dev %s\n", adev && adev[0] ? adev : "local");
						break;
					}
					if(strcmp(e->arg, "local") == 0)
						ndev = nil;
					else
						ndev = estrdup(e->arg);
					fprint(2, "new Dev '%s'\n", ndev);
					nva = openaudio(ndev, VOLUME);
					if(nva == nil){
						fprint(2, "openaudio %s: %r\n", ndev);
						break;
					}
					adev = ndev;
					if(va)
						va->close(va);
					va = nva;
					break;
				default:
					if(playlistexec(w, e) < 0){
						dprint("winwriteevent %s\n", e->text);
						winwriteevent(w, e);
					}
					break;
				}
				break;
			case 'l':
			case 'L':
				if(e->text && e->text[0]==':'){
					winwriteevent(w, e);
					break;
				}
				arg = expandarg(w, e);
				if(arg)
					threadcreate(navthread, arg, STACK);
				break;
			}
			break;
		case 1:	/* wait chan - maybe song ended */
			dprint("pl waitchan %ld playerpid %d\n", wm->pid, playerpid);
			if(wm->pid == playerpid){
				playerpid = 0;
				free(wm);
				goto case_Next;
			}
			free(wm);
			break;
		}
	}
out:
	stopaudio();
	qlock(&audiolock);
	windeleteall();
	threadexitsall(nil);
}

/*
 * Stop the player, but leave the window alone
 */
void
stopaudio(void)
{
	int pid;
	
	if(playerpid){
		pid = playerpid;
		playerpid = 0;
		nbsendp(unpausechan, 0);
		postnote(PNPROC, pid, "kill");
	}
	if(paused){
		paused = 0;
		nbsendp(unpausechan, 0);
	}
}

/*
 * Remove the > tags from the window, updating position q.
 */
uint
nomarkers(Win *w, uint q)
{
	uint qq;

	while(winaddr(w, "0/^>/") >= 0){
		qq = winreadaddr(w, nil);
		winwrite(w, "data", nil, 0);
		if(q > qq)
			q--;
	}
	return q;
}

/*
 * Find the position of the first > tag.
 */
uint
findmarker(Win *w)
{
	if(winaddr(w, "0/^>/") < 0)
		return ~0;
	return winreadaddr(w, nil);
}

/*
 * Start playing at the line containing the click.
 */
int
playlistexec(Win *w, Event *e)
{
	uint q;

	if(e->c2 == 'x')	/* ignore clicks in tags */
		return -1;

	/*
	 * start where the mouse was clicked, back up to line begin.
	 */
	if(winaddr(w, "#%ud", e->q0) < 0)
		fprint(2, "winaddr: %r\n");
	/* scan backward, but do not wrap around file */
	q = winreadaddr(w, nil);
	winaddr(w, "-/[^\\n]*/");
	if(q < winreadaddr(w, nil))
		winaddr(w, "#%ud", q);
	/* no selection */
	if(winaddr(w, "-#0") < 0){
		fprint(2, "winaddr: %r\n");
		return -1;
	}
	q = winreadaddr(w, nil);
	
	stopaudio();
	q = nomarkers(w, q);

	winaddr(w, "#%ud", q);
	playnext(w, 1);
	return 0;
}

/*
 * Set addr to one of the songs in the window.
 */
int
doshuffle(Win *w)
{
	int i;
	uint q, qq;
	static uint *a;
	static int n;

	if(playwinchanged){
		playwinchanged = 0;
		q = 0;
		while(winaddr(w, "#%ud/^(song\\/[0-9]+)/", q+1) >= 0){
			qq = winreadaddr(w, nil);
			if(qq <= q)
				break;
			q = qq;
			a = erealloc(a, (n+1)*sizeof(a[0]));
			a[n++] = q;
		}
	}
	if(n == 0)
		return -1;
	i = rand()%n;
	winaddr(w, "#%ud", a[i]);
	return 0;
}

/*
 * Expand the click further than acme usually does -- all non-white space is okay.
 */
char*
expandarg(Win *w, Event *e)
{
	if(e->oq0 == e->oq1 && e->q0 != e->q1){
		winaddr(w, "#%ud+#1-/[^ \t\\n]*/,#%ud-#1+/[^ \t\\n]*/", e->q0, e->q1);
		return winmread(w, "xdata");
	}
	return nil;
}

/*
 * Search forward in the playlist window for the next song to play, and start it.
 * A line beginning with "repeat" tells playnext to start over at the top of the
 * window when it reaches the end. 
 */
int startalarm(char*);
void
playnext(Win *w, int isclick)
{
	char buf[100];
	int n, repeated;
	uint q, qq;

	dprint("playnext\n");
	repeated = 0;
	if(!isclick && winaddr(w, "/^shuffle/") >= 0){
		if(doshuffle(w) < 0)
			return;
	}
	for(;;){
		q = winreadaddr(w, nil);
		dprint("playnext: search at %ud\n", q);
		if(winaddr(w, "#%ud/^(song\\/[0-9]+|repeat|alarm [0-9]+:[0-9]+|volume [+\\-]?[0-9]+)/", q) < 0){
			dprint("playnext: no songs: %r\n");
			break;
		}
		qq = winreadaddr(w, nil);
		if(qq < q){	/* wrapped */
			dprint("playnext: end of songs\n");
			break;
		}
		n = winread(w, "xdata", buf, sizeof buf-1);
		if(n < 0){
			dprint("playnext: reading xdata: %r");
			break;
		}
		buf[n] = 0;
		if(strcmp(buf, "repeat") == 0){
			if(repeated){
				dprint("playnext: already repeated\n");
				break;
			}
			dprint("playnext: repeat\n");
			repeated = 1;
			winaddr(w, "#0");
			continue;
		}
		if(strncmp(buf, "alarm ", 6) == 0){
			if(startalarm(buf+6) >= 0){
				winaddr(w, "#%ud", qq);
				winwrite(w, "data", ">", 1);
				dprint("alarm: started alarm\n");
				break;
			}
			dprint("alarm: failed\n");
		}
		if(strncmp(buf, "volume ", 7) == 0 && va){
			if(va->getvolume(va, "pcm", &n, &n) < 0)
				goto failv;
			if(buf[7]=='+' || buf[7]=='-')
				n += atoi(buf+7+(buf[7]=='+'));
			else
				n = atoi(buf+7);
			if(n > 100)
				n = 100;
			if(n < 0)
				n = 0;
			va->setvolume(va, "pcm", n, n);
		failv:;
		}
		if(strncmp(buf, "song/", 5) == 0){
			n = atoi(buf+5);
			dprint("playnext: song %d\n", n);
			if(startsong(n) >= 0){
				winaddr(w, "#%ud", qq);
				winwrite(w, "data", ">", 1);
				dprint("playnext: started song\n");
				break;
			}
			dprint("playnext: startsong failed\n");
		}
		/* no such song, so advance if possible */
		if(winaddr(w, "#%ud+1", qq) < 0){
			dprint("playnext: no more buffer\n");
			break;
		}
	}
}

/*
 * Try to start playing song #n.
 */
int
startsong(int n)
{
	char *file;
	int pid, p[2], fd[3], *arg;
	
	file = sysrun("jukesongfile %d", n);
	if(file == nil){
		dprint("startsong: sysrun failed: %r\n");
		return -1;
	}
/* why insist on files?  what about urls?
	if(access(file, AEXIST) < 0){
		dprint("startsong: jukesongfile returns non-existent %s\n", file);
		return -1;
	}
*/
#undef pipe	/* just in case kids want to use /dev/stdin etc */
	if(pipe(p) < 0){
		dprint("startsong: pipe: %r\n");
		return -1;
	}
	fd[0] = open("/dev/null", OREAD);
	fd[1] = p[1];
	fd[2] = dup(2, -1);
	playerpid = pid = threadspawnl(fd, "jukeplay", "jukeplay", file, nil);
	if(playerpid < 0){
		dprint("startsong: jukeplay: %r\n");
		fprint(2, "run jukeplay %s: %r\n", file);
		return -1;
	}
	nbsendp(unpausechan, 0);
	arg = emalloc(2*sizeof(int));
	arg[0] = pid;
	arg[1] = p[0];
	proccreate(audioproc, arg, STACK);
	return 0;
}

int
startalarm(char *s)
{
	int pid, p[2], fd[3], *arg;
	
#undef pipe	/* just in case kids want to use /dev/stdin etc */
	if(pipe(p) < 0){
		dprint("startsong: pipe: %r\n");
		return -1;
	}
	fd[0] = open("/dev/null", OREAD);
	fd[1] = p[1];
	fd[2] = dup(2, -1);
fprint(2, "waitfor %s\n", s);
	playerpid = pid = threadspawnl(fd, "waitfor", "waitfor", s, nil);
	if(playerpid < 0){
		dprint("startalarm: waitfor: %r\n");
		fprint(2, "run waitfor %s: %r\n", s);
		return -1;
	}
	nbsendp(unpausechan, 0);
	arg = emalloc(2*sizeof(int));
	arg[0] = pid;
	arg[1] = p[0];
	proccreate(audioproc, arg, STACK);
	return 0;
}

/*
 * Audio proc.  Read 44.1kHz big-endian 16-bit stereo PCM data 
 * from a decoder and send it to the sound card.  The copying has
 * two benefits.  First, the decoders need not know how to
 * access the sound card on each platform.  Second, we can close
 * the sound card while we are not playing, cooperating with other
 * programs on systems that only allow one connection to the card
 * at a time.
 */
void
audioproc(void *v)
{
	int ifd, n, pid, *arg;
	char *buf, *dev;

	threadsetname("audioproc");
	qlock(&audiolock);	/* only one audioproc can openaudio at a time */
	arg = v;
	pid = arg[0];
	ifd = arg[1];
	buf = emalloc(AudioBuf);
	dprint("audioproc %d: opening in pid %d\n", pid, getpid());
	dev = adev;
	aa = openaudio(dev, AUDIO);
	dprint("audioproc %d: opened %p\n", pid, aa);
	if(aa == nil)
		goto out;
	for(;;){
		// dprint("audioproc %d: read\n");
		if((n = readn(ifd, buf, AudioBuf)) <= 0){
			dprint("audioproc %d: read eof %d %r\n", pid, n);
			break;
		}
		if(pid != playerpid){
			dprint("audioproc %d: playerpid change\n", pid);
			break;
		}
		if(paused || dev != adev){
			dprint("audioproc %d: paused\n", pid);
			if(aa){
				dprint("closing %d\n", aa->fd);
				aa->close(aa);
			}
			aa = nil;
			while(paused && pid == playerpid)
				recvp(unpausechan);
			dprint("audioproc %d: unpaused\n", pid);
			dev = adev;
			aa = openaudio(dev, AUDIO);
			if(aa == nil)
				break;
			dprint("reopened %d\n", aa->fd);
		}
		if(write(aa->fd[1], buf, n) != n){
			fprint(2, "writing audio: %r");
			break;
		}
	}
out:
	dprint("audioproc %d: finishing\n", pid);
	free(buf);
	if(aa){
		dprint("close %d\n", aa->fd);
		aa->close(aa);
	}
	close(ifd);
	qunlock(&audiolock);
}

/*
 * manage a navigation - song list or search - window
 */
void
navthread(void *v)
{
	Win *w;
	char *arg, *arg2;
	Event e;
	int issearch;
	void *cpc;

	arg = v;
	cpc=&v;
	threadsetname("navthread %s", arg);
	dprint("navthread %s caller 0x%lux\n", arg, getcallerpc(cpc));
	w = newwin();
	dprint("navthread %s after-newwin caller 0x%lux 0x%lux\n", arg, getcallerpc(cpc));
	if(isjukepage(arg)){
		issearch = 0;
		winname(w, "Juke/%s", arg);
		if(strncmp(arg, "list/", 5) == 0 && arg[strlen(arg)-1] != '/')
			winprint(w, "tag", "Get Put ");
		twait(pipetowin(w, "body", 2, "jukeget %q | jukefmt %d", arg, w->id));
	}else{
		issearch = 1;
		dprint("navthread %s winname caller 0x%lux\n", arg, getcallerpc(cpc));
		winname(w, "Juke/search");
		dprint("navthread %s pipetowin caller 0x%lux\n", arg, getcallerpc(cpc));
		twait(pipetowin(w, "body", 2, "jukesearch %q | jukefmt %d", arg, w->id));
		dprint("navthread %s done caller 0x%lux\n", arg, getcallerpc(cpc));
	}
	dprint("navthread %s top caller 0x%lux\n", arg, getcallerpc(cpc));
	dprint("navthread %s clean\n", arg);
	wintop(w);
	dprint("navthread %s clean caller 0x%lux\n", arg, getcallerpc(cpc));
	winctl(w, "clean");

	dprint("navthread %s events caller 0x%lux\n", arg, getcallerpc(cpc));
	while(winreadevent(w, &e) > 0){
		dprint("navthread %s %c%c (caller 0x%lux)\n", arg, e.c1, e.c2, getcallerpc(cpc));
		if(e.c1 != 'M')
			continue;
		switch(e.c2){
		case 'x':
		case 'X':
			switch(lookup(e.text, cmds)){
			case Get:
				winaddr(w, ",");
				winwrite(w, "data", "", 0);
				if(issearch)
					twait(pipetowin(w, "body", 2, "jukesearch %q | jukefmt %d", arg, w->id));
				else
					twait(pipetowin(w, "body", 2, "jukeget %q | jukefmt %d", arg, w->id));
				wintop(w);
				winctl(w, "clean");
				break;
			case Put:
				if(!issearch){
					twait(pipewinto(w, "body", 2, "jukeput %q | jukefmt %d", arg, w->id));
					winctl(w, "clean");
				}
				break;
			case Del:
			case Delete:
				goto out;
			default:
				if(listexec(w, &e) < 0)
					winwriteevent(w, &e);
				break;
			}
			break;
		case 'l':
		case 'L':
			arg2 = expandarg(w, &e);
			if(arg2)
				threadcreate(navthread, arg2, STACK);
			break;
		}
	}
out:
	dprint("navthread %s exiting caller 0x%lux\n", arg, getcallerpc(cpc));
	free(arg);
	dprint("navthread %s exiting caller 0x%lux\n", arg, getcallerpc(cpc));
	winctl(w, "delete");
	dprint("navthread %s exiting caller 0x%lux\n", arg, getcallerpc(cpc));
	winfree(w);
	dprint("navthread %s exiting caller 0x%lux\n", arg, getcallerpc(cpc));
}

/*
 * expand to next newline and previous newline
 * then send selection to playlist window
 */
int
listexec(Win *w, Event *e)
{
	uint q0, q1;
	char buf[1024];
	int n, nl;

	winseek(playwin, "body", 0, 0);
	nl = 1;
	while((n = winread(playwin, "body", buf, sizeof buf)) > 0)
		nl = buf[n-1] == '\n';
	if(!nl)
		winwrite(playwin, "body", "\n", 1);
	
	dprint("listexec %ud,%ud\n", e->q0, e->q1);
	winaddr(w, "#%ud-/^/,#%ud+/^/", e->q0, e->q1);
	q0 = winreadaddr(w, &q1);
	dprint("listexec @%ud,%ud\n", q0, q1);
	if(q0 > e->q0)
		q0 = 0;
	dprint("listexec @%ud,%ud\n", q0, q1);
	if(q1 < e->q1)
		winaddr(w, "#%ud,$", q0);
	else
		winaddr(w, "#%ud,#%ud", q0, q1);
	q0 = winreadaddr(w, &q1);
	dprint("listexec @%ud,%ud\n", q0, q1);
	winseek(w, "xdata", 0, 0);
	nl = 1;
	while((n = winread(w, "xdata", buf, sizeof buf)) > 0){
		nl = buf[n-1] == '\n';
		winwrite(playwin, "body", buf, n);
	}
	if(!nl)
		winwrite(playwin, "body", "\n", 1);
	return 0;
}

char*
prefixes[] =
{
	"list",
	"song",
	"artist",
	"composer",
	"genre",
	"album",
	0
};

int
isjukepage(char *name)
{
	int i, l;
	
	for(i=0; prefixes[i]; i++){
		l = strlen(prefixes[i]);
		if(strncmp(name, prefixes[i], l) == 0 && (name[l]==0 || name[l]=='/'))
			return 1;
	}
	return 0;
}

void
usage(void)
{
	fprint(2, "usage: Juke\n");
	threadexitsall("usage");
}

extern int _threaddebuglevel;
void
threadmain(int argc, char **argv)
{
	ARGBEGIN{
	case '9':
		chatty9pclient = 1;
		break;
	case 'D':
		debug = 1;
		break;
	case 'T':
		_threaddebuglevel = 1;
		break;
	default:
		usage();
		break;
	}ARGEND

	doquote = needsrcquote;
	threadnotify(nil, 0);
	fmtinstall('E', eventfmt);
	quotefmtinstall();
	twaitchan = chancreate(sizeof(Waitreq), 0);
	unpausechan = chancreate(sizeof(void*), 1);
	playerwaitchan = chancreate(sizeof(Waitmsg*), 0);
	threadcreate(playlistthread, nil, STACK);
	threadcreate(waitthread, nil, STACK);
	threadexits(nil);
}

