/* テスト */
/*
 * Debug Status: Started checking from here (the beginning).
 * - Fixed __T on this page.
 */
/*
 * File: util.c
 * Purpose: Macro code, gamma correction, some high-level UI functions, inkey()
 *
 * Copyright (c) 1997 Ben Harrison, James E. Wilson, Robert A. Koeneke
 *
 * This work is free software; you can redistribute it and/or modify it
 * under the terms of either:
 *
 * a) the GNU General Public License as published by the Free Software
 *    Foundation, version 2, or
 *
 * b) the "Angband licence":
 *    This software may be copied and distributed for educational, research,
 *    and not for profit purposes provided that this copyright and statement
 *    are included in all such copies.  Other copyrights may also apply.
 */
#include "angband.h"
#include "game-event.h"
#include "randname.h"

/*
 * Convert a decimal to a single digit hex number
 */
static _TCHAR hexify(int i)
{
	return (hexsym[i % 16]);
}

/*
 * Convert a hexidecimal-digit into a decimal
 */
static int dehex(_TCHAR c)
{
	if (isdigit((_TCHAR)c))
		return (D2I(c));
	if (isalpha((_TCHAR)c))
		return (A2I(_totlower((_TCHAR)c)) + 10);
	return (0);
}

/*
 * Transform macro trigger name ('\[alt-D]' etc..)
 * into macro trigger key code ('^_O_64\r' or etc..)
 */
static size_t trigger_text_to_ascii(_TCHAR *buf, size_t max, const _TCHAR **strptr)
{
	const _TCHAR *str = *strptr;
	bool mod_status[MAX_MACRO_MOD];

	int i, len = 0;
	int shiftstatus = 0;
	const _TCHAR *key_code;

	size_t current_len = _tcslen(buf);

	/* No definition of trigger names */
	if (macro_template == NULL) return 0;

	/* Initialize modifier key status */
	for (i = 0; macro_modifier_chr[i]; i++)
		mod_status[i] = FALSE;

	str++;

	/* Examine modifier keys */
	while (1)
	{
		/* Look for modifier key name */
		for (i = 0; macro_modifier_chr[i]; i++)
		{
			len = _tcslen(macro_modifier_name[i]);

			if (!my_strnicmp(str, macro_modifier_name[i], len))
				break;
		}

		/* None found? */
		if (!macro_modifier_chr[i]) break;

		/* Proceed */
		str += len;

		/* This modifier key is pressed */
		mod_status[i] = TRUE;

		/* Shift key might be going to change keycode */
		if (macro_modifier_chr[i] == 'S')
			shiftstatus = 1;
	}

	/* Look for trigger name */
	for (i = 0; i < max_macrotrigger; i++)
	{
		len = _tcslen(macro_trigger_name[i]);

		/* Found it and it is ending with ']' */
		if (!my_strnicmp(str, macro_trigger_name[i], len) && (']' == str[len]))
			break;
	}

	/* Invalid trigger name? */
	if (i == max_macrotrigger)
	{
		/*
		 * If this invalid trigger name is ending with ']',
		 * skip whole of it to avoid defining strange macro trigger
		 */
		str = _tcschr(str, ']');

		if (str)
		{
			strnfcat(buf, max, &current_len, __T("\x1F\r"));

			*strptr = str; /* where **strptr == ']' */
		}

		return current_len;
	}

	/* Get keycode for this trigger name */
	key_code = macro_trigger_keycode[shiftstatus][i];

	/* Proceed */
	str += len;

	/* Begin with '^_' */
	strnfcat(buf, max, &current_len, __T("\x1F"));

	/* Write key code style trigger using template */
	for (i = 0; macro_template[i]; i++)
	{
		_TCHAR ch = macro_template[i];

		switch (ch)
		{
			/* Modifier key character */
			case '&':
			{
				size_t j;
				for (j = 0; macro_modifier_chr[j]; j++)
				{
					if (mod_status[j])
						strnfcat(buf, max, &current_len, __T("%c"), macro_modifier_chr[j]);
				}
				break;
			}

			/* Key code */
			case '#':
			{
				strnfcat(buf, max, &current_len, __T("%s"), key_code);
				break;
			}

			/* Fixed string */
			default:
			{
				strnfcat(buf, max, &current_len, __T("%c"), ch);
				break;
			}
		}
	}

	/* End with '\r' */
	strnfcat(buf, max, &current_len, __T("\r"));

	/* Succeed */
	*strptr = str; /* where **strptr == ']' */

	return current_len;
}

/*
 * Hack -- convert a printable string into real ascii
 *
 * This function will not work on non-ascii systems.
 *
 * To be safe, "buf" should be at least as large as "str".
 */
void text_to_ascii(_TCHAR *buf, size_t len, const _TCHAR *str)
{
	_TCHAR *s = buf;

	/* Analyze the "ascii" string */
	while (*str)
	{
		/* Check if the buffer is long enough */
		if (s >= buf + len - 1) break;

		/* Backslash codes */
		if (*str == '\\')
		{
			str++;
			if (*str == 0) break;

			switch (*str)
			{
				/* Macro trigger */
				case '[':
				{
					/* Terminate before appending the trigger */
					*s = 0;
					s += trigger_text_to_ascii(buf, len, &str);
					break;
				}

				/* Hex-mode */
				case 'x':
				{
					if (isxdigit((_TCHAR)(*(str + 1))) &&
					    isxdigit((_TCHAR)(*(str + 2))))
					{
						*s = 16 * dehex(*++str);
						*s++ += dehex(*++str);
					}
					else
					{
						/* HACK - Invalid hex number */
						*s++ = '?';
					}
					break;
				}

				case 'e':
					*s++ = ESCAPE;
					break;
				case 's':
					*s++ = ' ';
					break;
				case 'b':
					*s++ = '\b';
					break;
				case 'n':
					*s++ = '\n';
					break;
				case 'r':
					*s++ = '\r';
					break;
				case 't':
					*s++ = '\t';
					break;
				case 'a':
					*s++ = '\a';
					break;
				case '\\':
					*s++ = '\\';
					break;
				case '^':
					*s++ = '^';
					break;

				default:
					*s = *str;
					break;
			}

			/* Skip the final char */
			str++;
		}

		/* Normal Control codes */
		else if (*str == '^')
		{
			str++;
			if (*str == 0) break;

			*s++ = KTRL(*str);
			str++;
		}

		/* Normal chars */
		else
		{
			*s++ = *str++;
		}
	}

	/* Terminate */
	*s = 0;
}

/*
 * Transform macro trigger key code ('^_O_64\r' or etc..)
 * into macro trigger name ('\[alt-D]' etc..)
 */
static size_t trigger_ascii_to_text(_TCHAR *buf, size_t max, const _TCHAR **strptr)
{
	const _TCHAR *str = *strptr;
	_TCHAR key_code[100];
	int i;
	const _TCHAR *tmp;
	size_t current_len = _tcslen(buf);

	/* No definition of trigger names */
	if (macro_template == NULL) return 0;

	/* Trigger name will be written as '\[name]' */
	strnfcat(buf, max, &current_len, __T("\\["));

	/* Use template to read key-code style trigger */
	for (i = 0; macro_template[i]; i++)
	{
		_TCHAR ch = macro_template[i];

		switch (ch)
		{
			/* Read modifier */
			case '&':
			{
				size_t j;
				while ((tmp = _tcschr(macro_modifier_chr, *str)) != 0)
				{
					j = tmp - macro_modifier_chr;
					strnfcat(buf, max, &current_len, __T("%s"), macro_modifier_name[j]);
					str++;
				}
				break;
			}

			/* Read key code */
			case '#':
			{
				size_t j;
				for (j = 0; *str && (*str != '\r') && (j < _countof(key_code) - 1); j++)
					key_code[j] = *str++;
				key_code[j] = 0;
				break;
			}

			/* Skip fixed strings */
			default:
			{
				if (ch != *str) return 0;
				str++;
			}
		}
	}

	/* Key code style triggers always end with '\r' */
	if (*str++ != '\r') return 0;

	/* Look for trigger name with given keycode (normal or shifted keycode) */
	for (i = 0; i < max_macrotrigger; i++)
	{
		if (!my_stricmp(key_code, macro_trigger_keycode[0][i]) ||
		    !my_stricmp(key_code, macro_trigger_keycode[1][i]))
			break;
	}

	/* Not found? */
	if (i == max_macrotrigger) return 0;

	/* Write trigger name + "]" */
	strnfcat(buf, max, &current_len, __T("%s]"), macro_trigger_name[i]);

	/* Succeed */
	*strptr = str;
	return current_len;
}

/*
 * Hack -- convert a string into a printable form
 *
 * This function will not work on non-ascii systems.
 *
 * TODO This function probably needs checking.
 */
void ascii_to_text(_TCHAR *buf, size_t len, const _TCHAR *str)
{
	_TCHAR *s = buf;

	/* Analyze the "ascii" string */
	while (*str)
	{
		int i = *str++;

		/* Check if the buffer is long enough */
		/* HACK - always assume worst case (hex-value + 0) */
		if (s >= buf + len - 5) 
			break;

		if (i == ESCAPE)
		{
			*s++ = '\\';
			*s++ = 'e';
		}
		else if (i == ' ')
		{
			*s++ = '\\';
			*s++ = 's';
		}
		else if (i == '\b')
		{
			*s++ = '\\';
			*s++ = 'b';
		}
		else if (i == '\t')
		{
			*s++ = '\\';
			*s++ = 't';
		}
		else if (i == '\a')
		{
			*s++ = '\\';
			*s++ = 'a';
		}
		else if (i == '\n')
		{
			*s++ = '\\';
			*s++ = 'n';
		}
		else if (i == '\r')
		{
			*s++ = '\\';
			*s++ = 'r';
		}
		else if (i == '\\')
		{
			*s++ = '\\';
			*s++ = '\\';
		}
		else if (i == '^')
		{
			*s++ = '\\';
			*s++ = '^';
		}
		/* Macro Trigger */
		else if (i == 31)
		{
			size_t offset;

			/* Terminate before appending the trigger */
			*s = 0;

			offset = trigger_ascii_to_text(buf, len, &str);

			if (offset == 0)
			{
				/* No trigger found */
				*s++ = '^';
				*s++ = '_';
			}
			else
				s += offset;
		}
		else if (i < 32)
		{
			*s++ = '^';
			*s++ = UN_KTRL(i);
		}
		else if ((i < 0x7F) || (i > 0xA0)) /* TODO Check this doesn't break anything (I think it shouldn't) */
		{
			*s++ = i;
		}
		else
		{
			*s++ = '\\';
			*s++ = 'x';
			*s++ = hexify((int)i / 16);
			*s++ = hexify((int)i % 16);
		}
	}
	/* Terminate */
	*s = 0;
}

