/*
 * reader.c - functions for handling news/article reader windows.
 */

#include "headers.h"

Bool debug = true;
Bool frommessage = true;

/*
 * These determine whether we'll have one list and one article window,
 * that gets overwritten each time we change group/mailbox/article,
 * or whether we just spawn a new one.
 */

int rdMultiList = 0;
int rdMultiArt = 0;

/*
 * The windows currently active.
 */

rdWin *windows = 0;
static rdWin *Listwin = 0;
static rdWin *Artwin = 0;

/*
 * General wily comms stuff.
 */

Mqueue realwilyq;
Mqueue *wilyq = &realwilyq;
Fd wilyfd;


/*
 * default contents of tags
 */

char *rdListTagStr = " delete undelete comp reply quit exit ";
char *rdArtTagStr = " delete reply save inc ";

static rdWin *allocWin(rdWinType kind, const char *title);
static int connectWin(rdWin *w, char *filename);
static rdWin *getWin(int user, void *userp, rdWinType kind, char *title, char *filename);
static void clearWin(rdWin *w);
static void freeItems(rdWin *w);
static int loadItems(rdWin *w, char **items, int savecontents);
static int initWin(int user, void *userp, rdWin *w, char *title, char *filename);
static void anyEvent(rdWin *w);
static void winDelete(rdWin *w, int which, ulong p0, ulong p1);
static void winInsert(rdWin *w, int which, ulong p, char *str);
static void updateBody(rdWin *w, ulong p0, ulong p1, char *str);
static void winGoto(rdWin *w, ulong p0, char *str);
static void winExec(rdWin *w, char *cmd, char *arg);
static void DelWin(rdWin *w, char *arg);
static int rdBuiltin(rdWin *w, char *cmd, char *str);
static void addrEvent(rdWin *w);
static void bodyEvent(rdWin *w);
static size_t countlines(char *str, size_t len);
static void prefixlines(char *str, size_t len, char *prefix, size_t plen, char *buf);

/*
 * allocWin(kind, title) - create a new, blank window of a given kind.
 */

static rdWin *
allocWin(rdWinType kind, const char *title)
{
	rdWin *w = salloc(sizeof(*w));

	w->title = sstrdup((char *)title);
	w->wintype = kind;
	w->id = -1;
	w->m = 0;
	if (kind == rdList)
		w->items = 0;
	else
		w->body = 0;
	w->taglen = w->bodylen = 0;
	w->next = windows;
	windows = w;
	return w;
}

/*
 * connectWin(w, filename) - connect to wily, to create a new window.
 * If filename is true, then we get wily to open the given file. Otherwise,
 * we ask for a new, blank window.
 * Returns 0 for success.
 */

static int
connectWin(rdWin *w, char *filename)
{
	int fd;
	if (filename == 0)
		filename = "New";
	if (rpc_new(wilyq, &w->id, filename, false) < 0) {
		freeWin(w);
		return -1;
	}
	return 0;
}

/*
 * getWin(user, userp, kind, title, filename) - grab a window of the appropriate type.
 */

static rdWin *
getWin(int user, void *userp, rdWinType kind, char *title, char *filename)
{
	rdWin *w;

	if ((!Listwin || kind == rdList && rdMultiList) ||
		(!Artwin || kind == rdArticle && rdMultiArt)) {
		/* we can create a new window */
		w = allocWin(kind, title);
		if (connectWin(w,filename))
			return 0;
		if (kind == rdList)
			Listwin = w;
		else
			Artwin = w;
	} else {
		/* have to reuse the existing window */
		w = (kind == rdList)? Listwin : Artwin;
		clearWin(w);
	}
	initWin(user, userp, w, title, filename);
	return w;
}

/*
 * clearWin(w) - erase the tag and body of a window
 */

static void
clearWin(rdWin *w)
{
	if (w->taglen)
		(void) rpc_settag(wilyq, w->id, "");
	w->taglen = 0;
	if (w->bodylen) {
		(void) rpc_delete(wilyq, w->id, 0, w->bodylen);
		w->bodylen = 0;
		if (w->wintype == rdList)
			freeItems(w);
	}
}

/*
 * discard all the items in the window's list
 */

static void
freeItems(rdWin *w)
{
	rdItem *i, *j;

	for (i = w->items; i; i = j) {
		j = i->next;
		free(i);
	}
	w->items = 0;
}

/*
 * loadItems(w, items) - load an array of items into the list, and into the window.
 */

