/***************************************************************************
 *  Pinfo is a ncurses based lynx style info documentation browser
 *
 *  Copyright (C) 1999  Przemek Borys <pborys@dione.ids.pl>
 *  Copyright (C) 2005  Bas Zoetekouw <bas@debian.org>
 *  Copyright 2005  Nathanael Nerode <neroden@gcc.gnu.org>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of version 2 of the GNU General Public License as
 *  published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
 *  USA
 ***************************************************************************/
#include "common_includes.h"

#include <string>
using std::string;
#include <vector>
using std::vector;
#include <exception> // For 'throw'
#include <stdexcept> // For std::invalid_argument
#include <cctype> // For isdigit

#include "colors.h"
#include "curse_utils.h"
#include "filehandling_functions.h"
#include "initializelinks.h"
#include "keyboard.h"
#include "mainfunction.h"
#include "printinfo.h"
#include "regexp_search.h"
#include "utils.h"
#include "video.h"

/* File-scope globals.  FIXME */

/*
 * this flag is turned on when the engine receives a simulated `key.back',
 * caused by the sequential auto-pgdn reading code
 */
int toggled_by_menu = 0;
long pos;
long infomenu;
long infocolumn=0;

int cursor;

/* Line found by global search, to jump to.  -1 if not after global search. */
int found_line = -1;

/* 
 * Inline support functions formerly in menu_and_note_utils.cxx
 */

/*
 * Read the `$foo' header entry
 */
#define ERRNODE "ERR@!#$$@#!%%^#@!OR"
static inline string
get_foo_node(const char * const foo, const string & type)
{
	string::size_type start_idx = type.find(foo);
	if (start_idx == string::npos) {
		return string(ERRNODE);
	}

	start_idx += strlen(foo);
	string::size_type end_idx = type.find_first_of(",\n", start_idx);
	if (end_idx != string::npos) {
		return type.substr(start_idx, end_idx - start_idx);
	}
	throw std::invalid_argument("Unending line in get_foo_node");
}

/* read the `Next:' header entry */
static inline string
getnextnode(const string & type)
{
	return get_foo_node("Next: ", type);
}

/* read the `Prev:' header entry */
static inline string
getprevnode(const string & type)
{
	return get_foo_node("Prev: ", type);
}

/* read the `Up:' header entry */
static inline string
getupnode(const string & type)
{
	return get_foo_node("Up: ", type);
}

/* read the `Node:' header entry */
static inline string
getnodename(const string & type)
{
	return get_foo_node("Node: ", type);
}

/* More support functions */
static void
next_infomenu()
{
	if (hyperobjects.size() == 0) {
		infomenu = -1;
		return;
	}
	for (typeof(hyperobjects.size()) i = infomenu + 1;
	     i < hyperobjects.size(); i++) {
		if (hyperobjects[i].type <= 1) { /* menu item */
			infomenu = i;
			return;
		}
	}
	infomenu = -1;		/* no more menuitems found */
}

static void
rescan_cursor()
{
	for (typeof(hyperobjects.size()) i = 0; i < hyperobjects.size(); i++)
	{
		if ((hyperobjects[i].line >= pos) &&
				(hyperobjects[i].line < pos + lines_visible))
		{
			if (hyperobjects[i].type < HIGHLIGHT)
			{
				cursor = i;
				break;
			}
		}
	}
}

/*
 * Subroutine of totalsearch.
 */
static int
getnodeoffset(int tag_table_pos,
              typeof(indirect.size())& indirectstart)
							/* count node offset in file */
{
	int fileoffset = 0;
	if (!indirect.empty())
	{
		/* signed/unsigned.  Use iterators. FIXME */
		for (int i = indirect.size() - 1; i >= 0; i--)
		{
			if (indirect[i].offset <= tag_table[tag_table_pos].offset)
			{
				fileoffset +=(tag_table[tag_table_pos].offset - indirect[i].offset + FirstNodeOffset);
				indirectstart = i;
				break;
			}
		}
	}
	else
	{
		fileoffset +=(tag_table[tag_table_pos].offset - 2);
	}
	return fileoffset;
}