static _TCHAR *roman_order[] = {__T(""), __T("a"), __T("aa"), __T("aaa"), __T("ab"), __T("b"), __T("ba"), __T("baa"), __T("baaa"), __T("ac")};

/*
 * Fill in buf (up to size len) with the roman digits corresponding to the
 * input digit, based on the radix string specified. This will be the
 * characters of the current decimal position, e.g. IVX for digits, XLC for
 * tens, CDM for hundreds.
 *
 * Returns number of chars put in the string.
 */
static size_t roman_digits(_TCHAR *buf, size_t len, int digit, _TCHAR *radix)
{
	_TCHAR *template = roman_order[digit];
	size_t radix_len = _tcsclen(radix);
	size_t added = 0;

	while (*template && len) {
		size_t offset = *template - 'a';
		if (offset < radix_len)
		{
			*buf = radix[offset];
		}
		else
		{
			*buf = '?';
		}
		++buf;
		++template;
		++added;
		--len;
	}
	return added;
}

/*
 * Convert a number into a string in roman numerals
 * Ignores any value over a thousand, on the grounds that it's hard to
 * print characters with bars on top in ASCII...
 */
void romanify(int in, _TCHAR *buf, size_t len)
{
	int hundreds = (in / 100) % 10;
	int tens = (in / 10) % 10;
	int ones = in % 10;
	size_t ret;

	if (hundreds)
	{
		ret = roman_digits(buf, len, hundreds, __T("CDM"));
		buf += ret;
		len -= ret;
	}
	if (tens)
	{
		ret = roman_digits(buf, len, tens, __T("XLC"));
		buf += ret;
		len -= ret;
	}
	if (ones)
	{
		ret = roman_digits(buf, len, ones, __T("IVX"));
		buf += ret;
		len -= ret;
	}
	if (len)
	{
		*buf = 0;
	}
}

/*
 * Find the start of a possible Roman numerals suffix by going back from the
 * end of the string to a space, then checking that all the remaining chars
 * are valid Roman numerals.
 *
 * Return the start position, or NULL if there isn't a valid suffix.
 */
_TCHAR *find_roman_suffix_start(const _TCHAR *buf)
{
	const _TCHAR *start = _tcsrchr(buf, ' ');
	const _TCHAR *p;

	if (start)
	{
		start++;
		p = start;
		while (*p)
		{
			if (*p != 'I' && *p != 'V' && *p != 'X' && *p != 'L' &&
			    *p != 'C' && *p != 'D' && *p != 'M')
			{
				start = NULL;
				break;
			}
			++p;
		}
	}
	return (_TCHAR *)start;
}

/*
 * The "macro" package
 *
 * Functions are provided to manipulate a collection of macros, each
 * of which has a trigger pattern string and a resulting action string
 * and a small set of flags.
 */

/*
 * Determine if any macros have ever started with a given character.
 *
 * TODO Determine if more characters should be supported.
 */
static bool macro__use[256];

/*
 * Find the macro (if any) which exactly matches the given pattern
 */
int macro_find_exact(const _TCHAR *pat)
{
	int i;

	/* Nothing possible */
	if (!macro__use[pat[0]])
		return -1;

	/* Scan the macros */
	for (i = 0; i < macro__num; ++i)
	{
		if (streq(macro__pat[i], pat))
			return i;
	}

	/* No matches */
	return -1;
}

/*
 * Find the first macro (if any) which contains the given pattern
 */
static int macro_find_check(const _TCHAR *pat)
{
	int i;

	/* Nothing possible */
	if (!macro__use[pat[0]])
		return -1;

	/* Scan the macros */
	for (i = 0; i < macro__num; ++i)
	{
		if (prefix(macro__pat[i], pat))
			return i;
	}

	/* Nothing */
	return -1;
}

/*
 * Find the first macro (if any) which contains the given pattern and more
 */
static int macro_find_maybe(const _TCHAR *pat)
{
	int i;

	/* Nothing possible */
	if (!macro__use[pat[0]])
		return -1;

	/* Scan the macros */
	for (i = 0; i < macro__num; ++i)
	{
		if (prefix(macro__pat[i], pat) && !streq(macro__pat[i], pat))
			return i;
	}

	/* Nothing */
	return -1;
}

/*
 * Find the longest macro (if any) which starts with the given pattern
 */
static int macro_find_ready(const _TCHAR *pat)
{
	int i, t, n = -1, s = -1;

	/* Nothing possible */
	if (!macro__use[pat[0]])
		return -1;

	/* Scan the macros */
	for (i = 0; i < macro__num; ++i)
	{
		/* Skip macros which are not contained by the pattern */
		if (!prefix(pat, macro__pat[i]))
			continue;

		/* Obtain the length of this macro */
		t = _tcslen(macro__pat[i]);

		/* Only track the "longest" pattern */
		if ((n >= 0) && (s > t))
			continue;

		/* Track the entry */
		n = i;
		s = t;
	}

	/* Result */
	return n;
}

/*
 * Add a macro definition (or redefinition).
 *
 * We should use "act == NULL" to "remove" a macro, but this might make it
 * impossible to save the "removal" of a macro definition.  XXX XXX XXX
 *
 * We should consider refusing to allow macros which contain existing macros,
 * or which are contained in existing macros, because this would simplify the
 * macro analysis code.  XXX XXX XXX
 *
 * We should consider removing the "command macro" crap, and replacing it
 * with some kind of "powerful keymap" ability, but this might make it hard
 * to change the "roguelike" option from inside the game.  XXX XXX XXX
 */
errr macro_add(const _TCHAR *pat, const _TCHAR *act)
{
	int n;

	if (!pat || !act) return (-1);

	/* Look for any existing macro */
	n = macro_find_exact(pat);

	/* Replace existing macro */
	if (n >= 0)
	{
		string_free((void *)macro__act[n]);
	}
	else /* Create a new macro */
	{
		/* Get a new index */
		n = macro__num++;
		if (macro__num >= MACRO_MAX) quit(__T("Too many macros!"));

		/* Save the pattern */
		macro__pat[n] = _tcsdup(pat);
	}
	/* Save the action */
	macro__act[n] = _tcsdup(act);

	/* Efficiency */
	macro__use[pat[0]] = TRUE;

	/* Success */
	return (0);
}

/*
 * Initialize the "macro" package
 */
errr macro_init(void)
{
	/* Macro patterns */
	macro__pat = C_ZNEW(MACRO_MAX, _TCHAR *);

	/* Macro actions */
	macro__act = C_ZNEW(MACRO_MAX, _TCHAR *);

	/* Success */
	return (0);
}

/*
 * Free the macro package
 */
errr macro_free(void)
{
	int i;
	size_t j;

	/* Free the macros */
	for (i = 0; i < macro__num; ++i)
	{
		string_free(macro__pat[i]);
		string_free(macro__act[i]);
	}

	FREE(macro__pat);
	FREE(macro__act);

	/* Free the keymaps */
	for (i = 0; i < KEYMAP_MODES; ++i)
	{
		for (j = 0; j < N_ELEMENTS(keymap_act[i]); ++j)
		{
			string_free(keymap_act[i][j]);
			keymap_act[i][j] = NULL;
		}
	}
	/* Success */
	return (0);
}

/*
 * Free the macro trigger package
 */
errr macro_trigger_free(void)
{
	int i;
	int num;

	if (macro_template != NULL)
	{
		/* Free the template */
		string_free(macro_template);
		macro_template = NULL;

		/* Free the trigger names and keycodes */
		for (i = 0; i < max_macrotrigger; i++)
		{
			string_free(macro_trigger_name[i]);

			string_free(macro_trigger_keycode[0][i]);
			string_free(macro_trigger_keycode[1][i]);
		}
		/* No more macro triggers */
		max_macrotrigger = 0;

		/* Count modifier-characters */
		num = _tcslen(macro_modifier_chr);

		/* Free modifier names */
		for (i = 0; i < num; i++)
			string_free(macro_modifier_name[i]);

		/* Free modifier chars */
		string_free(macro_modifier_chr);
	}
	/* Success */
	return (0);
}

/*
 * Flush all pending input.
 *
 * Actually, remember the flush, using the "inkey_xtra" flag, and in the
 * next call to "inkey()", perform the actual flushing, for efficiency,
 * and correctness of the "inkey()" function.
 */
void flush(void)
{
	/* Do it later */
	inkey_xtra = TRUE;
}

/*
 * Flush all pending input if the OPT(flush_failure) option is set.
 */
void flush_fail(void)
{
	if (OPT(flush_failure)) flush();
}

/*
 * Local variable -- we are inside a "macro action"
 *
 * Do not match any macros until "ascii 30" is found.
 */
static bool parse_macro = FALSE;

/*
 * Local variable -- we are inside a "macro trigger"
 *
 * Strip all keypresses until a low ascii value is found.
 */
static bool parse_under = FALSE;

/*
 * Helper function called only from "inkey()"
 *
 * This function does almost all of the "macro" processing.
 *
 * We use the "Term_key_push()" function to handle "failed" macros, as well
 * as "extra" keys read in while choosing the proper macro, and also to hold
 * the action for the macro, plus a special "ascii 30" character indicating
 * that any macro action in progress is complete.  Embedded macros are thus
 * illegal, unless a macro action includes an explicit "ascii 30" character,
 * which would probably be a massive hack, and might break things.
 *
 * Only 500 (0+1+2+...+29+30) milliseconds may elapse between each key in
 * the macro trigger sequence.  If a key sequence forms the "prefix" of a
 * macro trigger, 500 milliseconds must pass before the key sequence is
 * known not to be that macro trigger.  XXX XXX XXX
 */
