/* Interface-independent natural language handling for Xconq.
   Copyright (C) 1987-1989, 1991-1998 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.  */

/* This file should be entirely replaced for non-English Xconq. */
/* (One way to do this would be to call this file "nlang-en.c", then
   symlink this or nlang-fr.c, etc to nlang.c when configuring;
   similarly for help.c.) */
/* (A better way to would be to run all these through a dispatch
   vector, then each player could get messages and such in a
   different language.) */

#include "conq.h"
#include "kernel.h"

static void notify_combat PARAMS ((Unit *unit, Unit *atker, char *str));
static void init_calendar PARAMS ((void));
static void maybe_mention_date PARAMS ((Side *side));
static int gain_count PARAMS ((Side *side, int u, int r));
static int loss_count PARAMS ((Side *side, int u, int r));
static int atkstats PARAMS ((Side *side, int a, int d));
static int hitstats PARAMS ((Side *side, int a, int d));

static char *tmpnbuf;

static char *tmpdbuf;

static char *pluralbuf;

/* Short names of directions. */

char *dirnames[] = DIRNAMES;

char *unitbuf = NULL;

char *past_unitbuf = NULL;

static char *side_short_title = NULL;

static char *gain_reason_names[] = { "Ini", "Bld", "Cap", "Oth" };

static char *loss_reason_names[] = { "Cbt", "Cap", "Stv", "Acc", "Dis", "Oth" };

/* Calendar handling. */

typedef enum {
    cal_unknown,
    cal_turn,
    cal_usual
} CalendarType;

static CalendarType calendar_type = cal_unknown;

typedef enum {
    ds_second,
    ds_minute,
    ds_hour,
    ds_day,
    ds_week,
    ds_month,
    ds_season,
    ds_year
} UsualDateStepType;

typedef struct a_usualdate {
    int second;
    int minute;
    int hour;
    int day;
    int month;
    int year;
} UsualDate;

static char *usual_date_string PARAMS ((int date));
static void parse_usual_date PARAMS ((char *datestr, UsualDate *udate));

static int turn_initial;

static UsualDateStepType datesteptype;

static int datestep;

static char *months[] = {
  "Jan", "Feb", "Mar", "Apr", "May", "Jun",
  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "???" };

static short monthdays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0 };

static char *seasons[] = { "Win", "Spr", "Sum", "Aut" };

static UsualDate *usual_initial;

char *datebuf;

char *turnname;

char *featurebuf;

/* This is the number of types to mention by name; any others will
   just be included in the count of missing images. */

#define NUMTOLIST 5

static char *missinglist;

/* This array allows for counting up to 4 classes of missing images. */

static int missing[4];

static int totlisted = 0;

/* Initialize things.  Note that this happens before any game is loaded, so
   can't do game-specific init here. */

void
init_nlang()
{
    if (tmpnbuf == NULL)
      tmpnbuf = xmalloc(BUFSIZE);
    if (tmpdbuf == NULL)
      tmpdbuf = xmalloc(BUFSIZE);
    if (pluralbuf == NULL)
      pluralbuf = xmalloc(BUFSIZE);
    if (datebuf == NULL)
      datebuf = xmalloc(BUFSIZE);
}

/* Send a message to everybody who's got a screen. */

#ifdef __STDC__
void
notify_all(char *fmt, ...)
#else
void
notify_all(fmt, a1, a2, a3, a4, a5, a6, a7, a8, a9)
char *fmt;
long a1, a2, a3, a4, a5, a6, a7, a8, a9;
#endif
{
    Side *side;

    for_all_sides(side) {
	if (active_display(side)) {
	    maybe_mention_date(side);
#ifdef __STDC__
	    {
		va_list ap;

		va_start(ap, fmt);
		vsprintf(tmpnbuf, fmt, ap);
		va_end(ap);
	    }
#else
	    sprintf(tmpnbuf, fmt, a1, a2, a3, a4, a5, a6, a7, a8, a9);
#endif
	    /* Always capitalize first char of notice. */
	    if (islower(tmpnbuf[0]))
	      tmpnbuf[0] = toupper(tmpnbuf[0]);
	    low_notify(side, tmpnbuf);
	}
    }
}

#ifdef __STDC__
void
notify(Side *side, char *fmt, ...)
#else
void
notify(side, fmt, a1, a2, a3, a4, a5, a6, a7, a8, a9)
Side *side;
char *fmt;
long a1, a2, a3, a4, a5, a6, a7, a8, a9;
#endif
{
    if (!active_display(side))
      return;
    maybe_mention_date(side);
#ifdef __STDC__
    {
	va_list ap;

	va_start(ap, fmt);
	vsprintf(tmpnbuf, fmt, ap);
	va_end(ap);
    }
#else
    sprintf(tmpnbuf, fmt, a1, a2, a3, a4, a5, a6, a7, a8, a9);
#endif
    /* Always capitalize first char of notice. */
    if (islower(tmpnbuf[0]))
      tmpnbuf[0] = toupper(tmpnbuf[0]);
    low_notify(side, tmpnbuf);
}

#if defined(__STDC__) || defined(MPW_C)
void
vnotify(side, fmt, ap)
Side *side;
char *fmt;
va_list ap;
{
    if (!active_display(side))
      return;
    maybe_mention_date(side);
    vsprintf(tmpnbuf, fmt, ap);
    /* Always capitalize first char of notice. */
    if (islower(tmpnbuf[0]))
      tmpnbuf[0] = toupper(tmpnbuf[0]);
    low_notify(side, tmpnbuf);
}
#endif /* __STDC__ */

static void
maybe_mention_date(side)
Side *side;
{
    /* Note that last_notice_date defaults to 0, so this means that
       any turn 0 notices will not have a date prepended (which is good). */
    if (g_turn() != side->last_notice_date) {
	sprintf(tmpdbuf, "%s:", absolute_date_string(g_turn()));
	low_notify(side, tmpdbuf);
	side->last_notice_date = g_turn();
    }
}

/* Pad a given buffer with blanks out to the given position, and cut off any
   additional content. */

void
pad_out(buf, n)
char *buf;
int n;
{
    int i, len = strlen(buf);

    if (n < 1)
      return;
    for (i = len; i < n; ++i) {
	buf[i] = ' ';
    }
    buf[n - 1] = '\0';
}

/* Get a char string naming the side.  Doesn't have to be pretty. */

/* (should synth complete name/adjective from other parts of speech) */

char *
side_name(side)
Side *side;
{
    return (side == NULL ? "independent" :
      (side->name ? side->name :
	    (side->adjective ? side->adjective :
	     (side->pluralnoun ? side->pluralnoun :
	      (side->noun ? side->noun :
	       "")))));
}

char *
side_adjective(side)
Side *side;
{
    return (side == NULL ? "independent" :
       (side->adjective ? side->adjective :
	    (side->noun ? side->noun :
	     (side->pluralnoun ? side->pluralnoun :
	      (side->name ? side->name :
	       "")))));
}

char *
short_side_title(side)
Side *side;
{
    if (side_short_title == NULL)
      side_short_title = xmalloc(BUFSIZE);
    if (side == NULL) {
	return " - ";
    } else if (side->name) {
	return side->name;
    } else if (side->pluralnoun) {
	sprintf(side_short_title, "the %s", side->pluralnoun);
    } else if (side->noun) {
	sprintf(side_short_title, "the %s", plural_form(side->noun));
    } else if (side->adjective) {
	sprintf(side_short_title, "the %s side", side->adjective);
    } else {
	return " - ";
    }
    return side_short_title;
}

char *
short_side_title_with_adjective(side, adjective)
Side *side;
char *adjective;
{
    if (side_short_title == NULL)
      side_short_title = xmalloc(BUFSIZE);
    side_short_title[0] = '\0';
    if (side == NULL) {
	return " - ";
    } else if (side->name) {
	if (empty_string(adjective))
	  return side->name;
	else {
	    strcat(side_short_title, adjective);
	    strcat(side_short_title, " ");
	    strcat(side_short_title, side->name);
	}
    } else if (side->pluralnoun) {
	strcat(side_short_title, "the ");
	if (!empty_string(adjective)) {
	    strcat(side_short_title, adjective);
	    strcat(side_short_title, " ");
	}
	strcat(side_short_title, side->pluralnoun);
    } else if (side->noun) {
	strcat(side_short_title, "the ");
	if (!empty_string(adjective)) {
	    strcat(side_short_title, adjective);
	    strcat(side_short_title, " ");
	}
	strcat(side_short_title, side->noun);
    } else if (side->adjective) {
	strcat(side_short_title, "the ");
	if (!empty_string(adjective)) {
	    strcat(side_short_title, adjective);
	    strcat(side_short_title, " ");
	}
	strcat(side_short_title, side->adjective);
	strcat(side_short_title, " side");
    } else {
	return " - ";
    }
    return side_short_title;
}

/* This indicates whether the above routine returns a singular or plural form
   of title. */

int
short_side_title_plural_p(side)
Side *side;
{
    if (side == NULL) {
	return FALSE;
    } else if (side->name) {
	return FALSE;
    } else if (side->pluralnoun) {
	return TRUE;
    } else if (side->noun) {
	return TRUE;
    } else if (side->adjective) {
	sprintf(side_short_title, "The %s side", side->adjective);
	return FALSE;
    } else {
	return FALSE;
    }
}

char *
shortest_side_title(side2, buf)
Side *side2;
char *buf;
{
    if (side2 == NULL) {
	return "-";
    } else if (side2->name) {
	return side2->name;
    } else if (side2->adjective) {
	return side2->adjective;
    } else if (side2->noun) {
	return side2->noun;
    } else if (side2->pluralnoun) {
	return side2->pluralnoun;
    } else {
	sprintf(buf, "(#%d)", side_number(side2));
    }
    return buf;
}

char *
sidemask_desc(buf, sidemask)
char *buf;
SideMask sidemask;
{
    int first = TRUE;
    Side *side2;

    if (sidemask < 0 || sidemask >= ((1 << (numsides + 1)) - 2))
      return "all";
    buf[0] = '\0';
    for_all_sides_plus_indep(side2) {
	if (side_in_set(side2, sidemask)) {
	    if (first)
	      first = FALSE;
	    else
	      strcat(buf, ", ");
	    strcat(buf, short_side_title(side2));
	}
    }
    return buf;
}

char *
side_score_desc(buf, side, sk)
char *buf;
Side *side;
Scorekeeper *sk;
{
    if (symbolp(sk->body)
		&& (match_keyword(sk->body, K_LAST_SIDE_WINS)
		    || match_keyword(sk->body, K_LAST_ALLIANCE_WINS))) {
	sprintf(buf, "Point Value: %d", side_point_value(side));
    } else {
	/* Compose the generic scorekeeper status display. */
	if (sk->title != NULL) {
	    strcpy(buf, sk->title);
	} else {
	    sprintf(buf, "SK #%d", sk->id);
	}
	if (sk->scorenum >= 0) {
	    tprintf(buf, ": %d", side->scores[sk->scorenum]);
	}
    }
    return buf;
}

char *
long_player_title(buf, player, thisdisplayname)
char *buf, *thisdisplayname;
Player *player;
{
    buf[0] = '\0';
    if (player == NULL) {
	/* Do nothing */
    } else if (player->displayname != NULL) {
	if (player->name != NULL) {
	    strcat(buf, player->name);
	    strcat(buf, "@");
	}
	if (thisdisplayname != NULL
	    && strcmp(player->displayname, thisdisplayname) == 0
	    && player->rid == my_rid) {
	    strcat(buf, "You");
	} else {
	    strcat(buf, player->displayname);
	}
	if (player->aitypename != NULL) {
	    strcat(buf, "(& AI ");
	    strcat(buf, player->aitypename);
	    strcat(buf, ")");
	}
    } else if (player->aitypename != NULL) {
	strcat(buf, "AI ");
	strcat(buf, player->aitypename);
    } else {
	strcat(buf, "-");
    }
    return buf;
}

