/* vi: set ts=2 shiftwidth=2 expandtab:
 *
 * Copyright (C) 2002-2006  Simon Baldwin, simon_baldwin@yahoo.com
 * Mac portions Copyright (C) 2002  Ben Hines
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2 of the GNU General Public License
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
 * USA
 */

/*
 * Glk interface for AGiliTy 1.1.1.1
 * -------------------------------
 *
 * This module contains the the Glk porting layer for AGiliTy.  It
 * defines the Glk arguments list structure, the entry points for the
 * Glk library framework to use, and all platform-abstracted I/O to
 * link to Glk's I/O.
 *
 * The following items are omitted from this Glk port:
 *
 *  o Calls to glk_tick().  The Glk documentation states that the
 *    interpreter should call glk_tick() every opcode or so.  This is
 *    intrusive to code (it goes outside of this module), and since
 *    most Glk libraries do precisely nothing in glk_tick(), there is
 *    little motivation to add it.
 *
 *  o Glk tries to assert control over _all_ file I/O.  It's just too
 *    disruptive to add it to existing code, so for now, the AGiliTy
 *    interpreter is still dependent on stdio and the like.
 */

#include <assert.h>
#include <errno.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <limits.h>
#include <stddef.h>

#ifndef GLK_ANSI_ONLY
# if !defined (__USE_POSIX)
#  define __USE_POSIX  /* Gets fdopen() definition. */
#  include <stdio.h>
#  undef __USE_POSIX
# else
#  include <stdio.h>
# endif

# if TARGET_RT_MAC_CFM
#  include <time.h>
   /* Sys/types.h absent on CW MSL (aka CFM), replace with fcntls. */
#  include <fcntl.h>
#  define  FD_SETSIZE  1024
   /*
    * CW's MSL defines exec() in unistd.h which conflicts with exec in
    * interp.  To work around this I'll put prototypes of the stuff we
    * use from it here.
    */
   _MSL_IMP_EXP_C int close (int);
# else
#  include <unistd.h>
#  include <fcntl.h>
#  include <sys/time.h>
#  include <sys/types.h>
# endif
#else
# include <stdio.h>
#endif

#include "agility.h"
#include "interp.h"

#include "glk.h"

/*
 * True and false definitions -- usually defined in glkstart.h, but we need
 * them early, so we'll define them here too.  We also need NULL, but that's
 * normally from stdio.h or one of it's cousins.
 */
#ifndef FALSE
# define FALSE 0
#endif
#ifndef TRUE
# define TRUE (!FALSE)
#endif


/*---------------------------------------------------------------------*/
/*  Module variables, miscellaneous externals not in header files      */
/*---------------------------------------------------------------------*/

/* Glk AGiliTy port version number. */
static const glui32 GAGT_PORT_VERSION = 0x00010701;

/*
 * We use two Glk windows; one is two lines at the top of the display area
 * for status, and the other is the remainder of the display area, used for,
 * well, everything else.  Where a particular Glk implementation won't do
 * more than one window, the status window remains NULL.
 */
static winid_t gagt_main_window = NULL,
               gagt_status_window = NULL;

/*
 * Transcript stream and input log.  These are NULL if there is no current
 * collection of these strings.
 */
static strid_t gagt_transcript_stream = NULL,
               gagt_inputlog_stream = NULL;

/* Input read log stream, for reading back an input log. */
static strid_t gagt_readlog_stream = NULL;

/* Options that may be turned off or set by command line flags. */
static enum {
  FONT_AUTOMATIC, FONT_FIXED_WIDTH, FONT_PROPORTIONAL, FONT_DEBUG
}
gagt_font_mode = FONT_AUTOMATIC;
static enum {
  DELAY_FULL, DELAY_SHORT, DELAY_OFF
}
gagt_delay_mode = DELAY_SHORT;
static int gagt_replacement_enabled = TRUE,
           gagt_extended_status_enabled = TRUE,
           gagt_abbreviations_enabled = TRUE,
           gagt_commands_enabled = TRUE;

/* Forward declaration of event wait functions. */
static void gagt_event_wait (glui32 wait_type, event_t * event);
static void gagt_event_wait_2 (glui32 wait_type_1,
                               glui32 wait_type_2,
                               event_t * event);

/*
 * Forward declaration of the glk_exit() wrapper.  Normal functions in this
 * module should not to call glk_exit() directly; they should always call it
 * through the wrapper instead.
 */
static void gagt_exit (void);


/*---------------------------------------------------------------------*/
/*  Glk port utility functions                                         */
/*---------------------------------------------------------------------*/

/*
 * gagt_fatal()
 *
 * Fatal error handler.  The function returns, expecting the caller to
 * abort() or otherwise handle the error.
 */
static void
gagt_fatal (const char *string)
{
  /*
   * If the failure happens too early for us to have a window, print
   * the message to stderr.
   */
  if (!gagt_main_window)
    {
      fprintf (stderr, "\n\nINTERNAL ERROR: %s\n", string);

      fprintf (stderr, "\nPlease record the details of this error, try to"
                       " note down everything you did to cause it, and email"
                       " this information to simon_baldwin@yahoo.com.\n\n");
      return;
    }

  /* Cancel all possible pending window input events. */
  glk_cancel_line_event (gagt_main_window, NULL);
  glk_cancel_char_event (gagt_main_window);

  /* Print a message indicating the error. */
  glk_set_window (gagt_main_window);
  glk_set_style (style_Normal);
  glk_put_string ("\n\nINTERNAL ERROR: ");
  glk_put_string ((char *) string);

  glk_put_string ("\n\nPlease record the details of this error, try to"
                  " note down everything you did to cause it, and email"
                  " this information to simon_baldwin@yahoo.com.\n\n");
}


/*
 * gagt_malloc()
 * gagt_realloc()
 *
 * Non-failing malloc and realloc; call gagt_fatal() and exit if memory
 * allocation fails.
 */
static void *
gagt_malloc (size_t size)
{
  void *pointer;

  pointer = malloc (size);
  if (!pointer)
    {
      gagt_fatal ("GLK: Out of system memory");
      gagt_exit ();
    }

  return pointer;
}

static void *
gagt_realloc (void *ptr, size_t size)
{
  void *pointer;

  pointer = realloc (ptr, size);
  if (!pointer)
    {
      gagt_fatal ("GLK: Out of system memory");
      gagt_exit ();
    }

  return pointer;
}


/*
 * gagt_strncasecmp()
 * gagt_strcasecmp()
 *
 * Strncasecmp and strcasecmp are not ANSI functions, so here are local
 * definitions to do the same jobs.
 */
static int
gagt_strncasecmp (const char *s1, const char *s2, size_t n)
{
  size_t index;

  for (index = 0; index < n; index++)
    {
      int diff;

      diff = glk_char_to_lower (s1[index]) - glk_char_to_lower (s2[index]);
      if (diff < 0 || diff > 0)
        return diff < 0 ? -1 : 1;
    }

  return 0;
}

static int
gagt_strcasecmp (const char *s1, const char *s2)
{
  size_t s1len, s2len;
  int result;

  s1len = strlen (s1);
  s2len = strlen (s2);

  result = gagt_strncasecmp (s1, s2, s1len < s2len ? s1len : s2len);
  if (result < 0 || result > 0)
    return result;
  else
    return s1len < s2len ? -1 : s1len > s2len ? 1 : 0;
}


/*
 * gagt_debug()
 *
 * Handler for module debug output.  If no debug, it ignores the call,
 * otherwise it prints a debug message, prefixed by the function name.
 */
static void
gagt_debug (const char *function, const char *format, ...)
{
  if (DEBUG_OUT)
    {
      fprintf (debugfile, "%s (", function);
      if (format && strlen (format) > 0)
        {
          va_list ap;

          va_start (ap, format);
          vfprintf (debugfile, format, ap);
          va_end (ap);
        }

      fprintf (debugfile, ")\n");
      fflush (debugfile);
    }
}


/*---------------------------------------------------------------------*/
/*  Functions not ported - functionally unchanged from os_none.c       */
/*---------------------------------------------------------------------*/

/*
 * agt_tone()
 *
 * Produce a hz-Hertz sound for ms milliseconds.
 */
void
agt_tone (int hz, int ms)
{
  gagt_debug ("agt_tone", "hz=%d, ms=%d", hz, ms);
}


/*
 * agt_rand()
 *
 * Return random number from a to b inclusive.  The random number generator
 * is seeded on the first call, to a reproducible sequence if stable_random,
 * otherwise using time().
 */
int
agt_rand (int a, int b)
{
  static int is_initialized = FALSE;

  int result;

  if (!is_initialized)
    {
      srand (stable_random ? 6 : time (0));

      is_initialized = TRUE;
      gagt_debug ("agt_rand", "[initialized]");
    }

  result = a + (rand () >> 2) % (b - a + 1);
  gagt_debug ("agt_rand", "a=%d, b=%d -> %d", a, b, result);
  return result;
}


/*---------------------------------------------------------------------*/
/*  Workrounds for bugs in core AGiliTy.                               */
/*---------------------------------------------------------------------*/

/*
 * gagt_workround_menus()
 *
 * Somewhere in AGiliTy's menu handling stuff is a condition that sets up
 * an eventual NULL dereference in rstrncpy(), called from num_name_func().
 * For some reason, perhaps memory overruns, perhaps something else, it
 * happens after a few turns have been made through agt_menu().  Replacing
 * agt_menu() won't avoid it.
 *
 * However, the menu stuff isn't too useful, or attractive, in a game, so one
 * solution is to simply disable it.  While not possible to do this directly,
 * there is a sneaky way, using our carnal knowledge of core AGiliTy.  In
 * runverb.c, there is code to prevent menu mode from being turned on where
 * verbmenu is NULL.  Verbmenu is set up in agil.c on loading the game, but,
 * crucially, is set up before agil.c calls start_interface().  So... here
 * we can free it, set it to NULL, set menu_mode to 0 (it probably is already)
 * and AGiliTy behaves as if the game prevents menu mode.
 */
static void
gagt_workround_menus (void)
{
  free (verbmenu);
  verbmenu = NULL;

  menu_mode = 0;
}


/*
 * gagt_workround_fileexist()
 *
 * This function verifies that the game file can be opened, in effect second-
 * guessing run_game().
 *
 * AGiliTy's fileexist() has in it either a bug, or a misfeature.  It always
 * passes a nofix value of 1 into try_open_file(), which defeats the code to
 * retry with both upper and lower cased filenames.  So here we have to go
 * round the houses, with readopen()/readclose().
 */
static int
gagt_workround_fileexist (fc_type fc, filetype ft)
{
  genfile file;
  char *errstr;

  errstr = NULL;
  file = readopen (fc, ft, &errstr);
  free (errstr);

  if (file)
    {
      readclose (file);
      return TRUE;
    }
  return FALSE;
}


/*---------------------------------------------------------------------*/
/*  I/O interface start and stop functions.                            */
/*---------------------------------------------------------------------*/

/* AGiliTy font_status values that indicate what font may be used. */
enum {
  GAGT_FIXED_REQUIRED = 1, GAGT_PROPORTIONAL_OKAY = 2
};


/*
 * start_interface()
 * close_interface()
 *
 * Startup and shutdown callout points.  The start function for Glk looks
 * at the value of font_status that the game sets, to see if it has a strong
 * view of the font to use.  If it does, then we'll reflect that in the
 * module's font contol, perhaps overriding any command line options that the
 * user has passed in.
 */
void
start_interface (fc_type fc)
{
  switch (font_status)
    {
    case GAGT_FIXED_REQUIRED:
      gagt_font_mode = FONT_FIXED_WIDTH;
      break;

    case GAGT_PROPORTIONAL_OKAY:
      gagt_font_mode = FONT_PROPORTIONAL;
      break;

    default:
      break;
    }

  gagt_workround_menus ();

  gagt_debug ("start_interface", "fc=%p", fc);
}

void
close_interface (void)
{
  if (filevalid (scriptfile, fSCR))
    close_pfile (scriptfile, 0);

  gagt_debug ("close_interface", "");
}


/*---------------------------------------------------------------------*/
/*  Code page 437 to ISO 8859 Latin-1 translations                     */
/*---------------------------------------------------------------------*/

/*
 * AGiliTy uses IBM code page 437 characters, and Glk works in ISO 8859
 * Latin-1.  There's some good news, in that a number of the characters,
 * especially international ones, in these two sets are the same.  The bad
 * news is that, for codes above 127 (that is, beyond 7-bit ASCII), or for
 * codes below 32, they live in different places.  So, here is a table of
 * conversions for codes not equivalent to 7-bit ASCII, and a pair of
 * conversion routines.
 *
 * Note that some code page 437 characters don't have ISO 8859 Latin-1
 * equivalents.  Predominantly, these are the box-drawing characters, which
 * is a pity, because these are the ones that are used the most.  Anyway,
 * in these cases, the table substitutes an approximated base ASCII char-
 * acter in its place.
 *
 * The first entry of table comments below is the character's UNICODE value,
 * just in case it's useful at some future date.
 */
typedef const struct
{
  const unsigned char cp437;      /* Code page 437 character. */
  const unsigned char iso8859_1;  /* ISO 8859 Latin-1 character. */
} gagt_char_t;
typedef gagt_char_t *gagt_charref_t;

static gagt_char_t GAGT_CHAR_TABLE[] = {
  /*
   * Low characters -- those below 0x20.   These are the really odd code
   * page 437 characters, rarely used by AGT games.  Low characters are
   * omitted from the reverse lookup, and participate only in the forwards
   * lookup from code page 437 to ISO 8859 Latin-1.
   */
  {0x01,  '@'},     /* 263a White smiling face */
  {0x02,  '@'},     /* 263b Black smiling face */
  {0x03,  '?'},     /* 2665 Black heart suit */
  {0x04,  '?'},     /* 2666 Black diamond suit */
  {0x05,  '?'},     /* 2663 Black club suit */
  {0x06,  '?'},     /* 2660 Black spade suit */
  {0x07, 0xb7},     /* 2022 Bullet */
  {0x08, 0xb7},     /* 25d8 Inverse bullet */
  {0x09, 0xb7},     /* 25e6 White bullet */
  {0x0a, 0xb7},     /* 25d9 Inverse white circle */
  {0x0b,  '?'},     /* 2642 Male sign */
  {0x0c,  '?'},     /* 2640 Female sign */
  {0x0d,  '?'},     /* 266a Eighth note */
  {0x0e,  '?'},     /* 266b Beamed eighth notes */
  {0x0f, 0xa4},     /* 263c White sun with rays */
  {0x10,  '>'},     /* 25b6 Black right-pointing triangle */
  {0x11,  '<'},     /* 25c0 Black left-pointing triangle */
  {0x12, 0xa6},     /* 2195 Up down arrow */
  {0x13,  '!'},     /* 203c Double exclamation mark */
  {0x14, 0xb6},     /* 00b6 Pilcrow sign */
  {0x15, 0xa7},     /* 00a7 Section sign */
  {0x16,  '#'},     /* 25ac Black rectangle */
  {0x17, 0xa6},     /* 21a8 Up down arrow with base */
  {0x18,  '^'},     /* 2191 Upwards arrow */
  {0x19,  'v'},     /* 2193 Downwards arrow */
  {0x1a,  '>'},     /* 2192 Rightwards arrow */
  {0x1b,  '<'},     /* 2190 Leftwards arrow */
  {0x1c,  '?'},     /* 2310 Reversed not sign */
  {0x1d,  '-'},     /* 2194 Left right arrow */
  {0x1e,  '^'},     /* 25b2 Black up-pointing triangle */
  {0x1f,  'v'},     /* 25bc Black down-pointing triangle */

  /*
   * High characters -- those above 0x7f.  These are more often used by AGT
   * games, particularly for box drawing.
   */
  {0x80, 0xc7},     /* 00c7 Latin capital letter c with cedilla */
  {0x81, 0xfc},     /* 00fc Latin small letter u with diaeresis */
  {0x82, 0xe9},     /* 00e9 Latin small letter e with acute */
  {0x83, 0xe2},     /* 00e2 Latin small letter a with circumflex */
  {0x84, 0xe4},     /* 00e4 Latin small letter a with diaeresis */
  {0x85, 0xe0},     /* 00e0 Latin small letter a with grave */
  {0x86, 0xe5},     /* 00e5 Latin small letter a with ring above */
  {0x87, 0xe7},     /* 00e7 Latin small letter c with cedilla */
  {0x88, 0xea},     /* 00ea Latin small letter e with circumflex */
  {0x89, 0xeb},     /* 00eb Latin small letter e with diaeresis */
  {0x8a, 0xe8},     /* 00e8 Latin small letter e with grave */
  {0x8b, 0xef},     /* 00ef Latin small letter i with diaeresis */
  {0x8c, 0xee},     /* 00ee Latin small letter i with circumflex */
  {0x8d, 0xec},     /* 00ec Latin small letter i with grave */
  {0x8e, 0xc4},     /* 00c4 Latin capital letter a with diaeresis */
  {0x8f, 0xc5},     /* 00c5 Latin capital letter a with ring above */
  {0x90, 0xc9},     /* 00c9 Latin capital letter e with acute */
  {0x91, 0xe6},     /* 00e6 Latin small ligature ae */
  {0x92, 0xc6},     /* 00c6 Latin capital ligature ae */
  {0x93, 0xf4},     /* 00f4 Latin small letter o with circumflex */
  {0x94, 0xf6},     /* 00f6 Latin small letter o with diaeresis */
  {0x95, 0xf2},     /* 00f2 Latin small letter o with grave */
  {0x96, 0xfb},     /* 00fb Latin small letter u with circumflex */
  {0x97, 0xf9},     /* 00f9 Latin small letter u with grave */
  {0x98, 0xff},     /* 00ff Latin small letter y with diaeresis */
  {0x99, 0xd6},     /* 00d6 Latin capital letter o with diaeresis */
  {0x9a, 0xdc},     /* 00dc Latin capital letter u with diaeresis */
  {0x9b, 0xa2},     /* 00a2 Cent sign */
  {0x9c, 0xa3},     /* 00a3 Pound sign */
  {0x9d, 0xa5},     /* 00a5 Yen sign */
  {0x9e,  'p'},     /* 20a7 Peseta sign */
  {0x9f,  'f'},     /* 0192 Latin small letter f with hook */
  {0xa0, 0xe1},     /* 00e1 Latin small letter a with acute */
  {0xa1, 0xed},     /* 00ed Latin small letter i with acute */
  {0xa2, 0xf3},     /* 00f3 Latin small letter o with acute */
  {0xa3, 0xfa},     /* 00fa Latin small letter u with acute */
  {0xa4, 0xf1},     /* 00f1 Latin small letter n with tilde */
  {0xa5, 0xd1},     /* 00d1 Latin capital letter n with tilde */
  {0xa6, 0xaa},     /* 00aa Feminine ordinal indicator */
  {0xa7, 0xba},     /* 00ba Masculine ordinal indicator */
  {0xa8, 0xbf},     /* 00bf Inverted question mark */
  {0xa9,  '.'},     /* 2310 Reversed not sign */
  {0xaa, 0xac},     /* 00ac Not sign */
  {0xab, 0xbd},     /* 00bd Vulgar fraction one half */
  {0xac, 0xbc},     /* 00bc Vulgar fraction one quarter */
  {0xad, 0xa1},     /* 00a1 Inverted exclamation mark */
  {0xae, 0xab},     /* 00ab Left-pointing double angle quotation mark */
  {0xaf, 0xbb},     /* 00bb Right-pointing double angle quotation mark */
  {0xb0,  '#'},     /* 2591 Light shade */
  {0xb1,  '#'},     /* 2592 Medium shade */
  {0xb2,  '#'},     /* 2593 Dark shade */
  {0xb3,  '|'},     /* 2502 Box light vertical */
  {0xb4,  '+'},     /* 2524 Box light vertical and left */
  {0xb5,  '+'},     /* 2561 Box vertical single and left double */
  {0xb6,  '|'},     /* 2562 Box vertical double and left single */
  {0xb7,  '+'},     /* 2556 Box down double and left single */
  {0xb8,  '+'},     /* 2555 Box down single and left double */
  {0xb9,  '+'},     /* 2563 Box double vertical and left */
  {0xba,  '|'},     /* 2551 Box double vertical */
  {0xbb, '\\'},     /* 2557 Box double down and left */
  {0xbc,  '/'},     /* 255d Box double up and left */
  {0xbd,  '+'},     /* 255c Box up double and left single */
  {0xbe,  '+'},     /* 255b Box up single and left double */
  {0xbf, '\\'},     /* 2510 Box light down and left */
  {0xc0, '\\'},     /* 2514 Box light up and right */
  {0xc1,  '+'},     /* 2534 Box light up and horizontal */
  {0xc2,  '+'},     /* 252c Box light down and horizontal */
  {0xc3,  '+'},     /* 251c Box light vertical and right */
  {0xc4,  '-'},     /* 2500 Box light horizontal */
  {0xc5,  '+'},     /* 253c Box light vertical and horizontal */
  {0xc6,  '|'},     /* 255e Box vertical single and right double */
  {0xc7,  '|'},     /* 255f Box vertical double and right single */
  {0xc8, '\\'},     /* 255a Box double up and right */
  {0xc9,  '/'},     /* 2554 Box double down and right */
  {0xca,  '+'},     /* 2569 Box double up and horizontal */
  {0xcb,  '+'},     /* 2566 Box double down and horizontal */
  {0xcc,  '+'},     /* 2560 Box double vertical and right */
  {0xcd,  '='},     /* 2550 Box double horizontal */
  {0xce,  '+'},     /* 256c Box double vertical and horizontal */
  {0xcf,  '='},     /* 2567 Box up single and horizontal double */
  {0xd0,  '+'},     /* 2568 Box up double and horizontal single */
  {0xd1,  '='},     /* 2564 Box down single and horizontal double */
  {0xd2,  '+'},     /* 2565 Box down double and horizontal single */
  {0xd3,  '+'},     /* 2559 Box up double and right single */
  {0xd4,  '+'},     /* 2558 Box up single and right double */
  {0xd5,  '+'},     /* 2552 Box down single and right double */
  {0xd6,  '+'},     /* 2553 Box down double and right single */
  {0xd7,  '+'},     /* 256b Box vertical double and horizontal single */
  {0xd8,  '+'},     /* 256a Box vertical single and horizontal double */
  {0xd9,  '/'},     /* 2518 Box light up and left */
  {0xda,  '/'},     /* 250c Box light down and right */
  {0xdb,  '@'},     /* 2588 Full block */
  {0xdc,  '@'},     /* 2584 Lower half block */
  {0xdd,  '@'},     /* 258c Left half block */
  {0xde,  '@'},     /* 2590 Right half block */
  {0xdf,  '@'},     /* 2580 Upper half block */
  {0xe0,  'a'},     /* 03b1 Greek small letter alpha */
  {0xe1, 0xdf},     /* 00df Latin small letter sharp s */
  {0xe2,  'G'},     /* 0393 Greek capital letter gamma */
  {0xe3,  'p'},     /* 03c0 Greek small letter pi */
  {0xe4,  'S'},     /* 03a3 Greek capital letter sigma */
  {0xe5,  's'},     /* 03c3 Greek small letter sigma */
  {0xe6, 0xb5},     /* 00b5 Micro sign */
  {0xe7,  't'},     /* 03c4 Greek small letter tau */
  {0xe8,  'F'},     /* 03a6 Greek capital letter phi */
  {0xe9,  'T'},     /* 0398 Greek capital letter theta */
  {0xea,  'O'},     /* 03a9 Greek capital letter omega */
  {0xeb,  'd'},     /* 03b4 Greek small letter delta */
  {0xec,  '.'},     /* 221e Infinity */
  {0xed,  'f'},     /* 03c6 Greek small letter phi */
  {0xee,  'e'},     /* 03b5 Greek small letter epsilon */
  {0xef,  '^'},     /* 2229 Intersection */
  {0xf0,  '='},     /* 2261 Identical to */
  {0xf1, 0xb1},     /* 00b1 Plus-minus sign */
  {0xf2,  '>'},     /* 2265 Greater-than or equal to */
  {0xf3,  '<'},     /* 2264 Less-than or equal to */
  {0xf4,  'f'},     /* 2320 Top half integral */
  {0xf5,  'j'},     /* 2321 Bottom half integral */
  {0xf6, 0xf7},     /* 00f7 Division sign */
  {0xf7,  '='},     /* 2248 Almost equal to */
  {0xf8, 0xb0},     /* 00b0 Degree sign */
  {0xf9, 0xb7},     /* 2219 Bullet operator */
  {0xfa, 0xb7},     /* 00b7 Middle dot */
  {0xfb,  '/'},     /* 221a Square root */
  {0xfc,  'n'},     /* 207f Superscript latin small letter n */
  {0xfd, 0xb2},     /* 00b2 Superscript two */
  {0xfe,  '#'},     /* 25a0 Black square */
  {0xff, 0xa0},     /* 00a0 No-break space */
  {0, 0}            /* 0000 [END OF TABLE] */
};


/*
 * gagt_cp_to_iso()
 *
 * Convert a string from code page 437 into ISO 8859 Latin-1.  The input and
 * output buffers may be one and the same.
 */