static ui_event_data inkey_aux(int scan_cutoff)
{
	int k = 0, n, p = 0, w = 0;

	ui_event_data ke, ke0;
	_TCHAR ch;

	const _TCHAR *pat;
	_TCHAR *act;

	_TCHAR buf[1024];

	/* Initialize the no return */
	ke0.type = EVT_NONE;
	ke0.key = 0;
	ke0.index = 0; /* To fix GCC warnings on X11 */
	ke0.mousey = 0;
	ke0.mousex = 0;

	/* Debug : Initialize to 0 as it doesn't seem to do any harm */
	ke.type = EVT_NONE;
	ke.key = 0;
	ke.index = 0;
	ke.mousey = 0;
	ke.mousex = 0;

	/* Wait for a keypress */
	if (scan_cutoff == SCAN_OFF)
	{
		(void)(Term_inkey(&ke, TRUE, TRUE));
		ch = ke.key;
	}
	else
	{
		w = 0;

		/* Wait only as long as macro activation would wait*/
		while (Term_inkey(&ke, FALSE, TRUE) != 0)
		{
			/* Increase "wait" */
			w++;

			/* Excessive delay */
			if (w >= scan_cutoff)
			{
				ke0.type = EVT_KBRD;
				return ke0;
			}
			/* Delay */
			Term_xtra(TERM_XTRA_DELAY, 10);
		}
		ch = ke.key;
	}
	/* End "macro action" */
	if ((ch == 30) || (ch == MOUSEY)) 
	{
		parse_macro = FALSE;
		return (ke);
	}
	/* Inside "macro action" */
	if (parse_macro)
		return (ke);

	/* Inside "macro trigger" */
	if (parse_under)
		return (ke);

	/* Save the first key, advance */
	buf[p++] = ch;
	buf[p] = 0;

	/* Check for possible macro */
	k = macro_find_check(buf);

	/* No macro pending */
	if (k < 0) 
		return (ke);

	/* Wait for a macro, or a timeout */
	while (TRUE)
	{
		/* Check for pending macro */
		k = macro_find_maybe(buf);

		/* No macro pending */
		if (k < 0) 
			break;

		/* Check for (and remove) a pending key */
		if (0 == Term_inkey(&ke, FALSE, TRUE))
		{
			/* Append the key */
			buf[p++] = ke.key;
			buf[p] = 0; /* 0 */

			/* Restart wait */
			w = 0;
		}
		else /* No key ready */
		{
			/* Increase "wait" */
			w ++;

			/* Excessive delay */
			if (w >= SCAN_MACRO) break;

			/* Delay */
			Term_xtra(TERM_XTRA_DELAY, 10);
		}
	}
	/* Check for available macro */
	k = macro_find_ready(buf);

	/* No macro available */
	if (k < 0)
	{
		/* Push all the "keys" back on the queue */
		/* The most recent event may not be a keypress. */
		if(p)
		{
			if(Term_event_push(&ke)) return (ke0);
			p--;
		}
		while (p > 0)
		{
			/* Push the key, notice over-flow */
			if (Term_key_push(buf[--p])) return (ke0);
		}

		/* Wait for (and remove) a pending key */
		(void)Term_inkey(&ke, TRUE, TRUE);

		/* Return the key */
		return (ke);
	}
	/* Get the pattern */
	pat = macro__pat[k];

	/* Get the length of the pattern */
	n = _tcslen(pat);

	/* Push the "extra" keys back on the queue */
	while (p > n)
	{
		/* Push the key, notice over-flow */
		if (Term_key_push(buf[--p])) return (ke0);
	}
	/* Begin "macro action" */
	parse_macro = TRUE;

	/* Push the "end of macro action" key */
	if (Term_key_push(30))
		return (ke0);

	/* Access the macro action */
	act = macro__act[k];

	/* Get the length of the action */
	n = _tcslen(act);

	/* Push the macro "action" onto the key queue */
	while (n > 0)
	{
		/* Push the key, notice over-flow */
		if (Term_key_push(act[--n]))
			return (ke0);
	}

	/* Hack -- Force "inkey()" to call us again */
	return (ke0);
}

/*
 * Mega-Hack -- special "inkey_next" pointer.  XXX XXX XXX
 *
 * This special pointer allows a sequence of keys to be "inserted" into
 * the stream of keys returned by "inkey()".  This key sequence will not
 * trigger any macros, and cannot be bypassed by the Borg.  It is used
 * in Angband to handle "keymaps".
 */
static const _TCHAR *inkey_next = NULL;

#ifdef ALLOW_BORG

/*
 * Mega-Hack -- special "inkey_hack" hook.  XXX XXX XXX
 *
 * This special function hook allows the "Borg" (see elsewhere) to take
 * control of the "inkey()" function, and substitute in fake keypresses.
 */
char (*inkey_hack)(int flush_first) = NULL;

#endif /* ALLOW_BORG */

/*
 * Get a keypress from the user.
 *
 * This function recognizes a few "global parameters".  These are variables
 * which, if set to TRUE before calling this function, will have an effect
 * on this function, and which are always reset to FALSE by this function
 * before this function returns.  Thus they function just like normal
 * parameters, except that most calls to this function can ignore them.
 *
 * If "inkey_xtra" is TRUE, then all pending keypresses will be flushed,
 * and any macro processing in progress will be aborted.  This flag is
 * set by the "flush()" function, which does not actually flush anything
 * itself, but rather, triggers delayed input flushing via "inkey_xtra".
 *
 * If "inkey_scan" is TRUE, then we will immediately return "zero" if no
 * keypress is available, instead of waiting for a keypress.
 *
 * If "inkey_base" is TRUE, then all macro processing will be bypassed.
 * If "inkey_base" and "inkey_scan" are both TRUE, then this function will
 * not return immediately, but will wait for a keypress for as long as the
 * normal macro matching code would, allowing the direct entry of macro
 * triggers.  The "inkey_base" flag is extremely dangerous!
 *
 * If "inkey_flag" is TRUE, then we will assume that we are waiting for a
 * normal command, and we will only show the cursor if "OPT(hilite_player)" is
 * TRUE (or if the player is in a store), instead of always showing the
 * cursor.  The various "main-xxx.c" files should avoid saving the game
 * in response to a "menu item" request unless "inkey_flag" is TRUE, to
 * prevent savefile corruption.
 *
 * If we are waiting for a keypress, and no keypress is ready, then we will
 * refresh (once) the window which was active when this function was called.
 *
 * Note that "back-quote" is automatically converted into "escape" for
 * convenience on machines with no "escape" key.  This is done after the
 * macro matching, so the user can still make a macro for "backquote".
 *
 * Note the special handling of "ascii 30" (ctrl-caret, aka ctrl-shift-six)
 * and "ascii 31" (ctrl-underscore, aka ctrl-shift-minus), which are used to
 * provide support for simple keyboard "macros".  These keys are so strange
 * that their loss as normal keys will probably be noticed by nobody.  The
 * "ascii 30" key is used to indicate the "end" of a macro action, which
 * allows recursive macros to be avoided.  The "ascii 31" key is used by
 * some of the "main-xxx.c" files to introduce macro trigger sequences.
 *
 * Hack -- we use "ascii 29" (ctrl-right-bracket) as a special "magic" key,
 * which can be used to give a variety of "sub-commands" which can be used
 * any time.  These sub-commands could include commands to take a picture of
 * the current screen, to start/stop recording a macro action, etc.
 *
 * If "angband_term[0]" is not active, we will make it active during this
 * function, so that the various "main-xxx.c" files can assume that input
 * is only requested (via "Term_inkey()") when "angband_term[0]" is active.
 *
 * Mega-Hack -- This function is used as the entry point for clearing the
 * "signal_count" variable, and of the "character_saved" variable.
 *
 * Hack -- Note the use of "inkey_next" to allow "keymaps" to be processed.
 *
 * Mega-Hack -- Note the use of "inkey_hack" to allow the "Borg" to steal
 * control of the keyboard from the user.
 */
ui_event_data inkey_ex(void)
{
	bool cursor_state;
	ui_event_data kk;
	ui_event_data ke;

	bool done = FALSE;

	term *old = Term;

	/* Initialise keypress */
	ke.key = 0;
	ke.type = EVT_NONE;

	/* Hack -- Use the "inkey_next" pointer */
	if (inkey_next && *inkey_next && !inkey_xtra)
	{
		/* Get next character, and advance */
		ke.key = *inkey_next++;
		ke.type = EVT_KBRD;

		/* Cancel the various "global parameters" */
		inkey_base = inkey_xtra = inkey_flag = FALSE;
		inkey_scan = 0;

		/* Accept result */
		return (ke);
	}

	/* Forget pointer */
	inkey_next = NULL;

#ifdef ALLOW_BORG
	/* Mega-Hack -- Use the special hook */
	if (inkey_hack && ((ke.key = (*inkey_hack)(inkey_xtra)) != 0))
	{
		/* Cancel the various "global parameters" */
		inkey_base = inkey_xtra = inkey_flag = FALSE;
		inkey_scan = 0;
		ke.type = EVT_KBRD;

		/* Accept result */
		return (ke);
	}
#endif /* ALLOW_BORG */

	/* Hack -- handle delayed "flush()" */
	if (inkey_xtra)
	{
		/* End "macro action" */
		parse_macro = FALSE;

		/* End "macro trigger" */
		parse_under = FALSE;

		/* Forget old keypresses */
		Term_flush();
	}
	/* Get the cursor state */
	(void)Term_get_cursor(&cursor_state);

	/* Show the cursor if waiting, except sometimes in "command" mode */
	if (!inkey_scan && (!inkey_flag || OPT(hilite_player) || character_icky))
	{
		/* Show the cursor */
		(void)Term_set_cursor(TRUE);
	}
	/* Hack -- Activate main screen */
	Term_activate(term_screen);

	/* Get a key */
	while (ke.type == EVT_NONE)
	{
		/* Hack -- Handle "inkey_scan == SCAN_INSTANT */
		if (!inkey_base && inkey_scan == SCAN_INSTANT &&
		   (0 != Term_inkey(&kk, FALSE, FALSE)))
		{
			ke.type = EVT_KBRD;
			break;
		}
		/* Hack -- Flush output once when no key ready */
		if (!done && (0 != Term_inkey(&kk, FALSE, FALSE)))
		{

			/* Hack -- activate proper term */
			Term_activate(old);

			/* Flush output */
			Term_fresh();

			/* Hack -- activate main screen */
			Term_activate(term_screen);

			/* Mega-Hack -- reset saved flag */
			character_saved = FALSE;

			/* Mega-Hack -- reset signal counter */
			signal_count = 0;

			/* Only once */
			done = TRUE;
		}
		/* Hack -- Handle "inkey_base" */
		if (inkey_base)
		{
			int w = 0;

			/* Wait forever */
			if (!inkey_scan)
			{
				/* Wait for (and remove) a pending key */
				if (0 == Term_inkey(&ke, TRUE, TRUE))
				{
					/* Done */
					ke.type = EVT_KBRD;
					break;
				}

				/* Oops */
				break;
			}
			/* Wait only as long as macro activation would wait*/
			while (TRUE)
			{
				/* Check for (and remove) a pending key */
				if (0 == Term_inkey(&ke, FALSE, TRUE))
				{
					/* Done */
					ke.type = EVT_KBRD;
					break;
				}
				else /* No key ready */
				{
					/* Increase "wait" */
					w ++;

					/* Excessive delay */
					if (w >= SCAN_MACRO) break;

					/* Delay */
					Term_xtra(TERM_XTRA_DELAY, 10);

				}
			}
			/* Done */
			ke.type = EVT_KBRD;
			break;
		}
		/* Get a key (see above) */
		ke = inkey_aux(inkey_scan);

		/* Handle mouse buttons */
		if ((ke.type == EVT_MOUSE) && (OPT(mouse_buttons)))
		{
			/* Check to see if we've hit a button */
			/* Assuming text buttons here for now - this would have to
			 * change for GUI buttons */
			_TCHAR key = button_get_key(ke.mousex, ke.mousey);
			if (key)
			{
				/* Rewrite the event */
				ke.type = EVT_BUTTON;
				ke.key = key;
				ke.index = 0;
				ke.mousey = 0;
				ke.mousex = 0;

				/* Done */
				break;
			}
		}
		/* Handle "control-right-bracket" */
		if (ke.key == 29)
		{
			/* Strip this key */
			ke.type = EVT_NONE;

			/* Continue */
			continue;
		}
		/* Treat back-quote as escape */
		if (ke.key == '`') 
			ke.key = ESCAPE;

		/* End "macro trigger" */
		if (parse_under && (ke.key >=0 && ke.key <= 32))
		{
			/* Strip this key */
			ke.type = EVT_NONE;
			ke.key = 0;

			/* End "macro trigger" */
			parse_under = FALSE;
		}
		/* Handle "control-caret" */
		if (ke.key == 30)
		{
			/* Strip this key */
			ke.type = EVT_NONE;
			ke.key = 0;
		}
		/* Handle "control-underscore" */
		else if (ke.key == 31)
		{
			/* Strip this key */
			ke.type = EVT_NONE;
			ke.key = 0;

			/* Begin "macro trigger" */
			parse_under = TRUE;
		}
		/* Inside "macro trigger" */
    	else if (parse_under)
		{
			/* Strip this key */
			ke.type = EVT_NONE;
			ke.key = 0;
		}
	}
	/* Hack -- restore the term */
	Term_activate(old);

	/* Restore the cursor */
	Term_set_cursor(cursor_state);

	/* Cancel the various "global parameters" */
	inkey_base = inkey_xtra = inkey_flag = FALSE;
	inkey_scan = 0;

	/* Return the keypress */
	return (ke);
}

