/*
 * Netrek player DB maintenance
 *
 * edit.c - get user input
 */
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <curses.h>
#include <sys/types.h>
#include <time.h>
#include <math.h>
#include <stdlib.h>		/* For malloc 10/20/94 [007] */
#include "pledit.h"
#include "defs.h"
#include "struct.h"
#include "data.h"


/*
 * ---------------------------------------------------------------------------
 *	Static data
 * ---------------------------------------------------------------------------
 */



#define DASHES	"--------------------------------------------------------------------------------"
#define CMDSTR	"Enter command:   (? for help)"
#define CMDPOSN	15


WINDOW *mainw;			/* the interesting window */
WINDOW *glw;			/* window with global data in it */

/*
 * ---------------------------------------------------------------------------
 *	Private variables
 * ---------------------------------------------------------------------------
 */
static char *pl_filename;	/* player file filename */
static char *gl_filename;	/* global file filename */
static int pl_saved;		/* has the .players file been saved? */
static int gl_saved;		/* has the .global file been saved? */
static int num_players;		/* #of players in the database */
static int num_ent;		/* amount of space in players array */
static int top_player;		/* #of player at top of screen */
static int cursor;		/* where the cursor is (player #) */
static int tilde = 0;		/* if TRUE, then we have tildefied the orig */

#define ENT_QUANTUM	512
typedef enum { E_ALIVE, E_DELETED } SE_STATE;
typedef struct {
    SE_STATE state;
    struct statentry se;
} STATS;
static STATS *player_st = NULL;		/* array of players */

static struct status gl;		/* global stats */

typedef char col80[81];
static col80 *plyr_lines = NULL;	/* display array */


/*
 * ---------------------------------------------------------------------------
 *	The Program
 * ---------------------------------------------------------------------------
 */
static void
init_screen()
{
    char buf[80];

    /*
     * set up the terminal
     */
#ifdef ENHANCED_CURSES
    cbreak();
#else
    crmode();
#endif
    noecho();
#ifdef ENHANCED_CURSES
    keypad(stdscr, FALSE);	/* do it the hard way */
#endif

    /*
     * draw the screen
     */
    if (LINES < 24)
	fatal(0, "Need at least 24 lines");
    mvprintw(0, 0, "PlEdit v1.0 by Andy McFadden");
    sprintf(buf, "Now editing: %s", pl_filename);
    mvprintw(0, (strlen(pl_filename) > 34) ? 32 : 80-strlen(buf), "%.48s", buf);
    mvprintw(1, 0, DASHES);
    mvprintw(1, 1, "#");
    mvprintw(1, 4, "Rank");
    mvprintw(1, 16, "Pseudonym");
    mvprintw(1, 35, "Hours----DI----Last login");
    mvprintw(LINES-2, 0, DASHES);
    mvprintw(LINES-1, 0, CMDSTR);
    move(LINES-1, CMDPOSN);

    top_player = cursor = 0;

    /* this is the window where everything interesting happens */
    if ((mainw = subwin(stdscr, LINES-4, COLS-14, 2, 0)) == NULL)
	fatal(0, "couldn't make mainw");
    /*idlok(mainw, TRUE);*/
    scrollok(mainw, FALSE);

    /* this is used to display the globals off to the side */
    if ((glw = subwin(stdscr, 14, 13, 4, 80-13)) == NULL)
	fatal(0, "couldn't make glw");
    box(glw, VERTCH, HORCH);
    mvwprintw(glw, 0, 2, " global ");
    mvwprintw(glw, 2, 2, "Hours:");
    mvwprintw(glw, 4, 2, "Planets:");
    mvwprintw(glw, 6, 2, "Bombing:");
    mvwprintw(glw, 8, 2, "Offense:");
    mvwprintw(glw, 10, 2, "Defense:");
    overwrite(glw, mainw);

    plyr_lines = NULL;		/* init plyr_lines array to empty */

    refresh();
}


