/* Map graphics for the curses interface to Xconq.
   Copyright (C) 1986-1989, 1991-1996, 1998-2000 Stanley T. Shebs.

Xconq is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.  See the file COPYING.  */

#include "conq.h"
#include "cconq.h"

static int xform(int x, int y, int *sxp, int *syp);
static void draw_divider(void);
static void draw_terrain_row(int x, int y, int len);
static void draw_units(int x, int y);
static void draw_people(int x, int y);
static void draw_legend(int x, int y);
static void draw_unit_details(Unit *unit);
static void describe_cell(int x, int y, int tview, char *filler,
				  char *buf);
static void organize_list_contents(void);
static void add_unit_to_list(Unit *unit);
static void draw_unit_list_entry(int line);
static void draw_type_list_entry(int line);

char *dashbuffer;

/* When true, draw the terrain character in both character positions
   of a cell; otherwise draw in just the left position and put a blank
   in the right position. */

int use_both_chars = TRUE;

/* When true, use standout mode to highlite mode lines and such.
   Standout mode increases visual clutter somewhat, so it's not
   obviously good or obviously bad. */

int use_standout = FALSE;

int drawlinear = TRUE;

char linear_char = '\\';

char bord_char = '\0';

char conn_char = '\0';

int listtype = 0;

int firstvisible = 0;
int lastvisible = 30;
UnitVector *listvector = NULL;
int listnumunits = 0;

int first_visible_help_pos;
int last_visible_help_pos;

/* Completely redo a screen, making no assumptions about appearance.
   This is a last-gasp measure; most redrawing should be restricted to
   only the directly affected windows.  Also this shouldn't be done
   without the user's permission, since it will blow away impending
   input. */

void
redraw(void)
{
    if (active_display(dside)) {
	switch (mode) {
	  case SURVEY:
	  case MOVE:
	  case PROMPT:
	  case PROMPTXY:
	    clear();
	    draw_divider();
	    show_toplines();
	    show_game_date();
	    show_clock();
	    show_side_list();
	    show_list();
	    show_closeup();
	    show_map();
	    break;
	  case HELP:
	  case MORE:
	    show_help();
	    break;
	  default:
	    break;
	}
	show_cursor();
	refresh();
    }
}

static void
draw_divider(void)
{
    int i;

    for (i = 2; i < LINES; ++i) {
	mvaddstr(i, mw, "|");
    }
}

/* Decide whether given location is not too close to edge of screen.
   We do this because it's a pain to move units when half the adjacent
   cells aren't even visible.  This routine effectively places a lower
   limit of 5x5 for the map window. (I think) */

int
in_middle(int x, int y)
{
    int sx, sy;

    xform(x, y, &sx, &sy);
    return ((between (3, sx, mw - 3) || !between(2, x, area.width-3))
	    && (between (3, sy, mh - 3) || !between(2, y, area.height-3)));
}

static int
xform(int x, int y, int *sxp, int *syp)
{
    xform_cell(mvp, x, y, sxp, syp);
    return TRUE;
}

void
set_scroll(void)
{
    int sx, sy, hexadj = hexagon_adjust(mvp);

    sx = mvp->sx;  sy = mvp->sy; 
    if (mw < (mvp->totsw - hexadj))
      sx = max(sx, hexadj);
    else
      sx = hexadj;
    if (mh >= mvp->totsh)
      sy = 0;
    set_view_position(mvp, sx, sy);
}

void
set_map_viewport(void)
{
    /* Compute the size of the viewport. */
    vw = min(area.width, mw / 2 + 1);
    vh = min(area.height, mh);
    /* Compute the bottom visible row. */
    vy = (mvp->totsh - mvp->sy) - vh;
    /* Adjust to keep its value from being outside the area. */
    vy = max(0, min(vy, area.height - vh));
    /* Compute the first visible column. */
    vx = mvp->sx / 2 - vy / 2 - 1;
    DGprintf("Set %dx%d viewport at %d,%d\n", vw, vh, vx, vy);
}

/* Display a map and all of its paraphernalia. */