/*
 * Get a keypress or mouse click from the user.
 */
_TCHAR anykey(void)
{
	ui_event_data ke = EVENT_EMPTY;

	/* Only accept a keypress or mouse click*/
	do
	{
		ke = inkey_ex();
	} while ((ke.type != EVT_MOUSE) && (ke.type != EVT_KBRD));

	return ke.key;
}

/*
 * Get a "keypress" from the user.
 */
_TCHAR inkey(void)
{
	ui_event_data ke = EVENT_EMPTY;

	/* Only accept a keypress */
	do
	{
		ke = inkey_ex();
	} while ((ke.type != EVT_ESCAPE) && (ke.type != EVT_KBRD));

	/* Paranoia */
	if (ke.type == EVT_ESCAPE) ke.key = ESCAPE;
	return ke.key;
}

/*
 * Flush the screen, make a noise
 */
void bell(const _TCHAR *reason)
{
	/* Mega-Hack -- Flush the output */
	Term_fresh();

	/* Hack -- memorize the reason if possible */
	if (character_generated && reason)
	{
		message_add(reason, MSG_BELL);

		/* Window stuff */
		p_ptr->redraw |= (PR_MESSAGE);
		redraw_stuff();
	}
	/* Make a bell noise (if allowed) */
	if (OPT(ring_bell)) Term_xtra(TERM_XTRA_NOISE, 0);

	/* Flush the input (later!) */
	flush();
}

/*
 * Hack -- Make a (relevant?) sound
 */
void sound(int val)
{
	/* No sound */
	if (!OPT(use_sound) || !sound_hook) 
		return;

	sound_hook(val);
}

/*
 * Hack -- flush
 */
static void msg_flush(int x)
{
	byte a = TERM_L_BLUE;

	/* Pause for response */
	Term_putstr(x, 0, -1, a, __T("-more-"));

	if (!OPT(auto_more))
	{
		/* Get an acceptable keypress */
		while (1)
		{
			_TCHAR ch;
			ch = inkey();
			if (OPT(quick_messages)) break;
			if ((ch == ESCAPE) || (ch == ' ')) break;
			if ((ch == '\n') || (ch == '\r')) break;
			bell(__T("Illegal response to a 'more' prompt!"));
		}
	}
	/* Clear the line */
	Term_erase(0, 0, 255);
}

static int message_column = 0;

/*
 * Output a message to the top line of the screen.
 *
 * Break long messages into multiple pieces (40-72 chars).
 *
 * Allow multiple short messages to "share" the top line.
 *
 * Prompt the user to make sure he has a chance to read them.
 *
 * These messages are memorized for later reference (see above).
 *
 * We could do a "Term_fresh()" to provide "flicker" if needed.
 *
 * The global "msg_flag" variable can be cleared to tell us to "erase" any
 * "pending" messages still on the screen, instead of using "msg_flush()".
 * This should only be done when the user is known to have read the message.
 *
 * We must be very careful about using the "msg_print()" functions without
 * explicitly calling the special "msg_print(NULL)" function, since this may
 * result in the loss of information if the screen is cleared, or if anything
 * is displayed on the top line.
 *
 * Hack -- Note that "msg_print(NULL)" will clear the top line even if no
 * messages are pending.
 */
static void msg_print_aux(u16b type, const _TCHAR *msg)
{
	int n;
	_TCHAR *t;
	_TCHAR buf[1024];
	byte color;
	int w, h;

	/* Obtain the size */
	(void)Term_get_size(&w, &h);

	/* Hack -- Reset */
	if (!msg_flag) 
		message_column = 0;

	/* TODO Need special handling for double width characters */
	/* Message Length */
	n = (msg ? _tcslen(msg) : 0);

	/* Hack -- flush when requested or needed */
	if (message_column && (!msg || ((message_column + n) > (w - 8))))
	{
		/* Flush */
		msg_flush(message_column);

		/* Forget it */
		msg_flag = FALSE;

		/* Reset */
		message_column = 0;
	}
	/* No message */
	if (!msg) 
		return;

	/* Paranoia */
	if (n > 1000) 
		return;

	/* Memorize the message (if legal) */
	if (character_generated && !(p_ptr->is_dead))
		message_add(msg, type);

	/* Window stuff */
	p_ptr->redraw |= (PR_MESSAGE);

	/* Copy it */
	_tcscpy_s(buf, _countof(buf), msg);

	/* Analyze the buffer */
	t = buf;

	/* Get the color of the message */
	color = message_type_color(type);

	/* Split message */
	while (n > (w - 8))
	{
		_TCHAR oops;

		int check, split;

		/* Default split */
		split = (w - 8);

		/* Find the "best" split point */
		for (check = (w / 2); check < (w - 8); check++)
		{
			/* Found a valid split point */
			if (t[check] == ' ') split = check;
		}

		/* Save the split character */
		oops = t[split];

		/* Split the message */
		t[split] = 0; 

		/* Display part of the message */
		Term_putstr(0, 0, split, color, t);

		/* Flush it */
		msg_flush(split + 1);

		/* Restore the split character */
		t[split] = oops;

		/* Insert a space */
		t[--split] = ' ';

		/* Prepare to recurse on the rest of "buf" */
		t += split; n -= split;
	}
	/* Display the tail of the message */
	Term_putstr(message_column, 0, n, color, t);

	/* Remember the message */
	msg_flag = TRUE;

	/* Remember the position */
	message_column += n + 1;

	/* Send refresh event */
	event_signal(EVENT_MESSAGE);
}

/*
 * Print a message in the default color (white)
 */
void msg_print(const _TCHAR *msg)
{
	msg_print_aux(MSG_GENERIC, msg);
}

/*
 * Display a formatted message, using "vstrnfmt()" and "msg_print()".
 */
void msg_format(const _TCHAR *fmt, ...)
{
	va_list vp;

	_TCHAR buf[1024];

	/* Begin the Varargs Stuff */
	va_start(vp, fmt);

	/* Format the args, save the length */
	(void)vstrnfmt(buf, _countof(buf), fmt, vp);

	/* End the Varargs Stuff */
	va_end(vp);

	/* Display */
	msg_print_aux(MSG_GENERIC, buf);
}

/*
 * Display a message and play the associated sound.
 *
 * The "extra" parameter is currently unused.
 */
void message(u16b message_type, s16b extra, const _TCHAR *message)
{
	UNREFERENCED_PARAMETER(extra);

	sound(message_type);

	msg_print_aux(message_type, message);
}

/*
 * Display a formatted message and play the associated sound.
 *
 * The "extra" parameter is currently unused.
 */
void message_format(u16b message_type, s16b extra, const _TCHAR *fmt, ...)
{
	va_list vp;

	_TCHAR buf[1024];

	/* Begin the Varargs Stuff */
	va_start(vp, fmt);

	/* Format the args, save the length */
	(void)vstrnfmt(buf, _countof(buf), fmt, vp);

	/* End the Varargs Stuff */
	va_end(vp);

	/* Display */
	message(message_type, extra, buf);
}

/*
 * Print the queued messages.
 */
void message_flush(void)
{
	/* Hack -- Reset */
	if (!msg_flag) message_column = 0;

	/* Flush when needed */
	if (message_column)
	{
		/* Print pending messages */
		msg_flush(message_column);

		/* Forget it */
		msg_flag = FALSE;

		/* Reset */
		message_column = 0;
	}
}

/*
 * Save the screen, and increase the "icky" depth.
 *
 * This function must match exactly one call to "screen_load()".
 */
void screen_save(void)
{
	/* Hack -- Flush messages */
	message_flush();

	/* Save the screen (if legal) */
	Term_save();

	/* Increase "icky" depth */
	character_icky++;
}

/*
 * Load the screen, and decrease the "icky" depth.
 *
 * This function must match exactly one call to "screen_save()".
 */
void screen_load(void)
{
	/* Hack -- Flush messages */
	message_flush();

	/* Load the screen (if legal) */
	Term_load();

	/* Decrease "icky" depth */
	character_icky--;
}

