/*===========================================================================
 Copyright (c) 1998-2000, The Santa Cruz Operation
 All rights reserved.

 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:

 *Redistributions of source code must retain the above copyright notice,
 this list of conditions and the following disclaimer.

 *Redistributions in binary form must reproduce the above copyright notice,
 this list of conditions and the following disclaimer in the documentation
 and/or other materials provided with the distribution.

 *Neither name of The Santa Cruz Operation nor the names of its contributors
 may be used to endorse or promote products derived from this software
 without specific prior written permission.

 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS
 IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT falseT LIMITED TO,
 THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 CONSEQUENTIAL DAMAGES (INCLUDING, BUT falseT LIMITED TO, PROCUREMENT OF
 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 INTERRUPTION)
 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 DAMAGE.
 =========================================================================*/

/*    cscope - interactive C symbol cross-reference
 *
 *    display functions
 */

#include <ncurses.h>
#include <time.h>
#include <errno.h>

#include "global.h"
#include "build.h"
#include "colors.h"
#include "help.h"

#include "version.inc"

/* XXX */
#define MSGLINE 0							/* message line */
#define MSGCOL	0							/* message column */
static int * displine; /* screen line of displayed reference */

int filelen = sizeof("File") - 1;		/* file name display field length */
int fcnlen  = sizeof("Function") - 1;	/* function name display field length */
int numlen  = 0;						/* line number display field length */

unsigned int disprefs;						/* displayed references */
int			 field;							/* input field */
unsigned int mdisprefs;						/* maximum displayed references */
unsigned int nextline;						/* next line to be shown */
long		 searchcount;					/* count of files searched */
unsigned int totallines;					/* total reference lines */
unsigned int curdispline  = 0;
int			 current_page = 0;
int			 input_mode	  = INPUT_NORMAL;
const char	*prompts[]	  = {
		[INPUT_NORMAL] = "$ ",
		[INPUT_APPEND]						= "Append to file: ",
		[INPUT_PIPE]						= "Pipe to shell command: ",
		[INPUT_READ]						= "Read from file: ",
		[INPUT_CHANGE_TO]					= "To: ",
		[INPUT_CHANGE]						= "###"
};

unsigned int topline = 1; /* top line of page */

extern const char tooltip_winput[];
extern const char tooltip_wmode[];
extern const char tooltip_wresult[];

#define MAX(a, b) ((a) > (b) ? (a) : (b))

/* Selectable windows */
WINDOW *winput;
WINDOW *wmode;
WINDOW *wresult;
/* Non-Selectable windows */
WINDOW *whelp;
WINDOW *wtooltip;
WINDOW *wcase;
/* Selected window pointer */
WINDOW		  **current_window;
static WINDOW **last_window;

static int result_window_height;
static int second_col_width;
static int first_col_width;
static int input_window_height;
static int mode_window_height;
static int tooltip_width;

#define WRESULT_TABLE_BODY_START 4

int window_change;

static inline void display_cursor(void);
static inline void display_help(void);
static inline void display_frame(const bool border_only);
static inline void display_mode(void);
static inline void display_case(void);
static inline void display_command_field(void);
static inline void display_results(void);
static inline void display_tooltip(void);

static const char dispchars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

int dispchar2int(const char c) {
	int i = 0;
	while (dispchars[i] != c) {
		if (dispchars[i] == '\00') { return -1; }
		++i;
	}
	return i;
}

char lastmsg[MSGLEN + 1]; /* last message displayed */

struct {				  /* text of input fields */
		char *text1;
		char *text2;
} /* Paralel array to "field_searchers", indexed by "field" */
fields[FIELDS + 1] = {
	/* samuel has a search that is not part of the cscope display */
	{"Find this",	  "C symbol"							},
	{"Find this",	  "global definition"				 },
	{"Find",		 "functions called by this function"},
	{"Find",		 "functions calling this function"  },
	{"Find this",	  "text string"					   },
	{"Change this", "text string"						 },
	{"Find this",	  "egrep pattern"					 },
	{"Find this",	  "file"							 },
	{"Find",		 "files #including this file"		 },
	{"Find",		 "assignments to this symbol"		 },
	{"Find all",	 "function definitions"			   }, /* samuel only */
};