void
show_map(void)
{
    int y1, y2, y, x1, x2;
    int halfheight = area.height / 2;

    clear_window(mapwin);
    set_map_viewport();
    /* Compute top and bottom rows to be displayed. */
    y1 = min(vy + vh, area.height - 1);
    y2 = vy;
    for (y = y1; y >= y2; --y) {
	/* Adjust the right and left bounds to fill the viewport as
	   much as possible, without going too far (the drawing code
	   will clip, but clipped drawing is still expensive). */
	x1 = vx - (y - vy) / 2;
	x2 = x1 + vw + 2;
	/* If the area doesn't wrap, then we might have to stop
	   drawing before we reach the edge of the viewport. */
	if (!area.xwrap) {
	    x1 = max(0, min(x1, area.width - 1));
	    x2 = max(0, min(x2, area.width));
	    if (x1 + y > area.width + halfheight)
	      continue;
	    if (x2 + y < halfheight)
	      continue;
	    if (x2 + y > area.width + halfheight)
	      x2 = area.width + halfheight - y;
	    if (x1 + y < halfheight)
	      x1 = halfheight - y;
	}
	draw_row(x1, y, x2 - x1);
    }
    /* Draw modeline in standout if desired and possible. */
    if (use_standout)
      standout();
    tmpbuf[0] = '\0';
    if (!drawterrain) {
	if (strlen(tmpbuf) > 0)
	  strcat(tmpbuf, ",");
	strcat(tmpbuf, "noterrain");
    }
    if (drawunits) {
	if (strlen(tmpbuf) > 0)
	  strcat(tmpbuf, ",");
	strcat(tmpbuf, "units");
    }
    if (drawpeople) {
	if (strlen(tmpbuf) > 0)
	  strcat(tmpbuf, ",");
	strcat(tmpbuf, "people");
    }
    if (drawnames) {
	if (strlen(tmpbuf) > 0)
	  strcat(tmpbuf, ",");
	strcat(tmpbuf, "names");
    }
    memcpy(spbuf, dashbuffer, mw);
    memcpy(spbuf+2, "Map", 3);
    memcpy(spbuf+7, (mode == SURVEY ? "Survey" : "-Move-"), 6);
    memcpy(spbuf+17, "(", 1);
    memcpy(spbuf+18, tmpbuf, strlen(tmpbuf));
    memcpy(spbuf+18+strlen(tmpbuf), ")", 1);
    spbuf[mw] = '\0';
    /* (should add followaction flag to status line?) */
    draw_text(mapwin, 0, mh, spbuf);
    if (use_standout)
      standend();
}

void
draw_row(int x0, int y0, int len)
{
    int x;

    if (drawterrain) {
	draw_terrain_row(x0, y0, len);
    }
    /* Draw sparse things on top of the basic row. */
    if (people_sides_defined() && drawpeople) {
	for (x = x0; x < x0 + len; ++x) {
	    if (!inside_area(x, y0))
	      continue;
	    draw_people(x, y0);
	}
    }
    /* Draw units on top of people in cell. */
    if (drawunits) {
	for (x = x0; x < x0 + len; ++x) {
	    if (!inside_area(x, y0))
	      continue;
	    draw_units(x, y0);
	}
    }
    /* Draw names last.  A long name may obscure a number of units, but
       it obscures the terrain anyway, so you can't expect to play
       effectively when names are displayed. */
    if (drawnames) {
	for (x = x0; x < x0 + len; ++x) {
	    if (!inside_area(x, y0))
	      continue;
	    draw_legend(x, y0);
	}
    }
}

/* Draw a single row of just terrain. */

