/*
** pork_swindow.c - an ncurses scrolling window widget
** Copyright (C) 2002-2004 Ryan McCabe <ryan@numb.org>
**
** I've tried to keep this code independent from the rest of the program
** so that it might be useful outside of this particular program.
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License, version 2,
** as published by the Free Software Foundation.
*/

#include <config.h>

#include <unistd.h>
#include <ncurses.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <fcntl.h>
#include <errno.h>
#include <regex.h>
#include <sys/uio.h>

#include <pork.h>
#include <pork_missing.h>
#include <pork_util.h>
#include <pork_list.h>
#include <pork_set.h>
#include <pork_swindow.h>
#include <pork_cstr.h>
#include <pork_misc.h>
#include <pork_screen_cmd.h>

static void swindow_scroll(struct swindow *swindow, int n);


static u_int32_t imsg_wordwrapped_lines(struct swindow *swindow,
										struct imsg *imsg)
{
	u_int32_t len = MSG_LEN(swindow, imsg);
	chtype *ch = MSG_TEXT(swindow, imsg);
	chtype *end = &ch[len - 1];
	u_int32_t lines = 0;
	int add = 0;
	chtype cont_char = opt_get_char(OPT_WORDWRAP_CHAR);

	if (len <= swindow->cols)
		return (1);

	if (swindow->cols == 1)
		return (len);

	while (*ch != 0) {
		chtype *p;

		p = &ch[swindow->cols - 1 - add];
		if (p >= end) {
			++lines;
			break;
		}

		/*
		** If the last character on current line or the first character
		** on the next line is a space, there's no need to break the line
		*/
		if (chtype_get(p[0]) != ' ' && chtype_get(p[1]) != ' ') {
			chtype *temp = p;

			/* Find the last space, if any */
			do {
				temp--;
				if (temp <= ch) {
					/*
					** No spaces on the line, just split it at the
					** last column
					*/
					goto out;
				}
			} while (chtype_get(*temp) != ' ');
			/* Found a space, split the line after the space. */
			p = temp;
		}
out:
		if (cont_char != 0)
			add = 1;

		ch = &p[1];
		lines++;
	}

	return (lines);
}

/*
** Compute the number of rows the message will occupy
** on the screen.
*/

static inline u_int32_t imsg_lines(struct swindow *swindow, struct imsg *imsg) {
	u_int32_t len = MSG_LEN(swindow, imsg);

	if (len <= swindow->cols)
		return (1);

	if (!swindow->wordwrap)
		return ((len / swindow->cols) + (len % swindow->cols != 0));

	return (imsg_wordwrapped_lines(swindow, imsg));
}

/*
** Tag a message with a timestamp and store its length and the resulting
** tagged string in the imsg struct.
*/

static struct imsg *imsg_new(	struct swindow *swindow,
								chtype *msg,
								u_int32_t timestamp)
{
	char timebuf[16];
	u_int32_t len;
	chtype *msg_text;
	struct imsg *imsg;
	char *p;
	chtype *ch;
	chtype *c;

	if (timestamp) {
		time_t cur_time;
		struct tm *tm;

		time(&cur_time);
		tm = localtime(&cur_time);
		snprintf(timebuf, sizeof(timebuf), "[%02d:%02d] ",
			tm->tm_hour, tm->tm_min);
	} else
		timebuf[0] = '\0';

	len = strlen(timebuf) + cstrlen(msg) + 1;
	msg_text = xmalloc(len * sizeof(chtype));

	ch = msg_text;
	p = timebuf;
	while (*p != '\0')
		*ch++ = (chtype) *p++;

	c = msg;
	while (*c != 0)
		*ch++ = *c++;
	*ch = 0;

	imsg = xmalloc(sizeof(*imsg));
	imsg->text = msg_text;
	imsg->serial = swindow->serial++;
	imsg->timestamp = timestamp;
	imsg->len = len - 1;
	imsg->lines = imsg_lines(swindow, imsg);

	return (imsg);
}

/*
** Return a pointer to the first character of the nth
** line of the message, given the current screen width.
*/