static void
gagt_cp_to_iso (const unsigned char *from_string, unsigned char *to_string)
{
  static int is_initialized = FALSE;
  static unsigned char table[UCHAR_MAX + 1];

  int index;
  unsigned char cp437, iso8859_1;
  assert (from_string && to_string);

  if (!is_initialized)
    {
      gagt_charref_t entry;

      /*
       * Create a lookup entry for each code in the main table.  Fill in gaps
       * for 7-bit characters with their ASCII equivalent values.  Any
       * remaining codes not represented in the main table will map to zeroes
       * in the lookup table, as static variables are initialized to zero.
       */
      for (entry = GAGT_CHAR_TABLE; entry->cp437; entry++)
        {
          cp437 = entry->cp437;
          iso8859_1 = entry->iso8859_1;

          assert (cp437 < 0x20 || (cp437 > SCHAR_MAX && cp437 <= UCHAR_MAX));
          table[cp437] = iso8859_1;
        }
      for (index = 0; index <= SCHAR_MAX; index++)
        {
          if (table[index] == 0)
            table[index] = index;
        }

      is_initialized = TRUE;
    }

  for (index = 0; from_string[index] != '\0'; index++)
    {
      cp437 = from_string[index];
      iso8859_1 = table[cp437];

      to_string[index] = iso8859_1 ? iso8859_1 : cp437;
    }

  to_string[index] = '\0';
}


/*
 * gagt_iso_to_cp()
 *
 * Convert a string from ISO 8859 Latin-1 to code page 437.  The input and
 * output buffers may be one and the same.
 */
static void
gagt_iso_to_cp (const unsigned char *from_string, unsigned char *to_string)
{
  static int is_initialized = FALSE;
  static unsigned char table[UCHAR_MAX + 1];

  int index;
  unsigned char iso8859_1, cp437;
  assert (from_string && to_string);

  if (!is_initialized)
    {
      gagt_charref_t entry;

      /*
       * Create a reverse lookup entry for each code in the main table,
       * overriding all of the low table entries (that is, anything under
       * 128) with their ASCII no matter what the table contained.
       *
       * Any codes not represented in the main table will map to zeroes in
       * the reverse lookup table, since static variables are initialized to
       * zero.  The first 128 characters are equivalent to ASCII.  Moreover,
       * some ISO 8859 Latin-1 entries are faked as base ASCII; where an
       * entry is already occupied, the main table entry is skipped, so the
       * match, which is n:1 in the reverse direction, works in first-found
       * mode.
       */
      for (entry = GAGT_CHAR_TABLE; entry->iso8859_1; entry++)
        {
          cp437 = entry->cp437;
          iso8859_1 = entry->iso8859_1;

          assert (iso8859_1 <= UCHAR_MAX);
          if (table[iso8859_1] == 0)
            table[iso8859_1] = cp437;
        }
      for (index = 0; index <= SCHAR_MAX; index++)
        table[index] = index;

      is_initialized = TRUE;
    }

  for (index = 0; from_string[index] != '\0'; index++)
    {
      iso8859_1 = from_string[index];
      cp437 = table[iso8859_1];

      to_string[index] = cp437 ? cp437 : iso8859_1;
    }

  to_string[index] = '\0';
}


/*---------------------------------------------------------------------*/
/*  Glk port status line functions                                     */
/*---------------------------------------------------------------------*/

/*
 * Buffered copy of the latest status line passed in by the interpreter.
 * Buffering it means it's readily available to print for Glk libraries
 * that don't support separate windows.  We also need a copy of the last
 * status buffer printed for non-windowing Glk libraries, for comparison.
 */
static char *gagt_status_buffer = NULL,
            *gagt_status_buffer_printed = NULL;

/*
 * Indication that we are in mid-delay.  The delay is silent, and can look
 * kind of confusing, so to try to make it less so, we'll have the status
 * window show something about it.
 */
static int gagt_inside_delay = FALSE;


/*
 * agt_statline()
 *
 * This function is called from our call to print_statline().  Here we'll
 * convert the string and buffer in an allocated area for later use.
 */
void
agt_statline (const char *cp_string)
{
  assert (cp_string);

  free (gagt_status_buffer);
  gagt_status_buffer = gagt_malloc (strlen (cp_string) + 1);
  gagt_cp_to_iso (cp_string, gagt_status_buffer);

  gagt_debug ("agt_statline", "string='%s'", cp_string);
}


/*
 * gagt_status_update_extended()
 *
 * Helper for gagt_status_update() and gagt_status_in_delay().  This function
 * displays the second line of any extended status display, giving a list of
 * exits from the compass rose, and if in an AGT delay, a waiting indicator.
 */
static void
gagt_status_update_extended (void)
{
  glui32 width, height;
  assert (gagt_status_window);

  glk_window_get_size (gagt_status_window, &width, &height);
  if (height > 1)
    {
      int index, exit;

      /* Clear the second status line only. */
      glk_window_move_cursor (gagt_status_window, 0, 1);
      glk_set_window (gagt_status_window);
      glk_set_style (style_User1);
      for (index = 0; index < width; index++)
        glk_put_char (' ');

      /*
       * Check bits in the compass rose, and print out exit names from
       * the exitname[] array.
       */
      glk_window_move_cursor (gagt_status_window, 0, 1);
      glk_put_string ("  Exits: ");
      for (exit = 0; exit < sizeof (exitname) / sizeof (exitname[0]); exit++)
        {
          if (compass_rose & (1 << exit))
            {
              glk_put_string ((char *) exitname[exit]);
              glk_put_char (' ');
            }
        }

      /* If the delay flag is set, print a waiting indicator at the right. */
      if (gagt_inside_delay)
        {
          glk_window_move_cursor (gagt_status_window,
                                  width - strlen ("Waiting... "), 1);
          glk_put_string ("Waiting... ");
        }

      glk_set_window (gagt_main_window);
    }
}


/*
 * gagt_status_update()
 *
 *
 * This function calls print_statline() to prompt the interpreter into calling
 * our agt_statline(), then if we have a status window, displays the status
 * string, and calls gagt_status_update_extended() if necessary to handle the
 * second status line.  If we don't see a call to our agt_statline, we output
 * a default status string.
 */
static void
gagt_status_update (void)
{
  glui32 width, height;
  int index;
  assert (gagt_status_window);

  glk_window_get_size (gagt_status_window, &width, &height);
  if (height > 0)
    {
      glk_window_clear (gagt_status_window);
      glk_window_move_cursor (gagt_status_window, 0, 0);
      glk_set_window (gagt_status_window);

      glk_set_style (style_User1);
      for (index = 0; index < width; index++)
        glk_put_char (' ');
      glk_window_move_cursor (gagt_status_window, 0, 0);

      /* Call print_statline() to refresh status line buffer contents. */
      print_statline ();

      /* See if we have a buffered status line available. */
      if (gagt_status_buffer)
        {
          glui32 print_width;

          /*
           * Print the basic buffered status string, truncating to the
           * current status window width if necessary, then try adding a
           * second line if extended status enabled.
           */
          print_width = width < strlen (gagt_status_buffer)
                        ? width : strlen (gagt_status_buffer);
          glk_put_buffer (gagt_status_buffer, print_width);

          if (gagt_extended_status_enabled)
            gagt_status_update_extended ();
        }
      else
        {
          /*
           * We don't (yet) have a status line.  Perhaps we're at the
           * very start of a game.  Print a standard message.
           */
          glk_put_string ("Glk AGiliTy version 1.1.1.1");
        }

      glk_set_window (gagt_main_window);
    }
}


/*
 * gagt_status_print()
 *
 * Print the current contents of the completed status line buffer out in the
 * main window, if it has changed since the last call.  This is for non-
 * windowing Glk libraries.
 *
 * Like gagt_status_update(), this function calls print_statline() to prompt
 * the interpreter into calling our agt_statline(), then if we have a new
 * status line, it prints it.
 */
static void
gagt_status_print (void)
{
  /* Call print_statline() to refresh status line buffer contents. */
  print_statline ();

  /*
   * Do no more if there is no status line to print, or if the status
   * line hasn't changed since last printed.
   */
  if (!gagt_status_buffer
      || (gagt_status_buffer_printed
          && strcmp (gagt_status_buffer, gagt_status_buffer_printed) == 0))
    return;

  /* Set fixed width font to try to preserve status line formatting. */
  glk_set_style (style_Preformatted);

  /*
   * Bracket, and output the status line buffer.  We don't need to put any
   * spacing after the opening bracket or before the closing one, because
   * AGiliTy puts leading/trailing spaces on its status lines.
   */
  glk_put_string ("[");
  glk_put_string (gagt_status_buffer);
  glk_put_string ("]\n");

  /* Save the details of the printed status buffer. */
  free (gagt_status_buffer_printed);
  gagt_status_buffer_printed = gagt_malloc (strlen (gagt_status_buffer) + 1);
  strcpy (gagt_status_buffer_printed, gagt_status_buffer);
}


/*
 * gagt_status_notify()
 *
 * Front end function for updating status.  Either updates the status window
 * or prints the status line to the main window.
 *
 * Functions interested in updating the status line should call either this
 * function, or gagt_status_redraw(), and not print_statline().
 */
static void
gagt_status_notify (void)
{
  if (!BATCH_MODE)
    {
      if (gagt_status_window)
        gagt_status_update ();
      else
        gagt_status_print ();
    }
}


/*
 * gagt_status_redraw()
 *
 * Redraw the contents of any status window with the buffered status string.
 * This function handles window sizing, and updates the interpreter with
 * status_width, so may, and should, be called on resize and arrange events.
 *
 * Functions interested in updating the status line should call either this
 * function, or gagt_status_notify(), and not print_statline().
 */
static void
gagt_status_redraw (void)
{
  if (!BATCH_MODE)
    {
      if (gagt_status_window)
        {
          glui32 width, height;
          winid_t parent;

          /*
           * Measure the status window, and update the interpreter's
           * status_width variable.
           */
          glk_window_get_size (gagt_status_window, &width, &height);
          status_width = width;

          /*
           * Rearrange the status window, without changing its actual
           * arrangement in any way.  This is a hack to work round
           * incorrect window repainting in Xglk; it forces a complete
           * repaint of affected windows on Glk window resize and
           * arrange events, and works in part because Xglk doesn't
           * check for actual arrangement changes in any way before
           * invalidating its windows.  The hack should be harmless to
           * Glk libraries other than Xglk, moreover, we're careful to
           * activate it only on resize and arrange events.
           */
          parent = glk_window_get_parent (gagt_status_window);
          glk_window_set_arrangement (parent,
                                      winmethod_Above | winmethod_Fixed,
                                      height, NULL);

          gagt_status_update ();
        }
    }
}


/*
 * gagt_status_in_delay()
 *
 * Tells status line functions whether the game is delaying, or not.  This
 * function updates the extended status line, if present, automatically.
 */
static void
gagt_status_in_delay (int inside_delay)
{
  if (!BATCH_MODE)
    {
      /* Save the new delay status flag. */
      gagt_inside_delay = inside_delay;

      /*
       * Update just the second line of the status window display, if
       * extended status is being displayed.
       */
      if (gagt_status_window && gagt_extended_status_enabled)
        gagt_status_update_extended ();
    }
}


/*
 * gagt_status_cleanup()
 *
 * Free memory resources allocated by status line functions.  Called on game
 * end.
 */
static void
gagt_status_cleanup (void)
{
  free (gagt_status_buffer);
  gagt_status_buffer = NULL;

  free (gagt_status_buffer_printed);
  gagt_status_buffer_printed = NULL;
}


/*---------------------------------------------------------------------*/
/*  Glk port color and text attribute handling                         */
/*---------------------------------------------------------------------*/

/*
 * AGT color and character attribute definitions.  This is the range of
 * values passed in to agt_textcolor().
 */
enum {
  AGT_BLACK = 0,
  AGT_BLUE = 1,
  AGT_GREEN = 2,
  AGT_CYAN = 3,
  AGT_RED = 4,
  AGT_MAGENTA = 5,
  AGT_BROWN = 6,
  AGT_NORMAL = 7,
  AGT_BLINKING = 8,
  AGT_WHITE = 9,
  AGT_FIXED_FONT = 10,
  AGT_VARIABLE_FONT = 11,
  AGT_EMPHASIS = -1,
  AGT_DE_EMPHASIS = -2
};

/*
 * AGiliTy colors and text attributes seem a bit confused.  Let's see if we
 * can sort them out.  Sadly, once we have, it's often not possible to
 * render the full range in all Glk's anyway.  Nevertheless...
 */
typedef struct {
  int color;     /* Text color. */
  int blink;     /* Text blinking flag. */
  int fixed;     /* Text fixed font flag. */
  int emphasis;  /* Text emphasized flag. */
} gagt_attrset_t;

/*
 * Attributes as currently set by AGiliTy.  The default values set up here
 * correspond to AGT_NORMAL.
 */
static gagt_attrset_t gagt_current_attribute_set = { AGT_WHITE, FALSE,
                                                     FALSE, FALSE };

/*
 * An extra flag to indicate if we have coerced fixed font override.  On
 * some occasions, we need to ensure that we get fixed font no matter what
 * the game says.
 */
static int gagt_coerced_fixed = FALSE;

/*
 * Bit masks for packing colors and attributes.  Normally, I don't like
 * bit-twiddling all that much, but for packing all of the above into a
 * single byte, that's what we need.  Stuff color into the low four bits,
 * convenient since color is from 0 to 9, then use three bits for the other
 * attributes.
 */
static const unsigned char GAGT_COLOR_MASK = 0x0f,
                           GAGT_BLINK_MASK = 1 << 4,
                           GAGT_FIXED_MASK = 1 << 5,
                           GAGT_EMPHASIS_MASK = 1 << 6;

/* Forward declaration of message function. */
static void gagt_standout_string (const char *message);


/*
 * agt_textcolor()
 *
 * The AGiliTy porting guide defines the use of this function as:
 *
 *   Set text color to color #c, where the colors are as follows:
 *    0=Black, 1=Blue,    2=Green, 3=Cyan,
 *    4=Red,   5=Magenta, 6=Brown,
 *    7=Normal("White"-- which may actually be some other color)
 *       This should turn off blinking, bold, color, etc. and restore
 *       the text mode to its default appearance.
 *    8=Turn on blinking.
 *    9= *Just* White (not neccessarily "normal" and no need to turn off
 *        blinking)
 *   10=Turn on fixed pitch font.
 *   11=Turn off fixed pitch font
 *   Also used to set other text attributes:
 *     -1=emphasized text, used (e.g.) for room titles
 *     -2=end emphasized text
 *
 * Here we try to make sense of all this.  Given an argument, we'll try to
 * update our separated color and text attributes flags to reflect the
 * expected text rendering.
 */
void
agt_textcolor (int color)
{
  switch (color)
    {
    case AGT_BLACK:
    case AGT_BLUE:
    case AGT_GREEN:
    case AGT_CYAN:
    case AGT_RED:
    case AGT_MAGENTA:
    case AGT_BROWN:
    case AGT_WHITE:
      gagt_current_attribute_set.color = color;
      break;

    case AGT_NORMAL:
      gagt_current_attribute_set.color = AGT_WHITE;
      gagt_current_attribute_set.blink = FALSE;
      gagt_current_attribute_set.fixed = FALSE;
      gagt_current_attribute_set.emphasis = FALSE;
      break;

    case AGT_BLINKING:
      gagt_current_attribute_set.blink = TRUE;
      break;

    case AGT_FIXED_FONT:
      gagt_current_attribute_set.fixed = TRUE;
      break;

    case AGT_VARIABLE_FONT:
      gagt_current_attribute_set.fixed = FALSE;
      break;

    case AGT_EMPHASIS:
      gagt_current_attribute_set.emphasis = TRUE;
      break;

    case AGT_DE_EMPHASIS:
      gagt_current_attribute_set.emphasis = FALSE;
      break;

    default:
      gagt_fatal ("GLK: Unknown color encountered");
      gagt_exit ();
    }

  gagt_debug ("agt_textcolor", "color=% d -> %d%s%s%s",
              color,
              gagt_current_attribute_set.color,
              gagt_current_attribute_set.blink ? " blink" : "",
              gagt_current_attribute_set.fixed ? " fixed" : "",
              gagt_current_attribute_set.emphasis ? " bold" : "");
}


/*
 * gagt_coerce_fixed_font()
 *
 * This coerces, or relaxes, a fixed font setting.  Used by box drawing, to
 * ensure that we get a temporary fixed font setting for known differenti-
 * ated parts of game output text.  Pass in TRUE to coerce fixed font, and
 * FALSE to relax it.
 */
static void
gagt_coerce_fixed_font (int coerce)
{
  gagt_coerced_fixed = coerce;
}


/*
 * gagt_pack_attributes()
 *
 * Pack a set of color and text rendering attributes into a single byte,
 * and return it.  This function is used so that a set of text attributes
 * can be encoded into a byte array that parallels the output strings that
 * we buffer from the interpreter.
 */
static unsigned char
gagt_pack_attributes (const gagt_attrset_t * attribute_set, int coerced)
{
  unsigned char packed;
  assert (attribute_set);

  /* Set the initial result to be color; these are the low bits. */
  assert ((attribute_set->color & ~GAGT_COLOR_MASK) == 0);
  packed = attribute_set->color;

  /*
   * Now OR in the text attributes settings, taking either the value for
   * fixed or the coerced fixed font.
   */
  packed |= attribute_set->blink ? GAGT_BLINK_MASK : 0;
  packed |= attribute_set->fixed || coerced ? GAGT_FIXED_MASK : 0;
  packed |= attribute_set->emphasis ? GAGT_EMPHASIS_MASK : 0;

  return packed;
}


/*
 * gagt_unpack_attributes()
 *
 * Unpack a set of packed current color and text rendering attributes from a
 * single byte, and return the result of unpacking.  This reconstitutes the
 * text attributes that were current at the time of packing.
 */
static void
gagt_unpack_attributes (unsigned char packed, gagt_attrset_t * attribute_set)
{
  assert (attribute_set);

  attribute_set->color = packed & GAGT_COLOR_MASK;
  attribute_set->blink = (packed & GAGT_BLINK_MASK) != 0;
  attribute_set->fixed = (packed & GAGT_FIXED_MASK) != 0;
  attribute_set->emphasis = (packed & GAGT_EMPHASIS_MASK) != 0;
}


/*
 * gagt_pack_current_attributes()
 *
 * Pack the current color and text rendering attributes into a single byte,
 * and return it.
 */
static unsigned char
gagt_pack_current_attributes (void)
{
  return gagt_pack_attributes (&gagt_current_attribute_set, gagt_coerced_fixed);
}


/*
 * gagt_init_user_styles()
 *
 * Attempt to set up two defined styles, User1 and User2, to represent
 * fixed font with AGT emphasis (rendered as Glk subheader), and fixed font
 * with AGT blink (rendered as Glk emphasis), respectively.
 *
 * The Glk stylehints here may not actually be honored by the Glk library.
 * We'll try to detect this later on.
 */
static void
gagt_init_user_styles (void)
{
  /*
   * Set User1 to be fixed width, bold, and not italic.  Here we're sort of
   * assuming that the style starts life equal to Normal.
   */
  glk_stylehint_set (wintype_TextBuffer, style_User1,
                     stylehint_Proportional, 0);
  glk_stylehint_set (wintype_TextBuffer, style_User1, stylehint_Weight, 1);
  glk_stylehint_set (wintype_TextBuffer, style_User1, stylehint_Oblique, 0);

  /*
   * Set User2 to be fixed width, normal, and italic, with the same
   * assumptions.
   */
  glk_stylehint_set (wintype_TextBuffer, style_User2,
                     stylehint_Proportional, 0);
  glk_stylehint_set (wintype_TextBuffer, style_User2, stylehint_Weight, 0);
  glk_stylehint_set (wintype_TextBuffer, style_User2, stylehint_Oblique, 1);
}


/*
 * gagt_confirm_appearance()
 *
 * Attempt to find out if a Glk style's on screen appearance matches a given
 * expectation.  There's a chance (often 100% with current Xglk) that we
 * can't tell, in which case we'll play safe, and say that it doesn't (our
 * caller is hoping it does).
 *
 * That is, when we return FALSE, we mean either it's not as expected, or we
 * don't know.
 */
static int
gagt_confirm_appearance (glui32 style, glui32 stylehint, glui32 expected)
{
  glui32 result;

  if (glk_style_measure (gagt_main_window, style, stylehint, &result))
    {
      /*
       * Measurement succeeded, so return TRUE if the result matches the
       * caller's expectation.
       */
      if (result == expected)
        return TRUE;
    }

  /* No straight answer, or the style's stylehint failed to match. */
  return FALSE;
}


/*
 * gagt_is_style_fixed()
 * gagt_is_style_bold()
 * gagt_is_style_oblique()
 *
 * Convenience functions for gagt_select_style().  A return of TRUE indicates
 * that the style has this attribute; FALSE indicates either that it hasn't,
 * or that it's not determinable.
 */
static int
gagt_is_style_fixed (glui32 style)
{
  return gagt_confirm_appearance (style, stylehint_Proportional, 0);
}

static int
gagt_is_style_bold (glui32 style)
{
  return gagt_confirm_appearance (style, stylehint_Weight, 1);
}

static int
gagt_is_style_oblique (glui32 style)
{
  return gagt_confirm_appearance (style, stylehint_Oblique, 1);
}


/*
 * gagt_select_style()
 *
 * Given a set of AGT text attributes, this function returns a Glk style that
 * is suitable (or more accurately, the best we can come up with) for render-
 * ing this set of attributes.
 *
 * For now, we ignore color totally, and just concentrate on the other attr-
 * ibutes.  This is because few, if any, games use color (no Photopia here),
 * few Glk libraries, at least on Linux, allow fine grained control over text
 * color, and even if you can get it, the scarcity of user-defined styles in
 * Glk makes it too painful to contemplate.
 */
static glui32
gagt_select_style (gagt_attrset_t * attribute_set)
{
  glui32 style;
  assert (attribute_set);

  /*
   * Glk styles are mutually exclusive, so here we'll work here by making a
   * precedence selection: AGT emphasis take precedence over AGT blinking,
   * which itself takes precedence over normal text.  Fortunately, few, if
   * any, AGT games set both emphasis and blinking (not likely to be a
   * pleasant combination).
   *
   * We'll try to map AGT emphasis to Glk Subheader, AGT blink to Glk
   * Emphasized, and normal text to Glk Normal, with modifications to this
   * for fixed width requests.
   *
   * First, then, see if emphasized text is requested in the attributes.
   */
  if (attribute_set->emphasis)
    {
      /*
       * Consider whether something requested a fixed width font or
       * disallowed a proportional one.
       *
       * Glk Preformatted is boring, flat, and lifeless.  It often offers no
       * fine grained control over emphasis, and so on.  So here we try to
       * find something better.  However, not all Glk libraries implement
       * stylehints, so we need to try to be careful to ensure that we get a
       * fixed width font, no matter what else we may miss out on.
       */
      if (attribute_set->fixed)
        {
          /*
           * To start off, we'll see if User1, the font we set up for fixed
           * width bold, really is fixed width and bold.  If it is, we'll
           * use it.
           *
           * If it isn't, we'll check Subheader.  Our Glk library probably
           * isn't implementing stylehints, but if Subheader is fixed width,
           * it may provide a better look than Preformatted -- certainly
           * it's worth a go.
           *
           * If Subheader isn't fixed width, we'll take another look at User1.
           * It could be that the check for bold wasn't definitive, but it
           * is nevertheless bold.  So check for fixed width -- if set, it's
           * probably good enough to use this font, certainly no worse than
           * Preformatted.
           *
           * If Subheader isn't guaranteed fixed width, nor is User1, we're
           * cornered into Preformatted.
           */
          if (gagt_is_style_fixed (style_User1)
              && gagt_is_style_bold (style_User1))
            style = style_User1;

          else if (gagt_is_style_fixed (style_Subheader))
            style = style_Subheader;

          else if (gagt_is_style_fixed (style_User1))
            style = style_User1;

          else
            style = style_Preformatted;
        }
      else
        /* This is the easy case, use Subheader. */
        style = style_Subheader;
    }
  else if (attribute_set->blink)
    {
      /*
       * Again, consider whether something requested a fixed width
       * font or disallowed a proportional one.
       */
      if (attribute_set->fixed)
        {
          /*
           * As above, try to find something better than Preformatted, first
           * trying User2, then Emphasized, then User2 again, and finally
           * settling for Preformatted if neither of these two looks any
           * better.
           */
          if (gagt_is_style_fixed (style_User2)
              && gagt_is_style_oblique (style_User2))
            style = style_User2;

          else if (gagt_is_style_fixed (style_Emphasized))
            style = style_Emphasized;

          else if (gagt_is_style_fixed (style_User2))
            style = style_User2;

          else
            style = style_Preformatted;
        }
      else
        /* This is the easy case, use Emphasized. */
        style = style_Emphasized;
    }
  else
    {
      /*
       * There's no emphasis or blinking in the attributes.  In this case,
       * use Preformatted for fixed width, and Normal for text that can be
       * rendered proportionally.
       */
      if (attribute_set->fixed)
        style = style_Preformatted;
      else
        style = style_Normal;
    }

  return style;
}


/*---------------------------------------------------------------------*/
/*  Glk port output buffering functions                                */
/*---------------------------------------------------------------------*/