static void
draw_terrain_row(int x0, int y0, int len)
{
    char ch, ch2;
    int x, sx, sy, t, t2;

    xform(x0, y0, &sx, &sy);
    for (x = x0; x < x0 + len; ++x) {
	ch = ((x % 2 == 0 && y0 % 2 == 0) ? unseen_char_2 : unseen_char_1);
	ch2 = ' ';
	if (terrain_visible(dside, x, y0)) {
	    t = terrain_at(x, y0);
	    ch = terrchars[t];
	    ch2 = (use_both_chars ? ch : ' ');
	    /* Just provide a hint at presence of aux terrain. */
	    if (drawlinear && any_aux_terrain_defined()) {
		for_all_terrain_types(t2) {
		    if (aux_terrain_defined(t2)) {
			if (aux_terrain_at(x, y0, t2)) {
			    ch2 = linear_char;
			    break;
			}
		    }
		}
	    }
	    /* Don't draw the second char if showing view coverage. */
	    if (draw_cover && !dside->show_all && cover(dside, x, y0) <= 0)
	      ch2 = ' ';
	}
	if (cur_at(mapwin, sx, sy))
	  addch(ch);
	if (cur_at(mapwin, sx + 1, sy))
	  addch(ch2);
	sx += 2;
    }
}

/* Draw a single unit char pair as appropriate. */

static void
draw_units(int x, int y)
{
    int draw = FALSE, xw, sx, sy, u, s;
    UnitView *uview;

    xw = wrapx(x);    
    /* Always make the curunit take precedence. */
    if (in_play(curunit) && curunit->x == xw && curunit->y == y) {
	u = curunit->type;
	s = curunit->side->id;
	draw = TRUE;
    } else if ((uview = unit_view_at(dside, x, y)) != NULL) {
	u = view_type(uview);
	s = view_side(uview)->id;
	draw = TRUE;
    }
    if (draw) {
	xform(x, y, &sx, &sy);
	if (cur_at(mapwin, sx, sy)) {
	    addch(unitchars[u]);
	}
	if (cur_at(mapwin, sx + 1, sy)) {
	    if (between(1, s, 9))
	      addch(s + '0');
	    else if (s >= 10)
	      /* This could get weird if s > 36, but not much
		 chance of that because MAXSIDES < 31 always. */
	      addch(s - 10 + 'A');
	}
    }
}

/* Indicate what kind of people are living in the given cell. */

static void
draw_people(int x, int y)
{
    int pop, sx, sy;

    if (terrain_visible(dside, x, y)
	&& (pop = people_side_at(wrapx(x), y)) != NOBODY) {
	xform(x, y, &sx, &sy);
	if (cur_at(mapwin, sx + 1, sy))
	  addch(pop + '0');
    }
}

/* Draw any text that should be associated with this cell. */

/* (could precompute what the string will lap over and move or truncate str),
   should be deterministic for each mag, so redraw doesn't scramble */

/* do geofeatures, label at a cell with nothing else, and declared as the
   feature's "center" */

static void
draw_legend(int x, int y)
{
    int sx, sy, pixlen;
    char legend[100], *str;
    Feature *feature;
    Unit *unit;
    UnitView *uview;

    uview = unit_view_at(dside, x, y);
    if (uview == NULL)
      return;
    /* Draw a unit's name or number. */
    /* (This will only do top unit in hex, what about others?) */
    if (drawunits
	&& (unit = unit_at(x, y)) != NULL) {
	/* still has a bug if types happen to match - need to use view date
	   instead? */
	if (view_type(uview) != unit->type)
	  return;
	if (unit->name != NULL) {
	    strcpy(legend, unit->name);
	} else {
	    /* Turns out we have nothing to make a legend with. */
	    /* Note that, unlike some other interfaces, this one
	       does not ever display unit numbers, since it makes the
	       screen too cluttered to read. */
	    return;
	}
	xform(x, y, &sx, &sy);
	draw_text(mapwin, sx + 2, sy, legend);
    } else {
	feature = feature_at(x, y);
	if (feature != NULL) {
	    if (feature->size == 1 && (str = feature_name_at(x, y)) != NULL) {
		xform(x, y, &sx, &sy);
		pixlen = strlen(str) / 2;
		draw_text(mapwin, sx - pixlen, sy, str);
	    }
	}
    }
}

/* (should change text[12] to an array) */