static chtype *imsg_partial(struct swindow *swindow,
							struct imsg *imsg,
							u_int32_t n)
{
	chtype *text = MSG_TEXT(swindow, imsg);
	u_int32_t offset;
	static chtype nul_ch = 0;

	--n;
	if (n == 0)
		return (text);

	offset = n * swindow->cols;

	/*
	** offset can end up larger than the length
	** of the string when the screen has been cleared.
	*/

	if (offset < imsg->len - (text - imsg->text)) {
		text += offset;
		return (text);
	}

	/*
	** If the offset is greater than the string's length
	** the caller doesn't want to write anything to the screen
	*/

	return (&nul_ch);
}

static int swindow_print_msg_wr(struct swindow *swindow,
								struct imsg *imsg,
								u_int32_t y,
								u_int32_t x,
								u_int32_t firstline,
								u_int32_t lastline)
{
	u_int32_t len = MSG_LEN(swindow, imsg);
	chtype *ch = MSG_TEXT(swindow, imsg);
	u_int32_t lines = 0;
	u_int32_t lines_printed = 0;
	chtype *end = &ch[len - 1];
	int add = 0;
	chtype cont_char = (chtype) opt_get_char(OPT_WORDWRAP_CHAR);

	if (swindow->cols == 1)
		cont_char = 0;

	while (*ch != 0) {
		chtype *p;

		p = &ch[swindow->cols - 1 - add];
		if (p >= end) {
			++lines;
			if (lines <= lastline) {
				mvwputstr(swindow->win, y + lines_printed, add, ch);
				++lines_printed;
			}
			break;
		}

		if (chtype_get(p[0]) != ' ' && chtype_get(p[1]) != ' ') {
			chtype *temp = p;

			do {
				temp--;
				if (temp <= ch)
					goto out;
			} while (chtype_get(*temp) != ' ');
			p = temp;
		}
out:
		++lines;
		if (lines >= firstline) {
			if (lines > lastline)
				break;

			mvwputnstr(swindow->win, y + lines_printed, add, ch, p - ch + 1);
			++lines_printed;
		}

		if (cont_char != 0) {
			mvwaddch(swindow->win, y + lines_printed, 0, cont_char);
			add = 1;
		}
		ch = &p[1];
	}

	return (lines_printed);
}

static int swindow_print_msg(	struct swindow *swindow,
								struct imsg *imsg,
								u_int32_t y,
								u_int32_t x,
								u_int32_t firstline,
								u_int32_t lastline)
{
	chtype *msg;

	if (swindow->wordwrap)
		return (swindow_print_msg_wr(swindow, imsg, y, x, firstline, lastline));

	if (lastline < firstline)
		return (-1);

	msg = imsg_partial(swindow, imsg, firstline);
	if (lastline >= imsg->lines)
		mvwputstr(swindow->win, y, x, msg);
	else {
		u_int32_t max_len;

		if (lastline == firstline)
			max_len = swindow->cols;
		else
			max_len = (lastline - firstline) * swindow->cols;

		mvwputnstr(swindow->win, y, x, msg, max_len);
	}

	return (0);
}

/*
** Initialize a new window.
*/

int swindow_init(	struct swindow *swindow,
					WINDOW *win,
					u_int32_t rows,
					u_int32_t cols,
					struct window_opts *wopt)
{
	set_default_win_opts(win);

	memset(swindow, 0, sizeof(*swindow));
	swindow->win = win;
	swindow->cols = cols;
	swindow->rows = rows;
	swindow->bottom_blank = rows;
	swindow->visible = 0;
	swindow->dirty = 1;

	swindow->scrollbuf_max = wopt_get_int(wopt, WOPT_SCROLLBUF_LEN);
	swindow->scroll_on_input = wopt_get_bool(wopt, WOPT_SCROLL_ON_INPUT);
	swindow->scroll_on_output = wopt_get_bool(wopt, WOPT_SCROLL_ON_OUTPUT);
	swindow->timestamp = wopt_get_bool(wopt, WOPT_TIMESTAMP);
	swindow->wordwrap = wopt_get_bool(wopt, WOPT_WORDWRAP);
	swindow->logged = wopt_get_bool(wopt, WOPT_LOG);
	swindow->logfile = wopt_get_str(wopt, WOPT_LOGFILE);