static int
loadItems(rdWin *w, char **items, int savecontents)
{
	rdItem *i, *j = 0;
	char *sep = "\n";
	ulong p0 = 0;
	ulong len, seplen = strlen(sep);
	char *buf = 0;
	int n;

	if (savecontents) {
		for (len = 0, n = 0; items[n]; n++)
			len += strlen(items[n]) + seplen;
		w->body = buf = salloc(len);
	}
	while (*items) {
		i = salloc(sizeof(*i));
		i->next = 0;
		i->p0 = p0;
		len = strlen(*items);
		if (j)
			j->next = i;
		else
			w->items = i;
		j = i;
		if (rpc_insert(wilyq, w->id, p0, *items)) {
			freeItems(w);
			return -1;
		}
		p0 += len;
		if (rpc_insert(wilyq, w->id, p0, sep)) {
			freeItems(w);
			return -1;
		}
		i->p1 = (p0 += seplen);
		items++;
	}
	w->bodylen = p0;
	return 0;
}

/*
 * changeItems(w, items, savecontents) - update the contents of a list window.
 */

int
changeItems(rdWin *w, char **items, int savecontents)
{
	if (w->wintype != rdList)
		return 1;
	freeItems(w);
	if (w->bodylen) {
		if (rpc_delete(wilyq, w->id, 0, w->bodylen))
			return 1;
		w->bodylen = 0;
	}
	return loadItems(w, items, savecontents);
}

/*
 * initWin(user, userp, w, title, filename) - initialise the window's tag, label, etc.
 */

static int
initWin(int user, void *userp, rdWin *w, char *title, char *filename)
{
	char tag[300];			/* XXX guess */
	char *tagstr = w->wintype == rdArticle? rdArtTagStr : rdListTagStr;
	if (rpc_setname(wilyq, w->id, title))
		return 1;
	sprintf(tag,"%s", tagstr);		/* XXX at the moment, we don't include the title */
	if (rpc_settag(wilyq, w->id, tag))
		return 1;
	w->taglen = strlen(tag);
	w->user = user;
	w->userp = userp;
	return 0;
}

/*
 * getArtWin(title, text, filename, savecontents) - get an article window, and load it with
 * either the given text, or from the given file.
 */

int
getArtWin(int user, void *userp, char *title, char *text, char *filename, int savecontents)
{
	rdWin *w = getWin(user, userp, rdArticle, title, filename);

	if (w == 0)
		return 1;
	if (text) {
		int l = strlen(text);
		w->bodylen = l;
		if (rpc_insert(wilyq, w->id, 0, text))
			return 1;
		if (savecontents) {
			w->body = salloc(l);
			strcpy(w->body, text);
		}
	}
	return 0;
}

/*
 * get a List window, and load it with the items in the array (which
 * must be null-terminated).
 */

int
getListWin(int user, void *userp, char *title, char **items, int savecontents)
{
	rdWin *w = getWin(user, userp, rdList, title, (char *)0);

	if (w == 0)
		return 1;
	return loadItems(w, items, savecontents);
}

/*
 * readerInit(title, items, ml, ma) - start things off by creating
 * a list window. Tell the reader whether we'll want one or more of
 * the list and article windows.
 */

int
readerInit(int user, char *userp, char *title, char **items, int ml, int ma, int savecontents)
{
	rdMultiList = ml;
	rdMultiArt = ma;
	if ((wilyfd = get_connect()) < 0)
		return 1;
	mq_init(wilyq, wilyfd);
	return getListWin(user, userp, title, items, savecontents);
}

/*
 * readerMainLoop() - get messages from wily, and act upon them.
 */

int
readerMainLoop(void)
{
	rdWin *w;
	Msg *m;
	int istag;

	while (windows) {
		if ((w = getActiveWin()) == 0) {
			fprintf(stderr,"No message available\n");
			return 1;
		}
		m = w->m;
		switch (m->mtype) {
			char *cmd, *arg;
			ulong p0, p1;
			case EMexec:
				decode_exec_e(m, &cmd, &arg);
				winExec(w, cmd, arg);
				break;
			case EMgoto:
				decode_goto_e(m, &p0, &arg);
				winGoto(w, p0, arg);
				break;
			case EMinsert:
				istag = IsBody;
				decode_insert_e(m, &p0, &arg);
				winInsert(w, istag, p0, arg);
				break;
			case EMdelete:
				istag = IsBody;
				decode_delete_e(m, &p0, &p1);
				winDelete(w, istag, p0, p1);
				break;
			default:
				fprintf(stderr,"Unknown msg type!\n");
				exit(1);
		}
	}
	return 1;
}