/* pop up a message, refresh, then erase it but don't refresh */
static void
popup(str)
char *str;
{
    WINDOW *w;

    if ((w = newwin(5, strlen(str)+4, (LINES-5)/2,
	    (COLS - (strlen(str)+4)) /2)) == NULL)
	fatal(0, "couldn't make popup w");
    box(w, VERTCH, HORCH);
    mvwprintw(w, 0, (strlen(str)+4 -5) / 2, " FYI ");
    mvwprintw(w, 2, 2, "%s", str);
    wrefresh(w);

    delwin(w);
#ifdef ENHANCED_CURSES
    touchline(mainw, (LINES-5)/2 -2, 5);
#else
    touchwin(mainw);
#endif
    /* don't refresh; this way it'll stick around until wrefresh(mainw) */
}


static void
read_files()
{
    char buf[80];
    int pl_fd, gl_fd;

    popup("Reading players and global files ");

    pl_saved = gl_saved = TRUE;
    num_players = 0;
    num_ent = ENT_QUANTUM;	/* default size is ENT_QUANTUM entries */

    if ((pl_fd = open(pl_filename, O_RDONLY)) < 0)
	fatal(errno, "unable to open %s", pl_filename);
    if ((gl_fd = open(gl_filename, O_RDONLY)) < 0)
	fatal(errno, "unable to open %s", gl_filename);

    player_st = (STATS *) malloc(sizeof(STATS) * num_ent);
    while (1) {
	if (num_players == num_ent) {
	    /* about to run out of space; realloc buffer */
	    num_ent += ENT_QUANTUM;
	    player_st = (STATS *) realloc(player_st, sizeof(STATS) * num_ent);
	}
	if (read(pl_fd, &(player_st[num_players].se), sizeof(struct statentry))
						== sizeof(struct statentry)) {
	    player_st[num_players].state = E_ALIVE;
	    /* fix for Pig borg corrupted scores files */
	    if (!player_st[num_players].se.stats.st_tticks) 
		player_st[num_players].se.stats.st_tticks = 1;
	    num_players++;
	} else
	    break;
    }

    if (!num_players)
	fatal(0, "no entries in player database");

    if (read(gl_fd, &gl, sizeof(struct status)) != sizeof(struct status))
	fatal(errno, "unable to read .global file");

    close(pl_fd);
    close(gl_fd);

    wrefresh(mainw);
    sprintf(buf, "Read %d player entries ", num_players);
    popup(buf);
    sleep(POP_DELAY);
    wrefresh(mainw);
}


#define WRITE_HEIGHT	5
#define WRITE_WIDTH	41
static void
write_files(exit_prompt)
int exit_prompt;
{
    WINDOW *w;
    char filename2[256];
    int i, fd, ch, err;

    if ((w = newwin(WRITE_HEIGHT, WRITE_WIDTH, (LINES-WRITE_HEIGHT)/2,
            (COLS - WRITE_WIDTH) /2)) == NULL)
        fatal(0, "couldn't make write screen");

    box(w, VERTCH, HORCH);
    mvwprintw(w, 0, (WRITE_WIDTH - 13)/2, "%s", " write files ");

    if (exit_prompt) {
	mvwprintw(w, 2, 2, "Save changes before leaving (y/n)? ");
    } else {
	mvwprintw(w, 2, 2, "Write .players and .global (y/n)? ");
    }

    wrefresh(w);
    ch = wgetch(w);

    if ((char) ch == 'y' || (char) ch == 'Y') {
	/* save files */
	strcpy(filename2, pl_filename);
	strcat(filename2, "~");
	if (!tilde) {
	    /* if this is the first time, back up the old file */
	    unlink(filename2);	/* remove existing ~ file */
	    if (link(pl_filename, filename2) < 0) goto failed;
	}
	if (unlink(pl_filename) < 0) goto failed;
	if ((fd = open(pl_filename, O_WRONLY|O_CREAT, 0644)) < 0) goto failed;
	for (i = 0; i < num_players; i++) {
	    if (player_st[i].state != E_ALIVE) continue;
	    if (write(fd, &(player_st[i].se), sizeof(struct statentry))
						!= sizeof(struct statentry)) {
		goto failed;
	    }
	}
	close(fd);

	strcpy(filename2, gl_filename);
	strcat(filename2, "~");
	if (!tilde) {
	    unlink(filename2);
	    if (link(gl_filename, filename2) < 0) goto failed;
	}
	if (unlink(gl_filename) < 0) goto failed;
	if ((fd = open(gl_filename, O_WRONLY | O_CREAT, 0644)) < 0) goto failed;
	if (write(fd, &gl, sizeof(struct status)) != sizeof(struct status))
	    goto failed;
	close(fd);

	popup("Saved ");
	sleep(POP_DELAY);
	/*wrefresh(mainw);*/

	pl_saved = TRUE;
	gl_saved = TRUE;
	tilde = 1;
    }

    delwin(w);
#ifdef ENHANCED_CURSES
    touchline(mainw, (LINES-WRITE_HEIGHT)/2 -2, WRITE_HEIGHT);
#else
    touchwin(mainw);
#endif
    wrefresh(mainw);

    return;

failed:
    err = errno;
    wclear(w);
    box(w, VERTCH, HORCH);
    mvwprintw(w, 0, (WRITE_WIDTH - 13)/2, "%s", " write error ");
    mvwprintw(w, 2, 2, "Error %d during save ", err);
    wgetch(w);

    delwin(w);
#ifdef ENHANCED_CURSES
    touchline(mainw, (LINES-WRITE_HEIGHT)/2 -2, WRITE_HEIGHT);
#else
    touchwin(mainw);
#endif
    wrefresh(mainw);
}