/*
 * Display a string on the screen using an attribute.
 *
 * At the given location, using the given attribute, if allowed,
 * add the given string.  Do not clear the line.
 */
void c_put_str(byte attr, const _TCHAR *str, int row, int col)
{
	/* Position cursor, Dump the attr/text */
	Term_putstr(col, row, -1, attr, str);
}

/*
 * As above, but in "white"
 */
void put_str(const _TCHAR *str, int row, int col)
{
	/* Spawn */
	Term_putstr(col, row, -1, TERM_WHITE, str);
}

/*
 * Display a string on the screen using an attribute, and clear
 * to the end of the line.
 */
void c_prt(byte attr, const _TCHAR *str, int row, int col)
{
	/* Clear line, position cursor */
	Term_erase(col, row, 255);

	/* Dump the attr/text */
	Term_addstr(-1, attr, str);
}

/*
 * As above, but in "white"
 */
void prt(const _TCHAR *str, int row, int col)
{
	/* Spawn */
	c_prt(TERM_WHITE, str, row, col);
}

/*
 * Print some (colored) text to the screen at the current cursor position,
 * automatically "wrapping" existing text (at spaces) when necessary to
 * avoid placing any text into the last column, and clearing every line
 * before placing any text in that line.  Also, allow "newline" to force
 * a "wrap" to the next line.  Advance the cursor as needed so sequential
 * calls to this function will work correctly.
 *
 * Once this function has been called, the cursor should not be moved
 * until all the related "text_out()" calls to the window are complete.
 *
 * This function will correctly handle any width up to the maximum legal
 * value of 256, though it works best for a standard 80 character width.
 */
void text_out_to_screen(byte a, const _TCHAR *str)
{
	int x, y;

	int wid, h;

	int wrap;

	const _TCHAR *s;

	/* Obtain the size */
	(void)Term_get_size(&wid, &h);

	/* Obtain the cursor */
	(void)Term_locate(&x, &y);

	/* Use special wrapping boundary? */
	if ((text_out_wrap > 0) && (text_out_wrap < wid))
		wrap = text_out_wrap;
	else
		wrap = wid;

	/* Process the string */
	for (s = str; *s; s++)
	{
		_TCHAR ch;

		/* Force wrap */
		if (*s == '\n')
		{
			/* Wrap */
			x = text_out_indent;
			y++;

			/* Clear line, move cursor */
			Term_erase(x, y, 255);

			continue;
		}
		/* Clean up the char */
		ch = (_istprint(*s) ? *s : ' ');

		/* Wrap words as needed */
		if ((x >= wrap - 1) && (ch != ' '))
		{
			int i, n = 0;

			byte av[256];
			_TCHAR cv[256];

			/* Wrap word */
			if (x < wrap)
			{
				/* Scan existing text */
				for (i = wrap - 2; i >= 0; i--)
				{
					/* Grab existing attr/char */
					Term_what(i, y, &av[i], &cv[i]);

					/* Break on space */
					if (cv[i] == ' ') break;

					/* Track current word */
					n = i;
				}
			}
			/* Special case */
			if (n == 0) n = wrap;

			/* Clear line */
			Term_erase(n, y, 255);

			/* Wrap */
			x = text_out_indent;
			y++;

			/* Clear line, move cursor */
			Term_erase(x, y, 255);

			/* Wrap the word (if any) */
			for (i = n; i < wrap - 1; i++)
			{
				/* Dump */
				Term_addch(av[i], cv[i]);

				/* Advance (no wrap) */
				if (++x > wrap) x = wrap;
			}
		}
		/* Dump */
		Term_addch(a, ch);

		/* Advance */
		if (++x > wrap) x = wrap;
	}
}

/*
 * Write text to the given file and apply line-wrapping.
 *
 * Hook function for text_out(). Make sure that text_out_file points
 * to an open text-file.
 *
 * Long lines will be wrapped at text_out_wrap, or at column 75 if that
 * is not set; or at a newline character.  Note that punctuation can
 * sometimes be placed one column beyond the wrap limit.
 *
 * You must be careful to end all file output with a newline character
 * to "flush" the stored line position.
 */
void text_out_to_file(byte a, const _TCHAR *str)
{
	/* Current position on the line */
	static int pos = 0;

	/* Wrap width */
	int wrap = (text_out_wrap ? text_out_wrap : 75);

	/* Current location within "str" */
	const _TCHAR *s = str;

	UNREFERENCED_PARAMETER(a);

	/* Process the string */
	while (*s)
	{
		_TCHAR ch;
		int n = 0;
		int len = wrap - pos;
		int l_space = -1;

		/* If we are at the start of the line... */
		if (pos == 0)
		{
			int i;

			/* Output the indent */
			for (i = 0; i < text_out_indent; i++)
			{
				file_writec(text_out_file, ' ');
				pos++;
			}
		}
		/* Find length of line up to next newline or end-of-string */
		while ((n < len) && !((s[n] == '\n') || (s[n] == 0))) 
		{
			/* Mark the most recent space in the string */
			if (s[n] == ' ') l_space = n;

			/* Increment */
			n++;
		}

		/* If we have encountered no spaces */
		if ((l_space == -1) && (n == len))
		{
			/* If we are at the start of a new line */
			if (pos == text_out_indent)
			{
				len = n;
			}
			/* HACK - Output punctuation at the end of the line */
			else if ((s[0] == ' ') || (s[0] == ',') || (s[0] == '.'))
			{
				len = 1;
			}
			else
			{
				/* Begin a new line */
				file_writec(text_out_file, '\n');

				/* Reset */
				pos = 0;

				continue;
			}
		}
		else
		{
			/* Wrap at the newline */
			if ((s[n] == '\n') || (s[n] == 0)) len = n; 

			/* Wrap at the last space */
			else len = l_space;
		}
		/* Write that line to file */
		for (n = 0; n < len; n++)
		{
			/* Ensure the character is printable */
			ch = (_istprint((_TCHAR) s[n]) ? s[n] : ' ');

			/* Write out the character */
			file_writec(text_out_file, ch);

			/* Increment */
			pos++;
		}
		/* Move 's' past the stuff we've written */
		s += len;

		/* If we are at the end of the string, end */
		if (*s == 0) return;

		/* Skip newlines */
		if (*s == '\n') s++;

		/* Begin a new line */
		file_writec(text_out_file, '\n');

		/* Reset */
		pos = 0;

		/* Skip whitespace */
		while (*s == ' ') s++;
	}
	/* We are done */
	return;
}

/*
 * Output text to the screen or to a file depending on the selected
 * text_out hook.
 */
void text_out(const _TCHAR *fmt, ...)
{
	_TCHAR buf[1024];
	va_list vp;

	/* Begin the Varargs Stuff */
	va_start(vp, fmt);

	/* Do the va_arg fmt to the buffer */
	(void)vstrnfmt(buf, _countof(buf), fmt, vp);

	/* End the Varargs Stuff */
	va_end(vp);

	/* Output now */
	text_out_hook(TERM_WHITE, buf);
}

/*
 * Output text to the screen (in color) or to a file depending on the
 * selected hook.
 */
void text_out_c(byte a, const _TCHAR *fmt, ...)
{
	_TCHAR buf[1024];
	va_list vp;

	/* Begin the Varargs Stuff */
	va_start(vp, fmt);

	/* Do the va_arg fmt to the buffer */
	(void)vstrnfmt(buf, _countof(buf), fmt, vp);

	/* End the Varargs Stuff */
	va_end(vp);

	/* Output now */
	text_out_hook(a, buf);
}

/*
 * Given a "formatted" chunk of text (i.e. one including tags like {red}{/})
 * in 'source', with starting point 'init', this finds the next section of
 * text and any tag that goes with it, return TRUE if it finds something to
 * print.
 *
 * If it returns TRUE, then it also fills 'text' with a pointer to the start
 * of the next printable section of text, and 'len' with the length of that
 * text, and 'end' with a pointer to the start of the next section.  This
 * may differ from "text + len" because of the presence of tags.  If a tag
 * applies to the section of text, it returns a pointer to the start of that
 * tag in 'tag' and the length in 'taglen'.  Otherwise, 'tag' is filled with
 * NULL.
 *
 * See text_out_e for an example of its use.
 */
static bool next_section(const _TCHAR *source, size_t init, const _TCHAR **text, size_t *len, const _TCHAR **tag, size_t *taglen, const _TCHAR **end)
{
	const _TCHAR *next;

	*tag = NULL;
	*text = source + init;
	if (*text[0] == 0) return FALSE;

	next = _tcschr(*text, '{');
	while (next)
	{
		const _TCHAR *s = next + 1;

		while (*s && isalpha(*s)) s++;

		/* Woo!  valid opening tag thing */
		if (*s == '}')
		{
			const _TCHAR *close = _tcsstr(s, __T("{/}"));

			/* There's a closing thing, so it's valid. */
			if (close)
			{
				/* If this tag is at the start of the fragment */
				if (next == *text)
				{
					*tag = *text + 1;
					*taglen = s - *text - 1;
					*text = s + 1;
					*len = close - *text;
					*end = close + 3;
					return TRUE;
				}
				/* Otherwise return the chunk up to this */
				else
				{
					*len = next - *text;
					*end = *text + *len;
					return TRUE;
				}
			}
			/* No closing thing, therefore all one lump of text. */
			else
			{
				*len = _tcslen(*text);
				*end = *text + *len;
				return TRUE;
			}
		}
		/* End of the string, that's fine. */
		else if (*s == 0)
		{
				*len = _tcslen(*text);
				*end = *text + *len;
				return TRUE;
		}
		/* An invalid tag, skip it. */
		else
		{
			next = next + 1;
		}

		next = _tcschr(next, '{');
	}
	/* Default to the rest of the string */
	*len = _tcslen(*text);
	*end = *text + *len;

	return TRUE;
}

/*
 * Output text to the screen or to a file depending on the
 * selected hook.  Takes strings with "embedded formatting",
 * such that something within {red}{/} will be printed in red.
 *
 * Note that such formatting will be treated as a "breakpoint"
 * for the printing, so if used within words may lead to part of the
 * word being moved to the next line.
 */