void
low_notify(Side *side, char *str)
{
    char *lastblank, extrabuf[BUFSIZE];

    if (!active_display(side))
      return;
    if (strlen(str) > toplineswin->w) {
	strcpy(extrabuf, str);
	lastblank = strchr(extrabuf + toplineswin->w - 10, ' '); 
	if (lastblank) {
	    strcpy(text2, lastblank + 1);
	    *lastblank = '\0';
	}
	strcpy(text1, extrabuf);
    } else {
	strcpy(text1, str);
    }
    DGprintf("%s\n", text1);
    DGprintf("%s\n", text2);
    show_toplines();
    /* Put cursor back to where it was. */
    show_cursor();
    refresh();
}

void
show_toplines(void)
{
    clear_window(toplineswin);
    draw_text(toplineswin, 0, 0, text1);
    draw_text(toplineswin, 0, 1, text2);
}

void
clear_toplines(void)
{
    text1[0] = '\0';
    text2[0] = '\0';
    show_toplines();
}

/* Display all the details of the currently-selected unit/cell. */
/* (should use generic code) */

void
show_closeup(void)
{
    int u, t, tview;
    char *filler = "Empty ";
    Unit *unit = NULL;
    Side *side2;
    UnitView *uview;

    clear_window(closeupwin);
    if (inside_area(curx, cury)) {
	if (terrain_visible(dside, curx, cury)) {
	    tview = buildtview(terrain_at(curx, cury));
	} else {
	    tview = terrain_view(dside, curx, cury);
	}
	if (dside->show_all || in_play(curunit)) {
	    unit = (in_play(curunit) ? curunit : unit_at(curx, cury));
	    if (in_play(unit)) {
		/* If there is a unit there, we can at least see basic
                   info. */
		sprintf(tmpbuf, "%s", unit_handle(dside, unit));
		draw_text(closeupwin, 0, 0, tmpbuf);
		/* Show more about one of our own units. */
		if (unit->side == dside)
		  draw_unit_details(unit);
		filler = "In ";
	    }
	} else {
	    uview = unit_view_at(dside, curx, cury);
	    if (uview != NULL) {
		filler = "In ";
		u = view_type(uview);
		side2 = view_side(uview);
		sprintf(tmpbuf, "%s %s",
		    side_adjective(side2), u_type_name(u));
		draw_text(closeupwin, 0, 0, tmpbuf);
	    }
	}
	/* Describe the cell/transport here. */
	if (unit != NULL && unit->transport != NULL) {
	    t = terrain_at(unit->x, unit->y);
	    sprintf(tmpbuf, "In %s (%s at %d,%d)",
		    short_unit_handle(unit->transport), t_type_name(t),
		    unit->x, unit->y);
	} else {
	    describe_cell(curx, cury, tview, filler, tmpbuf);
	}
	draw_text(closeupwin, 0, 1, tmpbuf);
    } else {
	sprintf(tmpbuf, "??? Off-area %d,%d ???", curx, cury);
	draw_text(closeupwin, 0, 1, tmpbuf);
    }
}

static void
describe_cell(int x, int y, int tview, char *filler, char *buf)
{
    int t;
    char *featname;

    if (tview != UNSEEN) {
	t = vterrain(tview);
	/* Now describe terrain and position. */
	sprintf(buf, "%s%s", filler, t_type_name(t));
	featname = feature_name_at(x, y);
	if (featname != NULL) {
	    strcat(buf, " ");
	    strcat(buf, featname);
	}
	if (elevations_defined()) {
	    strcat(buf, " ");
	    elevation_desc(buf+strlen(buf), x, y);
	}
	/* (should put all weather on own line) */
	temperature_desc(buf+strlen(buf), x, y);
	/* add clouds and winds desc here */
	linear_desc(buf+strlen(buf), x, y);
    } else {
	sprintf(buf, "<unknown>");
    }
    tprintf(buf, " at %d,%d", x, y);
}

/* Describe the state of the given unit, in maximal detail. */