	if (swindow->scrollbuf_max < rows)
		swindow->scrollbuf_max = rows;

	if (swindow->logged)
		swindow_set_log(swindow);

	return (0);
}

/*
** We don't want the window scrolling when something is written to
** (max_col - 1, max_rows - 1), so scrolling is turned off by default.
** When we need to scroll the window, enable it, scroll the window, then
** disable it.
*/

static inline void swindow_scroll(struct swindow *swindow, int n) {
	scrollok(swindow->win, TRUE);
	wscrl(swindow->win, n);
	scrollok(swindow->win, FALSE);
	swindow->dirty = 1;
}

/*
** Prune the scroll buffer so that the number of total
** messages is not greater than swindow->scrollbuf_max.
**
*/

void swindow_prune(struct swindow *swindow) {
	int num = swindow->scrollbuf_len - swindow->scrollbuf_max;
	dlist_t *cur = swindow->scrollbuf_end;
	u_int32_t serial_top;
	u_int32_t serial_bot;

	serial_top = ((struct imsg *) swindow->scrollbuf_top->data)->serial;
	serial_bot = ((struct imsg *) swindow->scrollbuf_bot->data)->serial;

	while (cur != NULL && num > 0) {
		struct imsg *imsg = cur->data;
		dlist_t *next = cur->prev;

		/* Don't prune anything that's still on the screen */
		if (imsg->serial >= serial_top && imsg->serial <= serial_bot)
			break;

		swindow->scrollbuf_lines -= imsg->lines;
		swindow->scrollbuf_len--;
		num--;

		free(imsg->text);
		free(imsg);
		dlist_remove(swindow->scrollbuf, cur);
		cur = next;
	}

	swindow->scrollbuf_end = cur;
}

/*
** Adjust the swindow->scrollbuf_top pointer so that it's pointing to
** the line that's "n" lines from the current top of the screen.
*/

static void swindow_adjust_top(struct swindow *swindow, u_int32_t n) {
	dlist_t *cur = swindow->scrollbuf_top;

	while (1) {
		struct imsg *imsg = cur->data;
		u_int32_t visible_lines = imsg->lines;

		if (swindow->top_hidden != 0) {
			visible_lines -= swindow->top_hidden;
			swindow->top_hidden = 0;
		}

		if (visible_lines == n) {
			swindow->top_hidden = 0;
			cur = cur->prev;
			break;
		}

		if (visible_lines > n) {
			swindow->top_hidden = imsg->lines - (visible_lines - n);
			break;
		}

		n -= visible_lines;
		cur = cur->prev;
	}

	swindow->scrollbuf_top = cur;
}

/*
** Recalculate the number of lines for each message.
**
** This should be called when the window is resized
** and when timestamps are turned on or off.
*/

static void swindow_recalculate(struct swindow *swindow,
								u_int32_t old_rows,
								u_int32_t old_cols)
{
	dlist_t *cur = swindow->scrollbuf;
	u_int32_t total_lines = 0;
	struct imsg *imsg_top;
	u_int32_t old_top;

	if (swindow->scrollbuf_top == NULL) {
		swindow->bottom_blank = swindow->rows;
		return;
	}

	imsg_top = swindow->scrollbuf_top->data;
	old_top = imsg_top->lines;

	while (cur != NULL) {
		struct imsg *imsg = cur->data;

		imsg->lines = imsg_lines(swindow, imsg);
		total_lines += imsg->lines;
		cur = cur->next;
	}

	swindow->scrollbuf_lines = total_lines;

	/*
	** If the number of rows needed to display the top line has
	** changed, the number of hidden rows for the top message
	** has to be adjusted.
	**
	** The goal is for the line that was on the top of the display
	** before the resize to stay there.
	*/

	if (swindow->top_hidden != 0 && imsg_top->lines != old_top) {
		u_int32_t offset = old_cols * swindow->top_hidden;

		/*
		** If the dimensions of the window are the same, timestamps have
		** been toggled.
		*/

		if (old_cols == swindow->cols && old_rows == swindow->rows)
			offset -= TIME_HEADER_LEN;

		swindow->top_hidden = offset / swindow->cols;
	}
}