/*
 * Buffering game output happens at two levels.  The first level is a single
 * line buffer, used to catch text sent to us with agt_puts().  In parallel
 * with the text strings, we keep and buffer the game text attributes, as
 * handed to agt_textcolor(), that are in effect at the time the string is
 * handed to us, packed for brevity.
 *
 * As each line is completed, by a call to agt_newline(), this single line
 * buffer is transferred to a main text page buffer.  The main page buffer
 * has places in it where we can assign paragraph, font hints, and perhaps
 * other marker information to a line.  Initially unset, they're filled in
 * at the point where we need to display the buffer.
 */

/*
 * Definition of font hints values.  Font hints may be:
 *   o none, for lines not in a definite paragraph;
 *   o proportional, for lines that can probably be safely rendered in a
 *     proportional font (if the AGT game text attributes allow it) and
 *     where the newline may be replaced by a space;
 *   o proportional_newline, for lines that may be rendered using a
 *     proportional font, but where the newline looks like it matters;
 *   o proportional_newline_standout, for proportional_newline lines that
 *     are also standout (for spacing in display functions);
 *   o fixed_width, for tables and other text that looks like it is a
 *     candidate for fixed font output.
 */
typedef enum {
  HINT_NONE,
  HINT_PROPORTIONAL,
  HINT_PROPORTIONAL_NEWLINE,
  HINT_PROPORTIONAL_NEWLINE_STANDOUT,
  HINT_FIXED_WIDTH
} gagt_font_hint_t;

/* Magic number used to ensure a pointer points to a page buffer line. */
static const unsigned int GAGT_LINE_MAGIC = 0x5bc14482;

/*
 * Definition of a single line buffer.  This is a growable string and a
 * parallel growable attributes array.  The string is buffered without any
 * null terminator -- not needed since we retain length.
 */
typedef struct {
  unsigned char *data;        /* Buffered character data. */
  unsigned char *attributes;  /* Parallel character attributes, packed. */
  int allocation;             /* Bytes allocated to each of the above. */
  int length;                 /* Amount of data actually buffered. */
} gagt_string_t;
typedef gagt_string_t * gagt_stringref_t;

/*
 * Definition of a page buffer entry.  This is a structure that holds the
 * the result of a single line buffer above, plus additional areas that
 * describe line text positioning, a blank line flag, a paragraph pointer
 * (NULL if not in a paragraph), and a font hint.
 */
typedef struct gagt_line_s *gagt_lineref_t;
typedef struct gagt_paragraph_s *gagt_paragraphref_t;

struct gagt_line_s
{
  unsigned int magic;             /* Assertion check dog-tag. */

  gagt_string_t buffer;           /* Buffered line string data. */

  int indent;                     /* Line indentation. */
  int outdent;                    /* Trailing line whitespace. */
  int real_length;                /* Real line length. */
  int is_blank;                   /* Line blank flag. */
  int is_hyphenated;              /* Line hyphenated flag. */

  gagt_paragraphref_t paragraph;  /* Paragraph containing the line. */
  gagt_font_hint_t font_hint;     /* Line's font hint. */

  gagt_lineref_t next;            /* List next element. */
  gagt_lineref_t prior;           /* List prior element. */
};

/*
 * Definition of the actual page buffer.  This is a doubly-linked list of
 * lines, with a tail pointer to facilitate adding entries at the end.
 */
static gagt_lineref_t gagt_page_head = NULL,
                      gagt_page_tail = NULL;

/*
 * Definition of the current output line; this one is appended to on
 * agt_puts(), and transferred into the page buffer on agt_newline().
 */
static gagt_string_t gagt_current_buffer = { NULL, NULL, 0, 0 };

/*
 * gagt_string_append()
 * gagt_string_transfer()
 * gagt_string_free()
 *
 * String append, move, and allocation free for string_t buffers.
 */
static void
gagt_string_append (gagt_stringref_t buffer, const char *string,
                    unsigned char packed_attributes)
{
  int length, bytes;

  /*
   * Find the size we'll need from the line buffer to add this string,
   * and grow buffer if necessary.
   */
  length = strlen (string);
  for (bytes = buffer->allocation; bytes < buffer->length + length; )
    bytes = bytes == 0 ? 1 : bytes << 1;

  if (bytes > buffer->allocation)
    {
      buffer->data = gagt_realloc (buffer->data, bytes);
      buffer->attributes = gagt_realloc (buffer->attributes, bytes);

      buffer->allocation = bytes;
    }

  /* Add string to the line buffer, and store packed text attributes. */
  memcpy (buffer->data + buffer->length, string, length);
  memset (buffer->attributes + buffer->length, packed_attributes, length);

  buffer->length += length;
}

static void
gagt_string_transfer (gagt_stringref_t from, gagt_stringref_t to)
{
  *to = *from;
  from->data = from->attributes = NULL;
  from->allocation = from->length = 0;
}

static void
gagt_string_free (gagt_stringref_t buffer)
{
  free (buffer->data);
  free (buffer->attributes);
  buffer->data = buffer->attributes = NULL;
  buffer->allocation = buffer->length = 0;
}


/*
 * gagt_get_string_indent()
 * gagt_get_string_outdent()
 * gagt_get_string_real_length()
 * gagt_is_string_blank()
 * gagt_is_string_hyphenated()
 *
 * Metrics functions for string_t buffers.
 */
static int
gagt_get_string_indent (const gagt_stringref_t buffer)
{
  int indent, index;

  indent = 0;
  for (index = 0;
       index < buffer->length && isspace (buffer->data[index]);
       index++)
    indent++;

  return indent;
}

static int
gagt_get_string_outdent (const gagt_stringref_t buffer)
{
  int outdent, index;

  outdent = 0;
  for (index = buffer->length - 1;
       index >= 0 && isspace (buffer->data[index]); index--)
    outdent++;

  return outdent;
}


static int
gagt_get_string_real_length (const gagt_stringref_t buffer)
{
  int indent, outdent;

  indent = gagt_get_string_indent (buffer);
  outdent = gagt_get_string_outdent (buffer);

  return indent == buffer->length ? 0 : buffer->length - indent - outdent;
}

static int
gagt_is_string_blank (const gagt_stringref_t buffer)
{
  return gagt_get_string_indent (buffer) == buffer->length;
}

static int
gagt_is_string_hyphenated (const gagt_stringref_t buffer)
{
  int is_hyphenated;

  is_hyphenated = FALSE;

  if (!gagt_is_string_blank (buffer)
      && gagt_get_string_real_length (buffer) > 1)
    {
      int last;

      last = buffer->length - gagt_get_string_outdent (buffer) - 1;

      if (buffer->data[last] == '-')
        {
          if (isalpha (buffer->data[last - 1]))
            is_hyphenated = TRUE;
        }
    }

  return is_hyphenated;
}


/*
 * gagt_output_delete()
 *
 * Delete all buffered page and line text.  Free all malloc'ed buffer memory,
 * and return the buffer variables to their initial values.
 */
static void
gagt_output_delete (void)
{
  gagt_lineref_t line, next_line;

  for (line = gagt_page_head; line; line = next_line)
    {
      assert (line->magic == GAGT_LINE_MAGIC);
      next_line = line->next;

      gagt_string_free (&line->buffer);

      memset (line, 0, sizeof (*line));
      free (line);
    }

  gagt_page_head = gagt_page_tail = NULL;

  gagt_string_free (&gagt_current_buffer);
}


/*
 * agt_puts()
 *
 * Buffer the string passed in into our current single line buffer.  The
 * function converts to ISO 8859 Latin-1 encoding before buffering.
 */
void
agt_puts (const char *cp_string)
{
  assert (cp_string);

  if (!BATCH_MODE)
    {
      char *iso_string;
      unsigned char packed;
      int length;

      /* Update the apparent (virtual) window x position. */
      length = strlen (cp_string);
      curr_x += length;

      /*
       * Convert the buffer from IBM cp 437 to Glk's ISO 8859 Latin-1, and
       * add string and packed text attributes to the current line buffer.
       */
      iso_string = gagt_malloc (length + 1);
      gagt_cp_to_iso (cp_string, iso_string);
      packed = gagt_pack_current_attributes ();
      gagt_string_append (&gagt_current_buffer, iso_string, packed);

      /* Add the string to any script file. */
      if (script_on)
        textputs (scriptfile, iso_string);

      free (iso_string);
      gagt_debug ("agt_puts", "string='%s'", cp_string);
    }
}


/*
 * agt_newline()
 *
 * Accept a newline to the main window.  Our job here is to append the
 * current line buffer to the page buffer, and clear the line buffer to
 * begin accepting new text.
 */
void
agt_newline (void)
{
  if (!BATCH_MODE)
    {
      gagt_lineref_t line;

      /* Update the apparent (virtual) window x position. */
      curr_x = 0;

      /* Create a new line entry for the page buffer. */
      line = gagt_malloc (sizeof (*line));
      line->magic = GAGT_LINE_MAGIC;

      /* Move the line from the line buffer into the page buffer. */
      gagt_string_transfer (&gagt_current_buffer, &line->buffer);

      /* Fill in the line buffer metrics. */
      line->indent = gagt_get_string_indent (&line->buffer);
      line->outdent = gagt_get_string_outdent (&line->buffer);
      line->real_length = gagt_get_string_real_length (&line->buffer);
      line->is_blank = gagt_is_string_blank (&line->buffer);
      line->is_hyphenated = gagt_is_string_hyphenated (&line->buffer);

      /* For now, default the remaining page buffer fields for the line. */
      line->paragraph = NULL;
      line->font_hint = HINT_NONE;

      /* Add to the list, creating a new list if necessary. */
      line->next = NULL;
      line->prior = gagt_page_tail;
      if (gagt_page_head)
        gagt_page_tail->next = line;
      else
        gagt_page_head = line;
      gagt_page_tail = line;

      /* Add a newline to any script file. */
      if (script_on)
        textputs (scriptfile, "\n");

      gagt_debug ("agt_newline", "");
    }
}


/*
 * gagt_get_first_page_line()
 * gagt_get_next_page_line()
 * gagt_get_prior_page_line()
 *
 * Iterator functions for the page buffer.  These functions return the first
 * line from the page buffer, the next line, or the previous line, given a
 * line, respectively.  They return NULL if no lines, or no more lines, are
 * available.
 */
static gagt_lineref_t
gagt_get_first_page_line (void)
{
  gagt_lineref_t line;

  line = gagt_page_head;
  assert (!line || line->magic == GAGT_LINE_MAGIC);
  return line;
}

static gagt_lineref_t
gagt_get_next_page_line (const gagt_lineref_t line)
{
  gagt_lineref_t next_line;
  assert (line && line->magic == GAGT_LINE_MAGIC);

  next_line = line->next;
  assert (!next_line || next_line->magic == GAGT_LINE_MAGIC);
  return next_line;
}

static gagt_lineref_t
gagt_get_prior_page_line (const gagt_lineref_t line)
{
  gagt_lineref_t prior_line;
  assert (line && line->magic == GAGT_LINE_MAGIC);

  prior_line = line->prior;
  assert (!prior_line || prior_line->magic == GAGT_LINE_MAGIC);
  return prior_line;
}


/*---------------------------------------------------------------------*/
/*  Glk port paragraphing functions and data                           */
/*---------------------------------------------------------------------*/

/* Magic number used to ensure a pointer points to a paragraph. */
static const unsigned int GAGT_PARAGRAPH_MAGIC = 0xb9a2297b;

/* Forward definition of special paragraph reference. */
typedef const struct gagt_special_s *gagt_specialref_t;

/*
 * Definition of a paragraph entry.  This is a structure that holds a
 * pointer to the first line buffer in the paragraph.
 */
struct gagt_paragraph_s
{
  unsigned int magic;             /* Assertion check dog-tag. */

  gagt_lineref_t first_line;      /* First line in the paragraph. */
  gagt_specialref_t special;      /* Special paragraph entry. */

  int line_count;                 /* Number of lines in the paragraph. */
  int id;                         /* Paragraph id, sequence, for debug only. */

  gagt_paragraphref_t next;       /* List next element. */
  gagt_paragraphref_t prior;      /* List prior element. */
};

/*
 * A doubly-linked list of paragraphs, with a tail pointer to facilitate
 * adding entries at the end.
 */
static gagt_paragraphref_t gagt_paragraphs_head = NULL,
                           gagt_paragraphs_tail = NULL;

/*
 * gagt_paragraphs_delete()
 *
 * Delete paragraphs held in the list.  This function doesn't delete the
 * page buffer lines, just the paragraphs describing the page.
 */
static void
gagt_paragraphs_delete (void)
{
  gagt_paragraphref_t paragraph, next_paragraph;

  for (paragraph = gagt_paragraphs_head; paragraph; paragraph = next_paragraph)
    {
      assert (paragraph->magic == GAGT_PARAGRAPH_MAGIC);
      next_paragraph = paragraph->next;

      memset (paragraph, 0, sizeof (*paragraph));
      free (paragraph);
    }

  gagt_paragraphs_head = gagt_paragraphs_tail = NULL;
}


/*
 * gagt_find_paragraph_start()
 *
 * Find and return the next non-blank line in the page buffer, given a start
 * point.  Returns NULL if there are no more blank lines.
 */
static gagt_lineref_t
gagt_find_paragraph_start (const gagt_lineref_t begin)
{
  gagt_lineref_t line, match;

  /*
   * Advance line to the beginning of the next paragraph, stopping on the
   * first non-blank line, or at the end of the page buffer.
   */
  match = NULL;
  for (line = begin; line; line = gagt_get_next_page_line (line))
    {
      if (!line->is_blank)
        {
          match = line;
          break;
        }
    }

  return match;
}


/*
 * gagt_find_block_end()
 * gagt_find_blank_line_block_end()
 *
 * Find and return the apparent end of a paragraph from the page buffer,
 * given a start point, and an indentation reference.  The end is either
 * the point where indentation returns to the reference indentation, or
 * the next blank line.
 *
 * Indentation reference can be -1, indicating that only the next blank
 * line will end the paragraph.  Indentation references less than 1 are
 * also ignored.
 */
static gagt_lineref_t
gagt_find_block_end (const gagt_lineref_t begin, int indent)
{
  gagt_lineref_t line, match;

  /*
   * Initialize the match to be the start of the block, then advance line
   * until we hit a blank line or the end of the page buffer.  At this point,
   * match contains the last line checked.
   */
  match = begin;
  for (line = begin; line; line = gagt_get_next_page_line (line))
    {
      /*
       * Found if we reach a blank line, or when given an indentation to
       * check for, we find it.
       */
      if (line->is_blank || (indent > 0 && line->indent == indent))
        break;

      match = line;
    }

  return match;
}

static gagt_lineref_t
gagt_find_blank_line_block_end (const gagt_lineref_t begin)
{
  return gagt_find_block_end (begin, -1);
}


/*
 * gagt_find_paragraph_end()
 *
 * Find and return the apparent end of a paragraph from the page buffer,
 * given a start point.  The function attempts to recognize paragraphs by
 * the "shape" of indentation.
 */
static gagt_lineref_t
gagt_find_paragraph_end (const gagt_lineref_t first_line)
{
  gagt_lineref_t second_line;

  /*
   * If the start line is the last line in the buffer, or if the next line
   * is a blank line, return the start line as also being the end of the
   * paragraph.
   */
  second_line = gagt_get_next_page_line (first_line);
  if (!second_line || second_line->is_blank)
    {
      return first_line;
    }

  /*
   * Time to look at line indentations...
   *
   * If either line is grossly indented, forget about trying to infer
   * anything from this, and just break the paragraph on the next blank line.
   */
  if (first_line->indent > screen_width / 4
      || second_line->indent > screen_width / 4)
    {
      return gagt_find_blank_line_block_end (second_line);
    }

  /*
   * If the first line is indented more than the second, end the paragraph
   * on a blank line, or on a return in indentation to the level of the
   * first line.  Here we're looking for paragraphs with the shape
   *
   *     aksjdj jfkasjd fjkasjd ajksdj fkaj djf akjsd fkjas dfs
   * kasjdlkfjkj fj aksd jfjkasj dlkfja skjdk flaks dlf jalksdf
   * ksjdf kjs kdf lasjd fkjalks jdfkjasjd flkjasl djfkasjfdkl
   */
  else if (first_line->indent > second_line->indent)
    {
      return gagt_find_block_end (second_line, first_line->indent);
    }

  /*
   * If the second line is more indented than the first, this may indicate
   * a title line, followed by normal indented paragraphing.  In this case,
   * use the second line indentation as the reference, and begin searching
   * at the next line.  This finds
   *
   * ksjdkfjask ksadf
   *     kajskd fksjkfj jfkj jfkslaj fksjlfj jkjskjlfa j fjksal
   * sjkkdjf sj fkjkajkdlfj lsjak dfjk djkfjskl dklf alks dfll
   * fjksja jkj dksja kjdk kaj dskfj aksjdf aksjd kfjaks fjks
   *
   * and
   *
   * asdfj kjsdf kjs
   *     akjsdkj fkjs kdjfa lskjdl fjalsj dlfjksj kdj fjkd jlsjd
   *     jalksj jfk slj lkfjsa lkjd lfjlaks dlfkjals djkj alsjd
   *     kj jfksj fjksjl alkjs dlkjf lakjsd fkjas ldkj flkja fsd
   */
  else if (second_line->indent > first_line->indent)
    {
      gagt_lineref_t third_line;

      /*
       * See if we have a third buffer line to look at.  If we don't, or if
       * we do but it's blank, the paragraph ends here.
       */
      third_line = gagt_get_next_page_line (second_line);
      if (!third_line || third_line->is_blank)
        {
          return second_line;
        }

      /* As above, give up on gross indentation. */
      if (second_line->indent > screen_width / 4
          || third_line->indent > screen_width / 4)
        {
          return gagt_find_blank_line_block_end (third_line);
        }

      /*
       * If the second line indentation exceeds the third, this is probably
       * a paragraph with a title line.  In this case, end the paragraph on
       * a return to the indentation of the second line.  If not, just find
       * the next blank line.
       */
      else if (second_line->indent > third_line->indent)
        {
          return gagt_find_block_end (third_line, second_line->indent);
        }
      else
        {
          return gagt_find_blank_line_block_end (third_line);
        }
    }

  /*
   * Otherwise, the first and second line indentations are the same, so
   * break only on the next empty line.  This finds the simple
   *
   * ksd kjal jdljf lakjsd lkj lakjsdl jfla jsldj lfaksdj fksj
   * lskjd fja kjsdlk fjlakjs ldkjfksj lkjdf kjalskjd fkjklal
   * skjd fkaj djfkjs dkfjal sjdlkfj alksjdf lkajs ldkjf alljjf
   */
  else
    {
      assert (second_line->indent == first_line->indent);
      return gagt_find_blank_line_block_end (second_line);
    }
}


/*
 * gagt_paragraph_page()
 *
 * This function breaks the page buffer into what appear to be paragraphs,
 * based on observations of indentation and blank separator lines.
 */
static void
gagt_paragraph_page (void)
{
  gagt_lineref_t start;

  assert (!gagt_paragraphs_head && !gagt_paragraphs_tail);

  /* Find the start of the first paragraph. */
  start = gagt_find_paragraph_start (gagt_get_first_page_line ());
  while (start)
    {
      gagt_paragraphref_t paragraph;
      gagt_lineref_t end, line;

      /* Create a new paragraph entry. */
      paragraph = gagt_malloc (sizeof (*paragraph));
      paragraph->magic = GAGT_PARAGRAPH_MAGIC;
      paragraph->first_line = start;
      paragraph->special = NULL;
      paragraph->line_count = 1;
      paragraph->id = gagt_paragraphs_tail ? gagt_paragraphs_tail->id + 1 : 0;

      /* Add to the list, creating a new list if necessary. */
      paragraph->next = NULL;
      paragraph->prior = gagt_paragraphs_tail;
      if (gagt_paragraphs_head)
        gagt_paragraphs_tail->next = paragraph;
      else
        gagt_paragraphs_head = paragraph;
      gagt_paragraphs_tail = paragraph;

      /* From the start, identify the paragraph end. */
      end = gagt_find_paragraph_end (start);

      /*
       * Set paragraph in each line identified as part of this paragraph,
       * and increment the paragraph's line count.
       */
      for (line = start;
           line != end; line = gagt_get_next_page_line (line))
        {
          line->paragraph = paragraph;
          paragraph->line_count++;
        }
      end->paragraph = paragraph;

      /*
       * If there's another line, look for the next paragraph there,
       * otherwise we're done.
       */
      line = gagt_get_next_page_line (end);
      if (line)
        start = gagt_find_paragraph_start (line);
      else
        start = NULL;
    }
}


/*
 * gagt_get_first_paragraph()
 * gagt_get_next_paragraph()
 *
 * Iterator functions for the paragraphs list.
 */
static gagt_paragraphref_t
gagt_get_first_paragraph (void)
{
  gagt_paragraphref_t paragraph;

  paragraph = gagt_paragraphs_head;
  assert (!paragraph || paragraph->magic == GAGT_PARAGRAPH_MAGIC);
  return paragraph;
}

static gagt_paragraphref_t
gagt_get_next_paragraph (const gagt_paragraphref_t paragraph)
{
  gagt_paragraphref_t next_paragraph;
  assert (paragraph && paragraph->magic == GAGT_PARAGRAPH_MAGIC);

  next_paragraph = paragraph->next;
  assert (!next_paragraph || next_paragraph->magic == GAGT_PARAGRAPH_MAGIC);
  return next_paragraph;
}


/*
 * gagt_get_first_paragraph_line()
 * gagt_get_next_paragraph_line()
 * gagt_get_prior_paragraph_line()
 *
 * Iterator functions for the page buffer.  These functions implement a
 * paragraph-based view of the page buffer.
 *
 * The functions find the first line of a given paragraph; given a line,
 * the next line in the same paragraph, or NULL if line is the last para-
 * graph line (or the last line in the page buffer); and given a line,
 * the previous line in the same paragraph, or NULL if line is the first
 * paragraph line (or the first line in the page buffer).
 */
static gagt_lineref_t
gagt_get_first_paragraph_line (const gagt_paragraphref_t paragraph)
{
  assert (paragraph && paragraph->magic == GAGT_PARAGRAPH_MAGIC);

  /* Return the first line for the requested paragraph. */
  return paragraph->first_line;
}

static gagt_lineref_t
gagt_get_next_paragraph_line (const gagt_lineref_t line)
{
  gagt_lineref_t next_line;

  /* Get the next line; return it if the paragraph matches, else NULL. */
  next_line = gagt_get_next_page_line (line);
  if (next_line && next_line->paragraph == line->paragraph)
    return next_line;
  else
    return NULL;
}

static gagt_lineref_t
gagt_get_prior_paragraph_line (const gagt_lineref_t line)
{
  gagt_lineref_t prior_line;

  /* Get the previous line; return it if the paragraph matches, else NULL. */
  prior_line = gagt_get_prior_page_line (line);
  if (prior_line && prior_line->paragraph == line->paragraph)
    return prior_line;
  else
    return NULL;
}


/*
 * gagt_get_paragraph_line_count()
 *
 * Return the count of lines contained in the paragraph.
 */
static int
gagt_get_paragraph_line_count (const gagt_paragraphref_t paragraph)
{
  assert (paragraph && paragraph->magic == GAGT_PARAGRAPH_MAGIC);

  return paragraph->line_count;
}


/*---------------------------------------------------------------------*/
/*  Glk port page buffer analysis functions                            */
/*---------------------------------------------------------------------*/

/*
 * Threshold for consecutive punctuation/spaces before we decide that a line
 * is in fact part of a table, and a small selection of characters to apply
 * a somewhat larger threshold to when looking for punctuation (typically,
 * characters that appear together multiple times in non-table text).
 */
static const int GAGT_THRESHOLD = 4,
                 GAGT_COMMON_THRESHOLD = 8;
static const char * const GAGT_COMMON_PUNCTUATION = ".!?";


/*
 * gagt_line_is_standout()
 *
 * Return TRUE if a page buffer line appears to contain "standout" text.
 * This is one of:
 *    - a line where all characters have some form of AGT text attribute
 *      set (blinking, fixed width font, or emphasis),
 *    - a line where each alphabetical character is uppercase.
 * Typically, this describes room and other miscellaneous header lines.
 */
static int
gagt_line_is_standout (const gagt_lineref_t line)
{
  int index, all_formatted, upper_count, lower_count;

  /*
   * Look at the line, for cases where all characters in it have AGT font
   * attributes, and counting the upper and lower case characters.  Iterate
   * over only the significant characters in the string.
   */
  all_formatted = TRUE;
  upper_count = lower_count = 0;
  for (index = line->indent;
       index < line->buffer.length - line->outdent; index++)
    {
      gagt_attrset_t attribute_set;
      unsigned char character;

      gagt_unpack_attributes (line->buffer.attributes[index], &attribute_set);
      character = line->buffer.data[index];

      /*
       * If no AGT attribute is set for this character, then not all of the
       * line is standout text.  In this case, reset the all_formatted flag.
       */
      if (!(attribute_set.blink
            || attribute_set.fixed || attribute_set.emphasis))
        all_formatted = FALSE;

      /* Count upper and lower case characters. */
      if (islower (character))
        lower_count++;
      else if (isupper (character))
        upper_count++;
    }

  /*
   * Consider standout if every character was formatted, or if the string
   * is all uppercase.
   */
  return all_formatted || (upper_count > 0 && lower_count == 0);
}


/*
 * gagt_set_font_hint_proportional()
 * gagt_set_font_hint_proportional_newline()
 * gagt_set_font_hint_fixed_width()
 *
 * Helpers for assigning font hints.  Font hints have strengths, and these
 * functions ensure that gagt_assign_paragraph_font_hints() only increases
 * strengths, and doesn't need to worry about checking before setting.  In
 * the case of newline, the function also adds standout to the font hint if
 * appropriate.
 */