static void
draw_unit_details(Unit *unit)
{
    int t;

    /* Say which unit this is. */
    sprintf(spbuf, "%s", unit_handle(dside, unit));
    /* Describe the "important" parameters like hit points and moves. */
    strcat(spbuf, "  ");
    hp_desc(tmpbuf, unit, TRUE);
    strcat(spbuf, tmpbuf);
    /* (should say something about parts here) */
    strcat(spbuf, "  ");
    acp_desc(tmpbuf, unit, TRUE);
    strcat(spbuf, tmpbuf);
    draw_text(closeupwin, 0, 0, spbuf);
    /* Mention transport and other units stacked here. */
    if (unit->transport != NULL) {
	sprintf(spbuf, "In %s", short_unit_handle(unit->transport));
    } else {
	t = terrain_at(unit->x, unit->y);
	describe_cell(unit->x, unit->y, buildtview(t), "In ", spbuf);
	others_here_desc(tmpbuf, unit_at(unit->x, unit->y));
	if (strlen(tmpbuf) > 0) {
	    strcat(spbuf, ", ");
	    strcat(spbuf, tmpbuf);
	}
    }
    draw_text(closeupwin, 0, 1, spbuf);
    /* Very briefly list the numbers and types of the occupants. */
    spbuf[0] = '\0';
    if (unit->occupant != NULL) {
	strcpy(spbuf, "Occ ");
	occupants_desc(tmpbuf, unit);
	strcat(spbuf, tmpbuf);
    }
    draw_text(closeupwin, 0, 2, spbuf);
    /* Describe the state of all the supplies. */
    supply_desc(spbuf, unit, 0);
    draw_text(closeupwin, 0, 3, spbuf);
    /* Describe the current plans, tasks, etc. */
    /* (needs much improvement) */
    if (unit->plan) {
	int row = 5;
	Task *task;

	plan_desc(spbuf, unit);
	draw_text(closeupwin, 0, 4, spbuf);
	for (task = unit->plan->tasks; task != NULL; task = task->next) {
	    task_desc(spbuf, unit->side, unit, task);
	    draw_text(closeupwin, 0, row++, spbuf);
	}
    }
}

/* Basic routine that displays the list of sides. */

void
show_side_list(void)
{
    char ismoving, progress[20], *dpyname;
    int sy = 0, totacp;
    Side *side2;
    extern int curpriority;

    /* Ensure subwin is clear. */
    clear_window(sideswin);
    for_all_sides(side2) {
    	ismoving = ' ';
	/* (should use generic test) */
	if ((g_use_side_priority()
	     ? (curpriority == side2->priority)
	     : (!side2->finishedturn)))
	  ismoving = '*';
	if (side2->designer) {
	    strcpy(progress, "DESIGN  ");
	} else if (side2->ingame) {
	    totacp = side_initacp(side2);
	    if (totacp > 0) {
		sprintf(progress, "%3d%%", (100 * side_acp(side2)) / totacp);
		/* We get to see our actual total acp as well. */
		if (dside == side2) {
		    tprintf(progress, "/%-3d", totacp);
		} else {
		    strcat(progress, "    ");
		}
	    } else {
		strcpy(progress, "   --   ");
	    }
	} else {
	    if (side_lost(side2)) {
		strcpy(progress, " Lost   ");
	    } else if (side_won(side2)) {
		strcpy(progress, " Won!   ");
	    } else {
		strcpy(progress, " Gone   ");
	    }
	}
	dpyname = "";
	if (side2->player && side2->player->displayname)
	  dpyname = side2->player->displayname;
	sprintf(spbuf, "%d%c %s %s (%s)",
		side_number(side2), ismoving, progress,
		short_side_title(side2), dpyname);
	draw_text(sideswin, 0, sy, spbuf);
	sy += 1;
    }
    /* Draw the modeline. */
    if (use_standout)
      standout();
    memcpy(spbuf, dashbuffer, lw);
    memcpy(spbuf+1, "Sides", 5);
    spbuf[lw] = '\0';
    draw_text(sideswin, 0, sh - 1, spbuf);
    if (use_standout)
      standend();
}

/* Display the date. */

void
show_game_date(void)
{
    clear_window(datewin);
    /* First line of the game state. */
    /* (should cache this date string a la Mac version?) */
    draw_text(datewin, 1, 0, absolute_date_string(g_turn()));
    /* (should use second line for something) */
}

/* General list display routine. */

/* (should track beginning/end of displayed list, draw only visible elts) */
/* (should add scrolling interaction) */