char *
short_player_title(buf, player, thisdisplayname)
char *buf, *thisdisplayname;
Player *player;
{
    buf[0] = '\0';
    if (player == NULL)
      return buf;
    if (player->name != NULL) {
	strcat(buf, player->name);
    }
    if (player->aitypename != NULL) {
	strcat(buf, ",");
	strcat(buf, player->aitypename);
    }
    if ((player->name != NULL || player->aitypename != NULL)
	&& player->displayname != NULL) {
	strcat(buf, "@");
    }
    if (thisdisplayname != NULL
	&& player->displayname != NULL
	&& strcmp(player->displayname, thisdisplayname) == 0) {
	strcat(buf, "You");
    } else if (player->displayname != NULL) {
	strcat(buf, player->displayname);
    }
    if (strlen(buf) == 0) {
	strcat(buf, "-");
    }
    return buf;
}

void
side_and_type_name(buf, side, u, side2)
char *buf;
Side *side, *side2;
int u;
{
    /* Decide how to identify the side. */
    if (side2 == NULL) {
	sprintf(buf, "independent ");
    } else if (side == side2) {
	sprintf(buf, "your ");
    } else {
	sprintf(buf, "%s ", side_adjective(side2));
    }
    /* Glue the pieces together and return it. */
    strcat(buf, u_type_name(u));
}

/* Build a short phrase describing a given unit to a given side,
   basically consisting of indication of unit's side, then of unit itself. */

char *
unit_handle(side, unit)
Side *side;
Unit *unit;
{
    char *utypename, *fmtstr, smallbuf[40], sidebuf[100];
    Side *side2, *side3;
    Obj *frest, *fmt1;

    if (unitbuf == NULL)
      unitbuf = xmalloc(BUFSIZE);
    /* Handle various weird situations. */
    if (unit == NULL)
      return "???";
    if (!alive(unit)) {
    	sprintf(unitbuf, "dead #%d", unit->id);
        return unitbuf;
    }
    /* If this unit represents "yourself", say so. */
    if (side != NULL && unit == side->self_unit)
      return "you";
    unitbuf[0] = '\0';
    /* Decide how to identify the side.  If the unit's original side
       is not its current side, list both of them. */
    side2 = unit->side;
    side3 = NULL;
    if (unit->origside != NULL && side2 != unit->origside) {
	side3 = unit->origside;
    }
    sidebuf[0] = '\0';
    if (side2 == NULL) {
	strcat(sidebuf, "the");
    } else if (side2 == side) {
	strcat(sidebuf, "your");
    } else {
	strcat(sidebuf, "the ");
	strcat(sidebuf, side_adjective(side2));
	if (side3 != NULL)
	  strcat(sidebuf, "-held");
    }
    if (side3 != NULL) {
	if (side3 == side) {
	    strcat(sidebuf, " (your)");
	} else {
	    strcat(sidebuf, " (");
	    strcat(sidebuf, side_adjective(side3));
	    strcat(sidebuf, ")");
	}
    }
    /* Now add the unit's unique description. */
    utypename = u_type_name(unit->type);
    /* If we have special formatting info, interpret it. */
    if (u_desc_format(unit->type) != lispnil) {
	for_all_list(u_desc_format(unit->type), frest) {
	    fmt1 = car(frest);
	    if (stringp(fmt1)) {
		strcat(unitbuf, c_string(fmt1));
	    } else if (symbolp(fmt1)) {
		fmtstr = c_string(fmt1);
		if (strcmp(fmtstr, "name") == 0) {
		    strcat(unitbuf, (unit->name ? unit->name : "anon"));
		} else if (strcmp(fmtstr, "position") == 0) {
		    sprintf(smallbuf, "%d,%d", unit->x, unit->y);
		    strcat(unitbuf, smallbuf);
		} else if (strcmp(fmtstr, "side") == 0) {
		    strcat(unitbuf, sidebuf);
		} else if (strcmp(fmtstr, "type") == 0) {
		    strcat(unitbuf, utypename);
		} else if (strcmp(fmtstr, "side-name") == 0) {
		    strcat(unitbuf, side_name(unit->side));
		} else if (strcmp(fmtstr, "side-adjective") == 0) {
		    strcat(unitbuf, side_adjective(unit->side));
		} else {
		    strcat(unitbuf, "??description-format??");
		}
	    } else {
		strcat(unitbuf, "??description-format??");
	    }
	}
	return unitbuf;
    } else {
	strcat(unitbuf, sidebuf);
	strcat(unitbuf, " ");
    }
    if (unit->name) {
	tprintf(unitbuf, "%s %s", utypename, unit->name);
    } else if (unit->number > 0) {
	tprintf(unitbuf, "%d%s %s",
		unit->number, ordinal_suffix(unit->number), utypename);
    } else {
	strcat(unitbuf, utypename);
    }
    return unitbuf;
}

/* Shorter unit description omits side name, but uses same buffer. */

char *
short_unit_handle(unit)
Unit *unit;
{
    int u;

    if (unitbuf == NULL)
      unitbuf = xmalloc(BUFSIZE);
    if (unit == NULL)
      return "???";
    if (!alive(unit)) {
    	sprintf(unitbuf, "dead #%d", unit->id);
        return unitbuf;
    }
    /* If this unit represents "yourself", say so. */
    if (unit->side != NULL && unit == unit->side->self_unit)
      return "you";
    u = unit->type;
    if (!empty_string(unit->name)) {
	strcpy(unitbuf, unit->name);
    } else if (!empty_string(u_short_name(u))) {
	sprintf(unitbuf, "%d%s %s",
		unit->number, ordinal_suffix(unit->number), u_short_name(u));
    } else {
	sprintf(unitbuf, "%d%s %s",
		unit->number, ordinal_suffix(unit->number), u_type_name(u));
    }
    return unitbuf;
}

/* Put either the unit's name or its number into the given buffer. */

void
name_or_number(unit, buf)
Unit *unit;
char *buf;
{
    if (unit->name) {
	strcpy(buf, unit->name);
    } else if (unit->number > 0) {
	sprintf(buf, "%d%s", unit->number, ordinal_suffix(unit->number));
    } else {
	buf[0] = '\0';
    }
}

/* Build a short phrase describing a given past unit to a given side,
   basically consisting of indication of unit's side, then of unit itself. */

char *
past_unit_handle(side, past_unit)
Side *side;
PastUnit *past_unit;
{
    char *utypename;
    Side *side2;

    if (past_unitbuf == NULL)
      past_unitbuf = xmalloc(BUFSIZE);
    /* Handle various weird situations. */
    if (past_unit == NULL)
      return "???";
    /* Decide how to identify the side. */
    side2 = past_unit->side;
    if (side2 == NULL) {
	sprintf(past_unitbuf, "the ");
    } else if (side2 == side) {
	sprintf(past_unitbuf, "your ");
    } else {
	sprintf(past_unitbuf, "the %s ", side_adjective(side2));
    }
    /* Now add the past_unit's unique description. */
    utypename = u_type_name(past_unit->type);
    if (past_unit->name) {
	tprintf(past_unitbuf, "%s %s", utypename, past_unit->name);
    } else if (past_unit->number > 0) {
	tprintf(past_unitbuf, "%d%s %s",
		past_unit->number, ordinal_suffix(past_unit->number),
		utypename);
    } else {
	strcat(past_unitbuf, utypename);
    }
    return past_unitbuf;
}

/* Shorter past_unit description omits side name, but uses same buffer. */

char *
short_past_unit_handle(past_unit)
PastUnit *past_unit;
{
    int u;

    if (past_unitbuf == NULL)
      past_unitbuf = xmalloc(BUFSIZE);
    if (past_unit == NULL)
      return "???";
    u = past_unit->type;
    if (!empty_string(past_unit->name)) {
	strcpy(past_unitbuf, past_unit->name);
    } else if (!empty_string(u_short_name(u))) {
	sprintf(past_unitbuf, "%d%s %s",
		past_unit->number, ordinal_suffix(past_unit->number),
		u_short_name(u));
    } else {
	sprintf(past_unitbuf, "%d%s %s",
		past_unit->number, ordinal_suffix(past_unit->number),
		u_type_name(u));
    }
    return past_unitbuf;
}

/* Given a unit and optional type u, summarize construction status
   and timing. */

void
construction_desc(buf, unit, u)
char *buf;
Unit *unit;
int u;
{
    int est, u2;
    char ubuf[10], tmpbuf[100];
    Task *task;
    Unit *unit2;

    if (u != NONUTYPE) {
	est = est_completion_time(unit, u);
	if (est >= 0) {
	    sprintf(ubuf, "[%2d] ", est);
	} else {
	    strcpy(ubuf, " --  ");
	}
    } else {
	ubuf[0] = '\0';
    }
    name_or_number(unit, tmpbuf);
    sprintf(buf, "%s%s %s", ubuf, u_type_name(unit->type), tmpbuf);
    pad_out(buf, 25);
    if (unit->plan
	&& unit->plan->tasks) {
	task = unit->plan->tasks;
	if (task->type == TASK_BUILD) {
	    u2 = task->args[0];
	    tprintf(buf, " %s ", (is_unit_type(u2) ? u_type_name(u2) : "?"));
	    unit2 = find_unit(task->args[1]);
	    if (in_play(unit2) && unit2->type == u2) {
		tprintf(buf, "%d/%d done ", unit2->cp, u_cp(unit2->type));
	    }
	    tprintf(buf, "(%d of %d)", task->args[2] + 1, task->args[3]);
	} else if (task->type == TASK_RESEARCH) {
	    u2 = task->args[0];
	    if (is_unit_type(u2)) {
		tprintf(buf, " %s tech %d/%d",
			u_type_name(u2), unit->side->tech[u2], task->args[1]);
	    }
	}
    }
}

/* This generates a textual description of a type's construction info,
   including estimated time for the given unit to build one, tooling &
   tech, plus number of that type in existence already. */

void
constructible_desc(buf, side, u, unit)
char *buf;
Side *side;
int u;
Unit *unit;
{
    char estbuf[20];
    char techbuf[50];
    char typenamebuf[50];
    int est, tp, num;

    if (unit != NULL) {
	est = est_completion_time(unit, u);
    	if (est >= 0) {
	    sprintf(estbuf, "[%2d] ", est);
	    if (uu_tp_to_build(unit->type, u) > 0) {
		tp = (unit->tooling ? unit->tooling[u] : 0);
		tprintf(estbuf, "(%2d) ", tp);
	    }
    	} else {
	    strcpy(estbuf, " --  ");
    	}
    } else {
	estbuf[0] = '\0';
    }
    if (u_tech_max(u) > 0) {
    	sprintf(techbuf, "[Tech %d/%d/%d] ",
		side->tech[u], u_tech_to_build(u), u_tech_max(u));
    } else {
	techbuf[0] = '\0';
    }
    strcpy(typenamebuf, u_type_name(u));
    /* If the single char for the type is different from the first character
       of its type name, mention the char. */
    if (!empty_string(u_uchar(u)) && (u_uchar(u))[0] != typenamebuf[0]) {
	tprintf(typenamebuf, "(%c)", (u_uchar(u))[0]);
    }
    sprintf(buf, "%s%s%-16.16s", estbuf, techbuf, typenamebuf);
    num = num_units_in_play(side, u);
    if (num > 0) {
	tprintf(buf, "  %3d", num);
    } else {
	strcat(buf, "     ");
    }
    num = num_units_incomplete(side, u);
    if (num > 0) {
	tprintf(buf, "(%d)", num);
    }
}