static void
gagt_set_font_hint_proportional (gagt_lineref_t line)
{
  /* The only weaker hint than proportional is none. */
  if (line->font_hint == HINT_NONE)
    line->font_hint = HINT_PROPORTIONAL;
}

static void
gagt_set_font_hint_proportional_newline (gagt_lineref_t line)
{
  /*
   * Proportional and none are weaker than newline.  Because of the way we
   * set font hints, this function can't be called with a current line hint
   * of proportional newline.
   */
  if (line->font_hint == HINT_NONE || line->font_hint == HINT_PROPORTIONAL)
    {
      if (gagt_line_is_standout (line))
        line->font_hint = HINT_PROPORTIONAL_NEWLINE_STANDOUT;
      else
        line->font_hint = HINT_PROPORTIONAL_NEWLINE;
    }
}

static void
gagt_set_font_hint_fixed_width (gagt_lineref_t line)
{
  /* Fixed width font is the strongest hint. */
  if (line->font_hint == HINT_NONE
      || line->font_hint == HINT_PROPORTIONAL
      || line->font_hint == HINT_PROPORTIONAL_NEWLINE
      || line->font_hint == HINT_PROPORTIONAL_NEWLINE_STANDOUT)
    line->font_hint = HINT_FIXED_WIDTH;
}


/*
 * gagt_assign_paragraph_font_hints()
 *
 * For a given paragraph in the page buffer, this function looks at the text
 * style used, and assigns a font hint value to each line.  Font hints
 * indicate whether the line probably requires fixed width font, or may be
 * okay in variable width, and for lines that look like they might be okay
 * in variable width, whether the newline should probably be rendered at the
 * end of the line, or if it might be omitted.
 */
static void
gagt_assign_paragraph_font_hints (const gagt_paragraphref_t paragraph)
{
  static int is_initialized = FALSE;
  static int threshold[UCHAR_MAX + 1];

  gagt_lineref_t line, first_line;
  int is_table, in_list;
  assert (paragraph);

  /* On first call, set up the table on punctuation run thresholds. */
  if (!is_initialized)
    {
      int character;

      for (character = 0; character <= UCHAR_MAX; character++)
        {
          /*
           * Set the threshold, either a normal value, or a larger one for
           * punctuation characters that tend to have consecutive runs in
           * non-table text.
           */
          if (ispunct (character))
            {
              threshold[character] = strchr (GAGT_COMMON_PUNCTUATION, character)
                                     ? GAGT_COMMON_THRESHOLD : GAGT_THRESHOLD;
            }
        }

      is_initialized = TRUE;
    }

  /*
   * Note the first paragraph line.  This value is commonly used, and under
   * certain circumstances, it's also modified later on.
   */
  first_line = gagt_get_first_paragraph_line (paragraph);
  assert (first_line);

  /*
   * Phase 1 -- look for pages that consist of just one paragraph,
   *            itself consisting of only one line.
   *
   * There is no point in attempting alignment of text in a one paragraph,
   * one line page.  This would be, for example, an error message from the
   * interpreter parser.  In this case, set the line for proportional with
   * newline, and return immediately.
   */
  if (gagt_get_first_paragraph () == paragraph
      && !gagt_get_next_paragraph (paragraph)
      && !gagt_get_next_paragraph_line (first_line))
    {
      /*
       * Set the first paragraph line for proportional with a newline, and
       * return.
       */
      gagt_set_font_hint_proportional_newline (first_line);
      return;
    }

  /*
   * Phase 2 -- try to identify paragraphs that are tables, based on
   *            looking for runs of punctuation.
   *
   * Search for any string that has a run of apparent line drawing or other
   * formatting characters in it.  If we find one, we'll consider the
   * paragraph to be a "table", that is, it has some quality that we might
   * destroy if we used a proportional font.
   */
  is_table = FALSE;
  for (line = first_line;
       line && !is_table; line = gagt_get_next_paragraph_line (line))
    {
      int index, counts[UCHAR_MAX + 1], total_counts;

      /*
       * Clear the initial counts.  Using memset() here is an order of
       * magnitude or two faster than a for-loop.  Also there's a total count
       * to detect when counts needs to be recleared, or is already clear.
       */
      memset (counts, 0, sizeof (counts));
      total_counts = 0;

      /*
       * Count consecutive punctuation in the line, excluding the indentation
       * and outdent.
       */
      for (index = line->indent;
           index < line->buffer.length - line->outdent && !is_table; index++)
        {
          int character;
          character = line->buffer.data[index];

          /* Test this character for punctuation. */
          if (ispunct (character))
            {
              /*
               * Increment the count for this character, and note that
               * counts are no longer empty, then compare against threshold.
               */
              counts[character]++;
              total_counts++;

              is_table = (counts[character] >= threshold[character]);
            }
          else
            {
              /*
               * Re-clear all counts, again with memset() for speed, but only
               * if they need clearing.  As they often won't, this optimization
               * saves quite a bit of work.
               */
              if (total_counts > 0)
                {
                  memset (counts, 0, sizeof (counts));
                  total_counts = 0;
                }
            }
        }
    }

  /*
   * Phase 3 -- try again to identify paragraphs that are tables, based
   *            this time on looking for runs of whitespace.
   *
   * If no evidence found so far, look again, this time searching for any
   * run of four or more spaces on the line (excluding any lead-in or
   * trailing spaces).
   */
  if (!is_table)
    {
      for (line = first_line;
           line && !is_table; line = gagt_get_next_paragraph_line (line))
        {
          int index, count;

          /*
           * Count consecutive spaces in the line, excluding the indentation
           * and outdent.
           */
          count = 0;
          for (index = line->indent;
               index < line->buffer.length - line->outdent && !is_table;
               index++)
            {
              int character;
              character = line->buffer.data[index];

              if (isspace (character))
                {
                  count++;
                  is_table = (count >= GAGT_THRESHOLD);
                }
              else
                count = 0;
            }
        }
    }

  /*
   * If the paragraph appears to be a table, and if it consists of more than
   * just a single line, mark all lines as fixed font output and return.
   */
  if (is_table && gagt_get_next_paragraph_line (first_line))
    {
      for (line = first_line;
           line; line = gagt_get_next_paragraph_line (line))
        {
          gagt_set_font_hint_fixed_width (line);
        }

      /* Nothing more to do. */
      return;
    }

  /*
   * Phase 4 -- consider separating the first line from the rest of
   *            the paragraph.
   *
   * Not a table, so the choice is between proportional rendering with a
   * newline, and proportional rendering without...
   *
   * If the first paragraph line is standout or short, render it pro-
   * portionally with a newline, and don't consider it as a further part of
   * the paragraph.
   */
  if (gagt_line_is_standout (first_line)
      || first_line->real_length < screen_width / 2)
    {
      /* Set the first paragraph line for a newline. */
      gagt_set_font_hint_proportional_newline (first_line);

      /*
       * Disassociate this line from the rest of the paragraph by moving on
       * the value of the first_line variable.  If it turns out that there
       * is no next paragraph line, then we have a one-line paragraph, and
       * there's no more to do.
       */
      first_line = gagt_get_next_paragraph_line (first_line);
      if (!first_line)
        return;
    }

  /*
   * Phase 5 -- try to identify lists by a simple initial look at line
   *            indentations.
   *
   * Look through the paragraph for apparent lists, and decide for each
   * line whether it's appropriate to output a newline, and render
   * proportionally, or just render proportionally.
   *
   * After this loop, each line will have some form of font hint assigned
   * to it.
   */
  in_list = FALSE;
  for (line = first_line;
       line; line = gagt_get_next_paragraph_line (line))
    {
      gagt_lineref_t next_line;

      next_line = gagt_get_next_paragraph_line (line);

      /*
       * Special last-iteration processing.  The newline is always output at
       * the end of a paragraph, so if there isn't a next line, then this
       * line is the last paragraph line.  Set its font hint appropriately,
       * and do no more for the line.
       */
      if (!next_line)
        {
          gagt_set_font_hint_proportional_newline (line);
          continue;
        }

      /*
       * If the next line's indentation is deeper that that of the first
       * line, this paragraph looks like it is trying to be some form of a
       * list.  In this case, make newline significant for the current line,
       * and set the in_list flag so we can delay the return to proportional
       * by one line.  On return to first line indentation, make newline
       * significant for the return line.
       */
      if (next_line->indent > first_line->indent)
        {
          gagt_set_font_hint_proportional_newline (line);
          in_list = TRUE;
        }
      else
        {
          if (in_list)
            gagt_set_font_hint_proportional_newline (line);
          else
            gagt_set_font_hint_proportional (line);
          in_list = FALSE;
        }
    }

  /*
   * Phase 6 -- look again for lines that look like they are supposed
   *            to stand out from their neighbors.
   *
   * Now rescan the paragraph, looking this time for lines that stand out
   * from their neighbours.  Make newline significant for each such line,
   * and the line above, if there is one.
   *
   * Here we split the loop on lines so that we avoid looking at the prior
   * line of the current first line -- because of "adjustments", it may not
   * be the real paragraph first line.
   *
   * So, deal with the current first line...
   */
  if (gagt_line_is_standout (first_line))
    {
      /* Make newline significant for this line. */
      gagt_set_font_hint_proportional_newline (first_line);
    }

  /* ... then deal with the rest of the lines, looking for standouts. */
  for (line = gagt_get_next_paragraph_line (first_line);
       line; line = gagt_get_next_paragraph_line (line))
    {
      if (gagt_line_is_standout (line))
        {
          gagt_lineref_t prior_line;

          /* Make newline significant for this line. */
          gagt_set_font_hint_proportional_newline (line);

          /*
           * Make newline significant for the line above.  There will always
           * be one because we start the loop past the first line.
           */
          prior_line = gagt_get_prior_paragraph_line (line);
          gagt_set_font_hint_proportional_newline (prior_line);
        }
    }

  /*
   * Phase 7 -- special case short lines at the paragraph start.
   *
   * Make a special case of lines that begin a paragraph, and are short and
   * followed by a much longer line.  This should catch games which output
   * room titles above descriptions without using AGT fonts/bold/whatever.
   * Without this trap, room titles and their descriptions are run together.
   * This is more programmatic guesswork than heuristics.
   */
  if (gagt_get_next_paragraph_line (first_line))
    {
      gagt_lineref_t next_line;

      next_line = gagt_get_next_paragraph_line (first_line);

      /*
       * See if the first line is less than half width, and the second line
       * is more than three quarters width.  If it is, set newline as
       * significant for the first paragraph line.
       */
      if (first_line->real_length < screen_width / 2
          && next_line->real_length > screen_width * 3 / 4)
        {
          gagt_set_font_hint_proportional_newline (first_line);
        }
    }

  /*
   * Phase 8 -- special case paragraphs of only short lines.
   *
   * Make a special case out of paragraphs where all lines are short.  This
   * catches elements like indented addresses.
   */
  if (gagt_get_next_paragraph_line (first_line))
    {
      int all_short;

      all_short = TRUE;
      for (line = first_line;
           line; line = gagt_get_next_paragraph_line (line))
        {
          /* Clear flag if this line isn't 'short'. */
          if (line->real_length >= screen_width / 2)
            {
              all_short = FALSE;
              break;
            }
        }

      /*
       * If all lines were short, mark the complete paragraph as having
       * significant newlines.
       */
      if (all_short)
        {
          for (line = first_line;
               line; line = gagt_get_next_paragraph_line (line))
            {
              gagt_set_font_hint_proportional_newline (line);
            }
        }
    }
}


/*
 * gagt_assign_font_hints()
 *
 * 
 * Sets a font hint for each line of each page buffer paragraph that is not
 * a special paragraph.
 */
static void
gagt_assign_font_hints (void)
{
  gagt_paragraphref_t paragraph;

  for (paragraph = gagt_get_first_paragraph ();
       paragraph; paragraph = gagt_get_next_paragraph (paragraph))
    {
      if (!paragraph->special)
        gagt_assign_paragraph_font_hints (paragraph);
    }
}


/*---------------------------------------------------------------------*/
/*  Glk port special paragraph functions and data                      */
/*---------------------------------------------------------------------*/

/*
 * It's helpful to handle some AGiliTy interpreter output specially, to im-
 * prove the look of the text where Glk fonts and styles are available.  We
 * build a table of paragraphs the interpreter can come out with, and the
 * replacement text we'll use when we see this paragraph.  Note that matches
 * are made after factoring out indentation, and replacement lines do not
 * automatically print with a newline.  All clear, then?  Here's the table
 * entry definition.
 */
enum { GAGT_SPECIAL_MATCH_MAX = 5 };

typedef const struct gagt_special_s
{
  const int line_count;
  const char * const compare[GAGT_SPECIAL_MATCH_MAX + 1];
  const char * const replace;
} gagt_special_t;

/*
 * Table of special AGiliTy interpreter strings and paragraphs -- where one
 * appears in game output, we'll print out its replacement instead.  Be
 * warned; these strings are VERY specific to AGiliTy 1.1.1.1, and are extre-
 * mely likely to change with any future interpreter releases.  They also
 * omit initializers with abandon, expecting the compiler to default these
 * to NULL/zero.  Replacement strings embed style encoding as |x, where x is
 * E(mphasized), S(ubheader), or N(ormal) for convenience.
 */
static gagt_special_t GAGT_SPECIALS[] = {

  /* Initial screen AGT game type line. */
  {1,
   {"[Created with Malmberg and Welch's Adventure Game Toolkit]"},
   "|ECreated with Malmberg and Welch's Adventure Game Toolkit|N\n"},

  /* Normal version of initial interpreter information block. */
  {4,
   {"This game is being executed by",
    "AGiliTy: The (Mostly) Universal AGT Interpreter  version 1.1.1.1",
    "Copyright (C) 1996-99,2001 by Robert Masenten",
    "Glk version"},
   "This game is being executed by:\n\n"
   "    |SAGiliTy, The (Mostly) Universal AGT Interpreter, Version 1.1.1.1|N\n"
   "    |ECopyright (C) 1996-1999,2001 by Robert Masenten|N\n"
   "    |EGlk version|N\n"},

  /* AGiliTy "information" screen header block. */
  {5,
   {"AGiliTy",
    "The (Mostly) Universal AGT Interpreter, version 1.1.1.1",
    "Copyright (C) 1996-1999,2001 by Robert Masenten",
    "[Glk version]",
    "-----------------------------------------------------------"},
   "|SAGiliTy, The (Mostly) Universal AGT Interpreter, Version 1.1.1.1|N\n"
   "|ECopyright (C) 1996-1999,2001 by Robert Masenten|N\n"
   "|EGlk version|N\n"},

  /* "HIT ANY KEY" message, usually displayed after a game's introduction. */
  {1,
   {"--- HIT ANY KEY ---"},
  "|E[Press any key...]|N"},

  /* Alternative, shrunken version of initial interpreter information block. */
  {2,
   {"Being run by AGiliTy  version 1.1.1.1, Copyright (C) 1996-99,2001"
      " Robert Masenten",
    "Glk version"},
   "This game is being executed by:\n\n"
   "    |SAGiliTy, The (Mostly) Universal AGT Interpreter, Version 1.1.1.1|N\n"
   "    |ECopyright (C) 1996-1999,2001 by Robert Masenten|N\n"
   "    |EGlk version|N\n"},

  /* Alternative, minimal version of initial interpreter information block. */
  {1,
   {"Being run by AGiliTy  version 1.1.1.1, Copyright (C) 1996-99,2001"
      " Robert Masenten"},
   "This game is being executed by:\n\n"
   "    |SAGiliTy, The (Mostly) Universal AGT Interpreter, Version 1.1.1.1|N\n"
   "    |ECopyright (C) 1996-1999,2001 by Robert Masenten|N\n"
   "    |EGlk version|N\n"},

  /* Lengthy version of the "Created with..." message. */
  {2,
   {"This game was created with Malmberg and Welch's Adventure Game Toolkit;"
      " it is",
    "being executed by"},
   "|ECreated with Malmberg and Welch's Adventure Game Toolkit|N\n"},

  /* Three-line version of initial interpreter information block. */
  {3,
   {"AGiliTy: The (Mostly) Universal AGT Interpreter  version 1.1.1.1",
    "Copyright (C) 1996-99,2001 by Robert Masenten",
    "Glk version"},
   "This game is being executed by:\n\n"
   "    |SAGiliTy, The (Mostly) Universal AGT Interpreter, Version 1.1.1.1|N\n"
   "    |ECopyright (C) 1996-1999,2001 by Robert Masenten|N\n"
   "    |EGlk version|N\n"},

  /*
   * Assorted special verb output messages, with the extra icky quality that
   * we have to spot messages that wrap because we forced screen_width to 80.
   */
  {2,
   {"[Now in BRIEF mode (room descriptions will only be printed"
      " when they are entered",
    "the first time)]"},
   "|E[Now in BRIEF mode: Room descriptions will only be printed"
   " when rooms are entered for the first time.]|N\n"},

  {2,
   {"[Now in VERBOSE mode (room descriptions will be printed"
      " every time you enter a",
    "room)]"},
   "|E[Now in VERBOSE mode: Room descriptions will be printed"
   " every time you enter a room.]|N\n"},

  {1,
   {"[LISTEXIT mode on: room exits will be listed.]"},
   "|E[LISTEXIT mode on: Room exits will be listed.]|N\n"},

  {1,
   {"[LISTEXIT mode off: room exits will not be listed.]"},
   "|E[LISTEXIT mode off: Room exits will not be listed.]|N\n"},

  /* End of table sentinel entry.  Do not delete. */
  {0, {NULL}, NULL}
};


/*
 * gagt_compare_special_line()
 * gagt_compare_special_paragraph()
 *
 * Helpers for gagt_find_equivalent_special().  Compare line data case-
 * insensitively, taking care to use lengths rather than relying on line
 * buffer data being NUL terminated (which it's not); and iterate a complete
 * special paragraph comparison.
 */
static int
gagt_compare_special_line (const char *compare, const gagt_lineref_t line)
{
  /*
   * Return true if the lengths match, and the real line data (excluding
   * indent and outdent) also matches, ignoring case.
   */
  return strlen (compare) == line->real_length
         && gagt_strncasecmp (compare,
                              line->buffer.data + line->indent,
                              line->real_length) == 0;
}

static int
gagt_compare_special_paragraph (const gagt_specialref_t special,
                                const gagt_paragraphref_t paragraph)
{
  /* If the line counts match, compare line by line. */
  if (special->line_count == gagt_get_paragraph_line_count (paragraph))
    {
      gagt_lineref_t line;
      int index, is_match;

      is_match = TRUE;
      for (index = 0, line = gagt_get_first_paragraph_line (paragraph);
           index < special->line_count && line;
           index++, line = gagt_get_next_paragraph_line (line))
        {
          if (!gagt_compare_special_line (special->compare[index], line))
            {
              is_match = FALSE;
              break;
            }
        }

      return is_match;
    }

  /* Line count mismatch; return FALSE. */
  return FALSE;
}


/*
 * gagt_find_equivalent_special()
 *
 * Given a paragraph, see if it matches any of the special ones set up in
 * our array.  Returns the special, or NULL if no match.
 */
static gagt_specialref_t
gagt_find_equivalent_special (gagt_paragraphref_t paragraph)
{
  gagt_specialref_t special, match;

  /* Check each special paragraph entry for a match against this paragraph. */
  match = NULL;
  for (special = GAGT_SPECIALS; special->replace; special++)
    {
      if (gagt_compare_special_paragraph (special, paragraph))
        {
          match = special;
          break;
        }
    }

  return match;
}


/*
 * gagt_mark_specials()
 *
 * Search for and mark any lines that match special paragraphs.
 */
static void
gagt_mark_specials (void)
{
  static int is_verified = FALSE;

  /*
   * Verify special paragraphs table contents.  This checks that each entry
   * ends with a NULL comparison, has a replacement, and that the line count
   * matches.
   */
  if (!is_verified)
    {
      gagt_specialref_t special;

      for (special = GAGT_SPECIALS; special->replace; special++)
        {
          int line_count, index;

          line_count = 0;
          for (index = 0; special->compare[index]; index++)
            line_count++;

          assert (special->line_count == line_count);
          assert (special->replace);
          assert (!special->compare[GAGT_SPECIAL_MATCH_MAX]);
        }

      is_verified = TRUE;
    }

  /*
   * Search all paragraphs for special matches, if enabled.  When a special
   * match is found, mark the paragraph with a pointer to the matching entry.
   */
  if (gagt_replacement_enabled)
    {
      gagt_paragraphref_t paragraph;

      for (paragraph = gagt_get_first_paragraph ();
           paragraph; paragraph = gagt_get_next_paragraph (paragraph))
        {
          paragraph->special = gagt_find_equivalent_special (paragraph);
        }
    }
}


/*
 * gagt_display_special()
 *
 * Display the replacement text for the specified special table entry.  The
 * current Glk style in force is passed in; we return the Glk style in force
 * after we've done.
 */
static glui32
gagt_display_special (const gagt_specialref_t special, glui32 current_style)
{
  glui32 set_style;
  int index, marker, length;
  const char *string;
  assert (special);

  /* Extract replacement string and length. */
  string = special->replace;
  assert (string);
  length = strlen (string);

  set_style = current_style;

  /*
   * Iterate each character in replacement string, looking for style escapes,
   * and flushing delayed output when one is found.
   */
  marker = 0;
  for (index = 0; index < length; index++)
    {
      if (string[index] == '|')
        {
          glui32 style;

          /* Flush delayed output accumulated so far, excluding escape. */
          glk_put_buffer ((char *) string + marker, index - marker);
          marker = index + 2;

          /* Determine any new text style. */
          style = set_style;
          switch (string[++index])
            {
            case 'E':
              style = style_Emphasized;
              break;

            case 'S':
              style = style_Subheader;
              break;

            case 'N':
              style = style_Normal;
              break;

            default:
              gagt_fatal ("GLK: Invalid replacement style escape");
              gagt_exit ();
            }

          /* If style changed, update Glk's style setting. */
          if (style != set_style)
            {
              glk_set_style (style);
              set_style = style;
            }
        }
    }

  /* Output any remaining delayed characters. */
  if (marker < length)
    glk_put_buffer ((char *) string + marker, length - marker);

  return set_style;
}


/*---------------------------------------------------------------------*/
/*  Glk port output functions                                          */
/*---------------------------------------------------------------------*/

/*
 * Flag for if the user entered "help" as their last input, or if hints have
 * been silenced as a result of already using a Glk command.
 */
static int gagt_help_requested = FALSE,
           gagt_help_hints_silenced = FALSE;

/*
 * gagt_display_register_help_request()
 * gagt_display_silence_help_hints()
 * gagt_display_provide_help_hint()
 *
 * Register a request for help, and print a note of how to get Glk command
 * help from the interpreter unless silenced.
 */
static void
gagt_display_register_help_request (void)
{
  gagt_help_requested = TRUE;
}

static void
gagt_display_silence_help_hints (void)
{
  gagt_help_hints_silenced = TRUE;
}

static glui32
gagt_display_provide_help_hint (glui32 current_style)
{
  if (gagt_help_requested && !gagt_help_hints_silenced)
    {
      glk_set_style (style_Emphasized);
      glk_put_string ("[Try 'glk help' for help on special interpreter"
                      " commands]\n");

      gagt_help_requested = FALSE;
      return style_Emphasized;
    }

  return current_style;
}


/*
 * gagt_display_text_element()
 *
 * Display an element of a buffer string using matching packed attributes.
 * The currently set Glk style is supplied, and the function returns the
 * new currently set Glk style.
 *
 * The function handles a flag to coerce fixed width font.
 */
static glui32
gagt_display_text_element (const char *string, const unsigned char *attributes,
                           int length, glui32 current_style, int fixed_width)
{
  int marker, index;
  glui32 set_style;
  assert (glk_stream_get_current ());

  set_style = current_style;

  /*
   * Iterate each character in the line range.  We actually delay output
   * until we see a change in style; that way, we can send a buffer of
   * characters to Glk, rather than sending them just one at a time.
   */
  marker = 0;
  for (index = 0; index < length; index++)
    {
      gagt_attrset_t attribute_set;
      glui32 style;
      assert (attributes && string);

      /*
       * Unpack the AGT font attributes for this character, and add fixed
       * width font coercion.
       */
      gagt_unpack_attributes (attributes[index], &attribute_set);
      attribute_set.fixed |= fixed_width;

      /*
       * Decide on any applicable new Glk text styling.  If it's different
       * to the current style, output the delayed characters, and update
       * Glk's style setting.
       */
      style = gagt_select_style (&attribute_set);
      if (style != set_style)
        {
          glk_put_buffer ((char *) string + marker, index - marker);
          marker = index;

          glk_set_style (style);
          set_style = style;
        }
    }

  /* Output any remaining delayed characters. */
  if (marker < length)
    glk_put_buffer ((char *) string + marker, length - marker);

  return set_style;
}


/*
 * gagt_display_line()
 *
 * Display a page buffer line, starting in the current Glk style, and
 * returning the new current Glk style.
 *
 * The function takes additional flags to force fixed width font, skip over
 * indentation and trailing line whitespace, and trim hyphens (if skipping
 * trailing whitespace).
 */