/* initialize display parameters */
void dispinit(void) {
	/* initialize the curses display package */
	initscr(); /* initialize the screen */
	start_color();
	use_default_colors();
	easy_init_pair(STD);
	easy_init_pair(FRAME);
	easy_init_pair(PROMPT);
	easy_init_pair(FIELD);
	easy_init_pair(FIELD_SELECTED);
	easy_init_pair(HELP);
	easy_init_pair(TOOLTIP);
	easy_init_pair(CASE);
	easy_init_pair(MESSAGE);
	easy_init_pair(PATTERN);
	easy_init_pair(TABLE_HEADER);
	easy_init_pair(TABLE_ID);
	easy_init_pair(TABLE_MARK);
	easy_init_pair(TABLE_COL_LINE);
	easy_init_pair(TABLE_COL_FILE);
	easy_init_pair(TABLE_COL_FUNCTION);
	easy_init_pair(TABLE_COL_TEXT);
	easy_init_pair(TABLE_ID);
	easy_init_pair(TABLE_SELECTED_ID);
	easy_init_pair(TABLE_SELECTED_MARK);
	easy_init_pair(TABLE_COL_SELECTED_LINE);
	easy_init_pair(TABLE_COL_SELECTED_FILE);
	easy_init_pair(TABLE_COL_SELECTED_FUNCTION);
	easy_init_pair(TABLE_COL_SELECTED_TEXT);
	easy_init_pair(PAGER_MSG);
	entercurses();

	/* Calculate section sizes */
	result_window_height = LINES - 2;
	input_window_height	 = 1;
	mode_window_height	 = LINES - input_window_height - 2 - 1;
	first_col_width	 = 48;	  // (((COLS - 2)%2 == 0) ? ((COLS-2)/2) : (((COLS-2)/2)+1));
	second_col_width = COLS - 2 - 1 - first_col_width;	  //((COLS - 2) / 2) - 1;
	mdisprefs		 = result_window_height - (WRESULT_TABLE_BODY_START + 1);
	tooltip_width =
		MAX(MAX(strlen(tooltip_winput), strlen(tooltip_wmode)), strlen(tooltip_wresult));
	static int case_width = sizeof("Case: XXX")-1;

	if(mdisprefs <= 0) {
		postfatal(PROGRAM_NAME ": screen too small\n");
		/* NOTREACHED */
	}
	if(mdisprefs > sizeof(dispchars)) { mdisprefs = sizeof(dispchars); }

	/* allocate the displayed line array */
	displine = malloc(mdisprefs * sizeof(*displine));

	/* readline */
	rlinit();

	/* initialize windows */
	winput	 = newwin(input_window_height, first_col_width, 1, 1);
	wmode	 = newwin(mode_window_height, first_col_width, input_window_height + 1 + 1, 1);
	wresult  = newwin(result_window_height, second_col_width, 1, first_col_width + 1 + 1);
	whelp	 = newwin(LINES - 2, COLS - 2, 1, 1);
	wtooltip = newwin(1, tooltip_width, LINES - 1, COLS - (tooltip_width + 4));
	wcase    = newwin(1, case_width, 0, COLS - case_width - 4);
	/* Set background */
	wbkgdset(winput, COLOR_PAIR(COLOR_PAIR_STD));
	wbkgdset(wmode, COLOR_PAIR(COLOR_PAIR_STD));
	wbkgdset(wresult, COLOR_PAIR(COLOR_PAIR_STD));
	wbkgdset(whelp, COLOR_PAIR(COLOR_PAIR_STD));
	wbkgdset(wtooltip, COLOR_PAIR(COLOR_PAIR_STD));
	wbkgdset(wcase, COLOR_PAIR(COLOR_PAIR_STD));

	refresh();

	last_window = &winput;
	current_window = &winput;
}