int
est_completion_time(unit, u2)
Unit *unit;
int u2;
{
    int u, tooluptime, tp;

    u = unit->type;
    if (uu_acp_to_create(u, u2) < 1)
      return (-1);
    tooluptime = 0;
    tp = (unit->tooling ? unit->tooling[u2] : 0);
    if (tp < uu_tp_to_build(u, u2)) {
	if (uu_acp_to_toolup(u, u2) < 1
	    || uu_tp_per_toolup(u, u2) <= 0
	    || u_acp(u) <= 0)
	  return (-1);
	tooluptime = ((uu_tp_to_build(u, u2) - tp) * uu_acp_to_toolup(u, u2))
	 / (uu_tp_per_toolup(u, u2) * u_acp(u));
    }
    return tooluptime + normal_completion_time(unit->type, u2);
}

void
historical_event_date_desc(hevt, buf)
HistEvent *hevt;
char *buf;
{
    sprintf(buf, "%d: ", hevt->startdate);
}

int
find_event_type(sym)
Obj *sym;
{
    int i;

    for (i = 0; hevtdefns[i].name != NULL; ++i) {
	if (strcmp(c_string(sym), hevtdefns[i].name) == 0)
	  return i;
    }
    return -1;
}

int pattern_matches_event PARAMS ((Obj *pattern, HistEvent *hevt));
void event_desc_from_list PARAMS ((Side *side, Obj *lis, HistEvent *hevt, char *buf));

/* (should abstract out evt arg -> unit/pastunit description code) */

void
historical_event_desc(side, hevt, buf)
Side *side;
HistEvent *hevt;
char *buf;
{
    int data0 = hevt->data[0];
    int data1 = hevt->data[1];
    Obj *rest, *head, *pattern, *text;
    Unit *unit;
    PastUnit *pastunit, *pastunit2;
    Side *side2;
    
    for_all_list(g_event_narratives(), rest) {
	head = car(rest);
	if (consp(head)) {
	    pattern = car(head);
	    if (symbolp(pattern)
		&& find_event_type(pattern) == hevt->type) {
		text = cadr(head);
		if (stringp(text)) {
		    sprintf(buf, c_string(text));
		} else {
		    sprintlisp(buf, text, 50);
		}
		return;
	    } else if (consp(pattern)
		       && symbolp(car(pattern))
		       && pattern_matches_event(pattern, hevt)
		       ) {
		text = cadr(head);
		if (stringp(text)) {
		    sprintf(buf, c_string(text));
		} else {
		    event_desc_from_list(side, text, hevt, buf);
		}
		return;
	    }
	}
    }
    /* Generate a default description of the event. */
    switch (hevt->type) {
      case H_LOG_STARTED:
	sprintf(buf, "we started recording events");
	break;
      case H_LOG_ENDED:
	sprintf(buf, "we stopped recording events");
	break;
      case H_GAME_STARTED:
	sprintf(buf, "we started the game");
	break;
      case H_GAME_SAVED:
	sprintf(buf, "we saved the game");
	break;
      case H_GAME_RESTARTED:
	sprintf(buf, "we restarted the game");
	break;
      case H_GAME_ENDED:
	sprintf(buf, "we ended the game");
	break;
      case H_SIDE_JOINED:
      	side2 = side_n(data0);
	sprintf(buf, "%s joined the game",
		(side == side2 ? "you" : side_name(side2)));
	break;
      case H_SIDE_LOST:
      	side2 = side_n(data0);
	sprintf(buf, "%s lost!", (side == side2 ? "you" : side_name(side2)));
	/* Include an explanation of the cause, if there is one. */
	if (data1 == -1) {
	    tprintf(buf, " (resigned)");
	} else if (data1 == -2) {
	    tprintf(buf, " (self-unit died)");
	} else if (data1 > 0) {
	    tprintf(buf, " (scorekeeper %d)", data1);
	} else {
	    tprintf(buf, " (don't know why)");
	}
	break;
      case H_SIDE_WITHDREW:
      	side2 = side_n(data0);
	sprintf(buf, "%s withdrew!", (side == side2 ? "you" : side_name(side2)));
	break;
      case H_SIDE_WON:
      	side2 = side_n(data0);
	sprintf(buf, "%s won!", (side == side2 ? "you" : side_name(side2)));
	/* Include an explanation of the cause, if there is one. */
	if (data1 > 0) {
	    tprintf(buf, " (scorekeeper %d)", data1);
	} else {
	    tprintf(buf, " (don't know why)");
	}
	break;
      case H_UNIT_CREATED:
      	side2 = side_n(data0);
	sprintf(buf, "%s created ",
		(side == side2 ? "you" : side_name(side2)));
	unit = find_unit(data1);
	if (unit != NULL) {
	    strcat(buf, unit_handle(side, unit));
	} else {
	    pastunit = find_past_unit(data1);
	    if (pastunit != NULL) {
		strcat(buf, past_unit_handle(side, pastunit));
	    } else {
		tprintf(buf, "%d??", data1);
	    }
	}
	break;
      case H_UNIT_COMPLETED:
      	side2 = side_n(data0);
	sprintf(buf, "%s completed ",
		(side == side2 ? "you" : side_name(side2)));
	unit = find_unit(data1);
	if (unit != NULL) {
	    strcat(buf, unit_handle(side, unit));
	} else {
	    pastunit = find_past_unit(data1);
	    if (pastunit != NULL) {
		strcat(buf, past_unit_handle(side, pastunit));
	    } else {
		tprintf(buf, "%d??", data1);
	    }
	}
	break;
      case H_UNIT_DAMAGED:
	unit = find_unit_dead_or_alive(data0);
	if (unit != NULL) {
	    strcpy(buf, unit_handle(side, unit));
	} else {
 	    pastunit = find_past_unit(data0);
	    if (pastunit != NULL) {
		strcpy(buf, past_unit_handle(side, pastunit));
	    } else {
		sprintf(buf, "%d??", data0);
	    }
	}
	tprintf(buf, " damaged (%d -> %d hp)", data1, hevt->data[2]);
	break;
      case H_UNIT_CAPTURED:
	buf[0] = '\0';
	/* Note that the second optional value, if present, is the id
	   of the unit that did the capturing. */
	unit = find_unit_dead_or_alive(data1);
	if (unit != NULL) {
	    strcat(buf, unit_handle(side, unit));
	} else {
 	    pastunit = find_past_unit(data1);
	    if (pastunit != NULL) {
		strcat(buf, past_unit_handle(side, pastunit));
	    } else if (data1 == 0) {
		tprintf(buf, "somebody");
	    } else {
		tprintf(buf, "%d??", data1);
	    }
	}
 	tprintf(buf, " captured ");
 	/* Describe the unit that was captured. */
	unit = find_unit_dead_or_alive(data0);
	if (unit != NULL) {
	    strcat(buf, unit_handle(side, unit));
	} else {
 	    pastunit = find_past_unit(data0);
	    if (pastunit != NULL) {
		strcat(buf, past_unit_handle(side, pastunit));
	    } else {
		tprintf(buf, "%d??", data0);
	    }
	}
	break;
      case H_UNIT_SURRENDERED:
	buf[0] = '\0';
 	/* Describe the unit that surrendered. */
	unit = find_unit_dead_or_alive(data0);
	if (unit != NULL) {
	    strcat(buf, unit_handle(side, unit));
	} else {
 	    pastunit = find_past_unit(data0);
	    if (pastunit != NULL) {
		strcat(buf, past_unit_handle(side, pastunit));
	    } else {
		tprintf(buf, "%d??", data0);
	    }
	}
 	tprintf(buf, " surrendered to ");
	/* The second optional value, if present, is the id of the
	   unit that accepted the surrender. */
	unit = find_unit_dead_or_alive(data1);
	if (unit != NULL) {
	    strcat(buf, unit_handle(side, unit));
	} else {
 	    pastunit = find_past_unit(data1);
	    if (pastunit != NULL) {
		strcat(buf, past_unit_handle(side, pastunit));
	    } else if (data1 == 0) {
		tprintf(buf, "somebody");
	    } else {
		tprintf(buf, "%d??", data1);
	    }
	}
	break;
      case H_UNIT_ACQUIRED:
	buf[0] = '\0';
	if (data1 >= 0) {
	    side2 = side_n(data1);
	    strcat(buf, (side == side2 ? "you" : side_name(side2)));
	} else {
	    tprintf(buf, "somebody");
	}
 	tprintf(buf, " acquired ");
 	/* Describe the unit that was acquired. */
	unit = find_unit_dead_or_alive(data0);
	if (unit != NULL) {
	    strcat(buf, unit_handle(side, unit));
	} else {
 	    pastunit = find_past_unit(data0);
	    if (pastunit != NULL) {
		strcat(buf, past_unit_handle(side, pastunit));
	    } else {
		tprintf(buf, "%d??", data0);
	    }
	}
	break;
      case H_UNIT_REVOLTED:
	buf[0] = '\0';
 	/* Describe the unit that revolted. */
	unit = find_unit_dead_or_alive(data0);
	if (unit != NULL) {
	    strcat(buf, unit_handle(side, unit));
	} else {
 	    pastunit = find_past_unit(data0);
	    if (pastunit != NULL) {
		strcat(buf, past_unit_handle(side, pastunit));
	    } else {
		tprintf(buf, "%d??", data0);
	    }
	}
 	tprintf(buf, " revolted");
 	if (data1 >= 0) {
	    side2 = side_n(data1);
	    tprintf(buf, ", went over to %s", (side == side2 ? "you" : side_name(side2)));
	}
	break;
      case H_UNIT_KILLED:
      case H_UNIT_DIED_IN_ACCIDENT:
      case H_UNIT_DIED_FROM_TEMPERATURE:
	/* Obviously, the unit mentioned here can only be a past unit. */
	pastunit = find_past_unit(data0);
	if (pastunit != NULL) {
	    sprintf(buf, "%s", past_unit_handle(side, pastunit));
	} else {
	    sprintf(buf, "%d??", data0);
	}
	if (hevt->type == H_UNIT_KILLED)
	  tprintf(buf, " was destroyed");
	else if (hevt->type == H_UNIT_DIED_IN_ACCIDENT)
	  tprintf(buf, " died in an accident");
	else
	  tprintf(buf, " died from excessive temperature");
	break;
      case H_UNIT_WRECKED:
      case H_UNIT_WRECKED_IN_ACCIDENT:
	pastunit = find_past_unit(data0);
	if (pastunit != NULL) {
	    sprintf(buf, "%s", past_unit_handle(side, pastunit));
	} else {
	    sprintf(buf, "%d??", data0);
	}
	if (hevt->type == H_UNIT_WRECKED)
	  tprintf(buf, " was wrecked");
	else
	  tprintf(buf, " was wrecked in an accident");
	break;
      case H_UNIT_VANISHED:
	pastunit = find_past_unit(data0);
	if (pastunit != NULL) {
	    sprintf(buf, "%s", past_unit_handle(side, pastunit));
	} else {
	    sprintf(buf, "%d??", data0);
	}
	tprintf(buf, " vanished");
	break;
      case H_UNIT_DISBANDED:
	pastunit = find_past_unit(data0);
	if (pastunit != NULL) {
	    sprintf(buf, "%s", past_unit_handle(side, pastunit));
	} else {
	    sprintf(buf, "%d??", data0);
	}
	tprintf(buf, " was disbanded");
	break;
      case H_UNIT_GARRISONED:
	pastunit = find_past_unit(data0);
	if (pastunit != NULL) {
	    sprintf(buf, "%s", past_unit_handle(side, pastunit));
	} else {
	    sprintf(buf, "%d??", data0);
	}
	tprintf(buf, " was used to garrison ");
	unit = find_unit(data1);
	if (unit != NULL) {
	    strcat(buf, unit_handle(side, unit));
	} else {
	    pastunit2 = find_past_unit(data1);
	    if (pastunit2 != NULL) {
		strcat(buf, past_unit_handle(side, pastunit2));
	    } else {
		/* Should never happen, but don't choke if it does. */
		tprintf(buf, "?????");
	    }
	}
	break;
      case H_UNIT_STARVED:
	pastunit = find_past_unit(data0);
	if (pastunit != NULL) {
	    sprintf(buf, "%s", past_unit_handle(side, pastunit));
	} else {
	    sprintf(buf, "%d??", data0);
	}
	tprintf(buf, " starved to death");
	break;
      case H_UNIT_MERGED:
	pastunit = find_past_unit(data0);
	if (pastunit != NULL) {
	    sprintf(buf, "%s", past_unit_handle(side, pastunit));
	} else {
	    sprintf(buf, "%d??", data0);
	}
	tprintf(buf, " merged into another");
	break;
      case H_UNIT_LEFT_WORLD:
	pastunit = find_past_unit(data0);
	if (pastunit != NULL) {
	    sprintf(buf, "%s", past_unit_handle(side, pastunit));
	} else {
	    sprintf(buf, "%d??", data0);
	}
	tprintf(buf, " left the world");
	break;
      case H_UNIT_NAME_CHANGED:
	pastunit = find_past_unit(data0);
	if (pastunit != NULL) {
	    sprintf(buf, "%s", past_unit_handle(side, pastunit));
	} else {
	    sprintf(buf, "%d??", data0);
	}
	unit = find_unit(data1);
	if (unit != NULL) {
	    if (unit->name != NULL)
	      tprintf(buf, " changed name to \"%s\"", unit->name);
	    else
	      tprintf(buf, " became anonymous");
	} else {
	    pastunit2 = find_past_unit(data1);
	    if (pastunit2 != NULL) {
		if (pastunit2->name != NULL)
		  tprintf(buf, " changed name to \"%s\"", pastunit2->name);
		else
		  tprintf(buf, " became anonymous");
	    } else
	      tprintf(buf, " no name change???");
	}
	break;
      default:
	/* Don't warn, will cause serious problems for windows that
	   display lists of events, but make sure the non-understood event
	   is obvious. */
	sprintf(buf, "?????????? \"%s\" ??????????",
		hevtdefns[hevt->type].name);
	break;
    }
}