/*
** Called when the dimensions of the window have changed.
*/

void swindow_resize(struct swindow *swindow,
					u_int32_t new_row,
					u_int32_t new_col)
{
	u_int32_t old_rows = swindow->rows;
	u_int32_t old_cols = swindow->cols;
	int ret;

	/*
	** This seems to be necessary to prevent some funny stuff
	** from happening.
	*/

	wclear(swindow->win);
	wnoutrefresh(swindow->win);

	ret = wresize(swindow->win, new_row, new_col);
	if (ret == -1)
		debug("wresize: %d (%u,%u)", ret, new_row, new_col);

	swindow->rows = new_row;
	swindow->cols = new_col;

	if (swindow->scrollbuf_max < swindow->rows)
		swindow->scrollbuf_max = swindow->rows;

	swindow_recalculate(swindow, old_rows, old_cols);
	swindow_redraw(swindow);
	swindow->dirty = 1;
}

inline int swindow_refresh(struct swindow *swindow) {
	if (swindow->visible && swindow->dirty) {
		wnoutrefresh(swindow->win);
		swindow->dirty = 0;

		return (1);
	}

	return (0);
}

/*
** Redraw the window keeping the line that's currently
** at the top of the screen in place.
*/

void swindow_redraw(struct swindow *swindow) {
	dlist_t *cur = swindow->scrollbuf_top;
	u_int32_t curs_pos = 0;

	if (cur == NULL)
		return;
	/*
	** If part of the top message is scrolled off
	** the top, print the visible part and advance
	** the pointer to the next message.
	*/
	if (swindow->top_hidden != 0) {
		struct imsg *imsg = cur->data;

		swindow_print_msg(swindow, imsg, 0, 0, swindow->top_hidden + 1, -1);
		curs_pos += imsg->lines - swindow->top_hidden;
		if (curs_pos > swindow->rows) {
			swindow->bottom_blank = 0;
			swindow->bottom_hidden = curs_pos - swindow->rows;
			swindow->scrollbuf_bot = cur;
		}

		cur = cur->prev;
	}

	while (cur != NULL && curs_pos < swindow->rows) {
		struct imsg *imsg = cur->data;

		if (curs_pos + imsg->lines > swindow->rows) {
			swindow->scrollbuf_bot = cur;
			swindow->bottom_blank = 0;
			swindow->bottom_hidden = imsg->lines - (swindow->rows - curs_pos);

			swindow_print_msg(swindow, imsg, curs_pos, 0,
				1, swindow->rows - curs_pos);
			break;
		}

		swindow_print_msg(swindow, imsg, curs_pos, 0, 1, -1);
		curs_pos += imsg->lines;

		if (cur->prev == NULL) {
			swindow->scrollbuf_bot = cur;
			swindow->bottom_blank = swindow->rows - curs_pos;
			swindow->bottom_hidden = 0;
			swindow->held = 0;
		} else if (curs_pos == swindow->rows) {
			swindow->scrollbuf_bot = cur;
			swindow->bottom_hidden = 0;
			swindow->bottom_blank = 0;
		}

		cur = cur->prev;
	}

	swindow->dirty = 1;
}

/*
** It's the caller's responsibility to make sure there's nothing
** funny in the message that's being added -- things like tabs
** and other control/non-printing characters will mess up the
** display.
*/

