/* Hostwin.c */

#include "Sys.h"
#include "Curses.h"

#include <ctype.h>
#include <signal.h>
#include <setjmp.h>

#include "Util.h"
#include "Cmds.h"
#include "Recent.h"
#include "Open.h"
#include "Hostwin.h"

#ifdef USE_CURSES

#include "WGets.h"

/* This is the full-screen window that pops up when you run the
 * host editor.  Not much is done with it, except display directions
 * and getting host editor commands.
 */
WINDOW *gHostWin = NULL;

/* This is a window devoted solely to serve as a scrolling list
 * of hosts.
 */
WINDOW *gHostListWin = NULL;

/* This is another full-screen window that opens when a user wants
 * to edit the parameters for a site.
 */
WINDOW *gEditHostWin = NULL;

extern void WAddCenteredStr(WINDOW *w, int y, char *str);
void WAttr(WINDOW *w, int attr, int on);

#endif	/* USE_CURSES */

/* This is an index into the host list.  This indicates the position
 * in the host list where we draw the current "page" of the host list.
 */
int gHostListWinStart;

/* This index is the currently selected host.  This index must be >=
 * to gHostListWinStart and less than gHostListWinStart + pageLen - 1,
 * so that this host will show up in the current page of the host list.
 */
int gHilitedHost;

/* This is a pointer to the actual information of the currently
 * selected host.
 */
RemoteSiteInfoPtr gCurHostListItem;

/* How many lines compose a "page" in the host list's scrolling window. */
int gHostListPageSize;

/* A flag saying if we need to erase a message after the next input key. */
int gNeedToClearMsg = 0;

/* When we edit gCurHostListItem's stuff, we actually edit a copy of it.
 * This is so we could restore the information if the user wanted to
 * abort the changes.
 */
RemoteSiteInfo gEditRsi;

jmp_buf gHostWinJmp;

extern int gWinInit, gNumRecents, gRmtInfoIsNew;
extern RemoteSiteInfoPtrList gHostNickNames;
extern RemoteSiteInfoPtr gHosts;
extern RemoteSiteInfo gRmtInfo;
extern string gEmailAddress, gAnonPassword;
extern unsigned int gFTPPort;

void AtoIMaybe(int *dst, char *str)
{
	char *cp;
	
	/* Don't change the value if the user just hit return. */
	for (cp = str; *cp != '\0'; cp++)
		if (isdigit(*cp))
			break;
	if (isdigit(*cp))
		*dst = atoi(str);
}	/* AtoIMaybe */




#ifdef USE_CURSES

/* Draws the screen when we're using the host editor's main screen.
 * You can can specify whether to draw each character whether it needs
 * it or not if you like.
 */
void UpdateHostWindows(int uptAll)
{
	if (uptAll) {
		DrawHostList();
		touchwin(gHostListWin);
		touchwin(gHostWin);
	}
	wnoutrefresh(gHostListWin);
	wnoutrefresh(gHostWin);
	doupdate();
}	/* UpdateHostWindows */



/* This draws the scrolling list of hosts, and hilites the currently
 * selected host.
 */
void DrawHostList(void)
{
	int lastLine, i;
	RemoteSiteInfoPtr rsip;
	string str;
	int maxy, maxx;
	int lmaxy, lmaxx;
	int begy, begx;
	char spec[32];

	getmaxyx(gHostListWin, lmaxy, lmaxx);
	getbegyx(gHostListWin, begy, begx);
	getmaxyx(gHostWin, maxy, maxx);
	/* We have a status line saying how many hosts there are in
	 * the list.  That way the user knows something is supposed to
	 * be there when the host list is totally empty, and also that
	 * there are more hosts to look at when the entire host list
	 * doesn't fit in the scroll window.
	 */
	WAttr(gHostWin, kUnderline, 1);
	mvwprintw(
		gHostWin,
		begy - 1,
		begx,
		"%s",
		"Number of hosts"
	);
	WAttr(gHostWin, kUnderline, 0);
	wprintw(
		gHostWin,
		": %3d",
		gNumRecents
	);
	sprintf(spec, "%%-16s %%-%ds", lmaxx - 17);
	lastLine = lmaxy + gHostListWinStart;
	for (i=gHostListWinStart; (i<lastLine) && (i<gNumRecents); i++) {
		rsip = gHostNickNames[i];
		if (rsip == gCurHostListItem)
			WAttr(gHostListWin, kReverse, 1);
		sprintf(str, spec, rsip->nickName, rsip->name);
		str[lmaxx] = '\0';
		mvwaddstr(gHostListWin, i - gHostListWinStart, 0, str);
		if (rsip == gCurHostListItem)
			WAttr(gHostListWin, kReverse, 0);
	}

	/* Add 'vi' style empty-lines. */
	for ( ; i<lastLine; ++i) {
		mvwaddstr(gHostListWin, i - gHostListWinStart, 0, "~");
		wclrtoeol(gHostListWin);
	}
	wmove(gHostWin, maxy - 3, 2);
	sprintf(spec, "%%-%ds", maxx - 4);
	if ((gCurHostListItem == NULL) || (gCurHostListItem->comment[0] == '\0'))
		str[0] = '\0';
	else {
		STRNCPY(str, "``");
		STRNCAT(str, gCurHostListItem->comment);
		AbbrevStr(str + 2, gCurHostListItem->comment, maxx - 8, 1);
		STRNCAT(str, "''");
	}
	wprintw(gHostWin, spec, str);
	wmove(gHostWin, maxy - 1, 0);
	UpdateHostWindows(0);
}	/* DrawHostList */