int
pattern_matches_event(pattern, hevt)
Obj *pattern;
HistEvent *hevt;
{
    int data0, u;
    Obj *rest, *subpat;
    PastUnit *pastunit;

    if (find_event_type(car(pattern)) != hevt->type)
      return FALSE;
    data0 = hevt->data[0];
    for_all_list(cdr(pattern), rest) {
	subpat = car(rest);
	if (symbolp(subpat)) {
	    switch (hevt->type) {
	      case H_UNIT_STARVED:
		u = utype_from_name(c_string(subpat));
		pastunit = find_past_unit(data0);
		if (pastunit != NULL && pastunit->type == u)
		  return TRUE;
		else
		  return FALSE;
		break;
	      default:
		return FALSE;
	    }
	} else {
	    /* (should warn of bad pattern syntax?) */
	    return FALSE;
	}
    }
    return TRUE;
}

void
event_desc_from_list(side, lis, hevt, buf)
Side *side;
Obj *lis;
HistEvent *hevt;
char *buf;
{
    int n;
    Obj *rest, *item;
    PastUnit *pastunit;

    buf[0] = '\0';
    for_all_list(lis, rest) {
	item = car(rest);
	if (stringp(item)) {
	    strcat(buf, c_string(item));
	} else if (numberp(item)) {
	    n = c_number(item);
	    if (between(0, n, 3)) {
		switch (hevt->type) {
		  case H_UNIT_STARVED:
		    pastunit = find_past_unit(hevt->data[0]);
		    if (pastunit != NULL) {
			strcat(buf, past_unit_handle(side, pastunit));
		    } else {
			tprintf(buf, "%d?", hevt->data[0]);
		    }
		    break;
		  /* (should add other event types) */
		  default:
		    break;
		}
	    } else {
		tprintf(buf, " ??%d?? ", n);
	    }
	} else {
	    strcat(buf, " ????? ");
	}
    }
}

char *
action_result_desc(rslt)
int rslt;
{
    char *str;
    
    switch (rslt) {
      case A_ANY_OK:
	str = "OK";
	break;
      case A_ANY_DONE:
	str = "done";
	break;
      case A_ANY_CANNOT_DO:
	str = "can never do";
	break;
      case A_ANY_NO_ACP:
	str = "insufficient acp";
	break;
      case A_ANY_NO_MATERIAL:
	str = "insufficient material";
	break;
      case A_ANY_TOO_FAR:
	str = "too far";
	break;
      case A_ANY_TOO_NEAR:
	str = "too near";
	break;
      case A_MOVE_NO_MP:
	str = "insufficient mp";
	break;
      case A_MOVE_CANNOT_LEAVE_WORLD:
	str = "cannot leave world";
	break;
      case A_MOVE_DEST_FULL:
	str = "destination full";
	break;
      case A_OVERRUN_FAILED:
	str = "overrun failed";
	break;
      case A_OVERRUN_SUCCEEDED:
	str = "overrun succeeded";
	break;
      case A_FIRE_INTO_OUTSIDE_WORLD:
	str = "cannot fire outside world";
	break;
      case A_CAPTURE_FAILED:
	str = "capture failed";
	break;
      case A_CAPTURE_SUCCEEDED:
	str = "capture succeeded";
	break;
      case A_ANY_ERROR:
	str = "misc error";
	break;
      default:
	str = "???";
	break;
    }
    return str;
}

/* Generate a description of the borders and connections in and around
   a location. */

void
linear_desc(buf, x, y)
char *buf;
int x, y;
{
    int t, dir;

    if (any_aux_terrain_defined()) {
	for_all_terrain_types(t) {
	    if (t_is_border(t)
		&& aux_terrain_defined(t)
		&& any_borders_at(x, y, t)) {
		tprintf(buf, " %s", t_type_name(t)); 
		for_all_directions(dir) {
		    if (border_at(x, y, dir, t)) {
			tprintf(buf, "/%s", dirnames[dir]);
		    }
		}
	    }
	    if (t_is_connection(t)
		&& aux_terrain_defined(t)
		&& any_connections_at(x, y, t)) {
		tprintf(buf, " %s", t_type_name(t)); 
		for_all_directions(dir) {
		    if (connection_at(x, y, dir, t)) {
			tprintf(buf, "/%s", dirnames[dir]);
		    }
		}
	    }
	}
    }
}

void
elevation_desc(buf, x, y)
char *buf;
int x, y;
{
    if (elevations_defined()) {
	sprintf(buf, "(Elev %d)", elev_at(x, y));
    }
}

char *
feature_desc(feature, buf)
Feature *feature;
char *buf;
{
    int i, capitalize = FALSE;
    char *str;

    if (feature == NULL)
      return NULL;
    if (feature->name) {
	/* Does the name need any substitutions done? */
	if (strchr(feature->name, '%')) {
	    i = 0;
	    for (str = feature->name; *str != '\0'; ++str) {
	    	if (*str == '%') {
		    /* Interpret substitution directives. */
		    switch (*(str + 1)) {
		      case 'T':
			capitalize = TRUE;
		      case 't':
			if (feature->typename) {
			    buf[i] = '\0';
			    strcat(buf, feature->typename);
			    if (capitalize && islower(buf[i]))
			      buf[i] = toupper(buf[i]);
			    i = strlen(buf);
			}
			++str;
			break;
		      default:
			break;
		    }
	    	} else {
		    buf[i++] = *str;
	    	}
	    }
	    /* Close off the string. */
	    buf[i] = '\0';
	    return buf;
	} else {
	    /* Return the name alone. */
	    return feature->name;
	}
    } else {
	if (feature->typename) {
	    strcpy(buf, "unnamed ");
	    strcat(buf, feature->typename);
	    return buf;
	}
    }
    /* No description of the location is available. */
    return "anonymous feature";
}

/* Generate a string describing what is at the given location. */

char *
feature_name_at(x, y)
int x, y;
{
    int fid = (features_defined() ? raw_feature_at(x, y) : 0);
    Feature *feature;

    if (fid == 0)
      return NULL;
    feature = find_feature(fid);
    if (feature != NULL) {
	if (featurebuf == NULL)
	  featurebuf = xmalloc(BUFSIZE);
	return feature_desc(feature, featurebuf);
    }
    /* No description of the location is available. */
    return NULL;
}

void
temperature_desc(buf, x, y)
char *buf;
int x, y;
{
    if (temperatures_defined()) {
	sprintf(buf, "(Temp %d)", temperature_at(x, y));
    }
}

#if 0
    int age, u;
    short view, prevview;
    Side *side2;

    /* Compose and display view history of this cell. */
    Dprintf("Drawing previous view info\n");
    age = side_view_age(side, curx, cury);
    prevview = side_prevview(side, curx, cury);
    if (age == 0) {
	if (prevview != view) {
	    if (prevview == EMPTY) {
		/* misleading if prevview was set during init. */
		sprintf(tmpbuf, "Up to date; had been empty.");
	    } else if (prevview == UNSEEN) {
		sprintf(tmpbuf, "Up to date; had been unexplored.");
	    } else {
		side2 = side_n(vside(prevview));
		u = vtype(prevview);
		if (side2 != side) {
		    sprintf(tmpbuf, "Up to date; had seen %s %s.",
			    (side2 == NULL ? "independent" :
			     side_name(side2)),
			    u_type_name(u));
		} else {
		    sprintf(tmpbuf,
			    "Up to date; had been occupied by your %s.",
			    u_type_name(u));
		}
	    }
	} else {
	    sprintf(tmpbuf, "Up to date.");
	}
    } else {
	if (prevview == EMPTY) {
	    sprintf(tmpbuf, "Was empty %d turns ago.", age);
	} else if (prevview == UNSEEN) {
	    sprintf(tmpbuf, "Terrain first seen %d turns ago.", age);
	} else {
	    side2 = side_n(vside(prevview));
	    u = vtype(prevview);
	    if (side2 != side) {
		sprintf(tmpbuf, "Saw %s %s, %d turns ago.",
			(side2 == NULL ? "independent" :
			 side_name(side2)),
			u_type_name(u), age);
	    } else {
		sprintf(tmpbuf, "Was occupied by your %s %d turns ago.",
			u_type_name(u), age);
	    }
	}
    }
#endif

void
hp_desc(buf, unit, label)
char *buf;
Unit *unit;
int label;
{
    if (label) {
	sprintf(buf, "HP ");
    } else {
	buf[0] = '\0';
    }
    /* (print '-' or some such for zero hp case?) */
    if (unit->hp == u_hp(unit->type)) {
	tprintf(buf, "%d", unit->hp);
    } else {
	tprintf(buf, "%d/%d", unit->hp, u_hp(unit->type));
    } 
}

void
acp_desc(buf, unit, label)
char *buf;
Unit *unit;
int label;
{
    int u = unit->type;

    if (!completed(unit)) {
	sprintf(buf, "%d/%d done", unit->cp, u_cp(u));
    } else if (unit->act && u_acp(u) > 0) {
    	if (label) {
    	    strcpy(buf, "ACP ");
    	} else {
	    buf[0] = '\0';
    	}
	if (unit->act->acp == unit->act->initacp) {
	    tprintf(buf, "%d", unit->act->acp);
	} else {
	    tprintf(buf, "%d/%d", unit->act->acp, unit->act->initacp);
	}
    } else {
	buf[0] = '\0';
    }
}