static glui32
gagt_display_line (const gagt_lineref_t line, glui32 current_style,
                   int fixed_width, int skip_indent, int skip_outdent,
                   int trim_hyphen)
{
  int start, length;
  glui32 set_style;

  /*
   * Check the skip indent flag to find the first character to display, and
   * the count of characters to display.
   */
  start = 0;
  length = line->buffer.length;
  if (skip_indent)
    {
      start += line->indent;
      length -= line->indent;
    }

  /* Adjust length for skipping outdent and trimming hyphens. */
  if (skip_outdent)
    {
      length -= line->outdent;
      if (trim_hyphen && line->is_hyphenated)
        length--;
    }

  /* Display this line segment. */
  set_style = gagt_display_text_element (line->buffer.data + start,
                                         line->buffer.attributes + start,
                                         length, current_style, fixed_width);

  return set_style;
}


/*
 * gagt_display_hinted_line()
 *
 * Display a page buffer line, starting in the current Glk style, and
 * returning the new current Glk style.  The function uses the font hints
 * from the line, and receives the font hint of the prior line.
 */
static glui32
gagt_display_hinted_line (const gagt_lineref_t line, glui32 current_style,
                          gagt_font_hint_t prior_hint)
{
  glui32 style;

  style = current_style;
  switch (line->font_hint)
    {
    case HINT_FIXED_WIDTH:
      /* Force fixed width font on the line. */
      style = gagt_display_line (line, style, TRUE, FALSE, FALSE, FALSE);

      glk_put_char ('\n');
      break;

    case HINT_PROPORTIONAL:
      /*
       * Permit proportional font, and suppress outdent.  Suppress indent
       * too if this line follows a line that suppressed newline, or is the
       * first line in the paragraph.  For all cases, trim the hyphen from
       * hyphenated lines.
       */
      if (prior_hint == HINT_PROPORTIONAL || prior_hint == HINT_NONE)
        style = gagt_display_line (line, style, FALSE, TRUE, TRUE, TRUE);
      else
        style = gagt_display_line (line, style, FALSE, FALSE, TRUE, TRUE);

      /*
       * Where the line is not hyphenated, output a space in place of newline.
       * This lets paragraph text to flow to the full display width.
       */
      if (!line->is_hyphenated)
        glk_put_char (' ');
      break;

    case HINT_PROPORTIONAL_NEWLINE:
    case HINT_PROPORTIONAL_NEWLINE_STANDOUT:
      /*
       * As above, permit proportional font, suppress outdent, and suppress
       * indent too under certain conditions; in this case, only when the
       * prior line suppressed newline.
       */
      if (prior_hint == HINT_PROPORTIONAL)
        style = gagt_display_line (line, style, FALSE, TRUE, TRUE, FALSE);
      else
        style = gagt_display_line (line, style, FALSE, FALSE, TRUE, FALSE);

      glk_put_char ('\n');
      break;

    case HINT_NONE:
      gagt_fatal ("GLK: Page buffer line with no font hint");
      gagt_exit ();

    default:
      gagt_fatal ("GLK: Invalid font hint encountered");
      gagt_exit ();
    }

  return style;
}


/*
 * gagt_display_auto()
 *
 * Display buffered output text to the Glk main window using a bunch of
 * occasionally rather dodgy heuristics to try to automatically set a suitable
 * font for the way the text is structured, while replacing special paragraphs
 * with altered text.
 */
static void
gagt_display_auto (void)
{
  gagt_paragraphref_t paragraph;
  glui32 style;

  style = style_Normal;
  glk_set_style (style);

  /* Handle each paragraph. */
  for (paragraph = gagt_get_first_paragraph ();
       paragraph; paragraph = gagt_get_next_paragraph (paragraph))
    {
      /* If a special paragraph, output replacement text instead. */
      if (paragraph->special)
        {
          style = gagt_display_special (paragraph->special, style);
          glk_put_char ('\n');
        }
      else
        {
          gagt_lineref_t line;
          gagt_font_hint_t prior_hint;

          /* Get the first line of the paragraph. */
          line = gagt_get_first_paragraph_line (paragraph);

          /*
           * Output a blank line where the first line of the first paragraph
           * is standout; this sets it apart from the prompt.
           */
          if (paragraph == gagt_get_first_paragraph ()
              && line == gagt_get_first_paragraph_line (paragraph))
            {
              if (line->font_hint == HINT_PROPORTIONAL_NEWLINE_STANDOUT)
                glk_put_char ('\n');
            }

          /* Handle each line of the paragraph. */
          prior_hint = HINT_NONE;
          for (; line; line = gagt_get_next_paragraph_line (line))
            {
              /*
               * Print this line according to its font hint, noting any change
               * of style and the line's font hint for use next iteration as
               * the prior hint.
               */
              style = gagt_display_hinted_line (line, style, prior_hint);
              prior_hint = line->font_hint;
            }
 
          /* Output the newline for the end of the paragraph. */
          glk_put_char ('\n');
        }
    }

  /* If no paragraphs at all, but a current buffer, output a newline. */
  if (!gagt_get_first_paragraph () && gagt_current_buffer.length > 0)
    glk_put_char ('\n');

  /* Output any help hint and unterminated line from the line buffer. */
  style = gagt_display_provide_help_hint (style);
  style = gagt_display_text_element (gagt_current_buffer.data,
                                     gagt_current_buffer.attributes,
                                     gagt_current_buffer.length, style, FALSE);
}


/*
 * gagt_display_manual()
 *
 * Display buffered output text in the Glk main window, with either a fixed
 * width or a proportional font.
 */
static void
gagt_display_manual (int fixed_width)
{
  gagt_lineref_t line;
  glui32 style;

  style = style_Normal;
  glk_set_style (style);

  for (line = gagt_get_first_page_line ();
       line; line = gagt_get_next_page_line (line))
    {
      gagt_paragraphref_t paragraph;

      paragraph = line->paragraph;

      /*
       * If this is a special paragraph, display the replacement text on
       * its first line and ignore remaining special lines.  Otherwise,
       * display the page buffer line using either fixed or proportional
       * font, as requested.
       */
      if (paragraph && paragraph->special)
        {
          if (gagt_get_first_paragraph_line (paragraph) == line)
            style = gagt_display_special (paragraph->special, style);
        }
      else
        {
          style = gagt_display_line (line, style, fixed_width,
                                     FALSE, FALSE, FALSE);
          glk_put_char ('\n');
        }
    }

  /* Output any help hint and unterminated line from the line buffer. */
  style = gagt_display_provide_help_hint (style);
  style = gagt_display_text_element (gagt_current_buffer.data,
                                     gagt_current_buffer.attributes,
                                     gagt_current_buffer.length,
                                     style, fixed_width);
}


/*
 * gagt_display_debug()
 *
 * Display the analyzed page buffer in a form that shows all of its gory
 * detail.
 */
static void
gagt_display_debug (void)
{
  gagt_lineref_t line;
  char buffer[256];

  glk_set_style (style_Preformatted);
  for (line = gagt_get_first_page_line ();
       line; line = gagt_get_next_page_line (line))
    {
      gagt_paragraphref_t paragraph;

      paragraph = line->paragraph;
      sprintf (buffer,
               "%2d:%2d->%2ld A=%-3d L=%-2d I=%-2d O=%-2d R=%-2d %c%c| ",
               paragraph ? paragraph->id + 1 : 0,
               paragraph ? paragraph->line_count : 0,
               paragraph && paragraph->special
                   ? paragraph->special - GAGT_SPECIALS + 1 : 0,
               line->buffer.allocation, line->buffer.length,
               line->indent, line->outdent,
               line->real_length,
               line->is_hyphenated ? 'h' : '_',
               line->is_blank ? 'b' :
                 line->font_hint == HINT_PROPORTIONAL ? 'P' :
                 line->font_hint == HINT_PROPORTIONAL_NEWLINE ? 'N' :
                 line->font_hint == HINT_PROPORTIONAL_NEWLINE_STANDOUT ? 'S' :
                 line->font_hint == HINT_FIXED_WIDTH ? 'F' : '_');
      glk_put_string (buffer);

      glk_put_buffer (line->buffer.data, line->buffer.length);
      glk_put_char ('\n');
    }

  if (gagt_current_buffer.length > 0)
    {
      sprintf (buffer,
               "__,__->__ A=%-3d L=%-2d I=__ O=__ R=__ %s| ",
               gagt_current_buffer.allocation, gagt_current_buffer.length,
               gagt_help_requested ? "HR" : "__");
      glk_put_string (buffer);

      glk_put_buffer (gagt_current_buffer.data, gagt_current_buffer.length);
    }

  gagt_help_requested = FALSE;
}


/*
 * gagt_output_flush()
 *
 * Flush any buffered output text to the Glk main window, and clear the
 * buffer ready for new output text.  The function concerns itself with
 * both the page buffer and any unterminated line in the line buffer.
 */
static void
gagt_output_flush (void)
{
  /*
   * Run the analysis of page buffer contents.  This will fill in the
   * paragraph and font hints fields, any any applicable special pointer,
   * for every line held in the buffer.
   */
  gagt_paragraph_page ();
  gagt_mark_specials ();
  gagt_assign_font_hints ();

  /*
   * Select the appropriate display routine to use, and call it.  The display
   * routines present somewhat different output, and are responsible for
   * displaying both the page buffer _and_ any buffered current line text.
   */
  switch (gagt_font_mode)
    {
    case FONT_AUTOMATIC:
      gagt_display_auto ();
      break;

    case FONT_FIXED_WIDTH:
      gagt_display_manual (TRUE);
      break;

    case FONT_PROPORTIONAL:
      gagt_display_manual (FALSE);
      break;

    case FONT_DEBUG:
      gagt_display_debug ();
      break;

    default:
      gagt_fatal ("GLK: Invalid font mode encountered");
      gagt_exit ();
    }

  /* Empty the buffer, ready for new game strings. */
  gagt_paragraphs_delete ();
  gagt_output_delete ();
}


/*
 * agt_clrscr()
 *
 * Clear the main playing area window.  Although there may be little point
 * in flushing (rather than emptying) the buffers, nevertheless that is
 * what we do.
 */
void
agt_clrscr (void)
{
  if (!BATCH_MODE)
    {
      /* Update the apparent (virtual) window x position. */
      curr_x = 0;

      /* Flush any pending buffered output, and clear the main window. */
      gagt_output_flush ();
      glk_window_clear (gagt_main_window);

      /* Add a series of newlines to any script file. */
      if (script_on)
        textputs (scriptfile, "\n\n\n\n");

      gagt_debug ("agt_clrscr", "");
    }
}


/*
 * gagt_styled_string()
 * gagt_styled_char()
 * gagt_standout_string()
 * gagt_standout_char()
 * gagt_normal_string()
 * gagt_normal_char()
 * gagt_header_string()
 *
 * Convenience functions to print strings in assorted styles.  A standout
 * string is one that hints that it's from the interpreter, not the game.
 */
static void
gagt_styled_string (glui32 style, const char *message)
{
  assert (message);

  glk_set_style (style);
  glk_put_string ((char *) message);
  glk_set_style (style_Normal);
}

static void
gagt_styled_char (glui32 style, char c)
{
  char buffer[2];

  buffer[0] = c;
  buffer[1] = '\0';
  gagt_styled_string (style, buffer);
}

static void
gagt_standout_string (const char *message)
{
  gagt_styled_string (style_Emphasized, message);
}

static void
gagt_standout_char (char c)
{
  gagt_styled_char (style_Emphasized, c);
}

static void
gagt_normal_string (const char *message)
{
  gagt_styled_string (style_Normal, message);
}

static void
gagt_normal_char (char c)
{
  gagt_styled_char (style_Normal, c);
}

static void
gagt_header_string (const char *message)
{
  gagt_styled_string (style_Header, message);
}


/*---------------------------------------------------------------------*/
/*  Glk port delay functions                                           */
/*---------------------------------------------------------------------*/

/* Number of milliseconds in a second (traditionally, 1000). */
static const int GAGT_MS_PER_SEC = 1000;

/*
 * Number of milliseconds to timeout.  Because of jitter in the way Glk
 * generates timeouts, it's worthwhile implementing a delay using a number
 * of shorter timeouts.  This minimizes inaccuracies in the actual delay.
 */
static const glui32 GAGT_DELAY_TIMEOUT = 50;

/* The character key that can be pressed to cancel, and suspend, delays. */
static const char GAGT_DELAY_SUSPEND = ' ';

/*
 * Flag to temporarily turn off all delays.  This is set when the user
 * cancels a delay with a keypress, and remains set until the next time
 * that AGiliTy requests user input.  This way, games that call agt_delay()
 * sequentially don't require multiple keypresses to jump out of delay
 * sections.
 */
static int gagt_delays_suspended = FALSE;


/*
 * agt_delay()
 *
 * Delay for the specified number of seconds.  The delay can be canceled
 * by a user keypress.
 */
void
agt_delay (int seconds)
{
  glui32 milliseconds, delayed;
  int delay_completed;

  /* Suppress delay if in fast replay or batch mode. */
  if (fast_replay || BATCH_MODE)
    return;

  /*
   * Do nothing if Glk doesn't have timers, if the delay state is set to
   * ignore delays, if a zero or negative delay was specified, or if delays
   * are currently temporarily suspended.
   */
  if (!glk_gestalt (gestalt_Timer, 0)
      || gagt_delay_mode == DELAY_OFF
      || seconds <= 0 || gagt_delays_suspended)
    return;

  /* Flush any pending buffered output, and refresh status to show waiting. */
  gagt_output_flush ();
  gagt_status_in_delay (TRUE);

  /* Calculate the number of milliseconds to delay. */
  milliseconds = (seconds * GAGT_MS_PER_SEC)
                 / (gagt_delay_mode == DELAY_SHORT ? 2 : 1);

  /* Request timer events, and let a keypress cancel the delay. */
  glk_request_char_event (gagt_main_window);
  glk_request_timer_events (GAGT_DELAY_TIMEOUT);

  /*
   * Implement the delay using a sequence of shorter Glk timeouts, with an
   * option to cancel the delay with a keypress.
   */
  delay_completed = TRUE;
  for (delayed = 0; delayed < milliseconds; delayed += GAGT_DELAY_TIMEOUT)
    {
      event_t event;

      /* Wait for the next timeout, or a character. */
      gagt_event_wait_2 (evtype_CharInput, evtype_Timer, &event);
      if (event.type == evtype_CharInput)
        {
          /*
           * If suspend requested, stop the delay, and set the delay
           * suspension flag, and a note that the delay loop didn't complete.
           * Otherwise, reissue the character input request.
           */
          if (event.val1 == GAGT_DELAY_SUSPEND)
            {
              gagt_delays_suspended = TRUE;
              delay_completed = FALSE;
              break;
            }
          else
            glk_request_char_event (gagt_main_window);
        }
    }

  /* Cancel any pending character input, and timer events. */
  if (delay_completed)
    glk_cancel_char_event (gagt_main_window);
  glk_request_timer_events (0);

  /* Clear the waiting indicator. */
  gagt_status_in_delay (FALSE);

  gagt_debug ("agt_delay", "seconds=%d [%lu mS] -> %s", seconds, milliseconds,
              delay_completed ? "completed" : "canceled");
}


/*
 * gagt_delay_resume()
 *
 * Unsuspend delays.  This function should be called by agt_input() and
 * agt_getkey(), to re-enable delays when the interpreter next requests
 * user input.
 */
static void
gagt_delay_resume (void)
{
  gagt_delays_suspended = FALSE;
}


/*---------------------------------------------------------------------*/
/*  Glk port box drawing functions                                     */
/*---------------------------------------------------------------------*/

/* Saved details of any current box dimensions and flags. */
static unsigned long gagt_box_flags = 0;
static int gagt_box_busy = FALSE,
           gagt_box_width = 0,
           gagt_box_height = 0,
           gagt_box_startx = 0;


/*
 * gagt_box_rule()
 * gagt_box_position()
 *
 * Draw a line at the top or bottom of a box, and position the cursor
 * with a box indent.
 */
static void
gagt_box_rule (int width)
{
  char *ruler;

  /* Write a +--...--+ ruler to delimit a box. */
  ruler = gagt_malloc (width + 2 + 1);
  memset (ruler + 1, '-', width);
  ruler[0] = ruler[width + 1] = '+';
  ruler[width + 2] = '\0';
  agt_puts (ruler);
  free (ruler);
}

static void
gagt_box_position (int indent)
{
  char *spaces;

  /* Write a newline before the indent. */
  agt_newline ();

  /* Write the indent to the start of box text. */
  spaces = gagt_malloc (indent + 1);
  memset (spaces, ' ', indent);
  spaces[indent] = '\0';
  agt_puts (spaces);
  free (spaces);
}


/*
 * agt_makebox()
 * agt_qnewline()
 * agt_endbox()
 *
 * Start a box of given width, height, and with given flags.  Write a new
 * line in the box.  And end the box.
 */
void
agt_makebox (int width, int height, unsigned long flags)
{
  assert (!gagt_box_busy);

  gagt_box_busy = TRUE;
  gagt_box_flags = flags;
  gagt_box_width = width;
  gagt_box_height = height;

  /* If no centering requested, set the indent to zero. */
  if (gagt_box_flags & TB_NOCENT)
    gagt_box_startx = 0;
  else
    {
      int centering_width;

      /*
       * Calculate the indent for centering, adding 4 characters for borders.
       * Here, since screen_width is artificial, we'll center off status_width
       * if it is less than screen width, otherwise we'll center by using
       * screen_width.  The reason for shrinking to screen_width is that if
       * we don't, we could drive curr_x to beyond screen_width with our box
       * indentations, and that confuses AGiliTy.
       */
      if (status_width < screen_width)
        centering_width = status_width;
      else
        centering_width = screen_width;
      if (gagt_box_flags & TB_BORDER)
        gagt_box_startx = (centering_width - gagt_box_width - 4) / 2;
      else
        gagt_box_startx = (centering_width - gagt_box_width) / 2;

      /* If the box turns out wider than the window, abandon centering. */
      if (gagt_box_startx < 0)
        gagt_box_startx = 0;
    }

  /*
   * When in a box, we'll coerce fixed width font by setting it in the AGT
   * font attributes.  This ensures that the box displays as accurately as
   * we're able to achieve.
   */
  gagt_coerce_fixed_font (TRUE);

  /* Position the cursor for the box, and if bordered, write the rule. */
  gagt_box_position (gagt_box_startx);
  if (gagt_box_flags & TB_BORDER)
    {
      gagt_box_rule (gagt_box_width + 2);
      gagt_box_position (gagt_box_startx);
      agt_puts ("| ");
    }

  gagt_debug ("agt_makebox", "width=%d, height=%d, flags=0x%lx",
              width, height, flags);
}

void
agt_qnewline (void)
{
  assert (gagt_box_busy);

  /* Write box characters for the current and next line. */
  if (gagt_box_flags & TB_BORDER)
    {
      agt_puts (" |");
      gagt_box_position (gagt_box_startx);
      agt_puts ("| ");
    }
  else
    gagt_box_position (gagt_box_startx);

  gagt_debug ("agt_qnewline", "");
}

void
agt_endbox (void)
{
  assert (gagt_box_busy);

  /* Finish off the current box. */
  if (gagt_box_flags & TB_BORDER)
    {
      agt_puts (" |");
      gagt_box_position (gagt_box_startx);
      gagt_box_rule (gagt_box_width + 2);
    }
  agt_newline ();

  /* An extra newline here improves the appearance. */
  agt_newline ();

  /* Back to allowing proportional font output again. */
  gagt_coerce_fixed_font (FALSE);

  gagt_box_busy = FALSE;
  gagt_box_flags = gagt_box_width = gagt_box_startx = 0;

  gagt_debug ("agt_endbox", "");
}


/*---------------------------------------------------------------------*/
/*  Glk command escape functions                                       */
/*---------------------------------------------------------------------*/

/*
 * gagt_command_script()
 *
 * Turn game output scripting (logging) on and off.
 */
static void
gagt_command_script (const char *argument)
{
  assert (argument);

  if (gagt_strcasecmp (argument, "on") == 0)
    {
      frefid_t fileref;

      if (gagt_transcript_stream)
        {
          gagt_normal_string ("Glk transcript is already on.\n");
          return;
        }

      fileref = glk_fileref_create_by_prompt (fileusage_Transcript
                                              | fileusage_TextMode,
                                              filemode_WriteAppend, 0);
      if (!fileref)
        {
          gagt_standout_string ("Glk transcript failed.\n");
          return;
        }

      gagt_transcript_stream = glk_stream_open_file (fileref,
                                                     filemode_WriteAppend, 0);
      glk_fileref_destroy (fileref);
      if (!gagt_transcript_stream)
        {
          gagt_standout_string ("Glk transcript failed.\n");
          return;
        }

      glk_window_set_echo_stream (gagt_main_window, gagt_transcript_stream);

      gagt_normal_string ("Glk transcript is now on.\n");
    }

  else if (gagt_strcasecmp (argument, "off") == 0)
    {
      if (!gagt_transcript_stream)
        {
          gagt_normal_string ("Glk transcript is already off.\n");
          return;
        }

      glk_stream_close (gagt_transcript_stream, NULL);
      gagt_transcript_stream = NULL;

      glk_window_set_echo_stream (gagt_main_window, NULL);

      gagt_normal_string ("Glk transcript is now off.\n");
    }

  else if (strlen (argument) == 0)
    {
      gagt_normal_string ("Glk transcript is ");
      gagt_normal_string (gagt_transcript_stream ? "on" : "off");
      gagt_normal_string (".\n");
    }

  else
    {
      gagt_normal_string ("Glk transcript can be ");
      gagt_standout_string ("on");
      gagt_normal_string (", or ");
      gagt_standout_string ("off");
      gagt_normal_string (".\n");
    }
}


/*
 * gagt_command_inputlog()
 *
 * Turn game input logging on and off.
 */
static void
gagt_command_inputlog (const char *argument)
{
  assert (argument);

  if (gagt_strcasecmp (argument, "on") == 0)
    {
      frefid_t fileref;

      if (gagt_inputlog_stream)
        {
          gagt_normal_string ("Glk input logging is already on.\n");
          return;
        }

      fileref = glk_fileref_create_by_prompt (fileusage_InputRecord
                                              | fileusage_BinaryMode,
                                              filemode_WriteAppend, 0);
      if (!fileref)
        {
          gagt_standout_string ("Glk input logging failed.\n");
          return;
        }

      gagt_inputlog_stream = glk_stream_open_file (fileref,
                                                   filemode_WriteAppend, 0);
      glk_fileref_destroy (fileref);
      if (!gagt_inputlog_stream)
        {
          gagt_standout_string ("Glk input logging failed.\n");
          return;
        }

      gagt_normal_string ("Glk input logging is now on.\n");
    }

  else if (gagt_strcasecmp (argument, "off") == 0)
    {
      if (!gagt_inputlog_stream)
        {
          gagt_normal_string ("Glk input logging is already off.\n");
          return;
        }

      glk_stream_close (gagt_inputlog_stream, NULL);
      gagt_inputlog_stream = NULL;

      gagt_normal_string ("Glk input log is now off.\n");
    }

  else if (strlen (argument) == 0)
    {
      gagt_normal_string ("Glk input logging is ");
      gagt_normal_string (gagt_inputlog_stream ? "on" : "off");
      gagt_normal_string (".\n");
    }

  else
    {
      gagt_normal_string ("Glk input logging can be ");
      gagt_standout_string ("on");
      gagt_normal_string (", or ");
      gagt_standout_string ("off");
      gagt_normal_string (".\n");
    }
}


/*
 * gagt_command_readlog()
 *
 * Set the game input log, to read input from a file.
 */
static void
gagt_command_readlog (const char *argument)
{
  assert (argument);

  if (gagt_strcasecmp (argument, "on") == 0)
    {
      frefid_t fileref;

      if (gagt_readlog_stream)
        {
          gagt_normal_string ("Glk read log is already on.\n");
          return;
        }

      fileref = glk_fileref_create_by_prompt (fileusage_InputRecord
                                              | fileusage_BinaryMode,
                                              filemode_Read, 0);
      if (!fileref)
        {
          gagt_standout_string ("Glk read log failed.\n");
          return;
        }

      if (!glk_fileref_does_file_exist (fileref))
        {
          glk_fileref_destroy (fileref);
          gagt_standout_string ("Glk read log failed.\n");
          return;
        }

      gagt_readlog_stream = glk_stream_open_file (fileref, filemode_Read, 0);
      glk_fileref_destroy (fileref);
      if (!gagt_readlog_stream)
        {
          gagt_standout_string ("Glk read log failed.\n");
          return;
        }

      gagt_normal_string ("Glk read log is now on.\n");
    }

  else if (gagt_strcasecmp (argument, "off") == 0)
    {
      if (!gagt_readlog_stream)
        {
          gagt_normal_string ("Glk read log is already off.\n");
          return;
        }

      glk_stream_close (gagt_readlog_stream, NULL);
      gagt_readlog_stream = NULL;

      gagt_normal_string ("Glk read log is now off.\n");
    }

  else if (strlen (argument) == 0)
    {
      gagt_normal_string ("Glk read log is ");
      gagt_normal_string (gagt_readlog_stream ? "on" : "off");
      gagt_normal_string (".\n");
    }

  else
    {
      gagt_normal_string ("Glk read log can be ");
      gagt_standout_string ("on");
      gagt_normal_string (", or ");
      gagt_standout_string ("off");
      gagt_normal_string (".\n");
    }
}