/* This prompts for a key of input when in the main host editor window. */
int HostWinGetKey(void)
{
	int c;
	int maxy, maxx;

	getmaxyx(gHostWin, maxy, maxx);
	wmove(gHostWin, maxy - 1, 0);
	c = wgetch(gHostWin);
	TraceMsg("[%c, 0x%x]\n", c, c);
	return (c);
}	/* HostWinGetKey */



static
void NewHilitedHostIndex(int newIdx)
{
	int oldIdx, lastLine;

	if (gNumRecents <= 0) {
		HostWinMsg(
"No hosts in the list.  Try a /new, or open a site manually to add one.");
	} else {
		oldIdx = gHilitedHost;
		if (gNumRecents < gHostListPageSize)
			lastLine = gHostListWinStart + gNumRecents - 1;
		else
			lastLine = gHostListWinStart + gHostListPageSize - 1;
		if (newIdx < gHostListWinStart) {
			/* Will need to scroll the window up. */
			if (newIdx < 0) {
				newIdx = 0;
				if (oldIdx == newIdx)
					HostWinMsg("You are at the top of the list.");
			}
			gHilitedHost = gHostListWinStart = newIdx;
		} else if (newIdx > lastLine) {
			/* Will need to scroll the window down. */
			if (newIdx > (gNumRecents - 1)) {
				newIdx = gNumRecents - 1;
				if (oldIdx == newIdx)
					HostWinMsg("You are at the bottom of the list.");
			}
			gHilitedHost = newIdx;
			gHostListWinStart = newIdx - (gHostListPageSize - 1);
			if (gHostListWinStart < 0)
				gHostListWinStart = 0;
		} else {
			/* Don't need to scroll window, just move pointer. */
			gHilitedHost = newIdx;
		}
		gCurHostListItem = gHostNickNames[gHilitedHost];
		if (oldIdx != newIdx)
			DrawHostList();
	}
}	/* NewHilitedHostIndex */




/* You can zip to a different area of the list without using the arrow
 * or page scrolling keys.  Just type a letter, and the list will scroll
 * to the first host starting with that letter.
 */
void HostWinZoomTo(int c)
{	
	int i, j;

	if (gNumRecents > 0) {
		if (islower(c))
			c = toupper(c);
		for (i=0; i<gNumRecents - 1; i++) {
			j = gHostNickNames[i]->nickName[0];
			if (islower(j))
				j = toupper(j);
			if (j >= c)
				break;
		}
		NewHilitedHostIndex(i);
	} else {
		HostWinMsg("No sites to select.  Try a /new.");
	}
	DrawHostList();
}	/* HostWinZoomTo */





void HostListLineUp(void)
{
	NewHilitedHostIndex(gHilitedHost - 1);
}	/* HostListLineUp */





void HostListLineDown(void)
{
	NewHilitedHostIndex(gHilitedHost + 1);
}	/* HostListLineDown */




void HostListPageUp(void)
{
	NewHilitedHostIndex(gHilitedHost - gHostListPageSize);
}	/* HostListPageUp */




void HostListPageDown(void)
{
	NewHilitedHostIndex(gHilitedHost + gHostListPageSize);
}	/* HostListPageDown */



/* This marks the start of a section that belongs to the Site Options
 * window.  This window pops up on top of the host editor's main window
 * when you wish to edit a site's settings.  When the user finishes,
 * we close it and the host editor resumes.
 */

/* This displays a message in the Site Options window. */
void EditHostWinMsg(char *msg)
{
	int maxy, maxx;

	getmaxyx(gEditHostWin, maxy, maxx);
	mvwaddstr(gEditHostWin, maxy - 2, 0, msg);
	wclrtoeol(gEditHostWin);
	wmove(gEditHostWin, maxy - 1, 0);
	wrefresh(gEditHostWin);
}	/* EditHostWinMsg */