/*
 * text has been deleted
 */

static void
winDelete(rdWin *w, int which, ulong p0, ulong p1)
{
	rdItem *i;
	ulong l = p1 - p0;

	if (which == IsTag) {
		w->taglen -= l;
		return;
	}
	if (w->wintype == rdArticle) {
		w->bodylen -= l;
		return;
	}
	for (i = w->items; i; i = i->next)
		if (i->p0 <= p0 && p1 <= i->p1)
			break;
	if (!i)
		return;
	i->p1 -= l;
	for (i = i->next; i; i = i->next) {
		i->p0 -= l;
		i->p1 -= l;
	}
	updateBody(w, p0, p1, (char *)0);
	return;
}

/*
 * text has been inserted
 */

static void
winInsert(rdWin *w, int which, ulong p, char *str)
{
	rdItem *i;
	int l = strlen(str);

	if (which == IsTag) {
		w->taglen += l;
		return;
	}
	if (w->wintype == rdArticle) {
		w->bodylen += l;
		return;
	}
	for (i = w->items; i; i = i->next)
		if (i->p0 <= p && p <= i->p1)
			break;
	if (!i)
		return;
	i->p1 += l;
	for (i = i->next; i; i = i->next) {
		i->p0 += l;
		i->p1 += l;
	}
	updateBody(w, p, (ulong)l, str);
	return;
}

/*
 * updateBody(w,p0,p1,str) - insert p1 chars of str at p0, or remove the text
 * between p0 and p1.
 * XXX - this is *utterly* revolting, and horrendously slow. But it's quick
 * and simple, and will do for now.
 */

static void
updateBody(rdWin *w, ulong p0, ulong p1, char *str)
{
	char *newbuf;
	ulong newlen;

	if (w->body == 0)
		return;
	if (str)
		newlen = w->bodylen + p1;
	else
		newlen = w->bodylen - (p1 - p0);
	newbuf = salloc(newlen);
	strncpy(newbuf, w->body, p0);
	if (str) {
		strncpy(newbuf + p0, str, p1);
		strcpy(newbuf + p0 + p1, w->body + p0);
	} else {
		strcpy(newbuf + p0, w->body + p1);
	}
	free(w->body);
	w->body = newbuf;
	w->bodylen = newlen;
}

int
itemNumber(rdWin *w, ulong p0, ulong p1, void **ptr)
{
	int n;
	rdItem *i;

	if (w->wintype != rdList)
		return -1;
	for (n = 0, i = w->items; i; i = i->next, n++)
		if (i->p0 <= p0 && p1 <= i->p1) {
			*ptr = (void *)i;
			return n;
		}
	return -1;		/* sigh */
}

void
highlightItem(rdWin *w, int n, void *ptr)
{
	static char addr[80];			/* XXX - overkill */
	rdItem *i = (rdItem *)ptr;

	if (i == 0)
		for (i = w->items; n--; i = i->next);
	sprintf(addr,"#%lu,#%lu", i->p0, i->p1);
	(void)rpc_goto(wilyq, w->id, addr);
}

/*
 * A B3 event has occured.
 */

static void
winGoto(rdWin *w, ulong p0, char *str)
{
	/* If this is a list,
	then it counts as a selection. Otherwise,  we just reflect it back to wily as a normal
	B3 event. */

	if (w->wintype == rdList) {
		int n;
		rdItem *i;

fprintf(stderr,"Hit at (%lu)\n",p0);
		if ((n = itemNumber(w, p0, p0, (void **)&i)) == -1)
			return;		/* sigh */
fprintf(stderr,"Is item %d expands to (%lu,%lu)\n",n, i->p0, i->p1);
		highlightItem(w, n, i);
		user_listSelection(w,n);
		return;
	} else
		(void)rpc_goto(wilyq, w->id, str);
	return;
}


/*
 * A B2(B1) command has been invoked.
 */