void text_out_e(const _TCHAR *fmt, ...)
{
	_TCHAR buf[1024];
	_TCHAR smallbuf[1024];
	va_list vp;

	const _TCHAR *start, *next, *text, *tag;
	size_t textlen, taglen = 0;

	/* Begin the Varargs Stuff */
	va_start(vp, fmt);

	/* Do the va_arg fmt to the buffer */
	vstrnfmt(buf, _countof(buf), fmt, vp);

	/* End the Varargs Stuff */
	va_end(vp);

	start = buf;
	while (next_section(start, 0, &text, &textlen, &tag, &taglen, &next))
	{
		int a = -1;

		memcpy(smallbuf, text, textlen * sizeof(_TCHAR));
		smallbuf[textlen] = 0;

		if (tag)
		{
			_TCHAR tagbuffer[11];

			/* Colour names are less than 11 characters long. */
			assert(taglen < 11);

			memcpy(tagbuffer, tag, taglen * sizeof(_TCHAR));
			tagbuffer[taglen] = 0;

			a = color_text_to_attr(tagbuffer);
		}
		if (a == -1)
			a = TERM_WHITE;

		/* Output now */
		text_out_hook(a, smallbuf);

		start = next;
	}
}

/*
 * Clear part of the screen
 */
void clear_from(int row)
{
	int y;

	/* Erase requested rows */
	for (y = row; y < Term->hgt; y++)
	{
		/* Erase part of the screen */
		Term_erase(0, y, 255);
	}
}

/*
 * The default "keypress handling function" for askfor_aux, this takes the
 * given keypress, input buffer, length, etc, and does the appropriate action
 * for each keypress, such as moving the cursor left or inserting a character.
 *
 * It should return TRUE when editing of the buffer is "complete" (e.g. on
 * the press of RETURN).
 */
bool askfor_aux_keypress(_TCHAR *buf, size_t buflen, size_t *curs, size_t *len, _TCHAR keypress, bool firsttime)
{
	switch (keypress)
	{
		case ESCAPE:
		{
			*curs = 0;
			return TRUE;
			break;
		}
		case '\n':
		case '\r':
		{
			*curs = *len;
			return TRUE;
			break;
		}
		case ARROW_LEFT:
		{
			if (firsttime) *curs = 0;
			if (*curs > 0) (*curs)--;
			break;
		}
		case ARROW_RIGHT:
		{
			if (firsttime) *curs = *len - 1;
			if (*curs < *len) (*curs)++;
			break;
		}
		case 0x7F:
		case 8: /* was '\010' */
		{
			/* If this is the first time round, backspace means "delete all" */
			if (firsttime)
			{
				buf[0] = 0;
				*curs = 0;
				*len = 0;

				break;
			}
			/* Refuse to backspace into oblivion */
			if (*curs == 0) break;

			/* Move the string from k to nul along to the left by 1 */
			memmove(&buf[*curs - 1], &buf[*curs], *len - *curs);

			/* Decrement */
			(*curs)--;
			(*len)--;

			/* Terminate */
			buf[*len] = 0;

			break;
		}
		default:
		{
			bool atnull = (buf[*curs] == 0);

			if (!_istprint((_TCHAR)keypress))
			{
				bell(__T("Illegal edit key!"));
				break;
			}
			/* Clear the buffer if this is the first time round */
			if (firsttime)
			{
				buf[0] = 0;
				*curs = 0;
				*len = 0;
				atnull = 1;
			}
			if (atnull)
			{
				/* Make sure we have enough room for a new character */
				if ((*curs + 1) >= buflen) break;
			}
			else
			{
				/* Make sure we have enough room to add a new character */
				if ((*len + 1) >= buflen) break;

				/* Move the rest of the buffer along to make room */
				memmove(&buf[*curs+1], &buf[*curs], *len - *curs);
			}
			/* Insert the character */
			buf[(*curs)++] = keypress;
			(*len)++;

			/* Terminate */
			buf[*len] = 0;

			break;
		}
	}
	/* By default, we aren't done. */
	return FALSE;
}

/*
 * Get some input at the cursor location.
 *
 * The buffer is assumed to have been initialized to a default string.
 * Note that this string is often "empty" (see below).
 *
 * The default buffer is displayed in yellow until cleared, which happens
 * on the first keypress, unless that keypress is Return.
 *
 * Normal chars clear the default and append the char.
 * Backspace clears the default or deletes the final char.
 * Return accepts the current buffer contents and returns TRUE.
 * Escape clears the buffer and the window and returns FALSE.
 *
 * Note that 'len' refers to the size of the buffer.  The maximum length
 * of the input is 'len-1'.
 *
 * 'keypress_h' is a pointer to a function to handle keypresses, altering
 * the input buffer, cursor position and suchlike as required.  See
 * 'askfor_aux_keypress' (the default handler if you supply NULL for
 * 'keypress_h') for an example.
 */
bool askfor_aux(_TCHAR *buf, size_t len, bool (*keypress_h)(_TCHAR *, size_t, size_t *, size_t *, _TCHAR, bool))
{
	int y, x;

	size_t k = 0;		/* Cursor position */
	size_t nul = 0;		/* Position of the null byte in the string */

	_TCHAR ch = 0;

	bool done = FALSE;
	bool firsttime = TRUE;

	if (keypress_h == NULL)
	{
		keypress_h = askfor_aux_keypress;
	}

	/* Locate the cursor */
	Term_locate(&x, &y);

	/* Paranoia */
	if ((x < 0) || (x >= 80)) x = 0;

	/* Restrict the length */
	if (x + len > 80) len = 80 - x;

	/* Truncate the default entry */
	buf[len-1] = 0;

	/* Get the position of the null byte */
	nul = _tcslen(buf);

	/* Display the default answer */
	Term_erase(x, y, (int)len);
	Term_putstr(x, y, -1, TERM_YELLOW, buf);

	/* Process input */
	while (!done)
	{
		/* Place cursor */
		Term_gotoxy(x + k, y);

		/* Get a key */
		ch = inkey();

		/* Let the keypress handler deal with the keypress */
		done = keypress_h(buf, len, &k, &nul, ch, firsttime);

		/* Update the entry */
		Term_erase(x, y, (int)len);
		Term_putstr(x, y, -1, TERM_WHITE, buf);

		/* Not the first time round anymore */
		firsttime = FALSE;
	}
	/* Done */
	return (ch != ESCAPE);
}

/*
 * A "keypress" handling function for askfor_aux, that handles the special
 * case of '*' for a new random "name" and passes any other "keypress"
 * through to the default "editing" handler.
 */
static bool get_name_keypress(_TCHAR *buf, size_t buflen, size_t *curs, size_t *len, _TCHAR keypress, bool firsttime)
{
	bool result;

	switch (keypress)
	{
		case '*':
		{
			*len = randname_make(RANDNAME_TOLKIEN, 4, 8, buf, buflen);
			buf[0] = _totupper(buf[0]);
			*curs = 0;
			result = FALSE;
			break;
		}
		default:
		{
			result = askfor_aux_keypress(buf, buflen, curs, len, keypress, firsttime);
			break;
		}
	}
	return result;
}

/*
 * Gets a name for the character, reacting to name changes.
 *
 * If sf is TRUE, we change the savefile name depending on the character name.
 *
 * What a horrible name for a global function.  XXX XXX XXX
 */
bool get_name(_TCHAR *buf, size_t buflen)
{
	bool res;

	/* Paranoia XXX XXX XXX */
	message_flush();

	/* Display prompt */
	prt(__T("Enter a name for your character (* for a random name): "), 0, 0);

	/* Save the player name */
	_tcscpy_s(buf, buflen, op_ptr->full_name);

	/* Ask the user for a string */
	res = askfor_aux(buf, buflen, get_name_keypress);

	/* Clear prompt */
	prt(__T(""), 0, 0);

	/* Revert to the old name if the player doesn't pick a new one. */
	if (!res)
	{
		_tcscpy_s(buf, buflen, op_ptr->full_name);
	}

	return res;
}

/*
 * Prompt for a string from the user.
 *
 * The "prompt" should take the form "Prompt: ".
 *
 * See "askfor_aux" for some notes about "buf" and "len", and about
 * the return value of this function.
 */
bool _tget_string(_TCHAR *prompt, _TCHAR *buf, size_t len)
{
	bool res;

	/* Paranoia XXX XXX XXX */
	message_flush();

	/* Display prompt */
	prt(prompt, 0, 0);

	/* Ask the user for a string */
	res = askfor_aux(buf, len, NULL);

	/* Clear prompt */
	prt(__T(""), 0, 0);

	/* Result */
	return (res);
}

bool get_string(const _TCHAR *prompt, _TCHAR *buf, size_t len)
{
	bool res;

	/* Paranoia XXX XXX XXX */
	message_flush();

	/* Display prompt */
	prt(prompt, 0, 0);

	/* Ask the user for a string */
	res = askfor_aux(buf, len, NULL);

	/* Clear prompt */
	prt(__T(""), 0, 0);

	/* Result */
	return (res);
}

/*
 * Request a "quantity" from the user
 *
 * Allow "p_ptr->command_arg" to specify a quantity
 */
s16b get_quantity(const _TCHAR *prompt, int max)
{
	int amt = 1;

	/* Use "command_arg" */
	if (p_ptr->command_arg)
	{
		/* Extract a number */
		amt = p_ptr->command_arg;

		/* Clear "command_arg" */
		p_ptr->command_arg = 0;
	}

	/* Prompt if needed */
	else if ((max != 1))
	{
		_TCHAR tmp[80];

		_TCHAR buf[80];

		/* Build a prompt if needed */
		if (!prompt)
		{
			/* Build a prompt */
			strnfmt(tmp, _countof(tmp), __T("Quantity (0-%d, *=all): "), max);

			/* Use that prompt */
			prompt = tmp;
		}
		/* Build the default */
		strnfmt(buf, _countof(buf), __T("%d"), amt);

		/* Ask for a quantity */
		if (!get_string(prompt, buf, 7))
			return (0);

		/* Extract a number */
		amt = _tstoi(buf);

		/* A star or letter means "all" */
		if ((buf[0] == '*') || isalpha(buf[0])) amt = max;
	}
	/* Enforce the maximum */
	if (amt > max) amt = max;

	/* Enforce the minimum */
	if (amt < 0) amt = 0;

	/* Return the result */
	return (amt);
}

/*
 * Verify something with the user
 *
 * The "prompt" should take the form "Query? "
 *
 * Note that "[y/n]" is appended to the prompt.
 */