int swindow_add(struct swindow *swindow, chtype *msg, u_int32_t timestamp) {
	struct imsg *imsg;
	chtype *msg_text;
	u_int32_t msg_line_start = 1;
	dlist_t *old_head = swindow->scrollbuf;
	int y_pos;

	/*
	** If the window is scrolled up, and the scroll on
	** output flag is set, scroll to the bottom of the
	** window before doing anything else.
	*/

	if ((swindow->scrollbuf_bot != swindow->scrollbuf ||
		swindow->bottom_hidden != 0) &&
		swindow->scroll_on_output)
	{
		swindow_scroll_to_end(swindow);
	}

	imsg = imsg_new(swindow, msg, timestamp);
	msg_text = MSG_TEXT(swindow, imsg);

	if (swindow->logged) {
		struct iovec wvec[2];
		u_int32_t msg_len = MSG_LEN(swindow, imsg);
		char *plaintext = cstr_to_plaintext(msg_text, msg_len);

		wvec[0].iov_base = plaintext;
		wvec[0].iov_len = msg_len;
		wvec[1].iov_base = "\n";
		wvec[1].iov_len = 1;

		if (writev(swindow->log_fd, wvec, 2) != (int) msg_len + 1) {
			screen_err_msg("Error writing logfile: %s",
				strerror(errno));
		}

		free(plaintext);
	}

	swindow->scrollbuf = dlist_add_head(swindow->scrollbuf, imsg);
	swindow->scrollbuf_len++;
	swindow->scrollbuf_lines += imsg->lines;

	/*
	** If this is the first message in the window, save a pointer
	** to it for use with the scrolling routines.
	*/

	if (old_head == NULL) {
		swindow->scrollbuf_end = swindow->scrollbuf;
		swindow->scrollbuf_top = swindow->scrollbuf;
	}

	/*
	** If the window is scrolled back, but the scroll
	** on output flag is not set, there's nothing left
	** to do here.
	*/

	if (old_head != swindow->scrollbuf_bot) {
		swindow->held += imsg->lines;
		swindow->activity = 1;
		return (0);
	}

	/*
	** The window is scrolling normally (i.e. the user has not scrolled up).
	** Set the current position to the new head of the list
	** (the element containing the message we just added).
	*/

	swindow->scrollbuf_bot = swindow->scrollbuf;

	if (imsg->lines <= swindow->bottom_blank) {
		y_pos = swindow->rows - swindow->bottom_blank;
		swindow->bottom_blank -= imsg->lines;
	} else {
		u_int32_t evict = imsg->lines - swindow->bottom_blank;

		swindow->bottom_blank = 0;
		swindow_adjust_top(swindow, evict);

		/*
		** The message could require more rows than the
		** screen has. In this case, print as much of the
		** message as possible such that the end of the message
		** is on the last row.
		*/

		y_pos = swindow->rows - imsg->lines;
		if (y_pos < 0) {
			y_pos = 0;
			msg_line_start = imsg->lines - swindow->rows + 1;
		}

		swindow_scroll(swindow, evict);
	}

	swindow_print_msg(swindow, imsg, y_pos, 0, msg_line_start, -1);
	swindow->dirty = 1;

	if (!swindow->visible)
		swindow->activity = 1;

	/*
	** If there's a maximum scroll buffer length, enforce it.
	*/

	if (swindow->scrollbuf_len > swindow->scrollbuf_max)
		swindow_prune(swindow);

	return (0);
}

/*
** This is a wrapper for swindow_add() used for adding
** a message to a window that was typed by the user.
*/

inline int swindow_input(struct swindow *swindow) {
	/*
	** If the window is scrolled up, and the scroll on
	** input flag is set, scroll to the bottom of the
	** window before doing anything else.
	*/

	if ((swindow->scrollbuf_bot != swindow->scrollbuf ||
		swindow->bottom_hidden != 0) &&
		swindow->scroll_on_input)
	{
		swindow_scroll_to_end(swindow);
	}

	return (0);
}

/*
** Scroll up one line.
*/