/*
 * gagt_command_abbreviations()
 *
 * Turn abbreviation expansions on and off.
 */
static void
gagt_command_abbreviations (const char *argument)
{
  assert (argument);

  if (gagt_strcasecmp (argument, "on") == 0)
    {
      if (gagt_abbreviations_enabled)
        {
          gagt_normal_string ("Glk abbreviation expansions are already on.\n");
          return;
        }

      gagt_abbreviations_enabled = TRUE;
      gagt_normal_string ("Glk abbreviation expansions are now on.\n");
    }

  else if (gagt_strcasecmp (argument, "off") == 0)
    {
      if (!gagt_abbreviations_enabled)
        {
          gagt_normal_string ("Glk abbreviation expansions are already off.\n");
          return;
        }

      gagt_abbreviations_enabled = FALSE;
      gagt_normal_string ("Glk abbreviation expansions are now off.\n");
    }

  else if (strlen (argument) == 0)
    {
      gagt_normal_string ("Glk abbreviation expansions are ");
      gagt_normal_string (gagt_abbreviations_enabled ? "on" : "off");
      gagt_normal_string (".\n");
    }

  else
    {
      gagt_normal_string ("Glk abbreviation expansions can be ");
      gagt_standout_string ("on");
      gagt_normal_string (", or ");
      gagt_standout_string ("off");
      gagt_normal_string (".\n");
    }
}


/*
 * gagt_command_fonts()
 *
 * Set the value for gagt_font_mode depending on the argument from the
 * user's command escape.
 *
 * Despite our best efforts, font control may still be wrong in some games.
 * This command gives us a chance to correct that.
 */
static void
gagt_command_fonts (const char *argument)
{
  assert (argument);

  if (gagt_strcasecmp (argument, "fixed") == 0)
    {
      if (gagt_font_mode == FONT_FIXED_WIDTH)
        {
          gagt_normal_string ("Glk font control is already 'fixed'.\n");
          return;
        }

      gagt_font_mode = FONT_FIXED_WIDTH;
      gagt_normal_string ("Glk font control is now 'fixed'.\n");
    }

  else if (gagt_strcasecmp (argument, "variable") == 0
           || gagt_strcasecmp (argument, "proportional") == 0)
    {
      if (gagt_font_mode == FONT_PROPORTIONAL)
        {
          gagt_normal_string ("Glk font control is already 'proportional'.\n");
        return;
      }

      gagt_font_mode = FONT_PROPORTIONAL;
      gagt_normal_string ("Glk font control is now 'proportional'.\n");
    }

  else if (gagt_strcasecmp (argument, "auto") == 0
           || gagt_strcasecmp (argument, "automatic") == 0)
    {
      if (gagt_font_mode == FONT_AUTOMATIC)
        {
          gagt_normal_string ("Glk font control is already 'automatic'.\n");
          return;
        }

      gagt_font_mode = FONT_AUTOMATIC;
      gagt_normal_string ("Glk font control is now 'automatic'.\n");
    }

  else if (gagt_strcasecmp (argument, "debug") == 0)
    {
      if (gagt_font_mode == FONT_DEBUG)
        {
          gagt_normal_string ("Glk font control is already 'debug'.\n");
          return;
        }

      gagt_font_mode = FONT_DEBUG;
      gagt_normal_string ("Glk font control is now 'debug'.\n");
    }

  else if (strlen (argument) == 0)
    {
      gagt_normal_string ("Glk font control is set to '");
      switch (gagt_font_mode)
        {
        case FONT_AUTOMATIC:
          gagt_normal_string ("automatic");
          break;

        case FONT_FIXED_WIDTH:
          gagt_normal_string ("fixed");
          break;

        case FONT_PROPORTIONAL:
          gagt_normal_string ("proportional");
          break;

        case FONT_DEBUG:
          gagt_normal_string ("debug");
          break;

        default:
          gagt_fatal ("GLK: Invalid font mode encountered");
          gagt_exit ();
        }
      gagt_normal_string ("'.\n");
    }

  else
    {
      /* Avoid mentioning the debug setting. */
      gagt_normal_string ("Glk font control can be ");
      gagt_standout_string ("fixed");
      gagt_normal_string (", ");
      gagt_standout_string ("proportional");
      gagt_normal_string (", or ");
      gagt_standout_string ("automatic");
      gagt_normal_string (".\n");
    }
}


/*
 * gagt_command_delays()
 *
 * Set a value for gagt_delay_mode depending on the argument from
 * the user's command escape.
 */
static void
gagt_command_delays (const char *argument)
{
  assert (argument);

  if (!glk_gestalt (gestalt_Timer, 0))
    {
      gagt_normal_string ("Glk delays are not available.\n");
      return;
    }

  if (gagt_strcasecmp (argument, "full") == 0
      || gagt_strcasecmp (argument, "on") == 0)
    {
      if (gagt_delay_mode == DELAY_FULL)
        {
          gagt_normal_string ("Glk delay mode is already 'full'.\n");
          return;
        }

      gagt_delay_mode = DELAY_FULL;
      gagt_normal_string ("Glk delay mode is now 'full'.\n");
    }

  else if (gagt_strcasecmp (argument, "short") == 0
           || gagt_strcasecmp (argument, "half") == 0)
    {
      if (gagt_delay_mode == DELAY_SHORT)
        {
          gagt_normal_string ("Glk delay mode is already 'short'.\n");
          return;
        }

      gagt_delay_mode = DELAY_SHORT;
      gagt_normal_string ("Glk delay mode is now 'short'.\n");
    }

  else if (gagt_strcasecmp (argument, "none") == 0
           || gagt_strcasecmp (argument, "off") == 0)
    {
      if (gagt_delay_mode == DELAY_OFF)
        {
          gagt_normal_string ("Glk delay mode is already 'none'.\n");
          return;
        }

      gagt_delay_mode = DELAY_OFF;
      gagt_normal_string ("Glk delay mode is now 'none'.\n");
    }

  else if (strlen (argument) == 0)
    {
      gagt_normal_string ("Glk delay mode is set to '");
      switch (gagt_delay_mode)
        {
        case DELAY_FULL:
          gagt_normal_string ("full");
          break;

        case DELAY_SHORT:
          gagt_normal_string ("short");
          break;

        case DELAY_OFF:
          gagt_normal_string ("none");
          break;

        default:
          gagt_fatal ("GLK: Invalid delay mode encountered");
          gagt_exit ();
        }
      gagt_normal_string ("'.\n");
    }

  else
    {
      gagt_normal_string ("Glk delay mode can be ");
      gagt_standout_string ("full");
      gagt_normal_string (", ");
      gagt_standout_string ("short");
      gagt_normal_string (", or ");
      gagt_standout_string ("none");
      gagt_normal_string (".\n");
    }
}


/*
 * gagt_command_width()
 *
 * Print out the (approximate) display width, from status_width.  It's
 * approximate because the main window might include a scrollbar that
 * the status window doesn't have, may use a different size font, and so
 * on.  But the main window won't tell us a width at all - it always
 * returns zero.  If we don't happen to have a status window available
 * to us, there's not much we can say.
 *
 * Note that this function uses the interpreter variable status_width,
 * so it's important to keep this updated with the current window size at
 * all times.
 */
static void
gagt_command_width (const char *argument)
{
  char buffer[16];
  assert (argument);

  if (!gagt_status_window)
    {
      gagt_normal_string ("Glk's current display width is unknown.\n");
      return;
    }

  gagt_normal_string ("Glk's current display width is approximately ");
  sprintf (buffer, "%d", status_width);
  gagt_normal_string (buffer);
  gagt_normal_string (status_width == 1 ? " character" : " characters");
  gagt_normal_string (".\n");
}


/*
 * gagt_command_replacements()
 *
 * Turn Glk special paragraph replacement on and off.
 */
static void
gagt_command_replacements (const char *argument)
{
  assert (argument);

  if (gagt_strcasecmp (argument, "on") == 0)
    {
      if (gagt_replacement_enabled)
        {
          gagt_normal_string ("Glk replacements are already on.\n");
          return;
        }

      gagt_replacement_enabled = TRUE;
      gagt_normal_string ("Glk replacements are now on.\n");
    }

  else if (gagt_strcasecmp (argument, "off") == 0)
    {
      if (!gagt_replacement_enabled)
        {
          gagt_normal_string ("Glk replacements are already off.\n");
          return;
        }

      gagt_replacement_enabled = FALSE;
      gagt_normal_string ("Glk replacements are now off.\n");
    }

  else if (strlen (argument) == 0)
    {
      gagt_normal_string ("Glk replacements are ");
      gagt_normal_string (gagt_replacement_enabled ? "on" : "off");
      gagt_normal_string (".\n");
    }

  else
    {
      gagt_normal_string ("Glk replacements can be ");
      gagt_standout_string ("on");
      gagt_normal_string (", or ");
      gagt_standout_string ("off");
      gagt_normal_string (".\n");
    }
}


/*
 * gagt_command_statusline()
 *
 * Turn the extended status line on and off.
 */
static void
gagt_command_statusline (const char *argument)
{
  assert (argument);

  if (!gagt_status_window)
    {
      gagt_normal_string ("Glk status window is not available.\n");
      return;
    }

  if (gagt_strcasecmp (argument, "extended") == 0
      || gagt_strcasecmp (argument, "full") == 0)
    {
      if (gagt_extended_status_enabled)
        {
          gagt_normal_string ("Glk status line mode is already 'extended'.\n");
          return;
        }

      /* Expand the status window down to a second line. */
      glk_window_set_arrangement (glk_window_get_parent (gagt_status_window),
                                  winmethod_Above | winmethod_Fixed, 2, NULL);
      gagt_extended_status_enabled = TRUE;

      gagt_normal_string ("Glk status line mode is now 'extended'.\n");
    }

  else if (gagt_strcasecmp (argument, "short") == 0
           || gagt_strcasecmp (argument, "normal") == 0)
    {
      if (!gagt_extended_status_enabled)
        {
          gagt_normal_string ("Glk status line mode is already 'short'.\n");
          return;
        }

      /* Shrink the status window down to one line. */
      glk_window_set_arrangement (glk_window_get_parent (gagt_status_window),
                                  winmethod_Above | winmethod_Fixed, 1, NULL);
      gagt_extended_status_enabled = FALSE;

      gagt_normal_string ("Glk status line mode is now 'short'.\n");
    }

  else if (strlen (argument) == 0)
    {
      gagt_normal_string ("Glk status line mode is set to '");
      gagt_normal_string (gagt_extended_status_enabled ? "extended" : "short");
      gagt_normal_string ("'.\n");
    }

  else
    {
      gagt_normal_string ("Glk status line can be ");
      gagt_standout_string ("extended");
      gagt_normal_string (", or ");
      gagt_standout_string ("short");
      gagt_normal_string (".\n");
    }
}


/*
 * gagt_command_print_version_number()
 * gagt_command_version()
 *
 * Print out the Glk library version number.
 */
static void
gagt_command_print_version_number (glui32 version)
{
  char buffer[64];

  sprintf (buffer, "%u.%u.%u",
           version >> 16, (version >> 8) & 0xff, version & 0xff);
  gagt_normal_string (buffer);
}

static void
gagt_command_version (const char *argument)
{
  glui32 version;
  assert (argument);

  gagt_normal_string ("This is version ");
  gagt_command_print_version_number (GAGT_PORT_VERSION);
  gagt_normal_string (" of the Glk AGiliTy port.\n");

  version = glk_gestalt (gestalt_Version, 0);
  gagt_normal_string ("The Glk library version is ");
  gagt_command_print_version_number (version);
  gagt_normal_string (".\n");
}


/*
 * gagt_command_commands()
 *
 * Turn command escapes off.  Once off, there's no way to turn them back on.
 * Commands must be on already to enter this function.
 */
static void
gagt_command_commands (const char *argument)
{
  assert (argument);

  if (gagt_strcasecmp (argument, "on") == 0)
    {
      gagt_normal_string ("Glk commands are already on.\n");
    }

  else if (gagt_strcasecmp (argument, "off") == 0)
    {
      gagt_commands_enabled = FALSE;
      gagt_normal_string ("Glk commands are now off.\n");
    }

  else if (strlen (argument) == 0)
    {
      gagt_normal_string ("Glk commands are ");
      gagt_normal_string (gagt_commands_enabled ? "on" : "off");
      gagt_normal_string (".\n");
    }

  else
    {
      gagt_normal_string ("Glk commands can be ");
      gagt_standout_string ("on");
      gagt_normal_string (", or ");
      gagt_standout_string ("off");
      gagt_normal_string (".\n");
    }
}


/*
 * gagt_command_license()
 *
 * Print licensing terms.
 */
static void
gagt_command_license (const char *argument)
{
  assert (argument);

  gagt_normal_string ("This program is free software; you can redistribute it"
                      " and/or modify it under the terms of version 2 of the"
                      " GNU General Public License as published by the Free"
                      " Software Foundation.\n\n");

  gagt_normal_string ("This program is distributed in the hope that it will be"
                      " useful, but ");
  gagt_standout_string ("WITHOUT ANY WARRANTY");
  gagt_normal_string ("; without even the implied warranty of ");
  gagt_standout_string ("MERCHANTABILITY");
  gagt_normal_string (" or ");
  gagt_standout_string ("FITNESS FOR A PARTICULAR PURPOSE");
  gagt_normal_string (".  See the GNU General Public License for more"
                      " details.\n\n");

  gagt_normal_string ("You should have received a copy of the GNU General"
                      " Public License along with this program; if not, write"
                      " to the Free Software Foundation, Inc., 51 Franklin"
                      " Street, Fifth Floor, Boston, MA 02110-1301 USA\n\n");

  gagt_normal_string ("Please report any bugs, omissions, or misfeatures to ");
  gagt_standout_string ("simon_baldwin@yahoo.com");
  gagt_normal_string (".\n");
}


/* Glk subcommands and handler functions. */
typedef const struct
{
  const char * const command;                     /* Glk subcommand. */
  void (* const handler) (const char *argument);  /* Subcommand handler. */
  const int takes_argument;                       /* Argument flag. */
} gagt_command_t;
typedef gagt_command_t *gagt_commandref_t;

static void gagt_command_summary (const char *argument);
static void gagt_command_help (const char *argument);

static gagt_command_t GAGT_COMMAND_TABLE[] = {
  {"summary",        gagt_command_summary,        FALSE},
  {"script",         gagt_command_script,         TRUE},
  {"inputlog",       gagt_command_inputlog,       TRUE},
  {"readlog",        gagt_command_readlog,        TRUE},
  {"abbreviations",  gagt_command_abbreviations,  TRUE},
  {"fonts",          gagt_command_fonts,          TRUE},
  {"delays",         gagt_command_delays,         TRUE},
  {"width",          gagt_command_width,          FALSE},
  {"replacements",   gagt_command_replacements,   TRUE},
  {"statusline",     gagt_command_statusline,     TRUE},
  {"version",        gagt_command_version,        FALSE},
  {"commands",       gagt_command_commands,       TRUE},
  {"license",        gagt_command_license,        FALSE},
  {"help",           gagt_command_help,           TRUE},
  {NULL, NULL, FALSE}
};


/*
 * gagt_command_summary()
 *
 * Report all current Glk settings.
 */
static void
gagt_command_summary (const char *argument)
{
  gagt_commandref_t entry;
  assert (argument);

  /*
   * Call handlers that have status to report with an empty argument,
   * prompting each to print its current setting.
   */
  for (entry = GAGT_COMMAND_TABLE; entry->command; entry++)
    {
      if (entry->handler == gagt_command_summary
            || entry->handler == gagt_command_license
            || entry->handler == gagt_command_help)
        continue;

      entry->handler ("");
    }
}


/*
 * gagt_command_help()
 *
 * Document the available Glk commands.
 */
static void
gagt_command_help (const char *command)
{
  gagt_commandref_t entry, matched;
  assert (command);

  if (strlen (command) == 0)
    {
      gagt_normal_string ("Glk commands are");
      for (entry = GAGT_COMMAND_TABLE; entry->command; entry++)
        {
          gagt_commandref_t next;

          next = entry + 1;
          gagt_normal_string (next->command ? " " : " and ");
          gagt_standout_string (entry->command);
          gagt_normal_string (next->command ? "," : ".\n\n");
        }

      gagt_normal_string ("Glk commands may be abbreviated, as long as"
                          " the abbreviation is unambiguous.  Use ");
      gagt_standout_string ("glk help");
      gagt_normal_string (" followed by a Glk command name for help on that"
                          " command.\n");
      return;
    }

  matched = NULL;
  for (entry = GAGT_COMMAND_TABLE; entry->command; entry++)
    {
      if (gagt_strncasecmp (command, entry->command, strlen (command)) == 0)
        {
          if (matched)
            {
              gagt_normal_string ("The Glk command ");
              gagt_standout_string (command);
              gagt_normal_string (" is ambiguous.  Try ");
              gagt_standout_string ("glk help");
              gagt_normal_string (" for more information.\n");
              return;
            }
          matched = entry;
        }
    }
  if (!matched)
    {
      gagt_normal_string ("The Glk command ");
      gagt_standout_string (command);
      gagt_normal_string (" is not valid.  Try ");
      gagt_standout_string ("glk help");
      gagt_normal_string (" for more information.\n");
      return;
    }

  if (matched->handler == gagt_command_summary)
    {
      gagt_normal_string ("Prints a summary of all the current Glk AGiliTy"
                          " settings.\n");
    }

  else if (matched->handler == gagt_command_script)
    {
      gagt_normal_string ("Logs the game's output to a file.\n\nUse ");
      gagt_standout_string ("glk script on");
      gagt_normal_string (" to begin logging game output, and ");
      gagt_standout_string ("glk script off");
      gagt_normal_string (" to end it.  Glk AGiliTy will ask you for a file"
                          " when you turn scripts on.\n");
    }

  else if (matched->handler == gagt_command_inputlog)
    {
      gagt_normal_string ("Records the commands you type into a game.\n\nUse ");
      gagt_standout_string ("glk inputlog on");
      gagt_normal_string (", to begin recording your commands, and ");
      gagt_standout_string ("glk inputlog off");
      gagt_normal_string (" to turn off input logs.  You can play back"
                          " recorded commands into a game with the ");
      gagt_standout_string ("glk readlog");
      gagt_normal_string (" command.\n");
    }

  else if (matched->handler == gagt_command_readlog)
    {
      gagt_normal_string ("Plays back commands recorded with ");
      gagt_standout_string ("glk inputlog on");
      gagt_normal_string (".\n\nUse ");
      gagt_standout_string ("glk readlog on");
      gagt_normal_string (".  Command play back stops at the end of the"
                          " file.  You can also play back commands from a"
                          " text file created using any standard editor.\n");
    }

  else if (matched->handler == gagt_command_abbreviations)
    {
      gagt_normal_string ("Controls abbreviation expansion.\n\nGlk AGiliTy"
                          " automatically expands several standard single"
                          " letter abbreviations for you; for example, \"x\""
                          " becomes \"examine\".  Use ");
      gagt_standout_string ("glk abbreviations on");
      gagt_normal_string (" to turn this feature on, and ");
      gagt_standout_string ("glk abbreviations off");
      gagt_normal_string (" to turn it off.  While the feature is on, you"
                          " can bypass abbreviation expansion for an"
                          " individual game command by prefixing it with a"
                          " single quote.\n");
    }

  else if (matched->handler == gagt_command_fonts)
    {
      gagt_normal_string ("Controls the way Glk AGiliTy uses fonts.\n\n"
                          "AGT games normally assume 80x25 monospaced font"
                          " displays.  Glk can often use proportional fonts."
                          "  To try to improve text display, Glk AGiliTy will"
                          " attempt to automatically detect when game text"
                          " can be displayed safely in a proportional font,"
                          " and when fixed width fonts are required.  For"
                          " some games, however, you may need to override"
                          " it.  Use ");
      gagt_standout_string ("glk fonts automatic");
      gagt_normal_string (", ");
      gagt_standout_string ("glk fonts proportional");
      gagt_normal_string (", and ");
      gagt_standout_string ("glk fonts fixed");
      gagt_normal_string (" to switch between Glk AGiliTy font modes.\n");
    }

  else if (matched->handler == gagt_command_delays)
    {
      gagt_normal_string ("Shortens, or eliminates, AGT game delays.\n\nUse ");
      gagt_standout_string ("glk delays full");
      gagt_normal_string (", ");
      gagt_standout_string ("glk delays short");
      gagt_normal_string (", or ");
      gagt_standout_string ("glk delays none");
      gagt_normal_string (".  In Glk AGiliTy, you can also end an AGT game's"
                          " delay early, by pressing Space while the game is"
                          " delaying.\n");
    }

  else if (matched->handler == gagt_command_width)
    {
      gagt_normal_string ("Prints the screen width available for fixed font"
                          " display.\n\nEven though Glk AGiliTy tries to handle"
                          " issues surrounding proportional font displays for"
                          " you automatically, some game elements may still"
                          " need to display in fixed width fonts.  These"
                          " elements will be happiest if the available screen"
                          " width is at least 80 columns.\n");
    }

  else if (matched->handler == gagt_command_replacements)
    {
      gagt_normal_string ("Controls game text scanning and replacement.\n\n"
                          "Glk AGiliTy can monitor the game's output, and"
                          " replace a few selected standard messages with"
                          " equivalents, printed using a style that stands"
                          " out better in Glk displays.  Use ");
      gagt_standout_string ("glk replacements on");
      gagt_normal_string (" to turn this feature on, and ");
      gagt_standout_string ("glk replacements off");
      gagt_normal_string (" to turn it off.\n");
    }

  else if (matched->handler == gagt_command_statusline)
    {
      gagt_normal_string ("Controls the Glk AGiliTy status line display.\n\n"
                          "Use ");
      gagt_standout_string ("glk statusline extended");
      gagt_normal_string (" to display a full, two line status display, and ");
      gagt_standout_string ("glk statusline short");
      gagt_normal_string (" for a single line status display.\n");
    }

  else if (matched->handler == gagt_command_version)
    {
      gagt_normal_string ("Prints the version numbers of the Glk library"
                          " and the Glk AGiliTy port.\n");
    }

  else if (matched->handler == gagt_command_commands)
    {
      gagt_normal_string ("Turn off Glk commands.\n\nUse ");
      gagt_standout_string ("glk commands off");
      gagt_normal_string (" to disable all Glk commands, including this one."
                          "  Once turned off, there is no way to turn Glk"
                          " commands back on while inside the game.\n");
    }

  else if (matched->handler == gagt_command_license)
    {
      gagt_normal_string ("Prints Glk AGiliTy's software license.\n");
    }

  else if (matched->handler == gagt_command_help)
    gagt_command_help ("");

  else
    gagt_normal_string ("There is no help available on that Glk command."
                        "  Sorry.\n");
}


/*
 * gagt_command_escape()
 *
 * This function is handed each input line.  If the line contains a specific
 * Glk port command, handle it and return TRUE, otherwise return FALSE.
 */
static int
gagt_command_escape (const char *string)
{
  int posn;
  char *string_copy, *command, *argument;
  assert (string);

  /*
   * Return FALSE if the string doesn't begin with the Glk command escape
   * introducer.
   */
  posn = strspn (string, "\t ");
  if (gagt_strncasecmp (string + posn, "glk", strlen ("glk")) != 0)
    return FALSE;

  /* Take a copy of the string, without any leading space or introducer. */
  string_copy = gagt_malloc (strlen (string + posn) + 1 - strlen ("glk"));
  strcpy (string_copy, string + posn + strlen ("glk"));

  /*
   * Find the subcommand; the first word in the string copy.  Find its end,
   * and ensure it terminates with a NUL.
   */
  posn = strspn (string_copy, "\t ");
  command = string_copy + posn;
  posn += strcspn (string_copy + posn, "\t ");
  if (string_copy[posn] != '\0')
    string_copy[posn++] = '\0';

  /*
   * Now find any argument data for the command, ensuring it too terminates
   * with a NUL.
   */
  posn += strspn (string_copy + posn, "\t ");
  argument = string_copy + posn;
  posn += strcspn (string_copy + posn, "\t ");
  string_copy[posn] = '\0';

  /*
   * Try to handle the command and argument as a Glk subcommand.  If it
   * doesn't run unambiguously, print command usage.  Treat an empty command
   * as "help".
   */
  if (strlen (command) > 0)
    {
      gagt_commandref_t entry, matched;
      int matches;

      /*
       * Search for the first unambiguous table command string matching
       * the command passed in.
       */
      matches = 0;
      matched = NULL;
      for (entry = GAGT_COMMAND_TABLE; entry->command; entry++)
        {
          if (gagt_strncasecmp (command, entry->command, strlen (command)) == 0)
            {
              matches++;
              matched = entry;
            }
        }

      /* If the match was unambiguous, call the command handler. */
      if (matches == 1)
        {
          gagt_normal_char ('\n');
          matched->handler (argument);

          if (!matched->takes_argument && strlen (argument) > 0)
            {
              gagt_normal_string ("[The ");
              gagt_standout_string (matched->command);
              gagt_normal_string (" command ignores arguments.]\n");
            }
        }

      /* No match, or the command was ambiguous. */
      else
        {
          gagt_normal_string ("\nThe Glk command ");
          gagt_standout_string (command);
          gagt_normal_string (" is ");
          gagt_normal_string (matches == 0 ? "not valid" : "ambiguous");
          gagt_normal_string (".  Try ");
          gagt_standout_string ("glk help");
          gagt_normal_string (" for more information.\n");
        }
    }
  else
    {
      gagt_normal_char ('\n');
      gagt_command_help ("");
    }

  /* The string contained a Glk command; return TRUE. */
  free (string_copy);
  return TRUE;
}