static void
organize_list_contents(void)
{
    Side *side2;
    Unit *unit;

    /* Build up the array of units for this list. */
    listnumunits = 0;
    clear_unit_vector(listvector);
    /* We always see our own units. */
    for_all_side_units(dside, unit) {
	add_unit_to_list(unit);
    }
    for_all_sides(side2) {
	if (dside != side2) {
	    for_all_side_units(side2, unit) {
		if (side_sees_image(dside, unit)) {
		    add_unit_to_list(unit);
		}
	    }
	}
    }
    /* Now sort the list according to its keys. */
    sort_unit_vector(listvector);
}

static void
add_unit_to_list(Unit *unit)
{
    if (alive(unit)) {
	add_unit_to_vector(listvector, unit, FALSE);
	/* (should apply other inclusion criteria too?) */
	++listnumunits;
    }
}

void
show_list(void)
{
    int i = 0, line = 1;
    char *maincat = "xxxxx", *filter = "?yyyy?";

    clear_window(listwin);
    tmpbuf[0] = '\0';
    switch (listtype) {
      case 0:
	if (listvector == NULL) {
	    listvector = make_unit_vector(1000);
	    listnumunits = 0;
	    for (i = 0; i < MAXSORTKEYS; ++i) {
		tmpsortkeys[i] = bynothing;
	    }
	    tmpsortkeys[0] = byside;
	    organize_list_contents();
	}
	for (line = firstvisible; line <= lastvisible; ++line) {
	    draw_unit_list_entry(line);
	}
	maincat = "Units";
	switch (listsides) {
	  case ourside:
	    filter = "-Own--";
	    break;
	  case ourallies:
	    filter = "Allied";
	    break;
	  case allsides:
	    filter = "-ALL--";
	    break;
	  default:
	    filter = "??????";
	    break;
	}
	for (i = 0; i < MAXSORTKEYS; ++i) {
	    if (i == 0) {
		strcat(tmpbuf, "by ");
	    } else if (tmpsortkeys[i] != bynothing) {
		strcat(tmpbuf, ",");
	    }
	    switch (tmpsortkeys[i]) {
	      case byside:
		strcat(tmpbuf, "side");
		break;
	      case bytype:
		strcat(tmpbuf, "type");
		break;
	      case byname:
		strcat(tmpbuf, "name");
		break;
	      case byactorder:
		strcat(tmpbuf, "act");
		break;
	      case bylocation:
		strcat(tmpbuf, "loc");
		break;
	      case bynothing:
		break;
	      default:
		strcat(tmpbuf, "???");
		break;
	    }
	}
	break;
      case 1:
	for_all_unit_types(line) {
	    draw_type_list_entry(line);
	} 
	maincat = "Types";
	filter = "------";
    }
    /* Draw the modeline, in standout if possible. */
    if (use_standout)
      standout();
    memcpy(spbuf, dashbuffer, lw);
    memcpy(spbuf+1, maincat, 5);
    memcpy(spbuf+7, filter, 6);
    memcpy(spbuf+14, tmpbuf, strlen(tmpbuf));
    spbuf[lw] = '\0';
    draw_text(listwin, 0, lh - 1, spbuf);
    if (use_standout)
      standend();
    refresh();
}

void
cycle_list_type(void)
{
    listtype = (listtype + 1) % 2;
}

void
cycle_list_filter(void)
{
    listsides = (listsides + 1) % 3;
}

void
cycle_list_order(void)
{
    tmpsortkeys[0] = (tmpsortkeys[0] + 1) % numsortkeytypes;
    sort_unit_vector(listvector);
}

/* Alter the numbers for a single type of unit.  Should be called right
   after any changes.  Formatted to look nice, but kind of messy to set
   up correctly; display should not jump back and forth as the numbers
   change in size. */

int firsttypevisible = 0;
int lasttypevisible = MAXUTYPES;