/* Prompts for a line of input. */
void EditHostWinGetStr(char *dst, size_t size, int canBeEmpty, int canEcho)
{
	string str;
	WGetsParams wgp;
	int maxy, maxx;

	WAttr(gEditHostWin, kBold, 1);
	getmaxyx(gEditHostWin, maxy, maxx);
	mvwaddstr(gEditHostWin, maxy - 1, 0, "> ");
	WAttr(gEditHostWin, kBold, 0);
	wclrtoeol(gEditHostWin);
	wrefresh(gEditHostWin);
	curs_set(1);

	wgp.w = gEditHostWin;
	wgp.sy = maxy - 1;
	wgp.sx = 2;
	wgp.fieldLen = maxx - 3;
	wgp.dst = str;
	wgp.dstSize = size;
	wgp.useCurrentContents = 0;
	wgp.echoMode = canEcho ? wg_RegularEcho : wg_BulletEcho;
	wgp.history = wg_NoHistory;
	(void) wg_Gets(&wgp);
	cbreak();						/* wg_Gets turns off cbreak and delay. */

	TraceMsg("[%s]\n", wgp.dst);
	
	/* See if the user just hit return.  We may not want to overwrite
	 * the dst here, which would make it an empty string.
	 */
	if ((wgp.changed) || (canBeEmpty == kOkayIfEmpty))
		strcpy(dst, str);

	wmove(gEditHostWin, maxy - 1, 0);
	wclrtoeol(gEditHostWin);
	wrefresh(gEditHostWin);
	curs_set(0);
}	/* EditHostWinGetStr */





/* Prompts for an integer of input. */
void EditHostWinGetNum(int *dst)
{
	WGetsParams wgp;
	string str;
	int maxy, maxx;

	getmaxyx(gEditHostWin, maxy, maxx);
	WAttr(gEditHostWin, kBold, 1);
	mvwaddstr(gEditHostWin, maxy - 1, 0, "> ");
	WAttr(gEditHostWin, kBold, 0);
	wclrtoeol(gEditHostWin);
	wrefresh(gEditHostWin);
	curs_set(1);

	wgp.w = gEditHostWin;
	wgp.sy = maxy - 1;
	wgp.sx = 2;
	wgp.fieldLen = maxx - 3;
	wgp.dst = str;
	wgp.dstSize = sizeof(str);
	wgp.useCurrentContents = 0;
	wgp.echoMode = wg_RegularEcho;
	wgp.history = wg_NoHistory;
	(void) wg_Gets(&wgp);
	cbreak();						/* wg_Gets turns off cbreak and delay. */

	TraceMsg("[%s]\n", str);
	AtoIMaybe(dst, str);
	wmove(gEditHostWin, maxy - 1, 0);
	wclrtoeol(gEditHostWin);
	wrefresh(gEditHostWin);
	curs_set(0);
}	/* EditHostWinGetNum */




/* This is the meat of the site options window.  We can selectively update
 * portions of the window by using a bitmask with bits set for items
 * we want to update.
 */