static void
do_totalsearch(const vector<string> & my_message,
               const string & type_str,
               FILE * id,
               const int & tag_table_pos,
               bool & skipsearch,
               int & found_line,
               int & return_value)
{
	skipsearch = false;
	typeof(indirect.size()) indirectstart = -1;
	int fileoffset;
	move(maxy - 1, 0);
	attrset(bottomline);
	echo();
	curs_set(1);
	string token_string;
	if (searchagain.search) {
		/* it IS searchagain */
		/* copy the token from searchagain buffer */
		token_string = searchagain.lastsearch;
		/* reset the searchagain switch (until it's set again
		 * by the keys.searchagain key handler) */
		searchagain.search = 0;
	} else {
		/* if searchagain key wasn't hit */
		token_string = getstring(_("Enter regular expression: "));
		/* save it to searchagain buffer */
		searchagain.lastsearch = token_string;	
		/*
		 * give a hint, which key to ungetch to call this procedure
		 * by searchagain
		 */
		searchagain.type = keys.totalsearch_1;
	}
	if (token_string == "") {
		skipsearch = true;
		return;
	}
	curs_set(0);
	noecho();
	attrset(normal);

	/* Calculate current info file offset...  */
	fileoffset = 0;
	for (int i = 0; i <= pos; i++)	{
		/* count the length of current node
		 * up to and including current line
		 */
		fileoffset += my_message[i].length();
	}
	fileoffset += type_str.length();	/* add also header length */

	fileoffset += getnodeoffset(tag_table_pos, indirectstart);	/* also load the variable indirectstart */

	/* Searching part...  */
	found_line = -1;

	/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
	return_value = -1;
	/* the info is of indirect type; we'll search through several files */
	if (!indirect.empty()) {
		FILE *fd = NULL;
		long tokenpos;
		long starttokenpos;
		long filelen;
		/* Signed/unsigned issues. */
		for (signed int j = indirectstart; j < indirect.size(); j++) {
			fd = openinfo(indirect[j].filename, 1);	/* get file length. */
			fseek(fd, 0, SEEK_END);
			filelen = ftell(fd);

			/*
			 * seek to the beginning of search area. At the first
			 * time it is `fileoffset', then it is the first node's
			 * offset
			 */
			if (j == indirectstart) {
				fseek(fd, fileoffset, SEEK_SET);
			} else {
				fseek(fd, FirstNodeOffset, SEEK_SET);
			}
			starttokenpos = ftell(fd);

			char *tmp = new char[filelen - starttokenpos + 10];
			/* read data */
			fread(tmp, 1, filelen - starttokenpos, fd);
			tmp[filelen - starttokenpos + 1] = 0;

			tokenpos = regexp_search(token_string.c_str(), tmp);	/* search */

			if (tokenpos != -1)	{
				/* something was found */
				/*
				 * add the offset of the part of file, which wasn't
				 * read to the memory
				 */
				tokenpos += starttokenpos;
				{	/* local scope for tmpvar, matched */
					int tmpvar = -1;
					int matched = 0;
					for (int i = tag_table.size() - 1; i >= 0; i--) {
						if ((tag_table[i].offset > tag_table[tmpvar].offset) &&
								((tag_table[i].offset - indirect[j].offset + FirstNodeOffset) <= tokenpos))
						{
							return_value = i;
							tmpvar = i;
							matched = 1;
						}
					}
				}
				if (return_value != -1) {
					/* this means, that indirect entry was found.  */
					fseek(fd, tag_table[return_value].offset - indirect[j].offset + FirstNodeOffset, SEEK_SET);
					/* seek to the found node offset */
					while (fgetc(fd) != INFO_TAG);
					fgetc(fd);	/* skip newline */

					found_line = 0;

					/*
					 * count how many lines are before the token line.
					 */
					while (ftell(fd) < tokenpos) {
						int chr = fgetc(fd);
						if (chr == '\n') {
							found_line++;
						} else if (chr == EOF) {
							break;
						}
					}
					/*
					 * the number of the line where the token was found, is
					 * now in the variable `found_line'
					 */
					/* something was found */
					if (tmp) {
						delete [] tmp;
						tmp = 0;
					}
					break;
					/* end: if (indirect entry was found) */
				}	
				/* end: if (tokenpos) */
			}
			if (tmp) {
				delete [] tmp;
				tmp = 0;
			}
			/* end: indirect file loop */
		}
		fclose(fd);
	  /* end: if (indirect) */
	} else {
		/* if not indirect */
		/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
		long filelen;
		long filepos = ftell(id);
		long tokenpos;
		long starttokenpos;

		fseek(id, 0, SEEK_END);	/* calculate filelength */
		filelen = ftell(id);

		/* seek at the start of search area. */
		fseek(id, fileoffset, SEEK_SET);

		/* remember the number of skipped bytes.*/
		starttokenpos = ftell(id);

		char *tmp;
		/* read data */
		tmp = new char[filelen - starttokenpos + 10]; /* FIXME */
		fread(tmp, 1, filelen - starttokenpos, id);
		tmp[filelen - starttokenpos + 1] = 0;

		/* search */
		tokenpos = regexp_search(token_string.c_str(), tmp);

		if (tokenpos != -1)	{
			/* we've found something */
			/*
			 * add offset of the start of search area to this token
			 * position.
			 */
			tokenpos += starttokenpos;
			{		/* local scope for tmpvar, matched */
				int tmpvar = -1, matched = 0;
				for (int i = tag_table.size() - 1; i >= 0; i--)
				{
					if ((tag_table[i].offset > tag_table[tmpvar].offset) &&
							(tag_table[i].offset <= tokenpos))
					{
						return_value = i;
						tmpvar = i;
						matched = 1;
					}
				}
			}
			/*
			 * this means, that we've found our entry, and we're
			 * one position too far with the `i' counter.
			 */
			if (return_value != -1) {
				fseek(id, tag_table[return_value].offset, SEEK_SET);
				/* seek to the node, which holds found line */
				while (fgetc(id) != INFO_TAG);
				fgetc(id);	/* skip newline */

				found_line = 0;
				/* count lines in found node, until found line is
				 * met. */
				while (ftell(id) < tokenpos) {
					int chr = fgetc(id);
					if (chr == '\n') {
						found_line++;
					}	else if (chr == EOF) {
						break;
					}
				}
				/* seek to old filepos. */
				fseek(id, filepos, SEEK_SET);	
			}
			/* end: if (tokenpos) <--> token found */
		}
		if (tmp) {
			delete [] tmp;
			tmp = 0;
		}
		/* end: if (!indirect) */
	}
	return;
}