static void
build_line(line)
int line;
{
    struct statentry *sep;
    time_t t;
    float bombing, offense, planets, di;

    sep = &(player_st[line].se);

    strcpy(plyr_lines[line], BLANKS);
    if (player_st[line].state == E_ALIVE) {
	sprintf(plyr_lines[line], "%3d %-10s  %s_", line,
		ranks[sep->stats.st_rank].name, sep->name);
    } else {
	sprintf(plyr_lines[line], "%3d %-10s  %s_", line,
		"(DELETED)", sep->name);
    }
    plyr_lines[line][strlen(plyr_lines[line])] = ' ';	/* nuke the '\0' */
    t = (time_t) sep->stats.st_lastlogin;

    bombing = (float) sep->stats.st_tarmsbomb * (float) gl.timeprod /
	((float) sep->stats.st_tticks * (float) gl.armsbomb);
    planets = (float) sep->stats.st_tplanets * (float) gl.timeprod /
	((float) sep->stats.st_tticks * (float) gl.planets);
    offense = (float) sep->stats.st_tkills * (float) gl.timeprod /
	((float) sep->stats.st_tticks * (float) gl.kills);
    di = (bombing + planets + offense) * sep->stats.st_tticks/36000.0;
    sprintf(plyr_lines[line]+33, "%7.1f  %6.1f  %.16s",
	    (float)(sep->stats.st_ticks + sep->stats.st_tticks)/36000.0,
	    di,
	    ctime(&t));
}


/* call with line to create or (-1) to build the whole thing */
static void
build_display(line)
int line;
{
    int i;

    if (line < 0) {
	/* rebuild whole thing */
	if (plyr_lines != NULL) {
	    free(plyr_lines);
	    plyr_lines = NULL;
	}
	plyr_lines = (col80 *) malloc(num_players * sizeof(col80));

	for (i = 0; i < num_players; i++)
	    build_line(i);
    } else {
	build_line(line);
    }
}


/* redraw the contents of the main window and the gl window */
static void
display()
{
    int i, max;

    max = (num_players < LINES-4) ? num_players : LINES-4;

    for (i = 0; i < max; i++) {
	if (i+top_player == cursor) {
	    /* draw first 3 in inverse */
	    wstandout(mainw);
	    mvwprintw(mainw, i, 0, "%.3s", plyr_lines[i+top_player]);
	    wstandend(mainw);
	    mvwprintw(mainw, i, 3, "%s", plyr_lines[i+top_player]+3);
	} else {
	    mvwprintw(mainw, i, 0, "%s", plyr_lines[i+top_player]);
	}
    }

    mvwprintw(glw, 3, 3, "%-8.1f", gl.timeprod/36000.0);	/* was time */
    mvwprintw(glw, 5, 3, "%-8d", gl.planets);
    mvwprintw(glw, 7, 3, "%-8d", gl.armsbomb);
    mvwprintw(glw, 9, 3, "%-8d", gl.kills);
    mvwprintw(glw, 11,3, "%-8d", gl.losses);

    wrefresh(mainw);
    wrefresh(glw);
}