void EditHostWinDraw(int flags, int hilite)
{
	int maxy, maxx;
	int i, f;
	string str;
	char spec[32];
	char *cp;
	size_t len;

	/* Draw the keys the user can type in reverse text. */
	WAttr(gEditHostWin, kReverse, 1);
	f = 5;
	for (i = kFirstEditWindowItem; i <= kLastEditWindowItem; i++) {
		if (TESTBIT(flags, i))
			mvwaddch(gEditHostWin, f + i, 2, 'A' + i);
	}
	
	/* The "quit" item is a special item that is offset a line, and
	 * always has the "X" key assigned to it.
	 */
	i = kQuitEditWindowItem;
	if (TESTBIT(flags, i))
		mvwaddch(gEditHostWin, 1 + f + i, 2, 'X');
	WAttr(gEditHostWin, kReverse, 0);
	
	/* We can use this to hilite a whole line, to indicate to the
	 * user that a certain item is being edited.
	 */
	if (hilite)
		WAttr(gEditHostWin, kReverse, 1);
	getmaxyx(gEditHostWin, maxy, maxx);
	sprintf(spec, " %%-26s%%-%ds",
		maxx - 32);

	/* Now draw the items on a case-by-case basis. */
	if (TESTBIT(flags, kNicknameEditWindowItem)) {
		mvwprintw(gEditHostWin, kNicknameEditWindowItem + f, 3, spec,
			"Nickname:",
			gEditRsi.nickName
		);
		wclrtoeol(gEditHostWin);
	}
	if (TESTBIT(flags, kHostnameEditWindowItem)) {
		mvwprintw(gEditHostWin, kHostnameEditWindowItem + f, 3, spec,
			"Hostname:",
			gEditRsi.name
		);
		wclrtoeol(gEditHostWin);
	}
	if (TESTBIT(flags, kUserEditWindowItem)) {
		mvwprintw(gEditHostWin, kUserEditWindowItem + f, 3, spec,
			"User:",
			gEditRsi.user[0] == '\0' ? "anonymous" : gEditRsi.user
		);
		wclrtoeol(gEditHostWin);
	}
	if (TESTBIT(flags, kPassEditWindowItem)) {
		if (gEditRsi.pass[0] == '\0' && gEditRsi.user[0] == '\0')
			STRNCPY(str, gAnonPassword);
		mvwprintw(gEditHostWin, kPassEditWindowItem + f, 3, spec,
			"Password:",
			strcmp(str, gAnonPassword) ? "********" : str
		);
		wclrtoeol(gEditHostWin);
	}
	if (TESTBIT(flags, kAcctEditWindowItem)) {
		mvwprintw(gEditHostWin, kAcctEditWindowItem + f, 3, spec,
			"Account:",
			gEditRsi.acct[0] == '\0' ? "none" : gEditRsi.acct
		);
		wclrtoeol(gEditHostWin);
	}
	if (TESTBIT(flags, kDirEditWindowItem)) {
		if (gEditRsi.dir[0] == '\0')
			STRNCPY(str, "/");
		else
			AbbrevStr(str, gEditRsi.dir, maxx - 32, 0);
		mvwprintw(gEditHostWin, kDirEditWindowItem + f, 3, spec,
			"Directory:",
			str
		);
		wclrtoeol(gEditHostWin);
	}
	if (TESTBIT(flags, kXferTypeEditWindowItem)) {
		if ((gEditRsi.xferType == 'I') || (gEditRsi.xferType == 'B'))
			cp = "Binary";
		else if (gEditRsi.xferType == 'A')
			cp = "ASCII Text";
		else
			cp = "Tenex";
		mvwprintw(gEditHostWin, kXferTypeEditWindowItem + f, 3, spec,
			"Transfer type:",
			cp
		);
		wclrtoeol(gEditHostWin);
	}
	if (TESTBIT(flags, kPortEditWindowItem)) {
		sprintf(str, "%u", gEditRsi.port);
		mvwprintw(gEditHostWin, kPortEditWindowItem + f, 3, spec,
			"Port:",
			str
		);
		wclrtoeol(gEditHostWin);
	}
	if (TESTBIT(flags, kSizeEditWindowItem)) {
		mvwprintw(gEditHostWin, kSizeEditWindowItem + f, 3, spec,
			"Has SIZE command:",
			gEditRsi.hasSIZE ? "Yes" : "No"
		);
		wclrtoeol(gEditHostWin);
	}
	if (TESTBIT(flags, kMdtmEditWindowItem)) {
		mvwprintw(gEditHostWin, kMdtmEditWindowItem + f, 3, spec,
			"Has MDTM command:",
			gEditRsi.hasMDTM ? "Yes" : "No"
		);
		wclrtoeol(gEditHostWin);
	}
	if (TESTBIT(flags, kPasvEditWindowItem)) {
		mvwprintw(gEditHostWin, kPasvEditWindowItem + f, 3, spec,
			"Can use passive FTP:",
			gEditRsi.hasPASV ? "Yes" : "No"
		);
		wclrtoeol(gEditHostWin);
	}
	if (TESTBIT(flags, kOSEditWindowItem)) {
		mvwprintw(gEditHostWin, kOSEditWindowItem + f, 3, spec,
			"Operating System:",
			(gEditRsi.isUnix == 1) ? "UNIX" : "Non-UNIX"
		);
		wclrtoeol(gEditHostWin);
	} 
	if (TESTBIT(flags, kCommentEditWindowItem)) {
		if (gEditRsi.comment[0] == '\0')
			STRNCPY(str, "(none)");
		else
			AbbrevStr(str, gEditRsi.comment, maxx - 32, 0);
		mvwprintw(gEditHostWin, kCommentEditWindowItem + f, 3, spec,
			"Comment:",
			str
		);
		wclrtoeol(gEditHostWin);
	}
	if (TESTBIT(flags, kQuitEditWindowItem)) {
		mvwprintw(gEditHostWin, kQuitEditWindowItem + f + 1, 3, spec,
			"(Done editing)",
			""
		);
		wclrtoeol(gEditHostWin);
	}

	if (hilite)
		WAttr(gEditHostWin, kReverse, 0);

	wmove(gEditHostWin, maxy - 1, 0);
	wrefresh(gEditHostWin);
}	/* EditHostWinDraw */