/*
 * Main work function
 */
WorkRVal
work(const vector<string>& my_message,
     const string & type_str,
     FILE * id, 
     int tag_table_pos)
{
	static WorkRVal rval;
	int key = 0;
	int return_value;
	bool statusline_locked = false;

	/* reset the static return value variable */
	rval.file = "";
	rval.node = "";
	rval.keep_going = false; /* Important */

	pos = 0;
	cursor = 0;
	infomenu = -1;	/* default position, and selected number */

#ifdef getmaxyx
	getmaxyx(stdscr, maxy, maxx);	/* initialize maxx, maxy */
#else
	maxx = 80;
	maxy = 25;
#endif /*  getmaxyx */
	/* Clear old hyperlink info */
	hyperobjects.clear();
	/* initialize node-links for every line */
	for (int i = 0; i < my_message.size() - 1; i++)
	{
		initializelinks(my_message[i], my_message[i + 1], i);
	}
	initializelinks(my_message[my_message.size() - 1],"",
	                my_message.size() - 1);

	/* infomenu will remain -1 if it's the last pos, or if there's no menu item */
	next_infomenu();

	if (npos != -1)
		pos = npos;			/* set eventual history pos */

	/* if we're in a node found using 's'earch function. */
	if (found_line != -1)
	{
		/* set pos to the found position */
		pos = found_line - 1; /* FIXME: lurking off-by-one issue */
		found_line = -1;
	}

	if (ncursor != -1) {
		cursor = ncursor;		/* set eventual cursor pos  */
		infomenu = nmenu;		/* same with last sequential reading menu pos */
	} else {
		rescan_cursor();		/* scan for cursor position */
	}
	if (toggled_by_menu) {
		/* this node will not be shown to the user--it shouldn't go to history */
		/* delete the history entry for this node--it's not even seen by the user */
		dellastinfohistory();
	}
	npos = -1;			/* turn off the `next-time' pos/cursor modifiers */
	ncursor = -1;
	nmenu = -1;
	addtopline(type_str,infocolumn);
	while (1) {
		/*
		 * read key, and show screen only if there is nothing in the input
		 * buffer.  Otherwise the scrolling would be too slow.
		 */
		nodelay(stdscr, TRUE);
		key = pinfo_getch();
		if (key == ERR)
		{
			if (!statusline_locked) {
				showscreen(my_message, pos, cursor, infocolumn);
			}
			waitforgetch();
			key = pinfo_getch();
		}
		nodelay(stdscr, FALSE);
		statusline_locked = false;
		if (winchanged)		/* SIGWINCH */
		{
			handlewinch();
			winchanged = 0;
			addtopline(type_str,infocolumn);
			key = pinfo_getch();
		}
		/***************************** keyboard handling ****************************/
		if (key != 0)
		{
			if ((key == keys.print_1) ||
					(key == keys.print_2))
			{
				if (yesno(_("Are you sure you want to print?"), 0) == 1) {
					printnode(my_message);
				}
			}
			/*==========================================================================*/
			if ((key == keys.pgdn_auto_1) ||
					(key == keys.pgdn_auto_2) ||
					(toggled_by_menu))
			{
				/* FIXME: This depends on a weird fallthrough to keys.pgdn in
				 * the 'normal' case.
				 */
				int wastoggled = toggled_by_menu;
				toggled_by_menu = 0;
				/* if hyperobject type <= 1, then we have a menu */
				if ((pos + lines_visible >= my_message.size()) || wastoggled) {
					/* Either toggled, or the end of the node is on the screen */
					if ((infomenu != -1) &&(!wastoggled))
					{
						cursor = infomenu;
						key = keys.followlink_1;	/* the handler for keys.followlink must be bellow this statement! */
					}
					else
						/* we shouldn't select a menu item if this node is called via `up:' from bottom, or if there is no menu */					{
						string next_node_name = getnextnode(type_str);
						if (next_node_name != ERRNODE) {
							key = keys.nextnode_1;
						}	else {
							string node_name = getnodename(type_str);
							if (FirstNodeName != node_name)	/* if it's not end of all menus */
							{
								if (wastoggled)	/* if we're in the temporary called up node */
									toggled_by_menu = KILL_HISTORY;
								else	/* if we are calling the up node from non-temporary bottom node */
									toggled_by_menu = KEEP_HISTORY;
								key = keys.upnode_1;
								ungetch(KEY_NOTHING);
							}
						}	/* end: else if nextnode==ERRNODE */
					}		/* end: if we shouldn't select a menu item */
				}		/* end: if position is right */
			}
			/*==========================================================================*/
			if ((key == keys.goline_1) ||
					(key == keys.goline_2))
			{
				long newpos;
				attrset(bottomline);	/* read user's value */
				move(maxy - 1, 0);
				echo();
				curs_set(1);
				string token_string = getstring(_("Enter line: "));
				curs_set(0);
				noecho();
				move(maxy - 1, 0);
				myclrtoeol();
				attrset(normal);
				if (token_string != "") {
					/*
					 * convert string to long.
					 * careful with nondigit strings.
					 */
					bool digit_val = true;
					for (string::size_type i = 0; i < token_string.length(); i++) {
						if (!isdigit(token_string[i]))
							digit_val = false;
					}
					if (digit_val) {
						/* go to specified line (as bottom line, 1-based) */
						newpos = atol(token_string.c_str());
						newpos -= lines_visible; /* Get top line, 0-based */
						if (newpos < 0) {
							pos = 0;
						} else if (newpos + lines_visible <= my_message.size()) {
							pos = newpos;
						} else if (lines_visible < my_message.size()) {
							pos = my_message.size() - lines_visible;
						} else { /* very short message */
							pos = 0;
						}
					}
				}
			}
			/*==========================================================================*/
			if ((key == keys.shellfeed_1) ||
					(key == keys.shellfeed_2))
			{
				FILE * pipe = NULL;
				/* get command name */
				attrset(bottomline);
				move(maxy - 1, 0);
				echo();
				curs_set(1);
				string token_string = getstring(_("Enter command: "));
				noecho();
				move(maxy - 1, 0);
				myclrtoeol();
				attrset(normal);

				myendwin();
				system("clear");
				pipe = popen(token_string.c_str(), "w");	/* open pipe */
				if (pipe != NULL) {
					/* and flush the msg to stdin */
					for (int i = 0; i < my_message.size(); i++)	
						fprintf(pipe, "%s", my_message[i].c_str());
					pclose(pipe);
					getchar();
				}
				doupdate();
				curs_set(0);
				if (pipe == NULL)
					mvaddstr(maxy - 1, 0, _("Operation failed..."));
			}
			/*==========================================================================*/
			if ((key == keys.dirpage_1) ||
					(key == keys.dirpage_2))
			{
				rval.file = "dir";
				rval.node = "";
				rval.keep_going = true;
				return rval;
			}
			/*==========================================================================*/
			if ((key == keys.refresh_1) ||
					(key == keys.refresh_2))
			{
				myendwin();
				doupdate();
				refresh();
				curs_set(0);
			}
			/*==========================================================================*/
			if ((key == keys.totalsearch_1) ||	/* search in all nodes later than this one */
					(key == keys.totalsearch_2))
			{
				int tmpfound_line = found_line; /* Save and restore */

				bool skipsearch = false;
				do_totalsearch(my_message, type_str, id, tag_table_pos,
				               skipsearch, found_line, return_value);
				if (!skipsearch) {
					if (found_line == -1) {
						attrset(bottomline);
            mymvhline(maxy - 1, 0, ' ', maxx);
            move(maxy - 1, 0);
						mvaddstr(maxy - 1, 0, _("Search string not found..."));
						statusline_locked = true;
					}

					if (return_value != -1) {
						infohistory[infohistory.size() - 1].pos = pos;
						infohistory[infohistory.size() - 1].cursor = cursor;
						infohistory[infohistory.size() - 1].menu = infomenu;
						rval.node = tag_table[return_value].nodename;
						rval.file = "";
						rval.keep_going = true;
						regex_is_global = true;
						regex_is_current = true;
						return rval;
					}
				}
				found_line = tmpfound_line;
				/* end: if key_totalsearch */
			}
			/*==========================================================================*/
			if ((key == keys.search_1) ||		/* search in current node */
					(key == keys.search_2))
			{
				int success = 0;
				move(maxy - 1, 0);
				attrset(bottomline);
				echo();
				curs_set(1);
				string token_string;
				if (searchagain.search) {
					token_string = searchagain.lastsearch;
					searchagain.search = 0;
				} else {
					token_string = getstring(_("Enter regular expression: "));
					searchagain.lastsearch = token_string;
					searchagain.type = key;
				}
				if (token_string == "") {
					goto skip_search;
				}
				curs_set(0);
				noecho();
				attrset(normal);
				/* compile the read token */
				if (pinfo_re_comp(token_string.c_str()) != 0)
				{
					/* print error message */
					attrset(bottomline);
					mymvhline(maxy - 1, 0, ' ', maxx);
					move(maxy - 1, 0);
					printw(_("Invalid regular expression;"));
					printw(" ");
					printw(_("Press any key to continue..."));
					getch();
					goto skip_search;
					
				}
				/* scan for the token in the following lines.  */
				for (int i = pos + 1; i < my_message.size(); i++) {
					/*
					 * TODO: handle matches over a long sequence of lines.
					 * Requires a *totally* different code structure; the old
					 * 'two-line' code was no good.  FIXME.
					 */
					if (pinfo_re_exec(my_message[i].c_str()))	{
						/* if found, enter here */
						success = 1;
						pos = i;
						regex_is_current = true;
						regex_is_global = false;
						break;
					}
				}
				if (!success)
				{
					attrset(bottomline);
          mymvhline(maxy - 1, 0, ' ', maxx);
          move(maxy - 1, 0);
					mvaddstr(maxy - 1, 0, _("Search string not found..."));
					statusline_locked = true;
				}
				rescan_cursor();	/* rescan cursor position in the new place */
			}
skip_search:
			/*==========================================================================*/
			if ((key == keys.search_again_1) ||	/* search again */
					(key == keys.search_again_2))
			{
				if (searchagain.type != 0)	/* if a search was made before */
				{
					searchagain.search = 1;	/* mark, that search routines should *
											 * use the searchagain token value   */
					ungetch(searchagain.type);	/* ungetch the proper *
												 * search key         */
				}
			}
			/*==========================================================================*/

			if ((key == keys.goto_1) ||	/* goto node */
					(key == keys.goto_2))
			{
				return_value = -1;
				move(maxy - 1, 0);
				attrset(bottomline);
				curs_set(1);
				string token_string = getstring(_("Enter node name: "));
				curs_set(0);
				noecho();
				attrset(normal);
				for (typeof(tag_table.size()) i = 0; i < tag_table.size(); i++)
				{
					/* if the name was found in the tag table */
					if (tag_table[i].nodename == token_string)
					{
						return_value = i;
						break;
					}
				}
				if (return_value != -1)	/* if the name was in tag table */
				{
					infohistory[infohistory.size() - 1].pos = pos;
					infohistory[infohistory.size() - 1].cursor = cursor;
					infohistory[infohistory.size() - 1].menu = infomenu;
					rval.node = tag_table[return_value].nodename;
					rval.file = "";
					rval.keep_going = true;
					return rval;
				} else {
					/* the name wasn't in tag table */
					/*
					 * scan for filename: filenames may be specified in format:
					 * (file)node
					 */
					string::size_type gotostart_idx = token_string.find('(');
					if (gotostart_idx != string::npos) {
						string::size_type gotoend_idx = token_string.find(')', gotostart_idx);
						if (gotoend_idx != string::npos) {
							rval.file = token_string.substr(gotostart_idx + 1, gotoend_idx - (gotostart_idx + 1));
							/* skip whitespace before nodename */
							string::size_type nodestart_idx = token_string.find_first_not_of(' ', gotoend_idx + 1);
							if (nodestart_idx != string::npos) {
								rval.node = token_string.substr(nodestart_idx);
							} else {
								rval.node = "";
							}
							rval.keep_going = true;
							return rval;
						}
					}	else if (    (token_string.length() > 5)
					            && (token_string.substr(token_string.length() - 5) == ".info")
					          ) {
						/* handle the `file.info' format of crossinfo goto. */
						rval.file = token_string;
						rval.node = "";
						rval.keep_going = true;
						return rval;
					} else {
						/* node not found */
						attrset(bottomline);
						mymvhline(maxy - 1, 0, ' ', maxx);
						move(maxy - 1, 0);
						printw(_("Node %s not found"), token_string.c_str());
						attrset(normal);
						move(0, 0);
					}
				}
				statusline_locked = true;
			}
			/*==========================================================================*/
			if ((key == keys.prevnode_1) ||	/* goto previous node */
					(key == keys.prevnode_2))
			{
				string token_str = getprevnode(type_str);
				return_value = gettagtablepos(token_str);
				if (return_value != -1)
				{
					infohistory[infohistory.size() - 1].pos = pos;
					infohistory[infohistory.size() - 1].cursor = cursor;
					infohistory[infohistory.size() - 1].menu = infomenu;
					rval.node = tag_table[return_value].nodename;
					rval.file = "";
					rval.keep_going = true;
					return rval;
				}
			}
			/*==========================================================================*/
			if ((key == keys.nextnode_1) ||	/* goto next node */
					(key == keys.nextnode_2))
			{
				string token_str;
				token_str = getnextnode(type_str);
				return_value = gettagtablepos(token_str);
				if (return_value != -1)
				{
					infohistory[infohistory.size() - 1].pos = pos;
					infohistory[infohistory.size() - 1].cursor = cursor;
					infohistory[infohistory.size() - 1].menu = infomenu;
					rval.node = tag_table[return_value].nodename;
					rval.file = "";
					rval.keep_going = true;
					return rval;
				}
			}
			/*==========================================================================*/
			if ((key == keys.upnode_1) ||		/* goto up node */
					(key == keys.upnode_2))
			{
				string token_str = getupnode(type_str);
				if (token_str.compare(0, 5, "(dir)") == 0)
				{
					ungetch(keys.dirpage_1);
				}
				return_value = gettagtablepos(token_str);
				if (return_value != -1)
				{
					if (toggled_by_menu == KEEP_HISTORY)
					{
						infohistory[infohistory.size() - 1].pos = pos;
						infohistory[infohistory.size() - 1].cursor = cursor;
						infohistory[infohistory.size() - 1].menu = infomenu;
					}
					rval.node = tag_table[return_value].nodename;
					rval.file = "";
					rval.keep_going = true;
					return rval;
				}
			}
			/*==========================================================================*/
			if ((key == keys.twoup_1) || (key == keys.twoup_2))
			{
				ungetch(keys.up_1);
				ungetch(keys.up_1);
			}
			/*==========================================================================*/
			if ((key == keys.up_1) ||
					(key == keys.up_2))
			{
				bool cursorchanged = false;
				if (cursor != (typeof(hyperobjects.size()))-1)	{
					/* if we must handle cursor... */
					if ((cursor > 0) && (hyperobjects.size()))
						/* if we really must handle it ;) */
						/*
						 * look if there's a cursor(link) pos available above,
						 * and if it is visible now.
						 */
						for (int i = cursor - 1; i >= 0; i--)
						{
							if ((hyperobjects[i].line >= pos) &&
									(hyperobjects[i].line < pos + lines_visible))
							{
								/* don't play with `highlight' objects */
								if (hyperobjects[i].type < HIGHLIGHT)
								{
									cursor = i;
									cursorchanged = true;
									break;
								}
							}
						}
				}
				if (!cursorchanged)	/* if the cursor wasn't changed */
				{
					if (pos > 0)	/* lower the nodepos */
						pos--;
					/* and scan for a hyperlink in the new line */
					for (typeof(hyperobjects.size()) i = 0;
					     i < hyperobjects.size(); i++) {
						if (hyperobjects[i].line == pos)
						{
							if (hyperobjects[i].type < HIGHLIGHT)
							{
								cursor = i;
								break;
							}
						}
					}
				}
			}
			/*==========================================================================*/
			if ((key == keys.end_1) ||
					(key == keys.end_2))
			{
				if (lines_visible < my_message.size()) {
					pos = my_message.size() - lines_visible;
				} else {
					pos = 0;
				}
				cursor = hyperobjects.size() - 1;
			}
			/*==========================================================================*/
			if ((key == keys.pgdn_1) ||
					(key == keys.pgdn_2))
			{
				/* Signed/unsigned issues in comparisons.  FIXME */
				if (pos + lines_visible + lines_visible <= my_message.size()) {
					pos += lines_visible;
					rescan_cursor();
				} else if (lines_visible < my_message.size()) {
					pos = my_message.size() - lines_visible;
					cursor = hyperobjects.size() - 1;
				} else {
					pos = 0;
					cursor = hyperobjects.size() - 1;
				}
			}
			/*==========================================================================*/
			if ((key == keys.home_1) ||
					(key == keys.home_2))
			{
				pos = 0;
				rescan_cursor();
			}
			/*==========================================================================*/
			if ((key == keys.pgup_1) |
					(key == keys.pgup_2))
			{
				if (pos >= lines_visible) {
					pos -= lines_visible;
				} else {
					pos = 0;
				}
				rescan_cursor();
			}
			/*==========================================================================*/
			if ((key == keys.pgup_auto_1) ||
					(key == keys.pgup_auto_2))
			{
				if (pos == 0)
					ungetch(keys.upnode_1);
			}
			/*==========================================================================*/
			if ((key == keys.twodown_1) ||
					(key == keys.twodown_2))	/* top+bottom line \|/ */
			{
				ungetch(keys.down_1);
				ungetch(keys.down_1);
			}
			/*==========================================================================*/
			if ((key == keys.down_1) ||
					(key == keys.down_2))	/* top+bottom line \|/ */
			{
				bool cursorchanged = false;	/* works similar to keys.up */
				if (cursor < hyperobjects.size())
					for (typeof(hyperobjects.size()) i = cursor + 1;
					     i < hyperobjects.size(); i++) {
						if ((hyperobjects[i].line >= pos) &&
								(hyperobjects[i].line < pos + lines_visible))
						{
							if (hyperobjects[i].type < HIGHLIGHT)
							{
								cursor = i;
								cursorchanged = true;
								break;
							}
						}
					}
				if (!cursorchanged)
				{
					/* FIXME: signed/unsigned issues */
					if (pos + lines_visible < my_message.size())
						pos++;
					for (typeof(hyperobjects.size()) i = cursor + 1;
					     i < hyperobjects.size(); i++)
					{
						if ((hyperobjects[i].line >= pos) &&
								(hyperobjects[i].line < pos + lines_visible))
						{
							if (hyperobjects[i].type < HIGHLIGHT)
							{
								cursor = i;
								cursorchanged = true;
								break;
							}
						}
					}
				}
			}
			/*==========================================================================*/
			if ((key == keys.top_1) ||
					(key == keys.top_2))
			{
				infohistory[infohistory.size() - 1].pos = pos;
				infohistory[infohistory.size() - 1].cursor = cursor;
				infohistory[infohistory.size() - 1].menu = infomenu;
				rval.node = FirstNodeName;
				rval.file = "";
				rval.keep_going = true;
				return rval;
			}
			/*==========================================================================*/
			if ((key == keys.back_1) ||
					(key == keys.back_2))
			{
				if (infohistory.size() > 1)
				{
					dellastinfohistory();	/* remove history entry for this node */
					/* now we deal with the previous node history entry */

					rval.node = infohistory[infohistory.size() - 1].node;
					rval.file = infohistory[infohistory.size() - 1].file;
					rval.keep_going = true;

					npos = infohistory[infohistory.size() - 1].pos;
					ncursor = infohistory[infohistory.size() - 1].cursor;
					nmenu = infohistory[infohistory.size() - 1].menu;
					dellastinfohistory();	/* remove history entry for previous node */
					return rval;
				}
			}
			/*==========================================================================*/
			if ((key == keys.followlink_1) ||
					(key == keys.followlink_2))
			{
				infohistory[infohistory.size() - 1].pos = pos;
				infohistory[infohistory.size() - 1].cursor = cursor;
				infohistory[infohistory.size() - 1].menu = infomenu;
				if (!toggled_by_menu) {
					infohistory[infohistory.size() - 1].menu = cursor;
				}
				if ((cursor >= 0) && (cursor < hyperobjects.size())) {
					if (    toggled_by_menu
					     || (   (hyperobjects[cursor].line >= pos)
					          && (hyperobjects[cursor].line < pos + lines_visible)
					        )
					   ) {
						toggled_by_menu = 0;
						if (hyperobjects[cursor].type < 4)	/* normal info link */
						{
							rval.node = hyperobjects[cursor].node;
							rval.file = hyperobjects[cursor].file;
							rval.keep_going = true;
							return rval;
						}
						else if (hyperobjects[cursor].type < HIGHLIGHT)	/* we deal with an url */
						{
							if (hyperobjects[cursor].type == 4)	/* http */
							{
								string tempbuf = httpviewer;
								tempbuf += " ";
								tempbuf += hyperobjects[cursor].node;
								myendwin();
								system(tempbuf.c_str());
								doupdate();
							}
							else if (hyperobjects[cursor].type == 5)	/* ftp */
							{
								string tempbuf = ftpviewer;
								tempbuf += " ";
								tempbuf += hyperobjects[cursor].node;
								myendwin();
								system(tempbuf.c_str());
								doupdate();
							}
							else if (hyperobjects[cursor].type == 6)	/* mail */
							{
								string tempbuf = maileditor;
								tempbuf += " ";
								tempbuf += hyperobjects[cursor].node;
								myendwin();
								system("clear");
								system(tempbuf.c_str());
								doupdate();
							}
						}
					}
				}
			}
			/*==========================================================================*/
			if ((key == keys.left_1) ||(key == keys.left_2))
			{
				if (infocolumn>0)
					infocolumn--;
				addtopline(type_str,infocolumn);
			}
			/*==========================================================================*/
			if ((key == keys.right_1) ||(key == keys.right_2))
			{
				infocolumn++;
				addtopline(type_str,infocolumn);
			}
			/*==========================================================================*/
			/**************************** end of keyboard handling **********************/
			/******************************** mouse handler *****************************/
#ifdef NCURSES_MOUSE_VERSION
			if (key == KEY_MOUSE)
			{
				MEVENT mouse;
				int done = 0;
				getmouse(&mouse);
				if (mouse.bstate == BUTTON1_CLICKED)
				{
					if ((mouse.y > 0) &&(mouse.y < maxy - 1))
					{
						for (typeof(hyperobjects.size()) i = cursor; i > 0; i--)
						{
							if (hyperobjects[i].line - pos == mouse.y - 1)
							{
								if (hyperobjects[i].col <= mouse.x - 1)
								{
									if (hyperobjects[i].col + hyperobjects[i].node.length() + hyperobjects[i].file.length() >= mouse.x - 1)
									{
										if (hyperobjects[i].type < HIGHLIGHT)
										{
											cursor = i;
											done = 1;
											break;
										}
									}
								}
							}
						}
						if (!done)
							for (typeof(hyperobjects.size()) i = cursor;
							     i < hyperobjects.size(); i++)
							{
								if (hyperobjects[i].line - pos == mouse.y - 1)
								{
									if (hyperobjects[i].col <= mouse.x - 1)
									{
										if (hyperobjects[i].col + hyperobjects[i].node.length() + hyperobjects[i].file.length() >= mouse.x - 1)
										{
											if (hyperobjects[i].type < HIGHLIGHT)
											{
												cursor = i;
												done = 1;
												break;
											}
										}
									}
								}
							}
					}		/* end: if (mouse.y not on top/bottom line) */
					else if (mouse.y == 0)
						ungetch(keys.up_1);
					else if (mouse.y == maxy - 1)
						ungetch(keys.down_1);
				}		/* end: button clicked */
				if (mouse.bstate == BUTTON1_DOUBLE_CLICKED)
				{
					if ((mouse.y > 0) &&(mouse.y < maxy - 1))
					{
						/* signed/unsigned.  Use iterators.  FIXME. */
						for (int i = cursor; i >= 0; i--)
						{
							if (hyperobjects[i].line - pos == mouse.y - 1)
							{
								if (hyperobjects[i].col <= mouse.x - 1)
								{
									if (hyperobjects[i].col + hyperobjects[i].node.length() + hyperobjects[i].file.length() >= mouse.x - 1)
									{
										if (hyperobjects[i].type < HIGHLIGHT)
										{
											cursor = i;
											done = 1;
											break;
										}
									}
								}
							}
						}
						if (!done)
							for (typeof(hyperobjects.size()) i = cursor;
							     i < hyperobjects.size(); i++)
							{
								if (hyperobjects[i].line - pos == mouse.y - 1)
								{
									if (hyperobjects[i].col <= mouse.x - 1)
									{
										if (hyperobjects[i].col + hyperobjects[i].node.length() + hyperobjects[i].file.length() >= mouse.x - 1)
										{
											if (hyperobjects[i].type < HIGHLIGHT)
											{
												cursor = i;
												done = 1;
												break;
											}
										}
									}
								}
							}
						if (done)
							ungetch(keys.followlink_1);
					}		/* end: if (mouse.y not on top/bottom line) */
					else if (mouse.y == 0)
						ungetch(keys.pgup_1);
					else if (mouse.y == maxy - 1)
						ungetch(keys.pgdn_1);
				}		/* end: button doubleclicked */
			}
#endif
			/*****************************************************************************/
		}
		if ((key == keys.quit_2) ||(key == keys.quit_1))
		{
			if (!ConfirmQuit)
				break;
			else
			{
				if (yesno(_("Are you sure you want to quit?"), QuitConfirmDefault))
					break;
			}
		}
	}
	return rval;
}