void
cxp_desc(buf, unit, label)
char *buf;
Unit *unit;
int label;
{
    int cxpmax = u_cxp_max(unit->type);

    buf[0] = '\0';
    if (cxpmax == 0)
      return;
    if (label)
      strcat(buf, "  cXP ");
    if (unit->cxp == cxpmax) {
	tprintf(buf, "%d", unit->cxp);
    } else {
	tprintf(buf, "%d/%d", unit->cxp, cxpmax);
    } 
}

void
morale_desc(buf, unit, label)
char *buf;
Unit *unit;
int label;
{
    int moralemax = u_morale_max(unit->type);

    buf[0] = '\0';
    if (moralemax == 0)
      return;
    if (label)
      strcat(buf, "  Mor ");
    if (unit->morale == moralemax) {
	tprintf(buf, "%d", unit->morale);
    } else {
	tprintf(buf, "%d/%d", unit->morale, moralemax);
    } 
}

int
supply_desc(buf, unit, mrow)
char *buf;
Unit *unit;
int mrow;
{
    int u = unit->type, m, mm, tmprow;

    tmprow = 0;
    buf[0] = '\0';
    mm = 0;
    for_all_material_types(m) {
	if (um_storage_x(u, m) > 0) {
	    if (mm > 0 && mm % 3 == 0)
	      ++tmprow;
	    if (tmprow == mrow) {
		tprintf(buf, "%s %d/%d  ",
			m_type_name(m), unit->supply[m], um_storage_x(u, m));
	    }
	    ++mm;
	}
    }
    return (strlen(buf) > 0);
}

void
location_desc(buf, side, unit, u, x, y)
char *buf;
Side *side;
Unit *unit;
int u, x, y;
{
    int t = terrain_at(x, y);
    char *featurename;

    if (unit != NULL && unit->transport != NULL) {
	sprintf(buf, "In %s", short_unit_handle(unit->transport));
    } else if (unit != NULL || u != NONUTYPE) {
	sprintf(buf, "In %s", t_type_name(t));
    } else if (terrain_visible(side, x, y)) {
	sprintf(buf, "Empty %s", t_type_name(t));
    } else {
	sprintf(buf, "Unknown");
    }
    if (terrain_visible(side, x, y)) {
	linear_desc(buf, x, y);
	featurename = feature_name_at(x, y);
	if (!empty_string(featurename))
	  tprintf(buf, " (%s)", featurename);
	if (elevations_defined())
	  tprintf(buf, " (El %d)", elev_at(x, y));
	if (temperatures_defined())
	  tprintf(buf, " (T %d)", temperature_at(x, y));
	if (winds_defined()) {
	    int wforce = wind_force_at(x, y);

	    if (wforce == 0)
	      tprintf(buf, " (W calm)");
	    else
	      tprintf(buf, " (W f%d %s)", wforce, dirnames[wind_dir_at(x, y)]);
	}
	/* (should optionally list other local weather also) */
    }
    tprintf(buf, " at %d,%d", x, y);
}

/* Given a cell, describe where it is. */
/* (should also have a destination_desc_relative) */

void
destination_desc(buf, side, x, y, z)
char *buf;
Side *side;
int x, y, z;
{
    int dir, x1, y1;
    Unit *unit;

    if (!in_area(x, y)) {
	sprintf(buf, "?%d,%d?", x, y);
	return;
    }
    unit = unit_at(x, y);
    if (unit != NULL && unit->side == side) {
	if (!empty_string(unit->name)) {
	    sprintf(buf, "%s (%d,%d)", unit->name, x, y);
	} else {
	    sprintf(buf, "%s (%d,%d)", u_type_name(unit->type), x, y);
	}
	return;
    }
    for_all_directions(dir) {
	if (interior_point_in_dir(x, y, dir, &x1, &y1)) {
	    unit = unit_at(x1, y1);
	    if (unit != NULL && unit->side == side) {
		if (!empty_string(unit->name)) {
		    sprintf(buf, "%s of %s (%d,%d)",
			    dirnames[opposite_dir(dir)], unit->name, x, y);
		} else {
		    sprintf(buf, "%s of %s (%d,%d)",
			    dirnames[opposite_dir(dir)],
			    u_type_name(unit->type), x, y);
		}
		return;
	    }
	}
    }
    /* This is the old reliable case. */
    sprintf(buf, "%d,%d", x, y);
    if (z != 0)
      tprintf(buf, ",%d", z);
}

void
latlong_desc(buf, x, y, xf, yf, which)
char *buf;
int x, y, xf, yf, which;
{
    char minbuf[10];
    int rawlat, latdeg, latmin, rawlon, londeg, lonmin;

    buf[0] = '\0';
    if (world.circumference <= 0)
      return;
    xy_to_latlong(x, y, xf, yf, &rawlat, &rawlon);
    if (which & 2) {
	latdeg = abs(rawlat) / 60;
	latmin = abs(rawlat) % 60;
	minbuf[0] = '\0';
	if (latmin != 0)
	  sprintf(minbuf, "%dm", latmin);
	sprintf(buf, "%dd%s %c",
		latdeg, minbuf, (rawlat >= 0 ? 'N' : 'S'));
    }
    if (which & 1) {
	londeg = abs(rawlon) / 60;
	lonmin = abs(rawlon) % 60;
	minbuf[0] = '\0';
	if (lonmin != 0)
	  sprintf(minbuf, "%dm", lonmin);
	if (!empty_string(buf))
	  strcat(buf, " ");
	sprintf(buf + strlen(buf), "%dd%s %c",
		londeg, minbuf, (rawlon >= 0 ? 'E' : 'W'));
    }
}

void
others_here_desc(buf, unit)
char *buf;
Unit *unit;
{
    int u2, first = TRUE, nums[MAXUTYPES], incomplete[MAXUTYPES];
    Unit *unit2, *top;

    buf[0] = '\0';
    top = unit_at(unit->x, unit->y);
    if (top != NULL && top->nexthere != NULL) {
	for_all_unit_types(u2)
	  nums[u2] = incomplete[u2] = 0;
	for_all_stack(unit->x, unit->y, unit2)
	  if (completed(unit2))
	    ++nums[unit2->type];
	  else
	    ++incomplete[unit2->type];
	/* Don't count ourselves. */
	if (completed(unit))
	  --nums[unit->type];
	else
	  --incomplete[unit->type];
	for_all_unit_types(u2) {
	    if (nums[u2] > 0 || incomplete[u2] > 0) {
		if (first)
		  first = FALSE;
		else
		  strcat(buf, " ");
		if (nums[u2] > 0)
		  tprintf(buf, "%d", nums[u2]);
		if (incomplete[u2] > 0)
		  tprintf(buf, "(%d)", incomplete[u2]);
		strcat(buf, " ");
		strcat(buf, shortest_generic_name(u2));
	    }
	}
	strcat(buf, " here also");
    }
}

void
occupants_desc(buf, unit)
char *buf;
Unit *unit;
{
    int u2, first = TRUE, nums[MAXUTYPES], incomplete[MAXUTYPES];
    Unit *occ;

    buf[0] = '\0';
    if (unit->occupant != NULL) {
	strcat(buf, "Occs ");
	for_all_unit_types(u2)
	  nums[u2] = incomplete[u2] = 0;
	for_all_occupants(unit, occ)
	  if (completed(occ))
	    ++nums[occ->type];
	  else
	    ++incomplete[occ->type];
	for_all_unit_types(u2) {
	    if (nums[u2] > 0 || incomplete[u2] > 0) {
		if (first)
		  first = FALSE;
		else
		  strcat(buf, " ");
		if (nums[u2] > 0)
		  tprintf(buf, "%d", nums[u2]);
		if (incomplete[u2] > 0)
		  tprintf(buf, "(%d)", incomplete[u2]);
		strcat(buf, " ");
		strcat(buf, shortest_generic_name(u2));
	    }
	}
    }
}

extern char *goal_desc PARAMS ((char *buf, Goal *goal));

void
plan_desc(buf, unit)
char *buf;
Unit *unit;
{
    char goalbuf[BUFSIZE];
    int i;
    Plan *plan = unit->plan;
    Task *task;

    if (plan == NULL) {
	buf[0] = '\0';
    	return;
    }
    sprintf(buf, "%s", plantypenames[plan->type]);
    if (plan->waitingfortasks)
      strcat(buf, " Waiting");
    if (plan->asleep)
      strcat(buf, " Asleep");
    if (plan->reserve)
      strcat(buf, " Reserve");
    if (plan->delayed)
      strcat(buf, " Delay");
    /* The more usual case is to allow AI control of units, so we only
       mention it when it's off. */
    if (!plan->aicontrol)
      strcat(buf, " NoAI");
    if (plan->supply_is_low)
      strcat(buf, " SupplyLow");
    if (plan->maingoal) {
	strcat(buf, " goal ");
	strcat(buf, goal_desc(goalbuf, plan->maingoal));
    }
    if (plan->formation) {
	strcat(buf, " formation ");
	goal_desc(buf+strlen(buf), unit->plan->formation);
    }
    if (plan->tasks) {
	i = 0;
	for_all_tasks(plan, task)
	  ++i;
	tprintf(buf, " %d task%s", i, (i == 1 ? "" : "s"));
    } 
}

/* Describe a task in a brief but intelligible way. */

void
task_desc(buf, side, task)
char *buf;
Side *side;
Task *task;
{
    int i, slen;
    char *argtypes;
    Unit *unit;

    if (task == NULL) {
	buf[0] = '\0';
	return;
    }
    sprintf(buf, "%s", taskdefns[task->type].name);
    switch (task->type) {
      case TASK_BUILD:
	tprintf(buf, " %s", u_type_name(task->args[0]));
	if (task->args[1] != 0) {
	    unit = find_unit(task->args[1]);
	    if (unit != NULL) {
		tprintf(buf, " (%d cp)", unit->cp);
	    }
	}
	tprintf(buf, ", %d of %d", task->args[2], task->args[3]);
	break;
      case TASK_HIT_POSITION:
	tprintf(buf, " ");
        destination_desc(buf+strlen(buf), side, task->args[0], task->args[1], 0);
	break;
      case TASK_HIT_UNIT:
	tprintf(buf, " at ");
        destination_desc(buf+strlen(buf), side, task->args[0], task->args[1], 0);
	if (task->args[2] != NONUTYPE) {
	    tprintf(buf, " (s%d %s)", task->args[3], shortest_unique_name(task->args[2]));
      	}
	break;
      case TASK_MOVE_DIR:
	tprintf(buf, " %s %d", dirnames[task->args[0]], task->args[1]);
	break;
      case TASK_MOVE_TO:
        if (task->args[3] == 0) {
	    tprintf(buf, " ");
        } else if (task->args[3] == 1) {
	    tprintf(buf, " adj ");
        } else {
	    tprintf(buf, " within %d of ", task->args[3]);
        }
        destination_desc(buf+strlen(buf), side, task->args[0], task->args[1], task->args[2]);
	break;
      case TASK_RESUPPLY:
	if (task->args[0] != NONMTYPE)
	  tprintf(buf, " %s", m_type_name(task->args[0]));
	if (task->args[1] != 0) {
	    unit = find_unit(task->args[1]);
	    if (unit != NULL) {
		tprintf(buf, " at %s", short_unit_handle(unit));
	    }
	}
	break;
      default:
	/* Default is just to dump out the raw parameters of the task. */
	argtypes = taskdefns[task->type].argtypes;
	slen = strlen(argtypes);
	for (i = 0; i < slen; ++i) {
	    tprintf(buf, "%c%d", (i == 0 ? ' ' : ','), task->args[i]);
	}
	break;
    }
    /* Always (should we?) include the number of executions and retries. */
    tprintf(buf, " x %d", task->execnum);
    if (task->retrynum > 0) {
	tprintf(buf, " fail %d", task->retrynum);
    }
}

/* Describe a goal in a human-intelligible way. */