/*---------------------------------------------------------------------*/
/*  Glk port input functions                                           */
/*---------------------------------------------------------------------*/

/* Longest line we're going to buffer for input. */
enum { GAGT_INPUTBUFFER_LENGTH = 256 };

/* Table of single-character command abbreviations. */
typedef const struct
{
  const char abbreviation;       /* Abbreviation character. */
  const char * const expansion;  /* Expansion string. */
} gagt_abbreviation_t;
typedef gagt_abbreviation_t *gagt_abbreviationref_t;

static gagt_abbreviation_t GAGT_ABBREVIATIONS[] = {
  {'c', "close"},    {'g', "again"},  {'i', "inventory"},
  {'k', "attack"},   {'l', "look"},   {'p', "open"},
  {'q', "quit"},     {'r', "drop"},   {'t', "take"},
  {'x', "examine"},  {'y', "yes"},    {'z', "wait"},
  {'\0', NULL}
};


/*
 * gagt_expand_abbreviations()
 *
 * Expand a few common one-character abbreviations commonly found in other
 * game systems, but not always normal in AGT games.
 */
static void
gagt_expand_abbreviations (char *buffer, int size)
{
  char *command, abbreviation;
  const char *expansion;
  gagt_abbreviationref_t entry;
  assert (buffer);

  /* Ignore anything that isn't a single letter command. */
  command = buffer + strspn (buffer, "\t ");
  if (!(strlen (command) == 1
        || (strlen (command) > 1 && isspace (command[1]))))
    return;

  /* Scan the abbreviations table for a match. */
  abbreviation = glk_char_to_lower ((unsigned char) command[0]);
  expansion = NULL;
  for (entry = GAGT_ABBREVIATIONS; entry->expansion; entry++)
    {
      if (entry->abbreviation == abbreviation)
        {
          expansion = entry->expansion;
          break;
        }
    }

  /*
   * If a match found, check for a fit, then replace the character with the
   * expansion string.
   */
  if (expansion)
    {
      if (strlen (buffer) + strlen (expansion) - 1 >= size)
        return;

      memmove (command + strlen (expansion) - 1, command, strlen (command) + 1);
      memcpy (command, expansion, strlen (expansion));

      gagt_standout_string ("[");
      gagt_standout_char (abbreviation);
      gagt_standout_string (" -> ");
      gagt_standout_string (expansion);
      gagt_standout_string ("]\n");
    }
}


/*
 * agt_input()
 *
 * Read a line from the keyboard, allocating space for it using malloc.
 * AGiliTy defines the following for the in_type argument:
 *
 *   in_type: 0=command, 1=number, 2=question, 3=userstr, 4=filename,
 *               5=RESTART,RESTORE,UNDO,QUIT
 *   Negative values are for internal use by the interface (i.e. this module)
 *   and so are free to be defined by the porter.
 *
 * Since it's unclear what use we can make of this information in Glk,
 * for the moment the argument is ignored.  It seems that no-one else
 * uses it, either.
 */
char *
agt_input (int in_type)
{
  event_t event;
  int length;
  char *buffer;

  /*
   * Update the current status line display, and flush any pending buffered
   * output.  Release any suspension of delays.
   */
  gagt_status_notify ();
  gagt_output_flush ();
  gagt_delay_resume ();

  /* Reset current x, as line input implies a newline. */
  curr_x = 0;

  /* Allocate a line input buffer, allowing 256 characters and a NUL. */
  length = GAGT_INPUTBUFFER_LENGTH + 1;
  buffer = gagt_malloc (length);

  /*
   * If we have an input log to read from, use that until it is exhausted.
   * On end of file, close the stream and resume input from line requests.
   */
  if (gagt_readlog_stream)
    {
      glui32 chars;

      /* Get the next line from the log stream. */
      chars = glk_get_line_stream (gagt_readlog_stream, buffer, length);
      if (chars > 0)
        {
          /* Echo the line just read in input style. */
          glk_set_style (style_Input);
          glk_put_buffer (buffer, chars);
          glk_set_style (style_Normal);

          /*
           * Convert the string from Glk's ISO 8859 Latin-1 to IBM cp 437,
           * add to any script, and return it.
           */
          gagt_iso_to_cp (buffer, buffer);
          if (script_on)
            textputs (scriptfile, buffer);
          return buffer;
        }

      /*
       * We're at the end of the log stream.  Close it, and then continue
       * on to request a line from Glk.
       */
      glk_stream_close (gagt_readlog_stream, NULL);
      gagt_readlog_stream = NULL;
    }

  /* Set this up as a read buffer for the main window, and wait. */
  glk_request_line_event (gagt_main_window, buffer, length - 1, 0);
  gagt_event_wait (evtype_LineInput, &event);

  /* Terminate the input line with a NUL. */
  assert (event.val1 < length);
  buffer[event.val1] = '\0';

  /*
   * If neither abbreviations nor local commands are enabled, use the data
   * read above without further massaging.
   */
  if (gagt_abbreviations_enabled || gagt_commands_enabled)
    {
      char *command;

      /*
       * If the first non-space input character is a quote, bypass all
       * abbreviation expansion and local command recognition, and use the
       * unadulterated input, less introductory quote.
       */
      command = buffer + strspn (buffer, "\t ");
      if (command[0] == '\'')
        {
          /* Delete the quote with memmove(). */
          memmove (command, command + 1, strlen (command));
        }
      else
        {
          /* Check for, and expand, any abbreviated commands. */
          if (gagt_abbreviations_enabled)
            gagt_expand_abbreviations (buffer, length);

          /*
           * Check for standalone "help", then for Glk port special commands;
           * suppress the interpreter's use of this input for Glk commands.
           */
          if (gagt_commands_enabled)
            {
              int posn;

              posn = strspn (buffer, "\t ");
              if (gagt_strncasecmp (buffer + posn, "help", strlen ("help"))== 0)
                {
                  if (strspn (buffer + posn + strlen ("help"), "\t ")
                      == strlen (buffer + posn + strlen ("help")))
                    {
                      gagt_display_register_help_request ();
                    }
                }

              if (gagt_command_escape (buffer))
                {
                  gagt_display_silence_help_hints ();
                  buffer[0] = '\0';
                  return buffer;
                }
            }
        }
    }

  /*
   * If there is an input log active, log this input string to it.  Note that
   * by logging here we get any abbreviation expansions but we won't log glk
   * special commands, nor any input read from a current open input log.
   */
  if (gagt_inputlog_stream)
    {
      glk_put_string_stream (gagt_inputlog_stream, buffer);
      glk_put_char_stream (gagt_inputlog_stream, '\n');
    }

  /*
   * Convert from Glk's ISO 8859 Latin-1 to IBM cp 437, and add to any script.
   */
  gagt_iso_to_cp (buffer, buffer);
  if (script_on)
    textputs (scriptfile, buffer);

  gagt_debug ("agt_input", "in_type=%d -> '%s'", in_type, buffer);
  return buffer;
}


/*
 * agt_getkey()
 *
 * Read a single character and return it.  AGiliTy defines the echo_char
 * argument as:
 *
 *   If echo_char=1, echo character. If 0, then the character is not
 *   required to be echoed (and ideally shouldn't be).
 *
 * However, I've found that not all other ports really do this, and in
 * practice it doesn't always look right.  So for Glk, the character is
 * always echoed to the main window.
 */
char
agt_getkey (rbool echo_char)
{
  event_t event;
  char buffer[3];
  assert (glk_stream_get_current ());

  /*
   * Update the current status line display, and flush any pending buffered
   * output.  Release any suspension of delays.
   */
  gagt_status_notify ();
  gagt_output_flush ();
  gagt_delay_resume ();

  /* Reset current x, as echoed character input implies a newline. */
  curr_x = 0;

  /*
   * If we have an input log to read from, use that as above until it is
   * exhausted.  We take just the first character of a given line.
   */
  if (gagt_readlog_stream)
    {
      glui32 chars;
      char logbuffer[GAGT_INPUTBUFFER_LENGTH + 1];

      /* Get the next line from the log stream. */
      chars = glk_get_line_stream (gagt_readlog_stream,
                                   logbuffer, sizeof (logbuffer));
      if (chars > 0)
        {
          /* Take just the first character, adding a newline if necessary. */
          buffer[0] = logbuffer[0];
          buffer[1] = buffer[0] == '\n' ? '\0' : '\n';
          buffer[2] = '\0';

          /* Echo the character just read in input style. */
          glk_set_style (style_Input);
          glk_put_string (buffer);
          glk_set_style (style_Normal);

          /*
           * Convert from Glk's ISO 8859 Latin-1 to IBM cp 437, add to any
           * script, and return the character.
           */
          gagt_iso_to_cp (buffer, buffer);
          if (script_on)
            textputs (scriptfile, buffer);
          return buffer[0];
        }

      /*
       * We're at the end of the log stream.  Close it, and then continue
       * on to request a character from Glk.
       */
      glk_stream_close (gagt_readlog_stream, NULL);
      gagt_readlog_stream = NULL;
    }

  /*
   * Request a single character from main window, and wait.  Ignore non-
   * ASCII codes that Glk returns for special function keys; we just want
   * one ASCII return value.  (Glk does treat Return as a special key,
   * though, and we want to pass that back as ASCII return.)
   */
  do
    {
      glk_request_char_event (gagt_main_window);
      gagt_event_wait (evtype_CharInput, &event);
    }
  while (event.val1 > UCHAR_MAX && event.val1 != keycode_Return);

  /*
   * Save the character into a short string buffer, converting Return
   * to newline, and adding a newline if not Return.
   */
  buffer[0] = event.val1 == keycode_Return ? '\n' : event.val1;
  buffer[1] = buffer[0] == '\n' ? '\0' : '\n';
  buffer[2] = '\0';

  /* If there is an input log active, log this input string to it. */
  if (gagt_inputlog_stream)
    glk_put_string_stream (gagt_inputlog_stream, buffer);

  /*
   * No matter what echo_char says, as it happens, the output doesn't look
   * great if we don't write out the character, and also a newline (c.f.
   * the "Yes/No" confirmation of the QUIT command)...
   */
  glk_set_style (style_Input);
  glk_put_string (buffer);
  glk_set_style (style_Normal);

  /*
   * Convert from Glk's ISO 8859 Latin-1 to IBM cp 437, and add to any
   * script.
   */
  gagt_iso_to_cp (buffer, buffer);
  if (script_on)
    textputs (scriptfile, buffer);

  gagt_debug ("agt_getkey", "echo_char=%d -> '%c'",
               echo_char, buffer[0] == '\n' ? '$' : buffer[0]);
  return buffer[0];
}


/*---------------------------------------------------------------------*/
/*  Glk port event functions                                           */
/*---------------------------------------------------------------------*/

/*
 * We have some clever atexit() finalizer handling for exit() calls that
 * come from the core interpreter.  However, an exit() call could also come
 * from Glk; Xkill for example.  To tell the difference, we'll have the
 * event wait functions set a flag to indicate when glk_select() is active.
 */
static int gagt_in_glk_select = FALSE;

/*
 * gagt_event_wait_2()
 * gagt_event_wait()
 *
 * Process Glk events until one of the expected type, or types, arrives.
 * Return the event of that type.
 */
static void
gagt_event_wait_2 (glui32 wait_type_1, glui32 wait_type_2, event_t * event)
{
  assert (event);

  do
    {
      gagt_in_glk_select = TRUE;
      glk_select (event);
      gagt_in_glk_select = FALSE;

      switch (event->type)
        {
        case evtype_Arrange:
        case evtype_Redraw:
          gagt_status_redraw ();
          break;
        }
    }
  while (!(event->type == wait_type_1 || event->type == wait_type_2));
}

static void
gagt_event_wait (glui32 wait_type, event_t * event)
{
  assert (event);
  gagt_event_wait_2 (wait_type, evtype_None, event);
}


/*
 * gagt_event_in_glk_select()
 *
 * Return TRUE if we're currently awaiting an event in glk_select().  Used
 * by the finalizer to distinguish interpreter and glk exit() calls.
 */
static int
gagt_event_in_glk_select (void)
{
  return gagt_in_glk_select;
}


/*---------------------------------------------------------------------*/
/*  Miscellaneous Glk port startup and options functions               */
/*---------------------------------------------------------------------*/

/*
 * Default screen height and width, and also a default status width for
 * use with Glk libraries that don't support separate windows.
 */
static const int GAGT_DEFAULT_SCREEN_WIDTH = 80,
                 GAGT_DEFAULT_SCREEN_HEIGHT = 25,
                 GAGT_DEFAULT_STATUS_WIDTH = 76;


/*
 * agt_option()
 *
 * Platform-specific setup and options handling.  AGiliTy defines the
 * arguments and options as:
 *
 *   If setflag is 0, then the option was prefixed with NO_. Return 1 if
 *   the option is recognized.
 *
 * The Glk port has no options file handling, so none of this is
 * implemented here.
 */
rbool
agt_option (int optnum, char *optstr[], rbool setflag)
{
  gagt_debug ("agt_option", "optnum=%d, optstr=%s, setflag=%d",
              optnum, optstr[0], setflag);
  return 0;
}


/*
 * agt_globalfile()
 *
 * Global options file handle handling.  For now, this is a stub, since
 * there is no .agilrc for this port.
 */
genfile
agt_globalfile (int fid)
{
  gagt_debug ("agt_globalfile", "fid=%d", fid);
  return badfile (fCFG);
}


/*
 * init_interface()
 *
 * General initialization for the module; sets some variables, and creates
 * the Glk windows to work in.  Called from the AGiliTy main().
 */
void
init_interface (int argc, char *argv[])
{
  glui32 status_height;

  /*
   * Begin with some default values for global variables that this module
   * is somehow responsible for.
   */
  script_on = center_on = par_fill_on = FALSE;
  scriptfile = badfile (fSCR);
  debugfile = stderr;

  /*
   * Set up AGT-specific Glk styles.  This needs to be done before any Glk
   * window is opened.
   */
  gagt_init_user_styles ();

  /*
   * Create the main game window.  The main game window creation must succeed.
   * If it fails, we'll return, and the caller can detect this by looking
   * for a NULL main window.
   */
  gagt_main_window = glk_window_open (0, 0, 0, wintype_TextBuffer, 0);
  if (!gagt_main_window)
    return;

  /*
   * Set the main window to be the default window, for convenience.  We do
   * this again in glk_main() -- this call is here just in case this version
   * of init_interface() is ever called by AGiliTy's main.
   */
  glk_set_window (gagt_main_window);

  /*
   * Screen height is something we don't use.  Linux Xglk returns dimensions
   * of 0x0 for text buffer windows, so we can't measure the main window
   * height anyway.  But... the height does come into play in AGiliTy's
   * agil.c, when the interpreter is deciding how to output game titles, and
   * how much of its own subsequent verbiage to output.  This gives us a
   * problem, since this "verbiage" is stuff we look for and replace with
   * our own special text.  So... sigh, set 25, and try to cope in the
   * special text we've set up with all the variations that ensue.
   *
   * Screen width does get used, but so, so many games, and for that matter
   * the interpreter itself, assume 80 chars, so it's simplest just to set,
   * and keep, this, and put up with the minor odd effects (making it match
   * status_width, or making it something like MAX_INT to defeat the game's
   * own wrapping, gives a lot of odder effects, trust me on this one...).
   */
  screen_width = GAGT_DEFAULT_SCREEN_WIDTH;
  screen_height = GAGT_DEFAULT_SCREEN_HEIGHT;

  /*
   * Create a status window, with one or two lines as selected by user
   * options or flags.  We can live without a status window if we have to.
   */
  status_height = gagt_extended_status_enabled ? 2 : 1;
  glk_stylehint_set (wintype_TextGrid, style_User1, stylehint_ReverseColor, 1);
  gagt_status_window = glk_window_open (gagt_main_window,
                                        winmethod_Above | winmethod_Fixed,
                                        status_height, wintype_TextGrid, 0);
  if (gagt_status_window)
    {
      /*
       * Call gagt_status_redraw() to set the interpreter's status_width
       * variable initial value.
       */
      gagt_status_redraw ();
    }
  else
    {
      /*
       * No status window, so set a suitable default status width.  In this
       * case, we're using a value four characters less than the set screen
       * width.  AGiliTy's status line code will fill to this width with
       * justified text, and we add two characters of bracketing when
       * displaying status lines for Glks that don't support separate windows,
       * making a total of 78 characters, which should be fairly standard.
       */
      status_width = GAGT_DEFAULT_STATUS_WIDTH;
    }

  agt_clrscr ();
  gagt_debug ("init_interface", "argc=%d, argv=%p", argc, argv);
}


/*---------------------------------------------------------------------*/
/*  Replacement interface.c functions                                  */
/*---------------------------------------------------------------------*/

/* Get_user_file() type codes. */
enum {
  AGT_SCRIPT = 0,
  AGT_SAVE = 1,
  AGT_RESTORE = 2,
  AGT_LOG_READ = 3,
  AGT_LOG_WRITE = 4
};

/* Longest acceptable filename. */
enum { GAGT_MAX_PATH = 1024 };


#ifdef GLK_ANSI_ONLY
/*
 * gagt_confirm()
 *
 * Print a confirmation prompt, and read a single input character, taking
 * only [YyNn] input.  If the character is 'Y' or 'y', return TRUE.
 *
 * This function is only required for the ANSI version of get_user_file().
 */
static int
gagt_confirm (const char *prompt)
{
  event_t event;
  unsigned char response;
  assert (prompt);

  /*
   * Print the confirmation prompt, in a style that hints that it's from the
   * interpreter, not the game.
   */
  gagt_standout_string (prompt);

  /* Wait for a single 'Y' or 'N' character response. */
  response = ' ';
  do
    {
      glk_request_char_event (gagt_main_window);
      gagt_event_wait (evtype_CharInput, &event);

      if (event.val1 <= UCHAR_MAX)
        response = glk_char_to_upper (event.val1);
    }
  while (!(response == 'Y' || response == 'N'));

  /* Echo the confirmation response, and a blank line. */
  glk_set_style (style_Input);
  glk_put_string (response == 'Y' ? "Yes" : "No");
  glk_set_style (style_Normal);
  glk_put_string ("\n");

  return response == 'Y';
}
#endif


/*
 * gagt_get_user_file()
 *
 * Alternative versions of functions to get a file name from the user, and
 * return a file stream structure.  These functions are front-ended by the
 * main get_user_file() function, which first converts the AGT file type
 * into Glk usage and filemode, and also a mode for fopen()/fdopen().
 *
 * The ANSI version of the function prompts for the file using the simple
 * method of querying the user through input in the main window.  It then
 * constructs a file stream around the path entered, and returns it.
 *
 * The non-ANSI, Glk version is more sneaky.  It prompts for a file using
 * Glk's functions to get filenames by prompt, file selection dialog, or
 * whatever.  Then it attempts to uncover which file descriptor Glk opened
 * its file on, dup's it, closes the Glk stream, and returns a file stream
 * built on this file descriptor.  This is all highly non-ANSI, requiring
 * dup() and fdopen(), and making some assumptions about the way that dup,
 * open, and friends work.  It works on Linux, and on Mac (CodeWarrior).
 * It may also work for you, but if it doesn't, or if your system lacks
 * things like dup or fdopen, define GLK_ANSI_ONLY and use the safe version.
 *
 * If GARGLK is used, non-ansi version calls garglk_fileref_get_name()
 * instead, and opens a file the highly portable way, but still with a
 * Glkily nice prompt dialog.
 */
#ifdef GLK_ANSI_ONLY
static genfile
gagt_get_user_file (glui32 usage, glui32 fmode, const char *fdtype)
{
  char filepath[GAGT_MAX_PATH];
  event_t event;
  int index, all_spaces;
  genfile retfile;
  assert (fdtype);

  /* Prompt in a similar way to Glk. */
  switch (usage)
    {
    case fileusage_SavedGame:
      gagt_normal_string ("Enter saved game");
      break;

    case fileusage_Transcript:
      gagt_normal_string ("Enter transcript file");
      break;

    case fileusage_InputRecord:
      gagt_normal_string ("Enter command record file");
      break;
    }
  switch (fmode)
    {
    case filemode_Read:
      gagt_normal_string (" to load: ");
      break;

    case filemode_Write:
      gagt_normal_string (" to store: ");
      break;
    }

  /* Get the path to the file from the user. */
  glk_request_line_event (gagt_main_window, filepath, sizeof (filepath) - 1, 0);
  gagt_event_wait (evtype_LineInput, &event);

  /* Terminate the file path with a NUL. */
  assert (event.val1 < sizeof (filepath));
  filepath[event.val1] = '\0';

  /* Reject file paths that only contain any whitespace characters. */
  all_spaces = TRUE;
  for (index = 0; index < strlen (filepath); index++)
    {
      if (!isspace (filepath[index]))
        {
          all_spaces = FALSE;
          break;
        }
    }
  if (all_spaces)
    return badfile (fSAV);

  /* Confirm overwrite of any existing file. */
  if (fmode == filemode_Write)
    {
      genfile file;

      file = fopen (filepath, "r");
      if (file)
        {
          fclose (file);

          if (!gagt_confirm ("Overwrite existing file? [y/n] "))
            return badfile (fSAV);
        }
    }

  /* Open and return a FILE* stream, or badfile if this fails. */
  retfile = fopen (filepath, fdtype);
  return retfile ? retfile : badfile (fSAV);
}
#endif

#ifndef GLK_ANSI_ONLY
static genfile
gagt_get_user_file (glui32 usage, glui32 fmode, const char *fdtype)
{
  frefid_t fileref;
  strid_t stream;
  int tryfd, glkfd, dupfd, retfd;
  genfile retfile;
  assert (fdtype);

  /* Try to get a Glk file reference with these attributes. */
  fileref = glk_fileref_create_by_prompt (usage, fmode, 0);
  if (!fileref)
    return badfile (fSAV);

  /*
   * Reject the file reference if we're expecting to read from it,
   * and the referenced file doesn't exist.
   */
  if (fmode == filemode_Read && !glk_fileref_does_file_exist (fileref))
    {
      glk_fileref_destroy (fileref);
      return badfile (fSAV);
    }

  /*
   * Now, it gets ugly.  Glk assumes that the interpreter will do all of
   * its reading and writing using the Glk streams read/write functions.
   * It won't; at least, not without major surgery.  So here we're going
   * to do some dangerous stuff...
   *
   * Since a Glk stream is opaque, it's hard to tell what the underlying
   * file descriptor is for it.  We can get it if we want to play around
   * in the internals of the strid_t structure, but it's unpleasant.
   * The alternative is, arguably, no more pleasant, but it makes for
   * (perhaps) more portable code.  What we'll do is to dup a file, then
   * immediately close it, and call glk_stream_open_file().  The open()
   * in glk_stream_open_file() will return the same file descriptor number
   * that we just close()d (in theory...).  This makes the following two
   * major assumptions:
   *
   *  1) glk_stream_open_file() opens precisely one file with open()
   *  2) open() always uses the lowest available file descriptor number,
   *     like dup()
   *
   * Believe it or not, this is better than the alternatives.  There is
   * no Glk function to return the filename from a frefid_t, and it
   * moves about in different Glk libraries so we can't just take it
   * from a given offset.  And there is no Glk function to return the
   * underlying file descriptor or FILE* from a Glk stream either. :-(
   */

#ifdef GARGLK
  retfile = fopen(garglk_fileref_get_name(fileref), fdtype);
#else

  /* So, start by dup()'ing the first file descriptor we can, ... */
  glkfd = -1;
  for (tryfd = 0; tryfd < FD_SETSIZE; tryfd++)
    {
      glkfd = fcntl (tryfd, F_DUPFD, 0);
      if (glkfd != -1)
        break;
    }
  if (tryfd >= FD_SETSIZE)
    {
      glk_fileref_destroy (fileref);
      return badfile (fSAV);
    }

  /* ...then closing it, ... */
  close (glkfd);

  /* ...now open the Glk stream, assuming it opens on file 'glkfd', ... */
  stream = glk_stream_open_file (fileref, fmode, 0);
  if (!stream)
    {
      glk_fileref_destroy (fileref);
      return badfile (fSAV);
    }

  /* ...dup() the Glk file onto another file descriptor, ... */
  dupfd = fcntl (glkfd, F_DUPFD, 0);
  assert (dupfd != -1);

  /* ...close and destroy the Glk edifice for this file, ... */
  glk_stream_close (stream, NULL);
  glk_fileref_destroy (fileref);

  /* ...for neatness, dup() back to the old Glk file descriptor, ... */
  retfd = fcntl (dupfd, F_DUPFD, 0);
  assert (retfd != -1 && retfd == glkfd);
  close (dupfd);

  /* ...and finally, open a FILE* stream onto the return descriptor. */
  retfile = fdopen (retfd, fdtype);
  if (!retfile)
    return badfile (fSAV);
#endif /* GARGLK */

  /*
   * The result of all of this should now be that retfile is a FILE* wrapper
   * round a file descriptor open on a file indicated by the user through Glk.
   * Return it.
   */
  return retfile;
}
#endif


/*
 * get_user_file()
 *
 * Get a file name from the user, and return the file stream structure.
 * This is a front-end to ANSI and non-ANSI variants of the function.
 */