#define SRCH_HEIGHT	5
#define SRCH_WIDTH	52
static int
search(start, fwd)
int start, fwd;
{
    WINDOW *w;
    int res, posn, incr, match;
    static char buf[16] = "               ";

    if ((w = newwin(SRCH_HEIGHT, SRCH_WIDTH, (LINES-SRCH_HEIGHT)/2,
            (COLS - SRCH_WIDTH) /2)) == NULL)
        fatal(0, "couldn't make search screen");

    box(w, VERTCH, HORCH);
    mvwprintw(w, 0, (SRCH_WIDTH - 18)/2, "%s", " search for entry ");
    mvwprintw(w, 2, 2, "Player name? ");

    match = -1;

    res = get_line(w, 2, 15, 15, buf);

    switch (res) {
    case 1:		/* no change, move ahead */
    case 2:		/* new string, move ahead */
    case -1:		/* no change, move backward */
    case -2:		/* new string, move backward */
	break;
    case 3:		/* no change, escape hit - exit */
	goto escape_hit;
    default:
	fatal(0, "internal error: bad result from get_line");
    }

    if (fwd) incr = 1;
    else     incr = -1;

    posn = start + incr;
    if (posn < 0) posn = num_players-1;
    if (posn >= num_players) posn = 0;

    while (posn != start) {
	if (!strcmp(player_st[posn].se.name, buf)) {
	    match = posn;
	    break;
	}
	posn += incr;
	if (posn < 0) posn = num_players-1;
	if (posn >= num_players) posn = 0;
    }

    if (match < 0) {
	popup("No match found ");
	sleep(POP_DELAY);
    }


escape_hit:
    delwin(w);
#ifdef ENHANCED_CURSES
    touchline(mainw, (LINES-SRCH_HEIGHT)/2 -2, SRCH_HEIGHT);
#else
    touchwin(mainw);
#endif
    wrefresh(mainw);

    return (match);
}


add_player()
{
    struct statentry *sep;
    int i;

    if (num_players == num_ent) {
	/* about to run out of space; realloc buffer */
	num_ent += ENT_QUANTUM;
	realloc(player_st, sizeof(STATS) * num_ent);
    }

    player_st[num_players].state = E_ALIVE;
    sep = &(player_st[num_players].se);
    MZERO(sep, sizeof(struct statentry));
    strcpy(sep->name, "New");
    strcpy(sep->password, "foo");
    sep->stats.st_tticks = 1;		/* prevent division errors */
    sep->stats.st_flags = 175;		/* default */
    for (i=0; i<95; i++) {
	sep->stats.st_keymap[i]=i+32;	/* init keymap */
    }
    num_players++;

    build_display(-1);		/* rebuild the whole thing */
    display();			/* only works if num_players < page */
}


static void
print_player(sep, glp)
struct statentry *sep;
struct status *glp;
{
    float bombing, planets, offense, di;
    char buf[32];

    bombing = (float) sep->stats.st_tarmsbomb * (float) glp->timeprod /
	((float) sep->stats.st_tticks * (float) glp->armsbomb);
    planets = (float) sep->stats.st_tplanets * (float) glp->timeprod /
	((float) sep->stats.st_tticks * (float) glp->planets);
    offense = (float) sep->stats.st_tkills * (float) glp->timeprod /
	((float) sep->stats.st_tticks * (float) glp->kills);
    di = (bombing + planets + offense) * sep->stats.st_tticks/36000.0;

/*    wclear(mainw);		/* use the whole screen */
    mvwprintw(mainw, 0, 24, "Editing player");

    sprintf(buf, "%s_", sep->name);
    mvwprintw(mainw, 2, 0, "Name: %-16s", buf);
    mvwprintw(mainw, 2, 37, "Password: %-13s", sep->password);