char *
goal_desc(buf, goal)
char *buf;
Goal *goal;
{
    int numargs, i, arg;
    char *argtypes;

    if (goal == NULL)
      return "<null goal>";
    switch (goal->type) {
      case GOAL_KEEP_FORMATION:
	sprintf(buf, " %d,%d from %s (var %d)",
		goal->args[1], goal->args[2],
		unit_handle(NULL, find_unit(goal->args[0])),
		goal->args[3]);
	break;
	/* (should add more cases specific to common types of goals) */
      default:
	sprintf(buf, "<goal s%d %s%s",
		side_number(goal->side), (goal->tf ? "" : "not "),
		goaldefns[goal->type].name);
	argtypes = goaldefns[goal->type].argtypes;
	numargs = strlen(argtypes);
	for (i = 0; i < numargs; ++i) {
	    arg = goal->args[i];
	    switch (argtypes[i]) {
	      case 'h':
		tprintf(buf, "%d", arg);
		break;
	      case 'm':
		if (is_material_type(arg))
		  tprintf(buf, " %s", m_type_name(arg));
		else
		  tprintf(buf, " m%d?", arg);
		break;
	      case 'S':
		tprintf(buf, " %s", short_side_title(side_n(arg)));
		break;
	      case 'u':
		if (is_unit_type(arg))
		  tprintf(buf, " %s", u_type_name(arg));
		else
		  tprintf(buf, " u%d?", arg);
		break;
	      case 'U':
		tprintf(buf, " `%s'", unit_handle(NULL, find_unit(arg)));
		break;
	      case 'w':
		tprintf(buf, " %dx", arg);
		break;
	      case 'x':
		tprintf(buf, " %d,", arg);
		break;
	      case 'y':
		tprintf(buf, "%d", arg);
		break;
	      default:
		tprintf(buf, " %d", arg);
		break;
	    }
	}
	strcat(buf, ">");
	break;
    }
    return buf;
}

/* Format a clock time into a standard form.  This routine will omit the hours
   part if it will be uninteresting. */

void
time_desc(buf, time, maxtime)
char *buf;
int time, maxtime;
{
    int hour, minute, second;

    if (time >= 0) {
	hour = time / 3600;  minute = (time / 60) % 60;  second = time % 60;
    	if (between(1, maxtime, 3600) && hour == 0) {
	    sprintf(buf, "%.2d:%.2d", minute, second);
	} else {
	    sprintf(buf, "%.2d:%.2d:%.2d", hour, minute, second);
	}
    } else {
    	sprintf(buf, "??:??:??");
    }
}

/* General-purpose routine to take an array of anonymous unit types and
   summarize what's in it, using numbers and unit chars. */

char *
summarize_units(buf, ucnts)
char *buf;
int *ucnts;
{
    char tmp[BUFSIZE];  /* should be bigger? */
    int u;

    buf[0] = '\0';
    for_all_unit_types(u) {
	if (ucnts[u] > 0) {
	    sprintf(tmp, " %d %s", ucnts[u], utype_name_n(u, 3));
	    strcat(buf, tmp);
	}
    }
    return buf;
}

char *get_word PARAMS ((char *str));
void notify_doctrine_1 PARAMS ((Side *side, Doctrine *doctrine));

void
notify_doctrine(side, spec)
Side *side;
char *spec;
{
    int u;
    char *arg, *rest, substr[BUFSIZE], outbuf[BUFSIZE];
    Doctrine *doctrine;

    if (side == NULL)
      return;
    if (!empty_string(spec))
      rest = get_next_arg(spec, substr, &arg);
    else
      arg = "";
    if ((doctrine = find_doctrine_by_name(arg)) != NULL) {
	/* Found a specific named doctrine. */
	/* (should say which unit types use it) */
    } else if ((u = utype_from_name(arg)) != NONUTYPE) {
	doctrine = side->udoctrine[u];
    } else if (strcmp(arg, "default") == 0) {
	doctrine = side->default_doctrine;
	/* (should say which unit types use it) */
    } else {
	if (!empty_string(arg))
	  notify(side, "\"%s\" not recognized as doctrine name or unit type.");
	/* Note that although we list all doctrines, including those belonging
	   to other sides, we don't know if the other side is actually *using*
	   any particular doctrine. */
	outbuf[0] = '\0';
	for (doctrine = doctrine_list; doctrine != NULL; doctrine = doctrine->next) {
	    if (!empty_string(doctrine->name))
	      tprintf(outbuf, " %s", doctrine->name);
	    else
	      tprintf(outbuf, " #%d", doctrine->id);
	    if (doctrine->next != NULL)
	      tprintf(outbuf, ",");
	}
	notify(side, "Doctrines available:%s", outbuf);
	return;
    }
    /* We now have a doctrine to display. */
    notify_doctrine_1(side, doctrine);
}

void
notify_doctrine_1(side, doctrine)
Side *side;
Doctrine *doctrine;
{
    if (!empty_string(doctrine->name))
      notify(side, "Doctrine '%s':", doctrine->name);
    else
      notify(side, "Doctrine #%d:", doctrine->id);
    notify(side, "  Resupply at %d%% of storage", doctrine->resupply_percent);
    notify(side, "  Rearm at %d%% of storage", doctrine->rearm_percent);
    notify(side, "  Repair at %d%% of hp", doctrine->repair_percent);
    /* (should display construction runs) */
}

#define first_person(unit) ((unit)->side && (unit)->side->self_unit == (unit))

int report_combat_special PARAMS ((Unit *unit1, Unit *unit2, char *str));
int type_matches_symbol PARAMS ((Obj *sym, int u));

void
report_combat(atker, other, str)
Unit *atker, *other;
char *str;
{
    int rslt;

    if (g_action_notices() != lispnil) {
	rslt = report_combat_special(atker, other, str);
	if (rslt)
	  return;
    }
    /* Default messages for each type of report. */
    if (strcmp(str, "destroy") == 0) {
	notify_combat(atker, other, (first_person(atker) ? "%s destroy %s!" : "%s destroys %s!"));
    } else if (strcmp(str, "destroy-occupant") == 0) {
	notify_combat(other, atker, (first_person(atker) ? "  (and destroy occupant %s!)" : "  (and destroys occupant %s!)"));
    } else if (strcmp(str, "hit") == 0) {
	notify_combat(atker, other, (first_person(atker) ? "%s hit %s!" : "%s hits %s!"));
    } else if (strcmp(str, "hit-occupant") == 0) {
	notify_combat(other, atker, (first_person(atker) ? "  (and hit occupant %s!)" : "  (and hits occupant %s!)"));
    } else if (strcmp(str, "miss") == 0) {
	notify_combat(atker, other, (first_person(atker) ? "%s miss %s." : "%s misses %s."));
    } else if (strcmp(str, "miss-occupant") == 0) {
	/* (this case is too uninteresting to mention) */
    } else if (strcmp(str, "resist") == 0) {
	notify_combat(other, atker, (first_person(other) ? "%s throw back %s!" : "%s throws back %s!"));
    } else if (strcmp(str, "resist/slaughter") == 0) {
	notify_combat(other, atker, (first_person(other) ? "%s resist capture; %s slaughtered!" : "%s resists capture; %s slaughtered!"));
    } else if (strcmp(str, "capture") == 0) {
	notify_combat(atker, other, (first_person(atker) ? "%s capture %s!" : "%s captures %s!"));
    } else if (strcmp(str, "liberate") == 0) {
	notify_combat(atker, other, (first_person(atker) ? "%s liberate %s!" : "%s liberates %s!"));
    } else if (strcmp(str, "escape") == 0) {
	notify_combat(other, atker, (first_person(other) ? "%s escape!" : "%s escapes!"));
    } else if (strcmp(str, "retreat") == 0) {
	notify_combat(other, atker, (first_person(other) ? "%s retreat!" : "%s retreats!"));
    } else if (strcmp(str, "detonate") == 0) {
	notify_combat(atker, other, (first_person(atker) ? "%s detonate!" : "%s detonates!"));
    } else {
	notify_combat(atker, other, str);
    }
}

static void
notify_combat(unit1, unit2, str)
Unit *unit1, *unit2;
char *str;
{
    char buf1[BUFSIZE], buf2[BUFSIZE];
    Side *side3;

    for_all_sides(side3) {
	if (active_display(side3)
	    && (all_see_all
		|| (unit1 != NULL && side3 == unit1->side)
		|| (unit2 != NULL && side3 == unit2->side))) {
	    strcpy(buf1, unit_handle(side3, unit1));
	    strcpy(buf2, unit_handle(side3, unit2));
	    notify(side3, str, buf1, buf2);
	}
    }
}

static int pattern_matches_combat PARAMS ((Obj *pattern, Unit *unit, Unit *unit2));
static void combat_desc_from_list PARAMS ((Side *side, Obj *lis, Unit *unit, Unit *unit2, char *str, char *buf));

int
report_combat_special(unit1, unit2, str)
Unit *unit1, *unit2;
char *str;
{
    int found = FALSE;
    char *soundname, abuf[BUFSIZE];
    Obj *rest, *head, *pat, *parms, *msgdesc;

    for_all_list(g_action_notices(), rest) {
	head = car(rest);
	if (!consp(head)) {
	    run_warning("Non-list in action-notices");
	    continue;
	}
	pat = car(head);
	if (symbolp(pat) && strcmp(c_string(pat), str) == 0) {
	    found = TRUE;
	    break;
	}
	if (consp(pat)
	    && symbolp(car(pat))
	    && strcmp(c_string(car(pat)), str) == 0
	    && pattern_matches_combat(cdr(pat), unit1, unit2)) {
	    found = TRUE;
	    break;
	}
    }
    /* If we have a match, do something with it. */
    if (found) {
	msgdesc = cadr(head);
	if (stringp(msgdesc)) {
	    strcpy(abuf, c_string(msgdesc));
	} else {
	    Side *side3;

	    for_all_sides(side3) {
		if (active_display(side3)
		    && (all_see_all
			|| (unit1 != NULL && side3 == unit1->side)
			|| (unit2 != NULL && side3 == unit2->side))) {
		    combat_desc_from_list(side3, msgdesc, unit1, unit2, str, abuf);
		    notify(side3, abuf);
		}
	    }
	}
    }
    return found;
}

static int
pattern_matches_combat(parms, unit, unit2)
Obj *parms;
Unit *unit, *unit2;
{
    Obj *head;

    head = car(parms);
    if (!type_matches_symbol(head, unit->type))
      return FALSE;
    parms = cdr(parms);
    head = car(parms);
    if (!type_matches_symbol(head, unit2->type))
      return FALSE;
    return TRUE;
}

int
type_matches_symbol(sym, u)
Obj *sym;
int u;
{
    char *typename;
    Obj *val, *rest, *head;

    if (!symbolp(sym))
      return FALSE;
    typename = u_type_name(u);
    if (strcmp(c_string(sym), typename) == 0)
      return TRUE;
    if (match_keyword(sym, K_USTAR))
      return TRUE;
    if (boundp(sym)) {
	val = eval(sym);
	if (symbolp(val) && strcmp(c_string(val), typename) == 0)
	  return TRUE;
	if (utypep(val) && c_number(val) == u)
	  return TRUE;
	if (consp(val)) {
	    for_all_list(val, rest) {
		head = car(rest);
		if (symbolp(head) && strcmp(c_string(head), typename) == 0)
		  return TRUE;
		if (utypep(head) && c_number(head) == u)
		  return TRUE;
	    }
	}
    }
    return FALSE;
}