/* The user can hit space to change the transfer type.  For these toggle
 * functions we do an update each time so the user can see the change
 * immediately.
 */
void ToggleXferType(void)
{
	int c;

	while (1) {
		c = wgetch(gEditHostWin);
		TraceMsg("[%c, 0x%x]\n", c, c);
		if ((c == 'x') || (c == 10) || (c == 13)
#ifdef KEY_ENTER
			|| (c == KEY_ENTER)
#endif
			)
			break;
		else if (isspace(c)) {
			if (gEditRsi.xferType == 'A')
				gEditRsi.xferType = 'I';
			else if ((gEditRsi.xferType == 'B') || (gEditRsi.xferType == 'I'))
				gEditRsi.xferType = 'T';
			else
				gEditRsi.xferType = 'A';
			EditHostWinDraw(BIT(kXferTypeEditWindowItem), kHilite);
		}
	}
}	/* ToggleXferType */




void EditWinToggle(int *val, int bitNum, int min, int max)
{
	int c;

	while (1) {
		c = wgetch(gEditHostWin);
		TraceMsg("[%c, 0x%x]\n", c, c);
		if ((c == 'x') || (c == 10) || (c == 13)
#ifdef KEY_ENTER
			|| (c == KEY_ENTER)
#endif
			)
			break;
		else if (isspace(c)) {
			*val = *val + 1;
			if (*val > max)
				*val = min;
			EditHostWinDraw(BIT(bitNum), kHilite);
		}
	}
}	/* EditWinToggle */



/* This opens and handles the site options window. */
void HostWinEdit(void)
{
	long amt;
	double rate;
	char rstr[32];
	int c, field;
	int needUpdate;
	string str;

	if (gCurHostListItem != NULL) {
		gEditHostWin = newwin(LINES, COLS, 0, 0);
		if (gEditHostWin == NULL)
			return;
		
		
		/* Set the clear flag for the first update. */
		wclear(gEditHostWin);

		/* leaveok(gEditHostWin, TRUE);	* Not sure if I like this... */
		WAttr(gEditHostWin, kBold, 1);
		WAddCenteredStr(gEditHostWin, 0, "Site Options");
		WAttr(gEditHostWin, kBold, 0);
		
		/* We'll be editing a copy of the current host's settings. */
		gEditRsi = *gCurHostListItem;

		if (gEditRsi.lastCall != (time_t) 0) {
			strcpy(str, ctime(&gEditRsi.lastCall));
			/* Why-o-why does ctime append a newline.  I hate that! */
			str[strlen(str) - 1] = '\0';
			mvwprintw(gEditHostWin, 2, 5,
				"Number of calls: %d.    Last call: %s",
				gEditRsi.nCalls,
				str
			);
			amt = gEditRsi.xferKbytes;
			if (amt > 0) {
				if (amt < 1000) {
					sprintf(str, "%ld kBytes");
				} else if (amt < 1000000) {
					sprintf(str, "%.2f MBytes", ((double) amt / 1000.0));
				} else {
					sprintf(str, "%.2f GBytes", ((double) amt / 1000000.0));
				}

				rate = (double) amt / gEditRsi.xferHSeconds * 100.0;
				if (rate > 0) {
					if (rate < 1000.0) {
						sprintf(rstr, "%.2f kBytes/sec", rate);
					} else if (rate < 1000000.0) {
						sprintf(rstr, "%.2f MBytes/sec", rate / 1000.0);
					} else {
						sprintf(rstr, "%.2f GBytes/sec", rate / 1000000.0);
					}
					mvwprintw(gEditHostWin, 3, 5,
						"You have transferred %s, averaging %s.",
						str,
						rstr
					);
				}
			}
		}

		EditHostWinDraw(kAllWindowItems, kNoHilite);
		field = 1;
		while (1) {
			EditHostWinMsg("Select an item to edit by typing its corresponding letter.");
			c = wgetch(gEditHostWin);
			TraceMsg("[%c, 0x%x]\n", c, c);
			if (islower(c))
				c = toupper(c);
			if (!isupper(c))
				continue;
			if (c == 'X')
				break;
			field = c - 'A';
			needUpdate = 1;
			
			/* Hilite the current item to edit. */
			EditHostWinDraw(BIT(field), kHilite);
			switch(field) {
				case kNicknameEditWindowItem:
					EditHostWinMsg("Type a new nickname, or hit <RETURN> to continue.");
					EditHostWinGetStr(gEditRsi.nickName, sizeof(gEditRsi.nickName), kNotOkayIfEmpty, kGetAndEcho);
					break;
					
				case kHostnameEditWindowItem:
					EditHostWinMsg("Type a new hostname, or hit <RETURN> to continue.");
					EditHostWinGetStr(gEditRsi.name, sizeof(gEditRsi.name), kNotOkayIfEmpty, kGetAndEcho);
					break;

				case kUserEditWindowItem:
					EditHostWinMsg("Type a username, or hit <RETURN> to signify anonymous.");
					EditHostWinGetStr(gEditRsi.user, sizeof(gEditRsi.user), kOkayIfEmpty, kGetAndEcho);
					break;

				case kPassEditWindowItem:
					EditHostWinMsg("Type a password, or hit <RETURN> if no password is required.");
					EditHostWinGetStr(gEditRsi.pass, sizeof(gEditRsi.pass), kOkayIfEmpty, kGetNoEcho);
					break;

				case kAcctEditWindowItem:
					EditHostWinMsg("Type an account name, or hit <RETURN> if no account is required.");
					EditHostWinGetStr(gEditRsi.acct, sizeof(gEditRsi.acct), kOkayIfEmpty, kGetAndEcho);
					break;

				case kDirEditWindowItem:
					EditHostWinMsg("Type a directory path to start in after a connection is established.");
					EditHostWinGetStr(gEditRsi.dir, sizeof(gEditRsi.dir), kOkayIfEmpty, kGetAndEcho);
					break;

				case kXferTypeEditWindowItem:
					EditHostWinMsg(kToggleMsg);
					ToggleXferType();
					break;

				case kPortEditWindowItem:
					EditHostWinMsg("Type a port number to use for FTP.");
					EditHostWinGetNum((int *) &gEditRsi.port);
					break;

				case kSizeEditWindowItem:
					EditHostWinMsg(kToggleMsg);
					EditWinToggle(&gEditRsi.hasSIZE, field, 0, 1);
					break;

				case kMdtmEditWindowItem:
					EditHostWinMsg(kToggleMsg);
					EditWinToggle(&gEditRsi.hasMDTM, field, 0, 1);
					break;

				case kPasvEditWindowItem:
					EditHostWinMsg(kToggleMsg);
					EditWinToggle(&gEditRsi.hasPASV, field, 0, 1);
					break;

				case kOSEditWindowItem:
					EditHostWinMsg(kToggleMsg);
					EditWinToggle(&gEditRsi.isUnix, field, 0, 1);
					break;

				case kCommentEditWindowItem:
					EditHostWinMsg("Enter a line of information to store about this site.");
					EditHostWinGetStr(gEditRsi.comment, sizeof(gEditRsi.comment), kOkayIfEmpty, kGetAndEcho);
					break;
				
				default:
					needUpdate = 0;
					break;
			}
			if (needUpdate)
				EditHostWinDraw(BIT(field), kNoHilite);
		}
		delwin(gEditHostWin);
		gEditHostWin = NULL;
		*gCurHostListItem = gEditRsi;
		SortNickNames();
		NewHilitedHostIndex(gCurHostListItem->index);
		UpdateHostWindows(1);
	}
}	/* HostWinEdit */