static void
draw_type_list_entry(int line)
{
    int u, num;

    u = line + firsttypevisible;
    if (!between(0, u, numutypes))
      return;
    if (u > lasttypevisible)
      return;
    sprintf(spbuf, " %c ", unitchars[u]);
    /* Our unit total (right-justified) */
    num = num_units_in_play(dside, u);
    if (num > 0)	{
	sprintf(tmpbuf, "%4d", num);
    } else {
	sprintf(tmpbuf, "    ");
    }
    strcat(spbuf, tmpbuf);
    /* Our units under construction. */
    num = num_units_incomplete(dside, u);
    if (num > 0) {
	sprintf(tmpbuf, "(%d)", num);
    } else {
	sprintf(tmpbuf, "    ");
    }
    strcat(spbuf, tmpbuf);
    /* Our total gains over the game. */
    if (total_gain(dside, u) > 0) {
	sprintf(tmpbuf, " %4d ", total_gain(dside, u));
    } else {
	sprintf(tmpbuf, "      ");
    }
    strcat(spbuf, tmpbuf);
    /* Our total losses over the game. */
    if (total_loss(dside, u) > 0) {
	sprintf(tmpbuf, "- %-4d", total_loss(dside, u));
    } else {
	sprintf(tmpbuf, "      ");
    }
    strcat(spbuf, tmpbuf);
    draw_text(listwin, 1, line, spbuf);
}

static void
draw_unit_list_entry(int line)
{
    Unit *unit = unit_in_vector(listvector, line);
    char tmpbuf[BUFSIZE];

    if (unit == NULL)
      return;
    if (unit == curunit) {
	draw_text(listwin, 0, line, "*");
    }
    if (alive(unit)) {
	name_or_number(unit, tmpbuf);
	sprintf(spbuf, "%c%d %-16s ",
		unitchars[unit->type], side_number(unit->side), tmpbuf);
	if (has_acp_left(unit)) {
	    tprintf(spbuf, "%3d", unit->act->acp);
	}
	if (unit->plan && unit->plan->asleep) {
	    strcat(spbuf, "z");
	}
	if (unit->plan && unit->plan->reserve) {
	    strcat(spbuf, "r");
	}
	if (unit->plan && unit->plan->waitingfortasks) {
	    strcat(spbuf, "w");
	}
	/* do hp also? */
    } else {
	sprintf(spbuf, "--");
    }
    draw_text(listwin, 1, line, spbuf);
}

void
show_clock(void)
{
#if 0
    int time = 0;

    if (realtime_game()) {
	time_desc(spbuf, time);
	draw_text(clockwin, 0, 0, spbuf);
    }
#endif
}

/* General window clearing. */

void
clear_window(struct ccwin *win)
{
    int i;

    if (win->x == 0
	&& win->y == 0
	&& win->w == COLS
	&& win->h == LINES) {
	clear();
    } else if (between(0, win->x, COLS-1)
	       && between(0, win->w, COLS-1)
	       && between(0, win->y, LINES-1)
	       && between(0, win->h, LINES-1)) {
	for (i = 0; i < win->w; ++i)
	  tmpbuf[i] = ' ';
	tmpbuf[win->w] = '\0';
	for (i = 0; i < win->h; ++i)
	  mvaddstr(win->y + i, win->x, tmpbuf);
    } else {
	printf("error: win %d is %dx%d @ %d,%d\n",
	       (int) win, win->w, win->h, win->x, win->y);
    }
}

/* Draw a large blot over the area. */

void
draw_blast(int x, int y, int r)
{
    int sx, sy;

    xform(x, y, &sx, &sy);
    if (cur_at(mapwin, sx, sy)) {
	addstr("##");
	refresh();
	if (r > 0) {
	    if (cur_at(mapwin, sx-1, sy+1))
	      addstr("####");
	    if (cur_at(mapwin, sx-2, sy))
	      addstr("######");
	    if (cur_at(mapwin, sx-1, sy-1))
	      addstr("####");
	    refresh();
	}
    }
}

/* Drawing text is easy, but we do need to do clipping manually. */