static void
combat_desc_from_list(side, lis, unit, unit2, str, buf)
Side *side;
Obj *lis;
Unit *unit, *unit2;
char *str, *buf;
{
    int n;
    char *symname;
    Obj *rest, *item;

    buf[0] = '\0';
    for_all_list(lis, rest) {
	item = car(rest);
	if (stringp(item)) {
	    strcat(buf, c_string(item));
	} else if (symbolp(item)) {
	    symname = c_string(item);
	    if (strcmp(symname, "actor") == 0) {
		strcat(buf, unit_handle(side, unit));
	    } else if (strcmp(symname, "actee") == 0) {
		strcat(buf, unit_handle(side, unit2));
	    } else {
		tprintf(buf, " ??%s?? ", symname);
	    }
	} else if (numberp(item)) {
	    n = c_number(item);
	    if (0 /* special processing */) {
	    } else {
		tprintf(buf, "%d", n);
	    }
	} else {
	    strcat(buf, " ????? ");
	}
    }
}

void
report_take(side, unit, needed, rslts)
Side *side;
Unit *unit;
int needed;
short *rslts;
{
    char buf[BUFSIZE];
    int m, something = FALSE;

    if (!needed) {
	notify(side, "%s needed nothing.", unit_handle(side, unit));
	return;
    }
    buf[0] = '\0';
    for_all_material_types(m) {
	if (rslts[m] > 0) {
	    tprintf(buf, " %d %s", rslts[m], m_type_name(m));
	    something = TRUE;
	}
    }
    if (something) {
	notify(side, "%s got%s.", unit_handle(side, unit), buf);
    } else {
	notify(side, "%s got nothing.", unit_handle(side, unit));
    }
}

/* Compose a one-line comment on the game. */

char *
exit_commentary(side)
Side *side;
{
    int numingame = 0, numwon = 0, numlost = 0;
    Side *side2, *lastside, *winner, *loser;

    for_all_sides(side2) {
	if (side2->ingame) {
	    ++numingame;
	    lastside = side2;
	}
	if (side_won(side2)) {
	    ++numwon;
	    winner = side2;
	}
	if (side_lost(side2)) {
	    ++numlost;
	    loser = side2;
	}
    }
    if (numingame > 0) {
    	if (0 /* could have been resolved, need to check scorekeepers */) {
	    sprintf(spbuf, "The outcome remains undecided");
#ifdef RUDE
	    if (numsides > 1) {
		strcat(spbuf, ", but you're probably the loser!");
	    } else {
		strcat(spbuf, "...");
	    }
#else
	    strcat(spbuf, "...");
#endif /* RUDE */
	} else {
	    sprintf(spbuf, "Game is over.");
    	}
    } else if (numwon == numsides) {
	sprintf(spbuf, "Everybody won!");
    } else if (numlost == numsides) {
	sprintf(spbuf, "Everybody lost!");
    } else if (numwon == 1) {
	sprintf(spbuf, "%s won!", (side == winner ? "You" : side_desig(winner)));
    } else if (numlost == 1) {
	sprintf(spbuf, "%s lost!", (side == loser ? "You" : side_desig(loser)));
    } else {
	sprintf(spbuf, "Some won and some lost.");
    }
    return spbuf;
}

void
notify_all_of_resignation(side, side2)
Side *side, *side2;
{
    Side *side3;

    for_all_sides(side3) {
	if (side3 != side) {
	    notify(side3,
		   "%s %s giving up!",
		   short_side_title_with_adjective(side,
#ifdef RUDE
		   (flip_coin() ? "cowardly" : "wimpy")
#else
		   NULL
#endif /* RUDE */
		   ),
		   (short_side_title_plural_p(side) ? "are" : "is"));
	    if (side2 != NULL) {
		notify(side3, "... and donating everything to %s!",
		       short_side_title(side2));
	    }
	}
    }
}

/* Given a number, figure out what suffix should go with it. */

char *
ordinal_suffix(n)
int n;
{
    if (n % 100 == 11 || n % 100 == 12 || n % 100 == 13) {
	return "th";
    } else {
	switch (n % 10) {
	  case 1:   return "st";
	  case 2:   return "nd";
	  case 3:   return "rd";
	  default:  return "th";
	}
    }
}

/* Pluralize a word, attempting to be smart about various possibilities
   that don't have a different plural form (such as "Chinese" and "Swiss"). */

/* There should probably be a test for when to add "es" instead of "s". */

char *
plural_form(word)
char *word;
{
    char endch = ' ', nextend = ' ';
    int len;

    if (word == NULL) {
	run_warning("plural_form given NULL string");
	pluralbuf[0] = '\0';
	return pluralbuf;
    }
    len = strlen(word);
    if (len > 0)
      endch = word[len - 1];
    if (len > 1)
      nextend = word[len - 2];
    if (endch == 'h' || endch == 's' || (endch == 'e' && nextend == 's')) {
	sprintf(pluralbuf, "%s", word);
    } else {
	sprintf(pluralbuf, "%ss", word);
    }
    return pluralbuf;
}

/* General text generation. */

char *
make_text(buf, maker, a1, a2, a3, a4)
char *buf;
Obj *maker;
long a1, a2, a3, a4;
{
    if (buf == NULL)
      buf = xmalloc(BUFSIZE);
    if (maker != lispnil) {
    } else {
	sprintf(buf, "%ld %ld %ld %ld", a1, a2, a3, a4);
    }
    return buf;
}

/* Compose a readable form of the given date. */

char *
absolute_date_string(date)
int date;
{
    /* The first time we ask for a date, interpret the calendar. */
    if (calendar_type == cal_unknown)
      init_calendar();
    switch (calendar_type) {
      case cal_turn:
	sprintf(datebuf, "%s%4d", turnname, date);
	return datebuf;
      case cal_usual:
	return usual_date_string(date);
      default:
	case_panic("calendar type", calendar_type);
    }
    return "!?!";
}

/* Interpret the calendar definition. */

static void
init_calendar()
{
    int caltypeunknown = FALSE;
    char *stepnamestr;
    Obj *cal, *caltype, *stepname, *step;

    cal = g_calendar();
    /* Turn-based calendar is the default. */
    calendar_type = cal_turn;
    turnname = "Turn";
    if (cal == lispnil) {
	/* Default is fine. */
    } else if (consp(cal)) {
	caltype = car(cal);
	if ((symbolp(caltype) || stringp(caltype))
	    && strcmp("usual", c_string(caltype)) == 0) {
	    calendar_type = cal_usual;
	    stepname = cadr(cal);
	    if (symbolp(stepname) || stringp(stepname)) {
		stepnamestr = c_string(stepname);
		if (strcmp(stepnamestr, "second") == 0) {
		    datesteptype = ds_second;
		} else if (strcmp(stepnamestr, "minute") == 0) {
		    datesteptype = ds_minute;
		} else if (strcmp(stepnamestr, "hour") == 0) {
		    datesteptype = ds_hour;
		} else if (strcmp(stepnamestr, "day") == 0) {
		    datesteptype = ds_day;
		} else if (strcmp(stepnamestr, "week") == 0) {
		    datesteptype = ds_week;
		} else if (strcmp(stepnamestr, "month") == 0) {
		    datesteptype = ds_month;
		} else if (strcmp(stepnamestr, "season") == 0) {
		    datesteptype = ds_season;
		} else if (strcmp(stepnamestr, "year") == 0) {
		    datesteptype = ds_year;
		} else {
		    init_warning("\"%s\" not a known date step name", stepnamestr);
		}
	    } else {
		init_warning("No name for date step type, substituting `day'");
		datesteptype = ds_day;
	    }
	    /* Collect an optional multiple. */
	    step = caddr(cal);
	    datestep = (numberp(step) ? c_number(step) : 1);
	    if (!empty_string(g_initial_date())) {
		set_initial_date(g_initial_date());
	    }
	} else {
	    caltypeunknown = TRUE;
	}
    } else if (stringp(cal)) {
	turnname = c_string(cal);
    } else {
	caltypeunknown = TRUE;
    }
    if (caltypeunknown)
      init_warning("Unknown calendar type");
    /* If calendar type is unknown, we drop back to using turns - not ideal,
       could allow bogus game designs to proceed. */
}

/* Compose a date, omitting components supplied by the base date. */

char *
relative_date_string(date, base)
int date, base;
{
    if (calendar_type == cal_unknown)
      init_calendar();
    switch (calendar_type) {
      case cal_turn:
	sprintf(datebuf, "%d(%d)", date, base);
	break;
      case cal_usual:
	/* (should do this for real eventually) */
	sprintf(datebuf, "%d(%d)", date, base);
	break;
      default:
	case_panic("calendar type", calendar_type);
	break;
    }
    return datebuf;
}

/* Given two dates, figure out how many turns encompassed by them. */

int
turns_between(datestr1, datestr2)
char *datestr1, *datestr2;
{
    int rslt, turn1, turn2;
    UsualDate date1, date2;

    if (calendar_type == cal_unknown)
      init_calendar();
    switch (calendar_type) {
      case cal_turn:
	sscanf("%d", datestr1, &turn1);
	sscanf("%d", datestr2, &turn2);
	return (turn2 - turn1);
      case cal_usual:
	parse_usual_date(datestr1, &date1);
	parse_usual_date(datestr2, &date2);
	rslt = date2.year - date1.year;
	if (datesteptype == ds_year)
	  return (rslt / datestep);
	if (datesteptype < ds_year) {
	    rslt = (12 * rslt) - (date2.month - date1.month);
	}
	if (datesteptype == ds_month)
	  return (rslt / datestep);
	if (datesteptype < ds_month) {
	    rslt = (30 * rslt) - (date2.day - date1.day);
	}
	if (datesteptype == ds_week)
	  return (((rslt + 6) / 7) / datestep);
	if (datesteptype == ds_day)
	  return (rslt / datestep);
	if (datesteptype < ds_day) {
	    rslt = (24 * rslt) - (date2.hour - date1.hour);
	}
	if (datesteptype == ds_hour)
	  return (rslt / datestep);
	return (rslt / datestep); /* semi-bogus */
      default:
	case_panic("calendar type", calendar_type);
	break;
    }
    return 1;  /* appease the compiler */
}

void
set_initial_date(str)
char *str;
{
    if (calendar_type == cal_unknown)
      init_calendar();
    switch (calendar_type) {
      case cal_turn:
	sscanf("%d", str, &turn_initial);
	break;
      case cal_usual:
	if (usual_initial == NULL)
	  usual_initial = (UsualDate *) xmalloc(sizeof(UsualDate));
	parse_usual_date(str, usual_initial);
	break;
      default:
	case_panic("calendar type", calendar_type);
	break;
    }
}

/* Pick a date out of the given string. Note that this implementation does not
   detect extra garbage in the string, should fix someday. */

/* (should use strtol etc instead of sscanf) */