    mvwprintw(mainw, 4, 0, "Non-tournament stats:");
    mvwprintw(mainw, 5, 2, "Ticks:  %10d    Kills:   %6d    Losses:    %6d",
	sep->stats.st_ticks, sep->stats.st_kills, sep->stats.st_losses);
    mvwprintw(mainw, 6, 2, "Armies: %10d    Planets: %6d    Maxkills: %7.2f",
	sep->stats.st_armsbomb, sep->stats.st_planets, sep->stats.st_maxkills);

    mvwprintw(mainw, 7, 0, "Tournament stats:");
    mvwprintw(mainw, 8, 2, "Ticks:  %10d    Kills:   %6d    Losses:    %6d",
	sep->stats.st_tticks, sep->stats.st_tkills, sep->stats.st_tlosses);
    mvwprintw(mainw, 9, 2, "Armies: %10d    Planets: %6d    DI:      %8.2f",
	sep->stats.st_tarmsbomb, sep->stats.st_tplanets, di);

    mvwprintw(mainw, 10, 0, "Starbase stats:");
    mvwprintw(mainw, 11, 2, "Ticks:  %10d    Kills:   %6d    Losses:    %6d",
	sep->stats.st_sbticks, sep->stats.st_sbkills, sep->stats.st_sblosses);
    mvwprintw(mainw, 12, 43, "Maxkills: %7.2f",
	sep->stats.st_sbmaxkills);

    mvwprintw(mainw, 14, 0, "Miscellaneous:");
    mvwprintw(mainw, 15, 2, "Last lg:%10d    Rank:    %6d    Flags:     %6d",
	sep->stats.st_lastlogin, sep->stats.st_rank, sep->stats.st_flags);
    mvwprintw(mainw, 16, 2, "Keymap: %.48s", sep->stats.st_keymap);
    mvwprintw(mainw, 17, 2, "        %s", sep->stats.st_keymap+48);

    mvwprintw(stdscr, LINES-3, 0,
	"You may: e)dit, r)eset keymap, a)bort, q)uit & save changes");

    wrefresh(mainw);
}

/*
 * I whipped up this fancy struct thing to make things semi-general.  I
 * think the only real reason for having this is so that all of the screen
 * coordinates are stored in one place; it doesn't really matter whether
 * stuff gets hard-coded in a static struct or hard-coded in static code.
 */
enum { F_INT, F_FLOAT, F_STRING };
static struct scrn_def_t {
    int x,y;
    int width;
    int format;
} scrn_def[] = {
    {  2,  6, 15, F_STRING },	/* name */
    {  2, 47, 13, F_STRING },	/* password */
    {  5, 10, 10, F_INT },	/* ticks */
    {  5, 33,  6, F_INT },	/* kills */
    {  5, 54,  6, F_INT },	/* losses */
    {  6, 10, 10, F_INT },	/* armies */
    {  6, 33,  6, F_INT },	/* planets */
    {  6, 53,  7, F_FLOAT },	/* maxkills */
    {  8, 10, 10, F_INT },	/* tticks */
    {  8, 33,  6, F_INT },	/* tkills */
    {  8, 54,  6, F_INT },	/* tlosses */
    {  9, 10, 10, F_INT },	/* tarmsbomb */
    {  9, 33,  6, F_INT },	/* tplanets */
    { 11, 10, 10, F_INT },	/* sbticks */
    { 11, 33,  6, F_INT },	/* sbkills */
    { 11, 54,  6, F_INT },	/* sblosses */
    { 12, 53,  7, F_FLOAT },	/* sbmaxkills */
    { 15, 10, 10, F_INT },	/* last login */
    { 15, 33,  6, F_INT },	/* rank */
    { 15, 54,  6, F_INT },	/* flags */
};
#define SCRN_SIZE	(sizeof(scrn_def) / sizeof(struct scrn_def_t))

static void
do_edit(sep, glp)
struct statentry *sep;
struct status *glp;
{
    char *crypt();
    int idx, newidx, res, new;
    char field[81], tmp[32];
    char oldpw[16];
    int ival;
    float fval;
    char *sval;

    strcpy(oldpw, sep->password);