/* Clones an existing site in the host list. */
void HostWinDup(void)
{
	if (gCurHostListItem != NULL) {
		gCurHostListItem = DuplicateRemoteInfo(gCurHostListItem);
		gHilitedHost = gCurHostListItem->index;
	} else
		HostWinMsg("Nothing to duplicate.");
	DrawHostList();
}	/* HostWinDup */




/* Removes a site from the host list. */
void HostWinDelete(void)
{
	RemoteSiteInfoPtr toDelete;
	int newi;
	
	if (gCurHostListItem != NULL) {
		toDelete = gCurHostListItem;

		/* Need to choose a new active host after deletion. */
		if (gHilitedHost == gNumRecents - 1) {
			if (gNumRecents == 1) {
				newi = -1;	/* None left. */
			} else {
				/* At last one before delete. */
				newi = gHilitedHost - 1;
			}
		} else {
			/* Won't need to increment gHilitedHost here, since after deletion,
			 * the next one will move up into this slot.
			 */
			newi = gHilitedHost;
		}
		DeleteRemoteInfo(toDelete);
		if (newi < 0)
			gCurHostListItem = NULL;
		else {
			gCurHostListItem = gHostNickNames[newi];
			gHilitedHost = newi;
		}
	} else
		HostWinMsg("Nothing to delete.");
	DrawHostList();
}	/* HostWinDelete */