u_int32_t swindow_scroll_up(struct swindow *swindow) {
	struct imsg *imsg;
	u_int32_t top_line;

	/*
	** If it's already at the top, it can't scroll anymore.
	*/

	if (swindow->scrollbuf_top == NULL)
		return (0);

	if (swindow->scrollbuf_top->next == NULL && swindow->top_hidden == 0)
		return (0);

	/*
	** Adjust the pointer to the bottom line.
	*/

	imsg = swindow->scrollbuf_bot->data;

	/*
	** If there are blank lines at the bottom of the screen,
	** there'll be one less after scrolling up a line, and the
	** pointer to the bottom on the screen won't change.
	*/

	if (swindow->bottom_blank > 0)
		swindow->bottom_blank--;
	else {
		/*
		** Adjust the pointer to the bottom line on the screen.
		*/

		swindow->bottom_hidden++;

		if (swindow->bottom_hidden == imsg->lines) {
			swindow->bottom_hidden = 0;
			swindow->scrollbuf_bot = swindow->scrollbuf_bot->next;
		}
	}

	swindow_scroll(swindow, -1);

	if (swindow->top_hidden > 0) {
		imsg = swindow->scrollbuf_top->data;
		swindow->top_hidden--;
	} else {
		swindow->scrollbuf_top = swindow->scrollbuf_top->next;
		imsg = swindow->scrollbuf_top->data;
		swindow->top_hidden = imsg->lines - 1;
	}

	top_line = swindow->top_hidden + 1;
	swindow_print_msg(swindow, imsg, 0, 0, top_line, top_line);
	return (1);
}

/*
** Scroll the screen down by one line.
*/

u_int32_t swindow_scroll_down(struct swindow *swindow) {
	struct imsg *imsg;
	u_int32_t bottom_line;

	/*
	** If it's already at the bottom, it can't scroll anymore.
	*/

	if (swindow->scrollbuf_bot == swindow->scrollbuf &&
		swindow->bottom_hidden == 0)
	{
		swindow->held = 0;
		return (0);
	}

	/*
	** If there are blank lines at the bottom of the screen,
	** the window can't scroll down.
	*/

	if (swindow->bottom_blank > 0) {
		swindow->held = 0;
		return (0);
	}

	/*
	** Adjust the pointer to the top line on the screen.
	*/

	imsg = swindow->scrollbuf_top->data;
	swindow->top_hidden++;

	if (swindow->top_hidden == imsg->lines) {
		swindow->top_hidden = 0;
		swindow->scrollbuf_top = swindow->scrollbuf_top->prev;
	}

	swindow_scroll(swindow, 1);

	if (swindow->bottom_hidden > 0) {
		imsg = swindow->scrollbuf_bot->data;
		swindow->bottom_hidden--;
	} else {
		swindow->scrollbuf_bot = swindow->scrollbuf_bot->prev;
		imsg = swindow->scrollbuf_bot->data;
		swindow->bottom_hidden = imsg->lines - 1;
	}

	bottom_line = imsg->lines - swindow->bottom_hidden;

	swindow_print_msg(swindow, imsg, swindow->rows - 1, 0,
		bottom_line, bottom_line);

	if (swindow->held > 0)
		swindow->held--;

	return (1);
}

/*
** Scroll to the end of the scroll buffer (the bottom line).
*/

void swindow_scroll_to_end(struct swindow *swindow) {
	dlist_t *cur;
	u_int32_t lines = 0;

	if (swindow->scrollbuf == NULL)
		return;

	/* Avoid a redraw if it's already at the bottom */
	if (swindow->scrollbuf_bot == swindow->scrollbuf &&
		swindow->bottom_hidden == 0)
	{
		return;
	}

	cur = swindow->scrollbuf;
	do {
		struct imsg *imsg = cur->data;

		lines += imsg->lines;
		if (lines >= swindow->rows) {
			swindow->scrollbuf_top = cur;
			swindow->top_hidden = lines - swindow->rows;
			break;
		}

		if (cur->next == NULL) {
			swindow->scrollbuf_top = cur;
			swindow->top_hidden = 0;
			break;
		}
		cur = cur->next;
	} while (1);

	swindow->held = 0;
	wclear(swindow->win);
	swindow_redraw(swindow);
}

/*
** Scroll to the beginning of the scroll buffer (the top line).
*/

void swindow_scroll_to_start(struct swindow *swindow) {
	if (swindow->scrollbuf == NULL)
		return;

	/* Avoid a redraw if it's already at the top */
	if (swindow->scrollbuf_top == swindow->scrollbuf_end &&
		swindow->top_hidden == 0)
	{
		return;
	}

	swindow->top_hidden = 0;
	swindow->scrollbuf_top = swindow->scrollbuf_end;

	wclear(swindow->win);
	swindow_redraw(swindow);
}