    for (idx = 0; idx < SCRN_SIZE; ) {
	strcpy(field, BLANKS);

	switch (idx) {
	case  0: sval = sep->name; break;
	case  1: sval = sep->password; break;
	case  2: ival = sep->stats.st_ticks; break;
	case  3: ival = sep->stats.st_kills; break;
	case  4: ival = sep->stats.st_losses; break;
	case  5: ival = sep->stats.st_armsbomb; break;
	case  6: ival = sep->stats.st_planets; break;
	case  7: fval = sep->stats.st_maxkills; break;
	case  8: ival = sep->stats.st_tticks; break;
	case  9: ival = sep->stats.st_tkills; break;
	case 10: ival = sep->stats.st_tlosses; break;
	case 11: ival = sep->stats.st_tarmsbomb; break;
	case 12: ival = sep->stats.st_tplanets; break;
	case 13: ival = sep->stats.st_sbticks; break;
	case 14: ival = sep->stats.st_sbkills; break;
	case 15: ival = sep->stats.st_sblosses; break;
	case 16: fval = sep->stats.st_sbmaxkills; break;
	case 17: ival = sep->stats.st_lastlogin; break;
	case 18: ival = sep->stats.st_rank; break;
	case 19: ival = sep->stats.st_flags; break;
	default:
	    fatal(0, "missing idx in do_edit get");
	}

	/* bad things might happen here if number is wider than field */
	switch (scrn_def[idx].format) {
	case F_INT:
	    sprintf(tmp, "%d", ival);
	    strcpy(field + (scrn_def[idx].width - strlen(tmp)), tmp);
	    break;
	case F_FLOAT:
	    sprintf(tmp, "%.2f", fval);
	    strcpy(field + (scrn_def[idx].width - strlen(tmp)), tmp);
	    break;
	case F_STRING:
	    strcpy(field, sval);
	    break;
	}

	/* remove the "sprintf()" '\0', then put in field null */
	field[strlen(field)] = ' ';
	field[scrn_def[idx].width] = '\0';

	/* result is ASCII string stored in get_line() */
	res = get_line(mainw, scrn_def[idx].x, scrn_def[idx].y,
		scrn_def[idx].width, field);

	newidx = idx;

	switch (res) {
	case 1:		/* no change, move ahead */
	    new = FALSE;
	    newidx++;
	    break;
	case 2:		/* new string, move ahead */
	    new = TRUE;
	    newidx++;
	    break;
	case -1:	/* no change, move backward */
	    new = FALSE;
	    newidx--;
	    break;
	case -2:	/* new string, move backward */
	    new = TRUE;
	    newidx--;
	    break;
	case 3:		/* no change, escape hit - exit */
	    goto escape_hit;
	default:
	    fatal(0, "internal error: bad result from get_line");
	}

	/* if it changed, we have work to do... */
	if (new) {
	    /* convert from ascii to value */
	    switch (scrn_def[idx].format) {
	    case F_INT:
		ival = atoi(field);
		break;
	    case F_FLOAT:
		fval = atof(field);
		break;
	    case F_STRING:
		sval = field;
		break;
	    }

	    switch (idx) {
	    case  0: strcpy(sep->name, sval); break;
	    case  1: strcpy(sep->password, sval); break;
	    case  2: sep->stats.st_ticks = ival; break;
	    case  3: sep->stats.st_kills = ival; break;
	    case  4: sep->stats.st_losses = ival; break;
	    case  5: sep->stats.st_armsbomb = ival; break;
	    case  6: sep->stats.st_planets = ival; break;
	    case  7: sep->stats.st_maxkills = fval; break;
	    case  8: sep->stats.st_tticks = ival; break;
	    case  9: sep->stats.st_tkills = ival; break;
	    case 10: sep->stats.st_tlosses = ival; break;
	    case 11: sep->stats.st_tarmsbomb = ival; break;
	    case 12: sep->stats.st_tplanets = ival; break;
	    case 13: sep->stats.st_sbticks = ival; break;
	    case 14: sep->stats.st_sbkills = ival; break;
	    case 15: sep->stats.st_sblosses = ival; break;
	    case 16: sep->stats.st_sbmaxkills = fval; break;
	    case 17: sep->stats.st_lastlogin = ival; break;
	    case 18: sep->stats.st_rank = ival; break;
	    case 19: sep->stats.st_flags = ival; break;
	    default:
		fatal(0, "missing idx in do_edit put");
	    }
	}
	idx = newidx;
	if (idx < 0) idx = SCRN_SIZE-1;

	print_player(sep, glp);
    }
escape_hit:
    print_player(sep, glp);