genfile
get_user_file (int type)
{
  glui32 usage = 0, fmode = 0;
  char *fdtype;
  genfile retfile;

  gagt_output_flush ();

  /* Map AGiliTy type to Glk usage and filemode. */
  switch (type)
    {
    case AGT_SCRIPT:
      usage = fileusage_Transcript;
      fmode = filemode_Write;
      break;

    case AGT_SAVE:
      usage = fileusage_SavedGame;
      fmode = filemode_Write;
      break;

    case AGT_RESTORE:
      usage = fileusage_SavedGame;
      fmode = filemode_Read;
      break;

    case AGT_LOG_READ:
      usage = fileusage_InputRecord;
      fmode = filemode_Read;
      break;

    case AGT_LOG_WRITE:
      usage = fileusage_InputRecord;
      fmode = filemode_Write;
      break;

    default:
      gagt_fatal ("GLK: Unknown file type encountered");
      gagt_exit ();
    }

  /* From these, determine a mode type for the f[d]open() call. */
  if (fmode == filemode_Write)
    fdtype = usage == fileusage_SavedGame ? "wb" : "w";
  else
    fdtype = usage == fileusage_SavedGame ? "rb" : "r";

  /* Get a file stream from these using the appropriate function. */
  retfile = gagt_get_user_file (usage, fmode, fdtype);

  gagt_debug ("get_user_file", "type=%d -> %p", type, retfile);
  return retfile;
}


/*
 * set_default_filenames()
 *
 * Set defaults for last save, log, and script filenames.
 */
void
set_default_filenames (fc_type fc)
{
  /*
   * There is nothing to do in this function, since Glk has its own ideas on
   * default names for files obtained with a prompt.
   */
  gagt_debug ("set_default_filenames", "fc=%p", fc);
}


/*---------------------------------------------------------------------*/
/*  Functions intercepted by link-time wrappers                        */
/*---------------------------------------------------------------------*/

/*
 * __wrap_toupper()
 * __wrap_tolower()
 *
 * Wrapper functions around toupper(), tolower(), and fatal().  The Linux
 * linker's --wrap option will convert calls to mumble() to __wrap_mumble()
 * if we give it the right options.  We'll use this feature to translate
 * all toupper() and tolower() calls in the interpreter code into calls to
 * Glk's versions of these functions.
 *
 * It's not critical that we do this.  If a linker, say a non-Linux one,
 * won't do --wrap, then just do without it.  It's unlikely that there
 * will be much noticeable difference.
 */
int
__wrap_toupper (int ch)
{
  unsigned char uch;

  uch = glk_char_to_upper ((unsigned char) ch);
  return (int) uch;
}

int
__wrap_tolower (int ch)
{
  unsigned char lch;

  lch = glk_char_to_lower ((unsigned char) ch);
  return (int) lch;
}


/*---------------------------------------------------------------------*/
/*  Replacements for AGiliTy main() and options parsing                */
/*---------------------------------------------------------------------*/

/* External declaration of interface.c's set default options function. */
extern void set_default_options (void);

/*
 * The following values need to be passed between the startup_code and main
 * functions.
 */
static int gagt_saved_argc = 0;         /* Recorded argc. */
static char **gagt_saved_argv = NULL,   /* Recorded argv. */
            *gagt_gamefile = NULL,      /* Name of game file. */
            *gagt_game_message = NULL;  /* Error message. */

/*
 * Flag to set if we want to test for a clean exit.  Without this it's a
 * touch tricky sometimes to corner AGiliTy into calling exit() for us; it
 * tends to require a broken game file.
 */
static int gagt_clean_exit_test = FALSE;


/*
 * gagt_parse_option()
 *
 * Glk-ified version of AGiliTy's parse_options() function.  In practice,
 * because Glk has got to them first, most options that come in here are
 * probably going to be single-character ones, since this is what we told
 * Glk in the arguments structure above.  The Glk font control and other
 * special tweaky flags will probably be the only multiple-character ones.
 */
static int
gagt_parse_option (const char *option)
{
  unsigned int index;
  assert (option);

  assert (option[0] == '-');
  for (index = 1; option[index]; index++)
    {
      switch (option[index])
        {
        case 'g':
          switch (option[++index])
            {
            case 'f':
              gagt_font_mode = FONT_FIXED_WIDTH;
              break;
            case 'p':
              gagt_font_mode = FONT_PROPORTIONAL;
              break;
            case 'a':
              gagt_font_mode = FONT_AUTOMATIC;
              break;
            case 'd':
              gagt_delay_mode = DELAY_FULL;
              break;
            case 'h':
              gagt_delay_mode = DELAY_SHORT;
              break;
            case 'n':
              gagt_delay_mode = DELAY_OFF;
              break;
            case 'r':
              gagt_replacement_enabled = FALSE;
              break;
            case 'x':
              gagt_abbreviations_enabled = FALSE;
              break;
            case 's':
              gagt_extended_status_enabled = TRUE;
              break;
            case 'l':
              gagt_extended_status_enabled = FALSE;
              break;
            case 'c':
              gagt_commands_enabled = FALSE;
              break;
            case 'D':
              DEBUG_OUT = TRUE;
              break;
            case '#':
              gagt_clean_exit_test = TRUE;
              break;
            default:
              return FALSE;
            }
          break;

        case 'p':
          debug_parse = TRUE;
          break;
        case 'a':
          DEBUG_DISAMBIG = TRUE;
          break;
        case 'd':
          DEBUG_AGT_CMD = TRUE;
          break;
        case 'x':
          DEBUG_EXEC_VERB = TRUE;
          break;
        case 's':
          DEBUG_SMSG = TRUE;
          break;
#ifdef MEM_INFO
        case 'M':
          DEBUG_MEM = TRUE;
          break;
#endif
        case 'm':
          descr_maxmem = 0;
          break;
        case 't':
          BATCH_MODE = TRUE;
          break;
        case 'c':
          make_test = TRUE;
          break;
        case '1':
          irun_mode = TRUE;
          break;
#ifdef OPEN_FILE_AS_TEXT
        case 'b':
          open_as_binary = TRUE;
          break;
#endif

        case '?':
        default:
          return FALSE;
        }
    }

  return TRUE;
}


/*
 * gagt_startup_code()
 * gagt_main()
 *
 * Together, these functions take the place of the original AGiliTy main().
 * The first one is called from glkunix_startup_code(), to parse and
 * generally handle options.  The second is called from glk_main(), and
 * does the real work of running the game.
 */
static int
gagt_startup_code (int argc, char *argv[])
{
  int argv_index;

  /*
   * Before doing anything else, stash argc and argv away for use by
   * gagt_main() below.
   */
  gagt_saved_argc = argc;
  gagt_saved_argv = argv;

  /* Make the mandatory call for initialization. */
  set_default_options ();

  /* Handle command line arguments. */
  for (argv_index = 1;
       argv_index < argc && argv[argv_index][0] == '-'; argv_index++)
    {
      /*
       * Handle an option string coming after "-".  If the options parse
       * fails, return FALSE.
       */
      if (!gagt_parse_option (argv[argv_index]))
        return FALSE;
    }

  /*
   * Get the name of the game file.  Since we need this in our call from
   * glk_main, we need to keep it in a module static variable.  If the game
   * file name is omitted, then here we'll set the pointer to NULL, and
   * complain about it later in main.  Passing the message string around
   * like this is a nuisance...
   */
  if (argv_index == argc - 1)
    {
      gagt_gamefile = argv[argv_index];
      gagt_game_message = NULL;
#ifdef GARGLK
      char *s;
      s = strrchr(gagt_gamefile, '\\');
      if (s) garglk_set_story_name(s+1);
      s = strrchr(gagt_gamefile, '/');
      if (s) garglk_set_story_name(s+1);
#endif /* GARGLK */
    }
  else
    {
      gagt_gamefile = NULL;
      if (argv_index < argc - 1)
        gagt_game_message = "More than one game file was given"
                            " on the command line.";
      else
        gagt_game_message = "No game file was given on the command line.";
    }

  /* All startup options were handled successfully. */
  return TRUE;
}

static void
gagt_main (void)
{
  fc_type fc;
  assert (gagt_saved_argc != 0 && gagt_saved_argv);

  /* Ensure AGiliTy internal types have the right sizes. */
  if (sizeof (integer) < 2 || sizeof (int32) < 4 || sizeof (uint32) < 4)
    {
      gagt_fatal ("GLK: Types sized incorrectly, recompilation is needed");
      gagt_exit ();
    }

  /*
   * Initialize the interface.  As it happens, init_interface() is in our
   * module here (above), and ignores argc and argv, but since the main() in
   * AGiliTy passes them, we'll do so here, just in case we ever want to go
   * back to using AGiliTy's main() function.
   *
   * init_interface() can fail if there is a problem creating the main
   * window.  As it doesn't return status, we have to detect this by checking
   * that gagt_main_window is not NULL.
   */
  init_interface (gagt_saved_argc, gagt_saved_argv);
  if (!gagt_main_window)
    {
      gagt_fatal ("GLK: Can't open main window");
      gagt_exit ();
    }
  glk_window_clear (gagt_main_window);
  glk_set_window (gagt_main_window);
  glk_set_style (style_Normal);

  /* If there's a problem with the game file, complain now. */
  if (!gagt_gamefile)
    {
      assert (gagt_game_message);
      if (gagt_status_window)
        glk_window_close (gagt_status_window, NULL);
      gagt_header_string ("Glk AGiliTy Error\n\n");
      gagt_normal_string (gagt_game_message);
      gagt_normal_char ('\n');
      gagt_exit ();
    }

  /*
   * Create a game file context, and try to ensure it will open successfully
   * in run_game().
   */
  fc = init_file_context (gagt_gamefile, fDA1);
  errno = 0;
  if (!(gagt_workround_fileexist (fc, fAGX)
        || gagt_workround_fileexist (fc, fDA1)))
    {
      if (gagt_status_window)
        glk_window_close (gagt_status_window, NULL);
      gagt_header_string ("Glk AGiliTy Error\n\n");
      gagt_normal_string ("Can't find or open game '");
      gagt_normal_string (gagt_gamefile);
      gagt_normal_char ('\'');
      if (errno != 0)
        {
          gagt_normal_string (": ");
          gagt_normal_string (strerror (errno));
        }
      gagt_normal_char ('\n');
      gagt_exit ();
    }

  /*
   * Run the game interpreter in AGiliTy.  run_game() releases the file
   * context, so we don't have to, don't want to, and shouldn't.
   */
  run_game (fc);

  /*
   * Handle any updated status, and flush all remaining buffered output;
   * this also frees all malloc'ed memory in the buffers.
   */
  gagt_status_notify ();
  gagt_output_flush ();

  /*
   * Free any temporary memory that may have been used by status line
   * functions.
   */
  gagt_status_cleanup ();

  /* Close any open transcript, input log, and/or read log. */
  if (gagt_transcript_stream)
    {
      glk_stream_close (gagt_transcript_stream, NULL);
      gagt_transcript_stream = NULL;
    }
  if (gagt_inputlog_stream)
    {
      glk_stream_close (gagt_inputlog_stream, NULL);
      gagt_inputlog_stream = NULL;
    }
  if (gagt_readlog_stream)
    {
      glk_stream_close (gagt_readlog_stream, NULL);
      gagt_readlog_stream = NULL;
    }
}


/*---------------------------------------------------------------------*/
/*  Linkage between Glk entry/exit calls and the AGiliTy interpreter   */
/*---------------------------------------------------------------------*/

/*
 * Safety flags, to ensure we always get startup before main, and that
 * we only get a call to main once.
 */
static int gagt_startup_called = FALSE,
           gagt_main_called = FALSE;

/*
 * We try to catch calls to exit() from the interpreter, and redirect them
 * to glk_exit().  To help tell these calls from a call to exit() from
 * glk_exit() itself, we need to monitor when interpreter code is running,
 * and when not.
 */
static int gagt_agility_running = FALSE;


/*
 * gagt_finalizer()
 *
 * ANSI atexit() handler.  This is the first part of trying to catch and re-
 * direct the calls the core AGiliTy interpreter makes to exit() -- we really
 * want it to call glk_exit(), but it's hard to achieve.  There are three
 * basic approaches possible, and all have drawbacks:
 *
 *   o #define exit to gagt_something, and provide the gagt_something()
 *     function.  This type of macro definition is portable for the most
 *     part, but tramples the code badly, and messes up the build of the
 *     non-interpreter "support" binaries.
 *   o Use ld's --wrap to wrapper exit.  This only works with Linux's linker
 *     and so isn't at all portable.
 *   o Register an exit handler with atexit(), and try to cope in it after
 *     exit() has been called.
 *
 * Here we try the last of these.  The one sticky part of it is that in our
 * exit handler we'll want to call glk_exit(), which will in all likelihood
 * call exit().  And multiple calls to exit() from a program are "undefined".
 *
 * In practice, C runtimes tend to do one of three things: they treat the
 * exit() call from the exit handler as if it was a return; they recurse
 * indefinitely through the hander; or they do something ugly (abort, for
 * example).  The first of these is fine, ideal in fact, and seems to be the
 * Linux and SVR4 behavior.  The second we can avoid with a flag.  The last
 * is the problem case, seen only with SVR3 (and even then, it occurs only
 * on program exit, after everything's cleaned up, and for that matter only
 * on abnormal exit).
 *
 * Note that here we're not expecting to get a call to this routine, and if
 * we do, and interpreter code is still running, it's a sign that we need
 * to take actions we'd hoped not to have to take.
 */
static void
gagt_finalizer (void)
{
  /*
   * If interpreter code is still active, and we're not in a glk_select(),
   * the core interpreter code called exit().  Handle cleanup.
   */
  if (gagt_agility_running && !gagt_event_in_glk_select ())
    {
      event_t event;

      /*
       * If we have a main window, try to update status (which may go to the
       * status window, or to the main window) and flush any pending buffered
       * output.
       */
      if (gagt_main_window)
        {
          gagt_status_notify ();
          gagt_output_flush ();
        }

      /*
       * Clear the flag to avoid recursion, and call glk_exit() to clean up
       * Glk and terminate.  This is the call that probably re-calls exit(),
       * and thus prods "undefined" bits of the C runtime, so we'll make it
       * configurable and overrideable for problem cases.
       */
      gagt_agility_running = FALSE;

#ifdef GARGLK
      return;
#endif /* GARGLK */

#ifndef GLK_CLEAN_EXIT
      if (!getenv ("GLKAGIL_CLEAN_EXIT"))
        {
          glk_exit ();
          return;
        }
#endif

      /*
       * We've decided not to take the dangerous route.
       *
       * In that case, providing we have a main window, fake a Glk-like-ish
       * hit-any-key-and-wait message using a simple string in the main
       * window.  Not great, but usable where we're forced into bypassing
       * glk_exit().  If we have no main window, there's no point in doing
       * anything more.
       */
      if (gagt_main_window)
        {
          glk_cancel_char_event (gagt_main_window);
          glk_cancel_line_event (gagt_main_window, NULL);

          glk_set_style (style_Alert);
          glk_put_string ("\n\nHit any key to exit.\n");
          glk_request_char_event (gagt_main_window);
          gagt_event_wait (evtype_CharInput, &event);
        }
    }
}


/*
 * gagt_exit()
 *
 * Glk_exit() local wrapper.  This is the second part of trying to catch
 * and redirect calls to exit().  Glk_finalizer() above needs to know that
 * we called glk_exit() already from here, so it doesn't try to do it again.
 */
static void
gagt_exit (void)
{
  assert (gagt_agility_running);

  /*
   * Clear the running flag to neutralize gagt_finalizer(), throw out any
   * buffered output data, and then call the real glk_exit().
   */
  gagt_agility_running = FALSE;
  gagt_output_delete ();
  glk_exit ();
}


/*
 * __wrap_exit()
 *
 * Exit() wrapper where a linker does --wrap.  This is the third part of
 * trying to catch and redirect calls to exit().
 *
 * This function is for use only with IFP, and avoids a nasty attempt at
 * reusing a longjmp buffer.   IFP will redirect calls to exit() into
 * glk_exit() as a matter of course.  It also handles atexit(), and we've
 * registered a function with atexit() that calls glk_exit(), and
 * IFP redirects glk_exit() to be an effective return from glk_main().  At
 * that point it calls finalizers.  So without doing something special for
 * IFP, we'll find ourselves calling glk_exit() twice -- once as the IFP
 * redirected exit(), and once from our finalizer.  Two returns from the
 * function glk_main() is a recipe for unpleasantness.
 *
 * As IFP is Linux-only, at present, --wrap will always be available to IFP
 * plugin builds.  So here, we'll wrap exit() before IFP can get to it, and
 * handle it safely.  For non-IFP/non-wrap links, this is just an unused
 * function definition, and can be safely ignored...
 */
void
__wrap_exit (int status)
{
  assert (gagt_agility_running);

  /*
   * In an IFP plugin, only the core interpreter code could have called exit()
   * here -- we don't, and IFP redirects glk_exit(), the only other potential
   * caller of exit().  (It also redirects exit() if we don't get to it here
   * first.)
   *
   * So, if we have a main window, flush it.  This is the same cleanup as
   * done by the finalizer.
   */
  if (gagt_main_window)
    {
      gagt_status_notify ();
      gagt_output_flush ();
    }

  /* Clear the running flag, and transform exit() into a glk_exit(). */
  gagt_agility_running = FALSE;
  glk_exit ();
}


/*
 * glk_main()
 *
 * Main entry point for Glk.  Here, all startup is done, and we call our
 * function to run the game.
 */
void
glk_main (void)
{
  assert (gagt_startup_called && !gagt_main_called);
  gagt_main_called = TRUE;

  /*
   * Register gagt_finalizer() with atexit() to cleanup on exit.  Note that
   * this module doesn't expect the atexit() handler to be called on all
   * forms of exit -- see comments in gagt_finalizer() for more.
   */
  if (atexit (gagt_finalizer) != 0)
    {
      gagt_fatal ("GLK: Failed to register finalizer");
      gagt_exit ();
    }

  /*
   * If we're testing for a clean exit, deliberately call exit() to see what
   * happens.  We're hoping for a clean process termination, but our exit
   * code explores "undefined" ANSI.  If we get something ugly, like a core
   * dump, we'll want to set GLK[AGIL]_CLEAN_EXIT.
   */
  if (gagt_clean_exit_test)
    {
      gagt_agility_running = TRUE;
      exit (0);
    }

  /*
   * The final part of trapping exit().  Set the running flag, and call the
   * interpreter main function.  Clear the flag when the main function returns.
   */
  gagt_agility_running = TRUE;
  gagt_main ();
  gagt_agility_running = FALSE;
}


/*---------------------------------------------------------------------*/
/*  Glk linkage relevant only to the UNIX platform                     */
/*---------------------------------------------------------------------*/
#if defined(__unix) || defined(GARGLK)

#include "glkstart.h"

/*
 * Glk arguments for UNIX versions of the Glk interpreter.
 */
glkunix_argumentlist_t glkunix_arguments[] = {
  {(char *) "-gf", glkunix_arg_NoValue,
   (char *) "-gf        Force Glk to use only a fixed width font"},
  {(char *) "-gp", glkunix_arg_NoValue,
   (char *) "-gp        Allow Glk to use only a proportional font"},
  {(char *) "-ga", glkunix_arg_NoValue,
   (char *) "-ga        Try to use a suitable Glk font automatically"},
  {(char *) "-gd", glkunix_arg_NoValue,
   (char *) "-gd        Delay for the full period in Glk"},
  {(char *) "-gh", glkunix_arg_NoValue,
   (char *) "-gh        Delay for approximately half the period in Glk"},
  {(char *) "-gn", glkunix_arg_NoValue,
   (char *) "-gn        Turn off all game delays in Glk"},
  {(char *) "-gr", glkunix_arg_NoValue,
   (char *) "-gr        Turn off Glk text replacement"},
  {(char *) "-gx", glkunix_arg_NoValue,
   (char *) "-gx        Turn off Glk abbreviation expansions"},
  {(char *) "-gs", glkunix_arg_NoValue,
   (char *) "-gs        Display a short status window in Glk"},
  {(char *) "-gl", glkunix_arg_NoValue,
   (char *) "-gl        Display an extended status window in Glk"},
  {(char *) "-gc", glkunix_arg_NoValue,
   (char *) "-gc        Turn off Glk command escapes in games"},
  {(char *) "-gD", glkunix_arg_NoValue,
   (char *) "-gD        Turn on Glk port module debug tracing"},
  {(char *) "-g#", glkunix_arg_NoValue,
   (char *) "-g#        Test for clean exit (Glk module debugging only)"},
  {(char *) "-1", glkunix_arg_NoValue,
   (char *) "-1         IRUN Mode: Print messages in first person"},
  {(char *) "-d", glkunix_arg_NoValue,
   (char *) "-d         Debug metacommand execution"},
  {(char *) "-t", glkunix_arg_NoValue,
   (char *) "-t         Test mode"},
  {(char *) "-c", glkunix_arg_NoValue,
   (char *) "-c         Create test file"},
  {(char *) "-m", glkunix_arg_NoValue,
   (char *) "-m         Force descriptions to be loaded from disk"},
#ifdef OPEN_AS_TEXT
  {(char *) "-b", glkunix_arg_NoValue,
   (char *) "-b         Open data files as binary files"},
#endif
  {(char *) "-p", glkunix_arg_NoValue,
   (char *) "-p         Debug parser"},
  {(char *) "-x", glkunix_arg_NoValue,
   (char *) "-x         Debug verb execution loop"},
  {(char *) "-a", glkunix_arg_NoValue,
   (char *) "-a         Debug disambiguation system"},
  {(char *) "-s", glkunix_arg_NoValue,
   (char *) "-s         Debug STANDARD message handler"},
#ifdef MEM_INFO
  {(char *) "-M", glkunix_arg_NoValue,
   (char *) "-M         Debug memory allocation"},
#endif
  {(char *) "", glkunix_arg_ValueCanFollow,
   (char *) "filename   game to run"},
  {NULL, glkunix_arg_End, NULL}
};


/*
 * glkunix_startup_code()
 *
 * Startup entry point for UNIX versions of Glk AGiliTy.  Glk will call
 * glkunix_startup_code() to pass in arguments.  On startup, we call our
 * function to parse arguments and generally set stuff up.
 */
int
glkunix_startup_code (glkunix_startup_t * data)
{
  assert (!gagt_startup_called);
  gagt_startup_called = TRUE;

#ifdef GARGLK
  garglk_set_program_name("Agility 1.1.1.1");
  garglk_set_program_info(
                  "AGiliTy 1.1.1.1 by Robert Masenten\n"
                  "Glk port by Simon Baldwin\n"
  );
#endif /* GARGLK */

  return gagt_startup_code (data->argc, data->argv);
}
#endif /* _unix */


/*---------------------------------------------------------------------*/
/*  Glk linkage relevant only to the Mac platform                      */
/*---------------------------------------------------------------------*/
#if TARGET_OS_MAC

#include "macglk_startup.h"

static strid_t gagt_mac_gamefile = NULL;
static short gagt_savedVRefNum = 0;
static long gagt_savedDirID = 0;


/*
 * gagt_mac_whenselected()
 * gagt_mac_whenbuiltin()
 * macglk_startup_code()
 *
 * Startup entry points for Mac versions of Glk AGiliTy.  Glk will call
 * macglk_startup_code() for details on what to do when the application is
 * selected.  On selection, an argv[] vector is built, and passed to the
 * normal AGiliTy startup code, after which, Glk will call glk_main().
 */
static Boolean
gagt_mac_whenselected (FSSpec * file, OSType filetype)
{
  static char *argv[2];
  assert (!gagt_startup_called);
  gagt_startup_called = TRUE;

  /* Set the WD to where the file is, so later fopens work. */
  if (HGetVol (0, &gagt_savedVRefNum, &gagt_savedDirID) != 0)
    {
      gagt_fatal ("GLK: HGetVol failed");
      return FALSE;
    }
  if (HSetVol (0, file->vRefNum, file->parID) != 0)
    {
      gagt_fatal ("GLK: HSetVol failed");
      return FALSE;
    }

  /* Put a CString version of the PString name into argv[1]. */
  argv[1] = gagt_malloc (file->name[0] + 1);
  BlockMoveData (file->name + 1, argv[1], file->name[0]);
  argv[1][file->name[0]] = '\0';
  argv[2] = NULL;

  return gagt_startup_code (2, argv);
}

static Boolean
gagt_mac_whenbuiltin (void)
{
  /* Not implemented yet. */
  return TRUE;
}

Boolean
macglk_startup_code (macglk_startup_t * data)
{
  static OSType gagt_mac_gamefile_types[] = { 'AGTS' };

  data->startup_model = macglk_model_ChooseOrBuiltIn;
  data->app_creator = 'cAGL';
  data->gamefile_types = gagt_mac_gamefile_types;
  data->num_gamefile_types = sizeof (gagt_mac_gamefile_types)
                             / sizeof (*gagt_mac_gamefile_types);
  data->savefile_type = 'BINA';
  data->datafile_type = 0x3f3f3f3f;
  data->gamefile = &gagt_mac_gamefile;
  data->when_selected = gagt_mac_whenselected;
  data->when_builtin = gagt_mac_whenbuiltin;
  /* macglk_setprefs(); */
  return TRUE;
}
#endif /* TARGET_OS_MAC */