static void
parse_usual_date(datestr, udate)
char *datestr;
UsualDate *udate;
{
    char aname[BUFSIZE];
    int i, cnt;
    
    udate->second = udate->minute = udate->hour = 0;
    udate->day = udate->month = udate->year = 0;
    aname[0] = '\0';
    if (!empty_string(datestr)) {
	/* Assume it's in a standard date format. */
	switch (datesteptype) {
	  case ds_second:
	    cnt = sscanf(datestr, "%d:%d:%d %d %s %d",
			 &(udate->second), &(udate->minute), &(udate->hour),
			 &(udate->day), aname, &(udate->year));
	    if (cnt != 6) {
		cnt = sscanf(datestr, "%d:%d:%d",
			     &(udate->second), &(udate->minute), &(udate->hour));
		if (cnt != 3)
		  goto bad_format;
		return;
	    }
	    --(udate->day);
	    break;
	  case ds_minute:
	    cnt = sscanf(datestr, "%d:%d %d %s %d",
			 &(udate->minute), &(udate->hour),
			 &(udate->day), aname, &(udate->year));
	    if (cnt != 5) {
		cnt = sscanf(datestr, "%d:%d",
			     &(udate->minute), &(udate->hour));
		if (cnt != 2)
		  goto bad_format;
		return;
	    }
	    --(udate->day);
	    break;
	  case ds_hour:
	    cnt = sscanf(datestr, "%d:00 %d %s %d",
			 &(udate->hour),
			 &(udate->day), aname, &(udate->year));
	    if (cnt != 4)
	      cnt = sscanf(datestr, "%d %d %s %d",
			   &(udate->hour),
			   &(udate->day), aname, &(udate->year));
	    if (cnt != 4)
	      goto bad_format;
	    --(udate->day);
	    break;
	  case ds_day:
	  case ds_week:
	    cnt = sscanf(datestr, "%d %s %d",
			 &(udate->day), aname, &(udate->year));
	    if (cnt != 3)
	      goto bad_format;
	    --(udate->day);
	    break;
	  case ds_month:
	    cnt = sscanf(datestr, "%s %d", aname, &(udate->year));
	    if (cnt != 2)
	      goto bad_format;
	    break;
	  case ds_season:
	    cnt = sscanf(datestr, "%s %d", aname, &(udate->year));
	    if (cnt != 2)
	      goto bad_format;
	    for (i = 0; i < 4; ++i) {
		if (strcmp(aname, seasons[i]) == 0) {
		    udate->month = i * 3;
		    return;
		}
	    }
	    init_warning("\"%s\" not a recognized season name", aname);
	    return;
	  case ds_year:
	    cnt = sscanf(datestr, "%d", &(udate->year));
	    if (cnt != 1)
	      goto bad_format;
	    return;
	  default:
	    init_warning("%d not an allowed date step type", datesteptype);
	    break;
	}
	for (i = 0; i < 12; ++i) {
	    /* (should make case-insensitive) */
	    if (strcmp(aname, months[i]) == 0) {
		udate->month = i;
		return;
	    }
	}
	init_warning("\"%s\" not a recognized month name", aname);
    }
    return;
  bad_format:
    init_warning("\"%s\" is a badly formatted date", datestr);
}

/* Given a numeric date, convert it into something understandable. */

static char *
usual_date_string(date)
int date;
{
    int year = 0, season = 0, month = 0, day = 0;
    int hour = 0, second = 0, minute = 0;
    int i;

    /* The date, which is a turn number, should be 1 or more, but this
       routine may be called before the game really starts, so return
       something that will be distinctive if it's ever displayed. */
    if (date <= 0)
      return "pregame";
    /* First displayed date is normally turn 1; be zero-based for
       the benefit of calculation. */
    --date;
    /* If multiples of basic step, convert to basic step by multiplying. */
    date *= datestep;
    if (usual_initial == NULL) {
	usual_initial = (UsualDate *) xmalloc(sizeof(UsualDate));
	if (!empty_string(g_initial_date()))
	  parse_usual_date(g_initial_date(), usual_initial);
    }
    switch (datesteptype) {
      case ds_second:
	second = date % 60;
	minute = date / 60;
	sprintf(datebuf, "%d:%d", minute, second);
	/* (should add day/month/year if available?) */
	break;
      case ds_minute:
	minute = date % 60;
	hour = date / 60 + usual_initial->hour;
	sprintf(datebuf, "%d:%d", hour, minute);
	/* (should add day/month/year if available?) */
	break;
      case ds_hour:
	date += usual_initial->hour;
 	hour = date % 24;
 	date /= 24;
	date += usual_initial->day;
	for (i = 0; i < usual_initial->month; ++i)
	  date += monthdays[i];
	day = date % 365;
	month = 0;
	for (i = 0; i < 12; ++i) {
	    if (day < monthdays[i])
	      break;
	    day -= monthdays[i];
	    ++month;
	}
	++day;
	year = date / 365 + usual_initial->year;
	sprintf(datebuf, "%d:00 %2d %s %d", hour, day, months[month], ABS(year));
	break;
      case ds_week:
	/* Convert to days, then proceed as for days. */
	date *= 7;
	/* Fall through. */
      case ds_day:
	date += usual_initial->day;
	for (i = 0; i < usual_initial->month; ++i)
	  date += monthdays[i];
	day = date % 365;
	month = 0;
	for (i = 0; i < 12; ++i) {
	    if (day < monthdays[i])
	      break;
	    day -= monthdays[i];
	    ++month;
	}
	++day;
	year = date / 365 + usual_initial->year;
	sprintf(datebuf, "%2d %s %d", day, months[month], ABS(year));
	break;
      case ds_month:
	date += usual_initial->month;
    	month = date % 12;
	year = date / 12 + usual_initial->year;
	sprintf(datebuf, "%s %d", months[month], ABS(year));
	break;
      case ds_season:
     	season = date % 4;
	year = date / 4 + usual_initial->year;
	sprintf(datebuf, "%s %d", seasons[season], ABS(year));
	break;
      case ds_year:
	year = date + usual_initial->year;
	sprintf(datebuf, "%d", ABS(year));
	break;
      default:
	sprintf(datebuf, "%d is unknown date step type", datesteptype);
	break;
    }
    if (year < 0) {
	strcat(datebuf, " BC");
    }
    return datebuf;
}

/* Show some overall numbers on performance of a side. */

void
write_side_results(fp, side)
FILE *fp;
Side *side;
{
    int i;

    if (side == NULL) {
	fprintf(fp, "Results for game as a whole:\n\n");
    } else {
	fprintf(fp, "Results for %s%s",
		short_side_title(side),
		(side_won(side) ? " (WINNER)" :
		 (side_lost(side) ? " (LOSER)" :
		  "")));
	for (i = 0; i < numscores; ++i) {
	    fprintf(fp, " %d", side->scores[i]);
	}
	fprintf(fp, ", played by %s:\n\n",
		long_player_title(spbuf, side->player, NULL));
    }
}

/* Display what is essentially a double-column bookkeeping of unit gains
   and losses. */

void
write_unit_record(fp, side)
FILE *fp;
Side *side;
{
    int u, gainreason, lossreason, totgain, totloss, val;

    fprintf(fp, "Unit Record (gains and losses by cause and unit type)\n");
    fprintf(fp, " Unit Type ");
    for (gainreason = 0; gainreason < num_gain_reasons; ++gainreason) {
	fprintf(fp, " %3s", gain_reason_names[gainreason]);
    }
    fprintf(fp, " Gain |");
    for (lossreason = 0; lossreason < num_loss_reasons; ++lossreason) {
	fprintf(fp, " %3s", loss_reason_names[lossreason]);
    }
    fprintf(fp, " Loss |");
    fprintf(fp, " Total\n");
    for_all_unit_types(u) {
	if (u_possible[u]) {
	    totgain = 0;
	    fprintf(fp, " %9s ", utype_name_n(u, 9));
	    for (gainreason = 0; gainreason < num_gain_reasons; ++gainreason) {
		val = gain_count(side, u, gainreason);
		if (val > 0) {
		    fprintf(fp, " %3d", val);
		    totgain += val;
		} else {
		    fprintf(fp, "    ");
		}
	    }
	    fprintf(fp, "  %3d |", totgain);
	    totloss = 0;
	    for (lossreason = 0; lossreason < num_loss_reasons; ++lossreason) {
		val = loss_count(side, u, lossreason);
		if (val > 0) {
		    fprintf(fp, " %3d", val);
		    totloss += val;
		} else {
		    fprintf(fp, "    ");
		}
	    }
	    fprintf(fp, "  %3d |", totloss);
	    fprintf(fp, "  %3d\n", totgain - totloss);
	}
    }
    fprintf(fp, "\n");
}

static int
gain_count(side, u, r)
Side *side;
int u, r;
{
    int sum;

    if (side != NULL)
      return side_gain_count(side, u, r);
    sum = 0;
    for_all_sides(side) {
	sum += side_gain_count(side, u, r);
    }
    return sum;
}

static int
loss_count(side, u, r)
Side *side;
int u, r;
{
    int sum;

    if (side != NULL)
      return side_loss_count(side, u, r);
    sum = 0;
    for_all_sides(side) {
	sum += side_loss_count(side, u, r);
    }
    return sum;
}

/* Nearly-raw combat statistics; hard to interpret, but they provide
   a useful check against subjective evaluation of performance. */

void
write_combat_results(fp, side)
FILE *fp;
Side *side;
{
    int a, d, atk;

    fprintf(fp,
	    "Unit Combat Results (average damage over # attacks against enemy, by type)\n");
    fprintf(fp, " A  D->");
    for_all_unit_types(d) {
	if (u_possible[d]) {
	    fprintf(fp, " %4s ", utype_name_n(d, 4));
	}
    }
    fprintf(fp, "\n");
    for_all_unit_types(a) {
	if (u_possible[a]) {
	    fprintf(fp, " %4s ", utype_name_n(a, 4));
	    for_all_unit_types(d) {
		if (u_possible[d]) {
		    atk = atkstats(side, a, d);
		    if (atk > 0) {
			fprintf(fp, " %5.2f",
				((float) hitstats(side, a, d)) / atk);
		    } else {
			fprintf(fp, "      ");
		    }
		}
	    }
	    fprintf(fp, "\n     ");
	    for_all_unit_types(d) {
		if (u_possible[d]) {
		    atk = atkstats(side, a, d);
		    if (atk > 0) {
			fprintf(fp, " %4d ", atk);
		    } else {
			fprintf(fp, "      ");
		    }
		}
	    }
	    fprintf(fp, "\n");
	}
    }
    fprintf(fp, "\n");
}

static int
atkstats(side, a, d)
Side *side;
int a, d;
{
    int sum;

    if (side != NULL)
      return side_atkstats(side, a, d);
    sum = 0;
    for_all_sides(side) {
	sum += side_atkstats(side, a, d);
    }
    return sum;
}

static int
hitstats(side, a, d)
Side *side;
int a, d;
{
    int sum;

    if (side != NULL)
      return side_hitstats(side, a, d);
    sum = 0;
    for_all_sides(side) {
	sum += side_hitstats(side, a, d);
    }
    return sum;
}

void
dice_desc(buf, dice)
char *buf;
int dice;
{
    int numdice, die, offset;

    if (dice >> 14 == 0 || dice >> 14 == 3) {
	sprintf(buf, "%d", dice);
    } else {
    	numdice = (dice >> 11) & 0x07;
    	die = (dice >> 7) & 0x0f;
    	offset = dice & 0x7f;
    	if (offset == 0) {
	    sprintf(buf, "%dd%d", numdice, die);
    	} else {
	    sprintf(buf, "%dd%d+%d", numdice, die, offset);
    	}
    }
}

/* The following code formats a list of types that are missing images. */

void
record_missing_image(typtyp, str)
int typtyp;
char *str;
{
    if (missinglist == NULL) {
	missinglist = xmalloc(BUFSIZE);
	missinglist[0] = '\0';
    }
    ++missing[typtyp];
    /* Add the name of the image-less type, but only if one of
       the first few. */
    if (between(1, totlisted, NUMTOLIST))
      strcat(missinglist, ",");
    if (totlisted < NUMTOLIST) {
	strcat(missinglist, str);
    } else if (totlisted == NUMTOLIST) {
	strcat(missinglist, "...");
    }
    ++totlisted;
}

/* Return true if any images could not be found, and provide some helpful info
   into the supplied buffer. */

int
missing_images(buf)
char *buf;
{
    if (missinglist == NULL)
      return FALSE;
    buf[0] = '\0';
    if (missing[UTYP] > 0)
      tprintf(buf, " %d unit images", missing[UTYP]);
    if (missing[TTYP] > 0)
      tprintf(buf, " %d terrain images", missing[TTYP]);
    if (missing[3] > 0)
      tprintf(buf, " %d emblems", missing[3]);
    tprintf(buf, " - %s", missinglist);
    return TRUE;
}