    if (strcmp(sep->password, oldpw)) {
	/* password changed, so recompute encrypted version */
	strcpy(sep->password, crypt(sep->password, sep->name));
    }
}

#define GLUP_HEIGHT	5
#define GLUP_WIDTH	46
static int
update_global(oldsep, newsep, glp)
struct statentry *oldsep, *newsep;
struct status *glp;
{
    WINDOW *w;
    int ch, res;

    if ((w = newwin(GLUP_HEIGHT, GLUP_WIDTH, (LINES-GLUP_HEIGHT)/2,
            (COLS - GLUP_WIDTH) /2)) == NULL)
        fatal(0, "couldn't make glup screen");

    box(w, VERTCH, HORCH);
    mvwprintw(w, 0, (GLUP_WIDTH - 15)/2, "%s", " global update ");
    mvwprintw(w, 2, 2, "Propagate changes to global stats (y/n)? ");

    wrefresh(w);
    ch = wgetch(w);

    res = FALSE;
    if ((char) ch == 'y' || (char) ch == 'Y') {
	/* update the globals */
	/*glp->time += (newsep->stats.st_tticks - oldsep->stats.st_tticks);*/
	glp->timeprod += (newsep->stats.st_tticks - oldsep->stats.st_tticks);
	glp->kills += (newsep->stats.st_tkills - oldsep->stats.st_tkills);
	glp->losses += (newsep->stats.st_tlosses - oldsep->stats.st_tlosses);
	glp->armsbomb +=(newsep->stats.st_tarmsbomb-oldsep->stats.st_tarmsbomb);
	glp->planets += (newsep->stats.st_tplanets - oldsep->stats.st_tplanets);
	res = TRUE;
    }

    delwin(w);
#ifdef ENHANCED_CURSES
    touchline(mainw, (LINES-GLUP_HEIGHT)/2 -2, GLUP_HEIGHT);
#else
    touchwin(mainw);
#endif
/*    wrefresh(mainw);*/	/* (only called when leaving edit_player) */
    return (res);
}

static void
edit_player(plyr)
int plyr;
{
    static struct statentry newse;
    static struct status newgl;
    struct statentry *sep;
    int i, ch, done;

    wclear(mainw);
    sep = &(player_st[plyr].se);
    MCOPY(sep, &newse, sizeof(struct statentry));
    MCOPY(&gl, &newgl, sizeof(struct status));

    print_player(&newse, &newgl);

    done = FALSE;
    while (!done) {
	move(LINES-1, CMDPOSN);
	refresh();
	ch = getch();

	switch (ch) {
	case 'e':	/* edit */
	    do_edit(&newse, &newgl);
	    print_player(&newse, &newgl);
	    pl_saved = FALSE;		/* assume the worst, it's easier */
	    gl_saved = FALSE;
	    break;
	case 'r':	/* reset keymap */
	    for (i=0; i<95; i++) {
		newse.stats.st_keymap[i]=i+32;
	    }
	    pl_saved = FALSE;
	    print_player(&newse, &newgl);
	    break;
	case 'a':	/* abort */
	case ' ':	/* super secret key equivalent */
	    done = TRUE;
	    break;
	case 'q':	/* quit & save */
	case 'Q':	/* accept capitals too */
	    /* if tournament stats changed, update global stats on request */
	    if (newse.stats.st_tticks != sep->stats.st_tticks ||
		newse.stats.st_tkills != sep->stats.st_tkills ||
		newse.stats.st_tlosses != sep->stats.st_tlosses ||
		newse.stats.st_tarmsbomb != sep->stats.st_tarmsbomb ||
		newse.stats.st_tplanets != sep->stats.st_tplanets) {

		if (update_global(sep, &newse, &newgl)) {
		    MCOPY(&newgl, &gl, sizeof(struct status));
		    build_display(-1);		/* update all (DI) */
		}
	    }
	    /* if nothing changed, this has no effect */
	    MCOPY(&newse, sep, sizeof(struct statentry));
	    done = TRUE;
	    break;
	case '\014':	/* ^L */
	case '\022':	/* ^R */
	    wrefresh(curscr);	/* redraw */
	    break;
	case '?':
	    popup("(all available commands are shown below)");
	    sleep(POP_DELAY);
	    wrefresh(mainw);
	    break;
	default:
	    break;
	    /* nada */
	}
    }

    build_display(plyr);	/* rebuild this one in case it got changed */
    wclear(mainw);
    display();
}