/* enter curses mode */
void entercurses(void) {
	incurses	  = true;
	window_change = CH_ALL;

	nonl();		 /* don't translate an output \n to \n\r */
	cbreak();	 /* single character input */
	noecho();	 /* don't echo input characters */
	curs_set(0);
	clear();	 /* clear the screen */
	// mouseinit(); /* initialize any mouse interface */
	// drawscrollbar(topline, nextline);
	keypad(stdscr, TRUE); /* enable the keypad */
	standend(); /* turn off reverse video */
}

/* exit curses mode */
void exitcurses(void) {
	/* clear the bottom line */
	move(LINES - 1, 0);
	clrtoeol();
	refresh();

	/* exit curses and restore the terminal modes */
	endwin();
	incurses = false;

	/* restore the mouse */
	// mousecleanup();
	fflush(stdout);
}

void force_window(void) {
	switch (input_mode) {
		case INPUT_CHANGE:
			current_window = &wresult;
		break;
		case INPUT_CHANGE_TO:
		case INPUT_APPEND:
		case INPUT_READ:
			current_window = &winput;
		break;
	}
}

static inline
void display_help(void) {
	// XXX: this could be optimized by only overriding the buffer if theres an actual
	// change
	werase(whelp);
	wmove(whelp, 0, 0);
	wattron(whelp, COLOR_PAIR(COLOR_PAIR_HELP));
	waddstr(whelp, help());
	wattroff(whelp, COLOR_PAIR(COLOR_PAIR_HELP));

	refresh();
	wrefresh(whelp);

	do_press_any_key = true;
}

static inline
void display_case(void) {
	wmove(wcase, 0, 0);
	wattron(wcase, COLOR_PAIR(COLOR_PAIR_CASE));
	waddstr(wcase, (caseless ? "Case: OFF" : "Case:  ON"));
	wattroff(wcase, COLOR_PAIR(COLOR_PAIR_CASE));
}

static inline
void display_frame(const bool border_only) {
	wattron(stdscr, COLOR_PAIR(COLOR_PAIR_FRAME));

	box(stdscr, 0, 0);
	/* Title*/
	const int LEFT_PADDING = 5;
	wmove(stdscr, 0, LEFT_PADDING);
	wprintw(stdscr, PROGRAM_NAME " version %d%s", FILEVERSION, FIXVERSION);
	/* --- */
	if(!border_only) {
		/* Vertical line */
		mvaddch(0, first_col_width + 1, ACS_TTEE);
		for(int i = 0; i < LINES - 2; i++) {
			mvaddch(i + 1, first_col_width + 1, ACS_VLINE);
		}
		mvaddch(LINES - 1, first_col_width + 1, ACS_BTEE);
		/* Horizontal line */
		wmove(stdscr, input_window_height + 1, 0);
		addch(ACS_LTEE);
		for(int i = 0; i < first_col_width; i++) {
			addch(ACS_HLINE);
		}
		addch(ACS_RTEE);
	}

	wattroff(stdscr, COLOR_PAIR(COLOR_PAIR_FRAME));
}

static inline
void display_mode(void) {
	werase(wmode);

	for(int i = 0; i < FIELDS; ++i) {
		if(i == field) {
			wattron(wmode,
				COLOR_PAIR(COLOR_PAIR_FIELD_SELECTED) | ATTRIBUTE_FIELD_SELECTED);
			mvwprintw(wmode, i, 0, "%s %s", fields[i].text1, fields[i].text2);
			wattroff(wmode,
				COLOR_PAIR(COLOR_PAIR_FIELD_SELECTED) | ATTRIBUTE_FIELD_SELECTED);
		} else {
			wattron(wmode, COLOR_PAIR(COLOR_PAIR_FIELD));
			mvwprintw(wmode, i, 0, "%s %s", fields[i].text1, fields[i].text2);
			wattroff(wmode, COLOR_PAIR(COLOR_PAIR_FIELD));
		}
	}
}