/* Adds a new site to the host list, with default settings in place. */
void HostWinNew(void)
{
	RemoteSiteInfoPtr rsip;
	RemoteSiteInfo rsi;

	SetNewRemoteInfoDefaults(&rsi);
	rsip = AddRemoteSiteInfoPtr(&rsi);
	rsip->port = gFTPPort;
	SortNickNames();
	gCurHostListItem = rsip;
	gHilitedHost = rsip->index;
	gHostListWinStart = rsip->index - gHostListPageSize + 1;
	if (gHostListWinStart < 0)
		gHostListWinStart = 0;
	DrawHostList();
}	/* HostWinNew */




/* This displays a message in the host editor's main window.
 * Used mostly for error messages.
 */
void HostWinMsg(char *msg)
{
	int maxy, maxx;

	getmaxyx(gHostWin, maxy, maxx);
	mvwaddstr(gHostWin, maxy - 2, 0, msg);
	wclrtoeol(gHostWin);
	wmove(gHostWin, maxy - 1, 0);
	wrefresh(gHostWin);
	beep();
	gNeedToClearMsg = 1;
}	/* HostWinMsg */




/* Prompts for a line of input. */
void HostWinGetStr(char *str, size_t size)
{
	WGetsParams wgp;
	int maxy, maxx;

	getmaxyx(gHostWin, maxy, maxx);
	mvwaddstr(gHostWin, maxy - 1, 0, "/");
	wclrtoeol(gHostWin);
	wrefresh(gHostWin);
	curs_set(1);
	wgp.w = gHostWin;
	wgp.sy = maxy - 1;
	wgp.sx = 1;
	wgp.fieldLen = maxx - 1;
	wgp.dst = str;
	wgp.dstSize = size;
	wgp.useCurrentContents = 0;
	wgp.echoMode = wg_RegularEcho;
	wgp.history = wg_NoHistory;
	(void) wg_Gets(&wgp);
	cbreak();						/* wg_Gets turns off cbreak and delay. */

	TraceMsg("[%s]\n", str);
	wmove(gHostWin, maxy - 1, 0);
	wclrtoeol(gHostWin);
	wrefresh(gHostWin);
	curs_set(0);
}	/* HostWinGetStr */




/*ARGSUSED*/
void SigIntHostWin(int sigNum)
{
	longjmp(gHostWinJmp, 1);
}	/* SigIntHostWin */


#endif	/* USE_CURSES */


/* Runs the host editor.  Another big use for this is to open sites
 * that are in your host list.
 */