bool get_check(const _TCHAR *prompt)
{
	ui_event_data ke;

	_TCHAR buf[80];

	bool repeat = FALSE;

	/* Paranoia XXX XXX XXX */
	message_flush();

	/* Hack -- Build a "useful" prompt */
	strnfmt(buf, 78, __T("%.70s[y/n] "), prompt);

	/* Hack - kill the repeat button */
	if (button_kill('n')) repeat = TRUE;

	/* Make some buttons */
	button_add(__T("[y]"), 'y');
	button_add(__T("[n]"), 'n');
	redraw_stuff();

	/* Prompt for it */
	prt(buf, 0, 0);

	/* Get an acceptable answer */
	while (TRUE)
	{
		ke = inkey_ex();
		if (OPT(quick_messages)) break;
		if (ke.key == ESCAPE) break;
		if (_tcschr(__T("YyNn"), ke.key)) break;
		bell(__T("Illegal response to a 'yes/no' question!"));
	}
	/* Kill the buttons */
	button_kill('y');
	button_kill('n');

	/* Hack - restore the repeat button */
	if (repeat) button_add(__T("[Rpt]"), 'n');
	redraw_stuff();

	/* Erase the prompt */
	prt(__T(""), 0, 0);

	/* Normal negation */
	if ((ke.key != 'Y') && (ke.key != 'y')) return (FALSE);

	/* Success */
	return (TRUE);
}

/**
 * Text-native way of getting a filename.
 */
static bool get_file_text(const _TCHAR *suggested_name, _TCHAR *path, size_t len)
{
	_TCHAR buf[160];

	/* Get filename */
	_tcscpy_s(buf, _countof(buf), suggested_name);
	if (!get_string(__T("File name: "), buf, _countof(buf))) return FALSE;

	/* Make sure it's actually a filename */
	if (buf[0] == 0 || buf[0] == ' ') return FALSE;

	/* Build the path */
	path_build(path, len, ANGBAND_DIR_USER, buf);

	/* Check if it already exists */
	if (_file_exists(buf))
	{
		_TCHAR buf2[160];
		strnfmt(buf2, _countof(buf2), __T("Replace existing file %s?"), buf);

		if (get_check(buf2) == FALSE)
			return FALSE;
	}
	return TRUE;
}

/**
 * Get a pathname to save a file to, given the suggested name.  Returns the
 * result in "path".
 */
bool (*get_file)(const _TCHAR *suggested_name, _TCHAR *path, size_t len) = get_file_text;

/*
 * Prompts for a keypress
 *
 * The "prompt" should take the form "Command: "
 *
 * Returns TRUE unless the character is "Escape"
 */
bool get_com(const _TCHAR *prompt, _TCHAR *command)
{
	ui_event_data ke;
	bool result;

	result = get_com_ex(prompt, &ke);
	*command = ke.key;

	return result;
}

bool get_com_ex(const _TCHAR *prompt, ui_event_data *command)
{
	ui_event_data ke;

	/* Paranoia XXX XXX XXX */
	message_flush();

	/* Display a prompt */
	prt(prompt, 0, 0);

	/* Get a key */
	ke = inkey_ex();

	/* Clear the prompt */
	prt(__T(""), 0, 0);

	/* Save the command */
	*command = ke;

	/* Done */
	return (ke.key != ESCAPE);
}

/*
 * Pause for user response
 *
 * This function is stupid.  XXX XXX XXX
 */
void pause_line(int row)
{
	prt(__T(""), row, 0);
	put_str(__T("[Press any key to continue]"), row, 23);
	(void)anykey();
	prt(__T(""), row, 0);
}

/*
 * Hack -- special buffer to hold the action of the current keymap
 */
static _TCHAR request_command_buffer[257];

/*
 * Request a command from the user.
 *
 * Sets p_ptr->command_cmd, p_ptr->command_dir, p_ptr->command_rep,
 * p_ptr->command_arg.  May modify p_ptr->command_new.
 *
 * Note that "caret" ("^") is treated specially, and is used to
 * allow manual input of control characters.  This can be used
 * on many machines to request repeated tunneling (Ctrl-H) and
 * on the Macintosh to request "Control-Caret".
 *
 * Note that "backslash" is treated specially, and is used to bypass any
 * keymap entry for the following character.  This is useful for macros.
 *
 * Note that this command is used both in the dungeon and in
 * stores, and must be careful to work in both situations.
 *
 * Note that "p_ptr->command_new" may not work any more.  XXX XXX XXX
 */
void request_command(void)
{
	int i;
	int mode;

	_TCHAR tmp[2] = { 0, 0 };

	ui_event_data ke = EVENT_EMPTY;

	const _TCHAR *act;

	if (OPT(rogue_like_commands))
		mode = KEYMAP_MODE_ROGUE;
	else
		mode = KEYMAP_MODE_ORIG;

	/* Reset command/argument/direction */
	p_ptr->command_cmd = 0;
	p_ptr->command_arg = 0;
	p_ptr->command_dir = 0;

	/* Get command */
	while (1)
	{
		/* Hack -- auto-commands */
		if (p_ptr->command_new)
		{
			/* Flush messages */
			message_flush();

			/* Use auto-command */
			ke.key = (_TCHAR)p_ptr->command_new;
			ke.type = EVT_KBRD;

			/* Forget it */
			p_ptr->command_new = 0;
		}
		else /* Get a keypress in "command" mode */
		{
			/* Hack -- no flush needed */
			msg_flag = FALSE;

			/* Activate "command mode" */
			inkey_flag = TRUE;

			/* Get a command */
			ke = inkey_ex();
		}

		/* Clear top line */
		prt(__T(""), 0, 0);

		/* Resize events XXX XXX */
		if (ke.type == EVT_RESIZE)
		{
			p_ptr->command_cmd_ex = ke;
			p_ptr->command_new = ' ';
		}
		/* Command Count */
		if (ke.key == '0')
		{
			int old_arg = p_ptr->command_arg;

			/* Reset */
			p_ptr->command_arg = 0;

			/* Begin the input */
			prt(__T("Count: "), 0, 0);

			/* Get a command count */
			while (1)
			{
				/* Get a new keypress */
				ke.key = inkey();

				/* Simple editing (delete or backspace) */
				if ((ke.key == 0x7F) || (ke.key == KTRL('H')))
				{
					/* Delete a digit */
					p_ptr->command_arg = p_ptr->command_arg / 10;

					/* Show current count */
					prt(format(__T("Count: %d"), p_ptr->command_arg), 0, 0);
				}
				/* Actual numeric data */
				else if (isdigit(ke.key))
				{
					/* Stop count at 9999 */
					if (p_ptr->command_arg >= 1000)
					{
						/* Warn */
						bell(__T("Invalid repeat count!"));

						/* Limit */
						p_ptr->command_arg = 9999;
					}
					/* Increase count */
					else
					{
						/* Incorporate that digit */
						p_ptr->command_arg = p_ptr->command_arg * 10 + D2I(ke.key);
					}

					/* Show current count */
					prt(format(__T("Count: %d"), p_ptr->command_arg), 0, 0);
				}
				else /* Exit on "unusable" input */
				{
					break;
				}
			}
			/* Hack -- Handle "zero" */
			if (p_ptr->command_arg == 0)
			{
				/* Default to 99 */
				p_ptr->command_arg = 99;

				/* Show current count */
				prt(format(__T("Count: %d"), p_ptr->command_arg), 0, 0);
			}
			/* Hack -- Handle "old_arg" */
			if (old_arg != 0)
			{
				/* Restore old_arg */
				p_ptr->command_arg = old_arg;

				/* Show current count */
				prt(format(__T("Count: %d"), p_ptr->command_arg), 0, 0);
			}
			/* Hack -- white-space means "enter command now" */
			if ((ke.key == ' ') || (ke.key == '\n') || (ke.key == '\r'))
			{
				/* Get a real command */
				if (!get_com(__T("Command: "), &ke.key))
				{
					/* Clear count */
					p_ptr->command_arg = 0;

					/* Continue */
					continue;
				}
			}
		}
		/* Special case for the arrow keys */
		if (isarrow(ke.key))
		{
			switch (ke.key)
			{
				case ARROW_DOWN:    ke.key = '2'; break;
				case ARROW_LEFT:    ke.key = '4'; break;
				case ARROW_RIGHT:   ke.key = '6'; break;
				case ARROW_UP:      ke.key = '8'; break;
				case ARROW_N:		ke.key = '8'; break; /* __T('↑') */ /* TODO This doesn't quite work yet */
				case ARROW_E:		ke.key = '6'; break; /* __T('→') */
				case ARROW_S:		ke.key = '2'; break; /* __T('↓') */
				case ARROW_W:		ke.key = '4'; break; /* __T('←') */
				case ARROW_NE:		ke.key = '9'; break; /* __T('↗') */
				case ARROW_SE:		ke.key = '3'; break; /* __T('↘') */
				case ARROW_SW:		ke.key = '1'; break; /* __T('↙') */
				case ARROW_NW:		ke.key = '7'; break; /* __T('↖') */
				case ARROW_WAIT:	ke.key = '5'; break; /* __T('↻') */
			}
		}
		/* Allow "keymaps" to be bypassed */
		if (ke.key == '\\')
		{
			/* Get a real command */
			(void)get_com(__T("Command: "), &ke.key);

			/* Hack -- bypass keymaps */
			if (!inkey_next) inkey_next = __T("");
		}
		/* Allow "control chars" to be entered */
		if (ke.key == '^')
		{
			/* Get a new command and controlify it */
			if (get_com(__T("Control: "), &ke.key)) ke.key = KTRL(ke.key);
		}
		/* Buttons are always specified in standard keyset */
		if (ke.type == EVT_BUTTON)
		{
			act = tmp;
			tmp[0] = ke.key;
		}
		/* Look up applicable keymap */
		else
			act = keymap_act[mode][ke.key];

		/* Apply keymap if not inside a keymap already */
		if (act && !inkey_next)
		{
			/* Install the keymap */
			_tcscpy_s(request_command_buffer, _countof(request_command_buffer), act);

			/* Start using the buffer */
			inkey_next = request_command_buffer;

			/* Continue */
			continue;
		}
		/* Paranoia */
		if (ke.key == 0) 
			continue;

		/* Use command */
		p_ptr->command_cmd = ke.key;
		p_ptr->command_cmd_ex = ke;

		/* Done */
		break;
	}
	/* Hack -- Auto-repeat certain commands */
	if (p_ptr->command_arg <= 0)
	{
		/* Hack -- auto repeat certain commands */
		if (_tcschr(AUTO_REPEAT_COMMANDS, p_ptr->command_cmd))
		{
			/* Repeat 99 times */
			p_ptr->command_arg = 99;
		}
	}
	/* Hack -- Scan equipment */
	for (i = INVEN_WIELD; i < INVEN_TOTAL; i++)
	{
		_TCHAR verify_inscrip[] = __T("^*");
		unsigned n;

		object_type *o_ptr = &inventory[i];

		/* Skip non-objects */
		if (!o_ptr->k_idx) 
			continue;

		/* Set up string to look for, e.g. "^d" */
		verify_inscrip[1] = p_ptr->command_cmd;

		/* Verify command */
		n = check_for_inscrip(o_ptr, __T("^*")) + check_for_inscrip(o_ptr, verify_inscrip);
		while (n--)
		{
			if (!get_check(__T("Are you sure? ")))
				p_ptr->command_cmd = '\n';
		}
	}
	/* Hack -- erase the message line. */
	prt(__T(""), 0, 0);

	/* Hack again -- apply the modified key command */
	p_ptr->command_cmd_ex.key = p_ptr->command_cmd;
}