static inline
void display_command_field(void) {
	werase(winput);
	wattron(winput, COLOR_PAIR(COLOR_PAIR_PROMPT));
	mvwaddstr(winput, 0, 0, prompts[input_mode]);
	wattroff(winput, COLOR_PAIR(COLOR_PAIR_PROMPT));
	waddstr(winput, rl_line_buffer);

	display_cursor();
}

static inline
void display_results(void) {
	int	  i;
	char *s;
	int	  screenline;			/* screen line number */
	int	  srctxtw;				/* source line display width */
	int	  color_swp;			/* holds the rigth ncurses color value,
								 * so we dont have to branch twice
								 * (at attron & attroff)
								 * because of selections
								 */
	int attr_swp;				/* holds the rigth ncurses attribute value,
								 * so we dont have to branch twice
								 * (at attron & attroff)
								 * because of selections
								 */
								/* column headings */
	char  file[PATHLEN + 1];	/* file name */
	char  function[PATLEN + 1]; /* function name */
	char  linenum[NUMLEN + 1];	/* line number */

	werase(wresult);
	nextline   = 1;

	/* --- Display the message --- */
	if(totallines == 0) {	 // Its a real message
		wmove(wresult, MSGLINE, 0);
		wclrtoeol(wresult);
		wattron(wresult, COLOR_PAIR(COLOR_PAIR_MESSAGE));
		waddstr(wresult, lastmsg);
		wattroff(wresult, COLOR_PAIR(COLOR_PAIR_MESSAGE));
		return;
	}
	if(input_mode == INPUT_CHANGE) {	// Its a pattern
		snprintf(lastmsg, MSGLEN, "Change \"%s\" to \"%s\"", input_line, newpat);
	} else {
		snprintf(lastmsg,
			MSGLEN,
			"%c%s: %s",
			toupper((unsigned char)fields[field].text2[0]),
			fields[field].text2 + 1,
			input_line);
	}
	wattron(wresult, COLOR_PAIR(COLOR_PAIR_PATTERN));
	waddstr(wresult, lastmsg);
	wattroff(wresult, COLOR_PAIR(COLOR_PAIR_PATTERN));

	/* --- Display the column headings --- */
	wattron(wresult, COLOR_PAIR(COLOR_PAIR_TABLE_HEADER));
	wmove(wresult, 2, 2);
	if (dispcomponents > 0) { wprintw(wresult, "%-*s ", filelen, "File"); }

	if (field == SYMBOL || field == CALLEDBY || field == CALLING) {
		wprintw(wresult, "%-*s ", fcnlen, "Function");
	}
	if (field != FILENAME) { waddstr(wresult, "Line"); }
	wattroff(wresult, COLOR_PAIR(COLOR_PAIR_TABLE_HEADER));

	/* --- Display table entries --- */
	wmove(wresult, WRESULT_TABLE_BODY_START, 0);

	/* calculate the source text column */
	/* NOTE: the +1s are column gaps */
	srctxtw = second_col_width;
	srctxtw -= 1 + 1;	 // dispchars
	if(dispcomponents > 0) { srctxtw -= filelen + 1; }
	if(field == SYMBOL || field == CALLEDBY || field == CALLING) {
		srctxtw -= fcnlen + 1;
	}
	srctxtw -= numlen + 1;

	/* decide where to list from */
	{
		int seekerr;
		do {
			seekerr = seekpage(current_page);
		} while(seekerr == -1 && current_page--);
	}

	/* until the max references have been displayed or
	   there is no more room */
	for(disprefs = 0, screenline = WRESULT_TABLE_BODY_START;
		disprefs < mdisprefs && screenline < (result_window_height - 1);
		++disprefs, ++screenline) {
		attr_swp = (disprefs != curdispline) ? A_NORMAL : ATTRIBUTE_RESULT_SELECTED;
		wattron(wresult, attr_swp);
		/* read the reference line */
		if(fscanf(refsfound,
			   "%" PATHLEN_STR "s%" PATHLEN_STR "s%" NUMLEN_STR "s %" TEMPSTRING_LEN_STR
			   "[^\n]",
			   file,
			   function,
			   linenum,
			   tempstring) < 4) {
			break;
		}

		++nextline;
		displine[disprefs] = screenline;

		color_swp = (disprefs != curdispline) ? COLOR_PAIR_TABLE_ID :
												COLOR_PAIR_TABLE_SELECTED_ID;
		wattron(wresult, COLOR_PAIR(color_swp));
		wprintw(wresult, "%c", dispchars[disprefs]);
		wattroff(wresult, COLOR_PAIR(color_swp));

		/* display any change mark */
		color_swp = (disprefs != curdispline) ? COLOR_PAIR_TABLE_MARK :
												COLOR_PAIR_TABLE_SELECTED_MARK;
		wattron(wresult, COLOR_PAIR(color_swp));
		if(input_mode == INPUT_CHANGE && change[topref + disprefs]) {
			waddch(wresult, '>');
		} else {
			waddch(wresult, ' ');
		}
		wattroff(wresult, COLOR_PAIR(color_swp));

		/* display the file name */
		color_swp = (disprefs != curdispline) ? COLOR_PAIR_TABLE_COL_FILE :
												COLOR_PAIR_TABLE_COL_SELECTED_FILE;
		wattron(wresult, COLOR_PAIR(color_swp));
		if(field == FILENAME) {
			wprintw(wresult, "%-*s ", filelen, file);
		} else {
			/* display the requested path components */
			if(dispcomponents > 0) {
				wprintw(wresult,
					"%-*.*s ",
					filelen,
					filelen,
					pathcomponents(file, dispcomponents));
			}
		} /* else(field == FILENAME) */
		wattroff(wresult, COLOR_PAIR(color_swp));

		/* display the function name */
		if(field == SYMBOL || field == CALLEDBY || field == CALLING) {
			color_swp = (disprefs != curdispline) ?
							COLOR_PAIR_TABLE_COL_FUNCTION :
							COLOR_PAIR_TABLE_COL_SELECTED_FUNCTION;
			wattron(wresult, COLOR_PAIR(color_swp));
			wprintw(wresult, "%-*.*s ", fcnlen, fcnlen, function);
			wattroff(wresult, COLOR_PAIR(color_swp));
		}
		if(field == FILENAME) {
			waddch(wresult, '\n'); /* go to next line */
			continue;
		}

		/* display the line number */
		color_swp = (disprefs != curdispline) ? COLOR_PAIR_TABLE_COL_LINE :
												COLOR_PAIR_TABLE_COL_SELECTED_LINE;
		wattron(wresult, COLOR_PAIR(color_swp));
		wprintw(wresult, "%*s ", numlen, linenum);
		wattroff(wresult, COLOR_PAIR(color_swp));
		/* there may be tabs in egrep output */
		while((s = strchr(tempstring, '\t')) != NULL) {
			*s = ' ';
		}

		/* display the source line */
		color_swp = (disprefs != curdispline) ? COLOR_PAIR_TABLE_COL_TEXT :
												COLOR_PAIR_TABLE_COL_SELECTED_TEXT;
		wattron(wresult, COLOR_PAIR(color_swp));
		s = tempstring;
		for(;;) {
			/* if the source line does not fit */
			if((i = strlen(s)) > srctxtw) {

				/* find the nearest blank */
				for(i = srctxtw; s[i] != ' ' && i > 0; --i) {
					;
				}

				if(i == 0) { i = srctxtw; /* no blank */ }
			}
			/* print up to this point */
			wprintw(wresult, "%.*s", i, s);
			s += i;

			/* if line didn't wrap around */
			if(i < srctxtw) { waddch(wresult, '\n'); /* go to next line */ }
			/* skip blanks */
			while(*s == ' ') {
				++s;
			}
			/* see if there is more text */
			if(*s == '\0') { break; }
			/* if the source line is too long */
			if(++screenline > result_window_height) {

				/* if this is the first displayed line,
				   display what will fit on the screen */
				if(topref == (nextline-1)) {
					disprefs++;
					/* break out of two loops */
					goto endrefs;
				}

				/* erase the reference */
				while(--screenline >= displine[disprefs]) {
					wmove(wresult, screenline, 0);
					wclrtoeol(wresult);
				}
				++screenline;

				/* go back to the beginning of this reference */
				--nextline;
				fseek(refsfound, 0, SEEK_SET);
				goto endrefs;
			}
			/* indent the continued source line */
			wmove(wresult, screenline, second_col_width - srctxtw);
		} /* for(ever) */
		wattroff(wresult, COLOR_PAIR(color_swp));
		wattroff(wresult, attr_swp);
	} /* for(reference output lines) */

endrefs:
	wattroff(wresult, attr_swp);
	/* --- display pager message --- */
	/* position cursor */
	i = result_window_height - 1;
	if(screenline < i) {
		waddch(wresult, '\n');
	} else {
		wmove(wresult, i, 0);
	}
	/**/
	wattron(wresult, COLOR_PAIR(COLOR_PAIR_PAGER_MSG));
	/* check for more references */
	i		   = totallines - nextline + 1;
	if(i > 0) {
		wprintw(wresult,
			"* Lines %d-%d of %d, %d more. *",
			topref,
			topref + nextline,
			totallines,
			i);
	}
	/* if this is the last page of references */
	else if(current_page > 0 && nextline > totallines) {
		waddstr(wresult, "* End of results. *");
	}
	wattroff(wresult, COLOR_PAIR(COLOR_PAIR_PAGER_MSG));
}