static void
winExec(rdWin *w, char *cmd, char *arg)
{
	ulong p0, p1;
	/*
	 * Lots of options here:
	*	- B2 with "|<>" - reflect back to wily.
	*	- B2 cmd recognised by the reader routines - Del is about it. Execute
	*	  them ourselves.
	*	- B2 not recognised by the reader routines - pass them onto the user,
	*	  after first getting the position of the argument within the body, if
	*	 necessary.
	*	- B2B1 recognised by the reader routines - none spring to mind, but
	*	  I might add some. Execute them ourselves.
	*	- B2B1 not recognised - pass onto user.
	*/

	if (!*cmd || strstr(cmd,"|<>")) {
		winReflectCmd(w,cmd, arg);
		return;
	}
	if (rdBuiltin(w,cmd,arg) == 0)
		return;
	if (rpc_addr(wilyq, w->id, ".", &p0, &p1))
		return;

	if (w->wintype == rdList) {
		rdItem *i;
		int n;

		if ((n = itemNumber(w, p0, p1, (void *)&i)) != -1) {
			highlightItem(w, n, i);
			p0 = i->p0;
			p1 = i->p1;
			user_cmdList(w,cmd,p0,p1,n,arg);
			return;
		}
	}
	user_cmdArt(w, cmd, p0, p1, arg);
	return;
}


void
winReflectCmd(rdWin *w, char *cmd, char *arg)
{
	(void)rpc_exec(wilyq, w->id,cmd,arg);
}

/*
 * delete this particular window from the list.
 */

static void
DelWin(rdWin *w, char *arg)
{
	closeWin(w);
}

void
closeWin(rdWin *w)
{
	/* get wily to close the pane */
	(void)rpc_exec(wilyq, w->id, "Del", "");

	/* free up resources */
	free(w->title);
	free(w->body);
	if (w->wintype == rdList)
		freeItems(w);
	/* XXX - currently leak the stateinfo stuff */
	freeWin(w);
	return;
}

void
freeWin(rdWin *w)
{
	rdWin **wp = &windows;

	/* remove from the window chain */
	while ((*wp) != w)
		wp = &((*wp)->next);
	*wp = w->next;
	if (w == Artwin)
		Artwin = 0;
	if (w == Listwin)
		Listwin = 0;
	if (windows == 0)
		exit(0);
	return;
}

static struct {
	char *cmd;
	void (*fn)(rdWin *, char *);
} builtins[] = {
	{ "Del",	DelWin },
	{ 0, 0 }
};

/*
 * check for builtin commands understood by the reader routines.
 */

static int
rdBuiltin(rdWin *w, char *cmd, char *str)
{
	int i;

	for (i = 0; builtins[i].cmd; i++)
		if (strcmp(builtins[i].cmd, cmd) == 0) {
			(*builtins[i].fn)(w, str);
			return 0;
		}
	return 1;
}



/*
 * "redisplay" a window that's already open.
 */

void
rdGotoWin(rdWin *w)
{
	(void)rpc_goto(wilyq, w->id, ".");
}

rdWin *
userpWin(void *ptr)
{
	rdWin *w = windows;

	while (w && w->userp != ptr)
		w = w->next;
	return w;
}

/*
 * Some routines which attempt to extract the body of a window,
 * and store it in a file. if p0==p1==0, assume all the body is wanted.
 */

void
rdBodyToFile(rdWin *w, char *filename)
{
	FILE *fp;
	char *buf = salloc(w->bodylen+1);

	if (rpc_read(wilyq, w->id, 0, w->bodylen, buf))
		return;
	if ((fp = fopen(filename, "w"))) {
		(void)fprintf(fp,"%s",buf);
		fclose(fp);
	}
	free(buf);
	return;
}

/*
 * change current settings for multi-list and multi-art windows.
 */

void
rdSetMulti(int list, int art)
{
	rdMultiList = list;
	rdMultiArt = art;
}

void
rdInclude(rdWin *w, char *str, size_t len)
{
	size_t nlines = countlines(str, len);
	char *buf;
	char *prefix = "> ";
	size_t nlen, plen = strlen(prefix);
	ulong p0, p1;

	if (rpc_addr(wilyq, w->id, ".", &p0, &p1))
		return;
	nlen = len + nlines*plen + 1;
	buf = salloc(nlen);
	prefixlines(str, len, prefix, plen, buf);
	rpc_insert(wilyq, w->id, p1, buf);
	w->bodylen += nlen;
	free(buf);
}

static size_t
countlines(char *str, size_t len)
{
	size_t nlines = 0;

	while (len--)
		if (*str++ == '\n')
			nlines++;
	return nlines;
}

static void
prefixlines(char *str, size_t len, char *prefix, size_t plen, char *buf)
{
	while (len) {
		strncpy(buf,prefix,plen);
		buf += plen;
		while (len-- && (*buf++ = *str++) != '\n')
			;
	}
	*buf = 0;
}