/*
 * Check a char for "vowel-hood"
 */
bool is_a_vowel(int ch)
{
	switch (_totlower((_TCHAR)ch))
	{
		case 'a':
		case 'e':
		case 'i':
		case 'o':
		case 'u':
		{
			return (TRUE);
		}
	}
	return (FALSE);
}

/*
 * Convert a "color letter" into an "actual" color
 * The colors are: dwsorgbuDWvyRGBU, as shown below
 */
int color_char_to_attr(_TCHAR c)
{
	switch (c)
	{
		case 'd': return (TERM_DARK);
		case 'w': return (TERM_WHITE);
		case 's': return (TERM_SLATE);
		case 'o': return (TERM_ORANGE);
		case 'r': return (TERM_RED);
		case 'g': return (TERM_GREEN);
		case 'b': return (TERM_BLUE);
		case 'u': return (TERM_UMBER);

		case 'D': return (TERM_L_DARK);
		case 'W': return (TERM_L_WHITE);
		case 'v': return (TERM_VIOLET);
		case 'y': return (TERM_YELLOW);
		case 'R': return (TERM_L_RED);
		case 'G': return (TERM_L_GREEN);
		case 'B': return (TERM_L_BLUE);
		case 'U': return (TERM_L_UMBER);
	}
	return (-1);
}

/*
 * Converts a string to a terminal color byte.
 */
int color_text_to_attr(_TCHAR *name)
{
	if (my_stricmp(name, __T("dark"))       == 0) return TERM_DARK;
	if (my_stricmp(name, __T("white"))      == 0) return TERM_WHITE;
	if (my_stricmp(name, __T("slate"))      == 0) return TERM_SLATE;
	if (my_stricmp(name, __T("gray"))       == 0) return TERM_SLATE;
	if (my_stricmp(name, __T("grey"))       == 0) return TERM_SLATE;
	if (my_stricmp(name, __T("orange"))     == 0) return TERM_ORANGE;
	if (my_stricmp(name, __T("red"))	== 0) return TERM_RED;
	if (my_stricmp(name, __T("green"))      == 0) return TERM_GREEN;
	if (my_stricmp(name, __T("blue"))       == 0) return TERM_BLUE;
	if (my_stricmp(name, __T("umber"))      == 0) return TERM_UMBER;
	if (my_stricmp(name, __T("brown"))      == 0) return TERM_UMBER;
	if (my_stricmp(name, __T("violet"))     == 0) return TERM_VIOLET;
	if (my_stricmp(name, __T("yellow"))     == 0) return TERM_YELLOW;
	if (my_stricmp(name, __T("lightdark"))  == 0) return TERM_L_DARK;
	if (my_stricmp(name, __T("lightwhite")) == 0) return TERM_L_WHITE;
	if (my_stricmp(name, __T("lightred"))   == 0) return TERM_L_RED;
	if (my_stricmp(name, __T("lightgreen")) == 0) return TERM_L_GREEN;
	if (my_stricmp(name, __T("lightblue"))  == 0) return TERM_L_BLUE;
	if (my_stricmp(name, __T("lightumber")) == 0) return TERM_L_UMBER;
	if (my_stricmp(name, __T("lightbrown")) == 0) return TERM_L_UMBER;

	/* Oops */
	return -1;
}

/*
 * Extract a textual representation of an attribute
 */
_TCHAR *attr_to_text(byte a)
{
	switch (a)
	{
		case TERM_DARK:    return __T("Dark");
		case TERM_WHITE:   return __T("White");
		case TERM_SLATE:   return __T("Slate");
		case TERM_ORANGE:  return __T("Orange");
		case TERM_RED:     return __T("Red");
		case TERM_GREEN:   return __T("Green");
		case TERM_BLUE:    return __T("Blue");
		case TERM_UMBER:   return __T("Umber");
		case TERM_L_DARK:  return __T("L.Dark");
		case TERM_L_WHITE: return __T("L.Slate");
		case TERM_VIOLET:  return __T("Violet");
		case TERM_YELLOW:  return __T("Yellow");
		case TERM_L_RED:   return __T("L.Red");
		case TERM_L_GREEN: return __T("L.Green");
		case TERM_L_BLUE:  return __T("L.Blue");
		case TERM_L_UMBER: return __T("L.Umber");
	}
	/* Oops */
	return __T("Icky");
}

#ifdef SUPPORT_GAMMA

/*
 * XXX XXX XXX Important note about "colors" XXX XXX XXX
 *
 * The "TERM_*" color definitions list the "composition" of each
 * "Angband color" in terms of "quarters" of each of the three color
 * components (Red, Green, Blue), for example, TERM_UMBER is defined
 * as 2/4 Red, 1/4 Green, 0/4 Blue.
 *
 * These values are NOT gamma-corrected.  On most machines (with the
 * Macintosh being an important exception), you must "gamma-correct"
 * the given values, that is, "correct for the intrinsic non-linearity
 * of the phosphor", by converting the given intensity levels based
 * on the "gamma" of the target screen, which is usually 1.7 (or 1.5).
 *
 * The actual formula for conversion is unknown to me at this time,
 * but you can use the table below for the most common gamma values.
 *
 * So, on most machines, simply convert the values based on the "gamma"
 * of the target screen, which is usually in the range 1.5 to 1.7, and
 * usually is closest to 1.7.  The converted value for each of the five
 * different "quarter" values is given below:
 *
 *  Given     Gamma 1.0       Gamma 1.5       Gamma 1.7     Hex 1.7
 *  -----       ----	    ----	    ----	  ---
 *   0/4	0.00	    0.00	    0.00	  #00
 *   1/4	0.25	    0.27	    0.28	  #47
 *   2/4	0.50	    0.55	    0.56	  #8f
 *   3/4	0.75	    0.82	    0.84	  #d7
 *   4/4	1.00	    1.00	    1.00	  #ff
 */

/* Table of gamma values */
byte gamma_table[256];

/* Table of ln(x / 256) * 256 for x going from 0 -> 255 */
static const s16b gamma_helper[256] =
{
	0, -1420, -1242, -1138, -1065, -1007, -961, -921, -887, -857, -830,
	-806, -783, -762, -744, -726, -710, -694, -679, -666, -652, -640,
	-628, -617, -606, -596, -586, -576, -567, -577, -549, -541, -532,
	-525, -517, -509, -502, -495, -488, -482, -475, -469, -463, -457,
	-451, -455, -439, -434, -429, -423, -418, -413, -408, -403, -398,
	-394, -389, -385, -380, -376, -371, -367, -363, -359, -355, -351,
	-347, -343, -339, -336, -332, -328, -325, -321, -318, -314, -311,
	-308, -304, -301, -298, -295, -291, -288, -285, -282, -279, -276,
	-273, -271, -268, -265, -262, -259, -257, -254, -251, -248, -246,
	-243, -241, -238, -236, -233, -231, -228, -226, -223, -221, -219,
	-216, -214, -212, -209, -207, -205, -203, -200, -198, -196, -194,
	-192, -190, -188, -186, -184, -182, -180, -178, -176, -174, -172,
	-170, -168, -166, -164, -162, -160, -158, -156, -155, -153, -151,
	-149, -147, -146, -144, -142, -140, -139, -137, -135, -134, -132,
	-130, -128, -127, -125, -124, -122, -120, -119, -117, -116, -114,
	-112, -111, -109, -108, -106, -105, -103, -102, -100, -99, -97, -96,
	-95, -93, -92, -90, -89, -87, -86, -85, -83, -82, -80, -79, -78,
	-76, -75, -74, -72, -71, -70, -68, -67, -66, -65, -63, -62, -61,
	-59, -58, -57, -56, -54, -53, -52, -51, -50, -48, -47, -46, -45,
	-44, -42, -41, -40, -39, -38, -37, -35, -34, -33, -32, -31, -30,
	-29, -27, -26, -25, -24, -23, -22, -21, -20, -19, -18, -17, -16,
	-14, -13, -12, -11, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1
};

/*
 * Build the gamma table so that floating point isn't needed.
 *
 * Note gamma goes from 0->256.  The old value of 100 is now 128.
 */
void build_gamma_table(int gamma)
{
	int i, n;

	/*
	 * value is the current sum.
	 * diff is the new term to add to the series.
	 */
	long value, diff;

	/* Hack - convergence is bad in these cases. */
	gamma_table[0] = 0;
	gamma_table[255] = 255;

	for (i = 1; i < 255; i++)
	{
		/*
		 * Initialise the Taylor series
		 *
		 * value and diff have been scaled by 256
		 */
		n = 1;
		value = 256L * 256L;
		diff = ((long)gamma_helper[i]) * (gamma - 256);

		while (diff)
		{
			value += diff;
			n++;

			/*
			 * Use the following identiy to calculate the gamma table.
			 * exp(x) = 1 + x + x^2/2 + x^3/(2*3) + x^4/(2*3*4) +...
			 *
			 * n is the current term number.
			 *
			 * The gamma_helper array contains a table of
			 * ln(x/256) * 256
			 * This is used because a^b = exp(b*ln(a))
			 *
			 * In this case:
			 * a is i / 256
			 * b is gamma.
			 *
			 * Note that everything is scaled by 256 for accuracy,
			 * plus another factor of 256 for the final result to
			 * be from 0-255.  Thus gamma_helper[] * gamma must be
			 * divided by 256*256 each itteration, to get back to
			 * the original power series.
			 */
			diff = (((diff / 256) * gamma_helper[i]) * (gamma - 256)) / (256 * n);
		}
		/*
		 * Store the value in the table so that the
		 * floating point pow function isn't needed.
		 */
		gamma_table[i] = (byte)(((value / 256) * i) / 256);
	}
}
#endif /* SUPPORT_GAMMA */