int
draw_text(struct ccwin *win, int x, int y, char *str)
{
    int i, slen, linestart = 0;

    if (y < 0)
      y = win->h - y;
    if (cur_at(win, x, y)) {
	slen = strlen(str);
	for (i = 0; i < slen; ++i) {
	    if (str[i] == '\n') {
		if (y < win->h) {
		    if (cur_at(win, x, ++y))
		      linestart = i;
		    else
		      break;
		} else {
		    /* Ran out of room */
		    return i;
		}
	    } else if (x + (i - linestart) < win->w) {
		addch(str[i]);
	    }
	}
    }
    return (-1);
}

/* Make a beep by writing ^G. */

void
xbeep(void)
{
#ifdef MAC /* the libcurses library already includes a beep function */
    beep();
#else
    putchar('\007');
#endif
}

/* (should break helpstring into lines before displaying, so scrolling
   calcs simpler) */

void
show_help(void)
{
    int i, slen, x = 0, y = 0, more = FALSE;
    char *str;

    clear();
    cur_at(helpwin, 4, y);
    if (use_standout)
      standout();
    else
      addstr("*** ");
    addstr(cur_help_node->key);
    if (use_standout)
      standend();
    else
      addstr(" ***");
    ++y;
    cur_at(helpwin, 0, y);
    str = get_help_text(cur_help_node);
    slen = strlen(str);
    for (i = first_visible_help_pos; i < slen; ++i) {
	if (str[i] == '\n' || x > helpwin->w - 2) {
	    if (y < helpwin->h - 2) {
		++y;
		cur_at(helpwin, 0, y);
		x = 0;
		if (!(str[i] == '\n')) {
		    addch(str[i]);
		    ++x;
		}
	    } else {
		more = TRUE;
		last_visible_help_pos = i;
		break;
	    }
	} else {
	    addch(str[i]);
	    ++x;
	}
    }
    y = helpwin->h - 1;
    cur_at(helpwin, (more ? 9 : 0), y);
    addstr(" ['n' for next, 'p' for prev, 'q' to end]");
    if (more) {
	cur_at(helpwin, 0, y);
	if (use_standout)
	  standout();
	addstr("--More--");
	if (use_standout)
	  standend();
    }
    /* Position the cursor. */
    cur_at(helpwin, (more ? 8 : 0), y);
}

/* Put the terminal's cursor at an appropriate place. */

void
show_cursor(void)
{
    int sx, sy;

    if (active_display(dside)) {
	switch (mode) {
	  case SURVEY:
	  case MOVE:
	    if (curunit != NULL
		&& in_play(curunit)
		&& !(curunit->x == curx && curunit->y == cury)) {
		curx = curunit->x;  cury = curunit->y;
	    }
	    if (!in_middle(curx, cury)) {
		set_view_focus(mvp, curx, cury);
		center_on_focus(mvp);
		set_map_viewport();
		show_map();
	    }
	    xform(curx, cury, &sx, &sy);
	    if (!cur_at(mapwin, sx, sy))
	      abort();
	    break;
	  case HELP:
	    cur_at(helpwin, 0, 0);
	    break;
	  case MORE:
	    xbeep();
	    break;
	  case PROMPT:
	    /* This doesn't account for two-line prompts. */
	    if (!cur_at(toplineswin, strlen(text1), 0))
	      abort();
	    break;
	  case PROMPTXY:
	    if (!in_middle(curx, cury)) {
		set_view_focus(mvp, curx, cury);
		center_on_focus(mvp);
		set_map_viewport();
		show_map();
	    }
	    xform(curx, cury, &sx, &sy);
	    if (!cur_at(mapwin, sx, sy))
	      abort();
	    break;
	  default:
	    abort();
	}
	refresh();
    }
}

/* Position the cursor, being careful to test for sensibility. */

int
cur_at(struct ccwin *win, int x, int y)
{
    int sx, sy;

    if (x < 0 || x >= win->w || y < 0 || y >= win->h) {
	/* Just return false if something is wrong; the caller should
	   test this and react apropriately. */
	return FALSE;
    } else {
	sx = win->x + x;  sy = win->y + y;
	if (between(0, sx, COLS-1) && between(0, sy, LINES-1)) {
	    move(sy, sx);
	} else {
	    /* Bad. Very bad. */
	    abort();
	}
	return TRUE;
    }
}