#define PAGE	(LINES-4)
static void
main_loop()
{
    int key, oldcursor, change, res;

    build_display(-1);
    display();

    while (1) {
	key = get_input(stdscr, LINES-1, CMDPOSN);

	oldcursor = cursor;
	change = 0;

	switch (key) {
	case KY_UP:
	    cursor--;
	    break;
	case KY_DN:
	    cursor++;
	    break;
	case KY_PGUP:
	    cursor -= PAGE;
	    top_player -= PAGE;		/* move screen a full page too */
	    break;
	case KY_PGDN:
	    cursor += PAGE;
	    top_player += PAGE;
	    break;
	case KY_HOME:
	    cursor = 0;
	    top_player = 0;
	    break;
	case KY_END:
	    cursor = num_players -1;
	    break;
	case KY_FWD_SRCH:
	    if ((res = search(cursor, TRUE)) >= 0) {
		cursor = res;
		if (cursor >= top_player + PAGE)
		    top_player = cursor - 5;
	    }
	    break;
	case KY_ADD:
	    add_player();
	    edit_player(num_players-1);		/* edit the new one */
	    pl_saved = FALSE;
	    break;
	case KY_EDIT:
	    edit_player(cursor);
	    break;
	case KY_DELETE:
	    if (player_st[cursor].state == E_ALIVE) {
		player_st[cursor].state = E_DELETED;
		build_display(cursor);
		change++;
		pl_saved = FALSE;
	    }
	    break;
	case KY_UNDELETE:
	    if (player_st[cursor].state == E_DELETED) {
		player_st[cursor].state = E_ALIVE;
		build_display(cursor);
		change++;
		/* no effect on pl_saved */
	    }
	    break;
	case KY_WRITE:
	    write_files(FALSE);
	    break;

	case KY_LFT:
	case KY_RGT:
	case KY_TAB:
	case KY_BKTAB:
	case KY_ESCAPE:
	    /* these don't do anything here */
	    break;
	case KY_KEYS:
	case KY_HELP:
	case KY_REDRAW:
	    /* these are handled by get_input() */
	    break;

	case KY_QUIT:
	    return;
	default:
	    fatal(0, "Got strange keypress in main_loop from get_input");
	    break;
	}

	/* update the display */
	if (cursor < 0) cursor = 0;
	if (cursor >= num_players) cursor = num_players-1;
	if (num_players > PAGE) {
	    if (top_player < 0) top_player = 0;
	    if (top_player >= (num_players-PAGE)) top_player = num_players-PAGE;
	    if (cursor < top_player)
		top_player = cursor;
	    if (cursor >= top_player + PAGE)
		top_player += cursor - (top_player+PAGE-1);
	    if (top_player >= (num_players-PAGE)) top_player = num_players-PAGE;
	} else {
	    top_player = 0;
	}

	if (cursor != oldcursor || change)
	    display();		/* "oldcursor" check eases load on curses */
    }
}


/*
 * Entry point from main.c
 */
void
edit_file(pfilename, gfilename)
char *pfilename, *gfilename;
{
    pl_filename = pfilename;
    gl_filename = gfilename;

    /* draw the screen, set up windows, etc */
    init_screen();

    /* read the .players and .global files in */
    read_files();

    /* go to the main input loop */
    main_loop();

    /* time to exit */
    if (!pl_saved || !gl_saved)
	write_files(TRUE);	/* ask before writing */

    move(LINES-1, 0);
    clrtoeol();
    refresh();
    return;
}