static inline
void display_cursor(void) {
	chtype i;
	int	   yoffset = 0, xoffset = 0;

	xoffset = strlen(prompts[input_mode]) + rl_point;

	wmove(*current_window, yoffset, xoffset);

	i = winch(*current_window);
	i |= A_REVERSE;
	waddch(*current_window, i);
}

void horswp_window(void) {
	if (input_mode != INPUT_NORMAL) { return; }

	if(current_window != &wresult) {
		if(totallines == 0) { return; }
		if(current_window == &winput) {
			window_change |= CH_INPUT;
		} else {
			window_change |= CH_MODE;
		}
		last_window	   = current_window;
		current_window = &wresult;
	} else {
		current_window = last_window;
		if(current_window == &winput) { window_change |= CH_INPUT; }
	}

	window_change |= CH_RESULT;
}

void verswp_window(void) {
	if(current_window == &wresult) { return; }
	current_window = (current_window == &winput) ? &wmode : &winput;
	window_change |= CH_INPUT | CH_MODE;
}

/* display search progress with default custom format */
void progress(char *what, long current, long max) {
	static long start;
	long		now;
	int			i;

	/* save the start time */
	if(searchcount == 0) { start = time(NULL); }
	if((now = time(NULL)) - start >= 1) {
		if(linemode == false) {
			wmove(wresult, MSGLINE, MSGCOL);
			wclrtoeol(wresult);
			waddstr(wresult, what);
			snprintf(lastmsg, sizeof(lastmsg), "%ld", current);
			wmove(wresult, MSGLINE, (COLS / 2) - (strlen(lastmsg) / 2));
			waddstr(wresult, lastmsg);
			snprintf(lastmsg, sizeof(lastmsg), "%ld", max);
			wmove(wresult, MSGLINE, COLS - strlen(lastmsg));
			waddstr(wresult, lastmsg);
			refresh();
		} else if(verbosemode == true) {
			snprintf(lastmsg, sizeof(lastmsg), "> %s %ld of %ld", what, current, max);
		}

		start = now;
		if((linemode == false) && (incurses == true)) {
			wmove(wresult, MSGLINE, MSGCOL);
			i = (float)COLS * (float)current / (float)max;

			standout();
			for(; i > 0; i--)
				waddch(wresult, inch());
			standend();
			refresh();
		} else if(linemode == false || verbosemode == true) {
			postmsg(lastmsg);
		}
	}
	++searchcount;
}