/*
** This should probably be rewritten to adjust the top (and top_hidden)
** scrollbuf variables and redrawing instead of scrolling up/down one
** line at a time if "lines" is more than a few lines..
*/

void swindow_scroll_by(struct swindow *swindow, int lines) {
	if (lines < 0) {
		int i;

		lines = -lines;

		for (i = 0 ; i < lines ; i++) {
			if (swindow_scroll_up(swindow) == 0)
				break;
		}
	} else {
		int i;

		for (i = 0 ; i < lines ; i++) {
			if (swindow_scroll_down(swindow) == 0)
				break;
		}
	}
}

/*
** Print all messages in the buffer for this window that match
** the specified regular expression.
*/

int swindow_print_matching(	struct swindow *swindow,
							const char *regex,
							u_int32_t options)
{
	int cflags = REG_EXTENDED;
	regex_t preg;
	dlist_t *cur;
	dlist_t *match_list = NULL;

	if (regex == NULL)
		return (-1);

	if (options & SWINDOW_FIND_ICASE)
		cflags |= REG_ICASE;

	if (options & SWINDOW_FIND_BASIC)
		cflags &= ~REG_EXTENDED;

	if (regcomp(&preg, regex, cflags) != 0)
		return (-1);

	for (cur = swindow->scrollbuf ; cur != NULL ; cur = cur->next) {
		struct imsg *imsg = cur->data;
		char *buf;

		buf = cstr_to_plaintext(MSG_TEXT(swindow, imsg), MSG_LEN(swindow, imsg));
		if (buf != NULL) {
			if (regexec(&preg, buf, 0, NULL, 0) == 0)
				match_list = dlist_add_head(match_list, imsg);
			free(buf);
		}
	}

	regfree(&preg);

	/*
	** Better to compile a list of matches and print them after scanning
	** the whole buffer. If we started scanning at the oldest message and
	** printed matches as we traversed the scrollbuffer list, bad interactions
	** with swindow_prune() could occur.
	*/
	cur = match_list;
	while (cur != NULL) {
		dlist_t *next = cur->next;

		swindow_add(swindow, MSG_TEXT(swindow, (struct imsg *) cur->data), 0);
		free(cur);
		cur = next;
	}

	return (0);
}

/*
** Turn timestamping on or off, depending on the value of "value"
*/

inline void swindow_set_timestamp(struct swindow *swindow, u_int32_t value) {
	/*
	** If nothing's changed, there's nothing to do.
	*/

	if (value == swindow->timestamp)
		return;

	swindow->timestamp = value;
	swindow_recalculate(swindow, swindow->rows, swindow->cols);
	wclear(swindow->win);
	swindow_redraw(swindow);
}

inline void swindow_set_wordwrap(struct swindow *swindow, u_int32_t value) {
	/*
	** Allow for updating after the continued char changed but the
	** wordwrap enabled setting didn't.
	*/
	swindow->wordwrap = value;
	swindow_recalculate(swindow, swindow->rows, swindow->cols);
	wclear(swindow->win);
	swindow_redraw(swindow);
}

/*
** dump everything in the window to the specified file.
*/

int swindow_dump_buffer(struct swindow *swindow, char *file) {
	int fd;
	dlist_t *cur;
	struct iovec wvec[2];

	if (swindow->scrollbuf_end == NULL)
		return (-1);

	fd = open(file, O_WRONLY | O_CREAT | O_APPEND, 0600);
	if (fd == -1)
		return (-1);

	wvec[1].iov_base = "\n";
	wvec[1].iov_len = 1;

	for (cur = swindow->scrollbuf_end ; cur != NULL ; cur = cur->prev) {
		struct imsg *imsg = cur->data;

		wvec[0].iov_base = cstr_to_plaintext(imsg->text, imsg->len);
		wvec[0].iov_len = imsg->len;

		if (writev(fd, wvec, 2) != (int) imsg->len + 1) {
			screen_err_msg("Error writing buffer to %s: %s",
				file, strerror(errno));
		}

		free(wvec[0].iov_base);
	}

	close(fd);
	return (0);
}