int HostWindow(void)
{
#ifdef USE_CURSES
	int c;
	string cmd;
	volatile RemoteSiteInfoPtr toOpen;
	VSig_t si;
	int maxy, maxx;
	int lmaxy, lmaxx;
	OpenOptions	openopt;

	si = kNoSignalHandler;
	if (gWinInit) {
		gHostListWin = NULL;
		gHostWin = NULL;

		gHostWin = newwin(LINES, COLS, 0, 0);
		if (gHostWin == NULL)
			return (kCmdErr);

		curs_set(0);
		cbreak();
		
		/* Set the clear flag for the first update. */
		wclear(gHostWin);
		keypad(gHostWin, TRUE);		/* For arrow keys. */
#ifdef HAVE_NOTIMEOUT
		notimeout(gHostWin, TRUE);
#endif

		if (setjmp(gHostWinJmp) == 0) {
			/* Gracefully cleanup the screen if the user ^C's. */
			si = (VSig_t) SIGNAL(SIGINT, SigIntHostWin);
			
			/* Initialize the page start and select a host to be
			 * the current one.
			 */
			gHostListWinStart = 0;
			gHilitedHost = 0;
			if (gNumRecents == 0)
				gCurHostListItem = NULL;
			else
				gCurHostListItem = gHostNickNames[gHilitedHost];
			
			/* Initially, we don't want to connect to any site in
			 * the host list.
			 */
			toOpen = NULL;
	
			WAttr(gHostWin, kBold, 1);
			WAddCenteredStr(gHostWin, 0, "Host Editor");
			WAttr(gHostWin, kBold, 0);
			
			mvwaddstr(gHostWin, 3, 2, "Open selected site:      <enter>");
			mvwaddstr(gHostWin, 4, 2, "Edit selected site:      /ed");
			mvwaddstr(gHostWin, 5, 2, "Delete selected site:    /del");
			mvwaddstr(gHostWin, 6, 2, "Duplicate selected site: /dup");
			mvwaddstr(gHostWin, 7, 2, "Add a new site:          /new");
			mvwaddstr(gHostWin, 9, 2, "Up one:                  <u>");
			mvwaddstr(gHostWin, 10, 2, "Down one:                <d>");
			mvwaddstr(gHostWin, 11, 2, "Previous page:           <p>");
			mvwaddstr(gHostWin, 12, 2, "Next page:               <n>");
			mvwaddstr(gHostWin, 14, 2, "Capital letters selects first");
			mvwaddstr(gHostWin, 15, 2, "  site starting with the letter.");
			mvwaddstr(gHostWin, 17, 2, "Exit the host editor:    <x>");
			
			/* Initialize the scrolling host list window. */
			gHostListWin = subwin(
				gHostWin,
				LINES - 7,
				40,
				3,
				COLS - 40 - 2
			);
			if (gHostListWin == NULL)
				return (kCmdErr);
			getmaxyx(gHostListWin, lmaxy, lmaxx);
			getmaxyx(gHostWin, maxy, maxx);
			gHostListPageSize = lmaxy;
			DrawHostList();
			wmove(gHostWin, maxy - 1, 0);
			UpdateHostWindows(1);

			while (1) {
				c = HostWinGetKey();
				if (gNeedToClearMsg) {
					wmove(gHostWin, maxy - 2, 0);
					wclrtoeol(gHostWin);
					wrefresh(gHostWin);
				}
				if ((c >= 'A') && (c <= 'Z')) {
					/* isupper can coredump if wgetch returns a meta key. */
					HostWinZoomTo(c);
				} else if (c == '/') {
					/* Get an "extended" command.  Sort of like vi's
					 * :colon commands.
					 */
					HostWinGetStr(cmd, sizeof(cmd));
	
					if (ISTREQ(cmd, "ed"))
						HostWinEdit();
					else if (ISTREQ(cmd, "dup"))
						HostWinDup();
					else if (ISTREQ(cmd, "del"))
						HostWinDelete();
					else if (ISTREQ(cmd, "new"))
						HostWinNew();
					else
						HostWinMsg("Invalid host editor command.");
				} else switch(c) {
					case 10:	/* ^J == newline */
					case 13:	/* ^M == carriage return */
#ifdef KEY_ENTER
					case KEY_ENTER:
#endif
						if (gCurHostListItem == NULL)
							HostWinMsg("Nothing to open.  Try 'open sitename' from the main screen.");
						else {
							toOpen = (volatile RemoteSiteInfoPtr) gCurHostListItem;
							goto done;
						}
						break;
	
					case kControl_L:
						UpdateHostWindows(1);
						break;
	
					case 'u':
					case 'k':	/* vi up key */
#ifdef KEY_UP
					case KEY_UP:
#endif
						HostListLineUp();
						break;
					
					case 'd':
					case 'j':	/* vi down key */
#ifdef KEY_DOWN
					case KEY_DOWN:
#endif
						HostListLineDown();
						break;
						
					case 'p':
#ifdef KEY_LEFT
					case KEY_LEFT:
#endif
#ifdef KEY_PPAGE
					case KEY_PPAGE:
#endif
						HostListPageUp();
						break;
						
					case 'n':
#ifdef KEY_RIGHT
					case KEY_RIGHT:
#endif
#ifdef KEY_NPAGE
					case KEY_NPAGE:
#endif
						HostListPageDown();
						break;
	
#ifdef KEY_END
					case KEY_END:
#endif
					case 'x':
					case 'q':
						goto done;
	
					default:
						HostWinMsg("Invalid key.");
						break;
				}
			}
		}
		SIGNAL(SIGINT, SIG_IGN);
done:
		if (gHostListWin != NULL)
			delwin(gHostListWin);
		if (gHostWin != NULL)
			delwin(gHostWin);
		gHostListWin = gHostWin = NULL;
		curs_set(1);
		nocbreak();
		UpdateScreen(1);
		flushinp();
		if (si != (Sig_t) kNoSignalHandler)
			SIGNAL(SIGINT, si);
		if (toOpen != (volatile RemoteSiteInfoPtr) 0) {
			/* If the user selected a site to open, connect to it now. */
			InitOpenOptions(&openopt);
			STRNCPY(openopt.hostname, ((RemoteSiteInfoPtr) toOpen)->nickName);
			GetRemoteInfo(openopt.hostname, sizeof(openopt.hostname));
			openopt.port = gRmtInfo.port;
			Beep(0);    /* Reset beep timer. */
			return (Open(&openopt));
		}
	}
#endif	/* USE_CURSES */
	Beep(0);    /* User should be aware that it took a while, so no beep. */
	return (kNoErr);
}	/* HostWindow */




int HostsCmd(int argc, char **argv)
{
#ifdef USE_CURSES
	if (!gWinInit) {
		Error(kDontPerror, "This only works in visual mode.\n");
		return (kCmdErr);
	}
	return HostWindow();
#else
	Error(kDontPerror,
	"You can't do this because the program doesn't have the curses library.\n");
	return (kCmdErr);
#endif	/* USE_CURSES */
}	/* HostsCmd */