/* print error message on system call failure */
void myperror(char *text) {
	char *s;

	s = strerror(errno);

	(void)snprintf(lastmsg, sizeof(lastmsg), "%s: %s", text, s);
	postmsg(lastmsg);
}

/* postmsg clears the message line and prints the message */
void postmsg(char *msg) {
	if (linemode == true
    ||  incurses == false) {
        puts(msg);
		fflush(stdout);
	} else {
		window_change |= CH_RESULT;
	}
	UNUSED(strncpy(lastmsg, msg, sizeof(lastmsg) - 1));
}

/* clearmsg2 clears the second message line */
void clearmsg2(void) {
	if(linemode == false) {
		wmove(wresult, MSGLINE + 1, 0);
		wclrtoeol(wresult);
	}
}

/* postmsg2 clears the second message line and prints the message */
void postmsg2(char *msg) {
	if(linemode == true) {
		(void)printf("%s\n", msg);
	} else {
		clearmsg2();
		waddstr(wresult, msg);
		wrefresh(wresult);
	}
}

/* display an error mesg - stdout or on second msg line */
void posterr(char *msg, ...) {
	va_list ap;
	char	errbuf[MSGLEN];

	va_start(ap, msg);
	if(linemode == true || incurses == false) {
		(void)vfprintf(stderr, msg, ap);
		(void)fputc('\n', stderr);
	} else {
		vsnprintf(errbuf, sizeof(errbuf), msg, ap);
		postmsg2(errbuf);
	}
	va_end(ap);
}