/*
** Set the window to log to "logfile"
*/

void swindow_set_logfile(struct swindow *swindow, char *logfile) {
	swindow->logfile = logfile;

	/*
	** If the window is currently being logged, close
	** the log having the old filename, and open a new
	** one.
	*/

	if (swindow->logged) {
		swindow_end_log(swindow);
		swindow_set_log(swindow);
	}
}

/*
** Turn logging for this window on, and open the logfile for writing.
*/

int swindow_set_log(struct swindow *swindow) {
	int fd;
	time_t cur_time;
	struct tm *tm;
	char timebuf[128];
	u_int32_t len;

	if (swindow->logfile == NULL) {
		screen_err_msg("No logfile has been specified for this window. Specify one with /win set logfile");
		return (-1);
	}

	fd = open(swindow->logfile, O_CREAT | O_APPEND | O_WRONLY, 0600);
	if (fd == -1) {
		swindow->logged = 0;
		screen_err_msg("Unable to open %s for writing: %s",
			swindow->logfile, strerror(errno));
		return (-1);
	}

	cur_time = time(NULL);
	tm = localtime(&cur_time);

	len = strftime(timebuf, sizeof(timebuf),
		"\n---------- Log started on %a %b %d %T %Z %Y ----------\n\n", tm);
	write(fd, timebuf, len);

	swindow->log_fd = fd;
	swindow->logged = 1;

	return (0);
}

/*
** End the log for this window.
*/

void swindow_end_log(struct swindow *swindow) {
	time_t cur_time;
	struct tm *tm;
	char timebuf[128];
	u_int32_t len;

	if (swindow->log_fd == -1)
		return;

	cur_time = time(NULL);
	tm = localtime(&cur_time);

	len = strftime(timebuf, sizeof(timebuf),
		"\n---------- Log ended on %a %b %d %T %Z %Y ----------\n\n", tm);
	write(swindow->log_fd, timebuf, len);

	close(swindow->log_fd);
	swindow->logged = 0;
}

/*
** Clear the window. This amounts to filling the screen with one
** screen full of blank lines. The window can be scrolled up to
** view text that was in the window before it was cleared. When the
** window is scrolled back down, the blank lines that were inserted
** will go away.
*/

void swindow_clear(struct swindow *swindow) {
	struct imsg *imsg;

	if (swindow->scrollbuf == NULL)
		return;

	imsg = swindow->scrollbuf->data;

	swindow->scrollbuf_top = swindow->scrollbuf;
	swindow->scrollbuf_bot = swindow->scrollbuf;
	swindow->top_hidden = imsg->lines;
	swindow->bottom_hidden = 0;
	swindow->held = 0;
	swindow->bottom_blank = swindow->rows;
	swindow->dirty = 1;

	wclear(swindow->win);
}

/*
** Cleanup function for use with dlist_destroy()
*/

static void swindow_free(void *param __notused, void *data) {
	struct imsg *imsg = data;

	free(imsg->text);
	free(imsg);
}

/*
** Remove all the messages from the scroll buffer and clear
** the screen.
*/

void swindow_erase(struct swindow *swindow) {
	dlist_destroy(swindow->scrollbuf, NULL, swindow_free);

	swindow->scrollbuf = NULL;
	swindow->scrollbuf_top = NULL;
	swindow->scrollbuf_bot = NULL;
	swindow->scrollbuf_end = NULL;
	swindow->top_hidden = 0;
	swindow->bottom_hidden = 0;
	swindow->scrollbuf_len = 0;
	swindow->scrollbuf_lines = 0;
	swindow->held = 0;
	swindow->serial = 0;
	swindow->activity = 0;
	swindow->bottom_blank = swindow->rows;
	swindow->dirty = 1;

	wclear(swindow->win);
}

int swindow_destroy(struct swindow *swindow) {
	if (swindow->logged)
		swindow_end_log(swindow);

	dlist_destroy(swindow->scrollbuf, NULL, swindow_free);
	delwin(swindow->win);

	return (0);
}