/* display a fatal error mesg -- stderr *after* shutting down curses */
void postfatal(const char *msg, ...) {

	if(incurses == true) { exitcurses(); }

	va_list ap;
	va_start(ap, msg);
	vfprintf(stderr, msg, ap);
	va_end(ap);

	myexit(1);
}

static inline
void display_tooltip(void) {
	wmove(wtooltip, 0, 0);
	const char *tooltip;
	if(*current_window == winput) {
		tooltip = tooltip_winput;
	} else if(*current_window == wmode) {
		tooltip = tooltip_wmode;
	} else if(*current_window == wresult) {
		tooltip = tooltip_wresult;
	}
	wattron(wtooltip, COLOR_PAIR(COLOR_PAIR_TOOLTIP));
	waddstr(wtooltip, tooltip);
	// XXX: cheap hack
	for(size_t i = 0; i < (tooltip_width - strlen(tooltip)); i++) {
		waddch(wtooltip, ' ');
	}
	wattroff(wtooltip, COLOR_PAIR(COLOR_PAIR_TOOLTIP));
}

void display(void) {
	// drawscrollbar(topline, nextline);    /* display the scrollbar */
	static void *lstwin = NULL; /* for the tooltip (see below) */

	if(window_change) {
		if(window_change == CH_HELP) {
			display_frame(true);
			display_help();

			window_change = CH_ALL;
			/* Since whelp is not Selectable,
			 * trigger the tooltip redisplay
			 * this way
			*/
			lstwin = NULL;
			/* Do not display over the help msg and
			 *  rely on setting CH_ALL for the next display
			 */
			return;
		}
		if(window_change == CH_ALL) { display_frame(false); }
		/* As it stands the tooltip has to be redisplayed
		 *  on every window change.
		 */
		if(lstwin != *current_window) {
			lstwin = *current_window;
			display_tooltip();
		}
		if(window_change & CH_CASE) { display_case(); }
		if(window_change & CH_INPUT) { display_command_field(); }
		if(window_change & CH_RESULT) { display_results(); }
		if(window_change & CH_MODE) { display_mode(); }

		refresh();
		wnoutrefresh(winput);
		wnoutrefresh(wmode);
		wnoutrefresh(wresult);
		wnoutrefresh(wtooltip);
		wnoutrefresh(wcase);
		doupdate();
	}

	window_change = CH_NONE;
}

void redisplay(void) {
	delwin(winput);
	delwin(wmode);
	delwin(wresult);
	delwin(whelp );
	delwin(wtooltip);
	delwin(wcase);

	endwin();

	dispinit();

	window_change = CH_ALL;

	display();
}
