/* vi: set ts=2 shiftwidth=2 expandtab:
 *
 * Copyright (C) 2003-2008  Simon Baldwin and Mark J. Tilford
 *
 * 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
 */

/*
 * Module notes:
 *
 * o The Glk interface makes no effort to set text colors, background colors,
 *   and so forth, and minimal effort to set fonts and other style effects.
 */

#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "scare.h"
#include "glk.h"

#include "scprotos.h" /* for SCARE_VERSION */

#undef _WIN32   /* Gargoyle */

/*
 * 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 other stuff                        */
/*---------------------------------------------------------------------*/

/* Glk SCARE interface version number. */
static const glui32 GSC_PORT_VERSION = 0x00010310;

/* Two windows, one for the main text, and one for a status line. */
static winid_t gsc_main_window = NULL,
               gsc_status_window = NULL;

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

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

/* Options that may be turned off or set by command line flags. */
static int gsc_commands_enabled = TRUE,
           gsc_abbreviations_enabled = TRUE,
           gsc_unicode_enabled = TRUE;

/* Adrift game to interpret. */
static sc_game gsc_game = NULL;

/* Special out-of-band os_confirm() options used locally with os_glk. */
static const sc_int GSC_CONF_SUBTLE_HINT = INT_MAX,
                    GSC_CONF_UNSUBTLE_HINT = INT_MAX - 1,
                    GSC_CONF_CONTINUE_HINTS = INT_MAX - 2;

/* Forward declaration of event wait functions, and a short delay. */
static void gsc_event_wait_2 (glui32 wait_type_1,
                              glui32 wait_type_2, event_t *event);
static void gsc_event_wait (glui32 wait_type, event_t *event);
static void gsc_short_delay (void);


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

/*
 * gsc_fatal()
 *
 * Fatal error handler.  The function returns, expecting the caller to
 * abort() or otherwise handle the error.
 */
static void
gsc_fatal (const char *string)
{
  /*
   * If the failure happens too early for us to have a window, print
   * the message to stderr.
   */
  if (!gsc_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 (gsc_main_window, NULL);
  glk_cancel_char_event (gsc_main_window);

  /* Print a message indicating the error, and exit. */
  glk_set_window (gsc_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");
}


/*
 * gsc_malloc()
 *
 * Non-failing malloc; call gsc_fatal and exit if memory allocation fails.
 */
static void *
gsc_malloc (size_t size)
{
  void *pointer;

  pointer = malloc (size > 0 ? size : 1);
  if (!pointer)
    {
      gsc_fatal ("GLK: Out of system memory");
      glk_exit ();
    }

  return pointer;
}


/*---------------------------------------------------------------------*/
/*  Glk port locale data                                               */
/*---------------------------------------------------------------------*/

/* Unicode values up to 256 are equivalent to iso 8859-1. */
static const glui32 GSC_ISO_8859_EQUIVALENCE = 256;

/*
 * Lookup table pair for converting a given single character into unicode and
 * iso 8859-1 (the lower byte of the unicode representation, assuming an upper
 * byte of zero), and an ascii substitute should nothing else be available.
 * Tables are 256 elements; although the first 128 characters of a codepage
 * are usually standard ascii, making tables full-sized allows for support of
 * codepages where they're not (dingbats, for example).
 */
enum { GSC_TABLE_SIZE = 256 };
typedef struct {
  const glui32 unicode[GSC_TABLE_SIZE];
  const sc_char *const ascii[GSC_TABLE_SIZE];
} gsc_codepages_t;

/*
 * Locale contains a name and a pair of codepage structures, a main one and
 * an alternate.  The latter is intended for monospaced output.
 */
typedef struct {
  const sc_char *const name;
  const gsc_codepages_t main;
  const gsc_codepages_t alternate;
} gsc_locale_t;


/*
 * Locale for Latin1 -- cp1252 and cp850.
 *
 * The ascii representations of characters in this table are based on the
 * general look of the characters, rather than pronounciation.  Accented
 * characters are generally rendered unaccented, and box drawing, shading,
 * and other non-alphanumeric glyphs as either a similar shape, or as a
 * character that might be recognizable as what it's trying to emulate.
 */
static const gsc_locale_t GSC_LATIN1_LOCALE = {
  "Latin1",
  /* cp1252 to unicode. */
{ { 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
    0x0000, 0x000a, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
    0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
    0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0020, 0x0021, 0x0022, 0x0023,
    0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c,
    0x002d, 0x002e, 0x002f, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035,
    0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e,
    0x003f, 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
    0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, 0x0050,
    0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059,
    0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, 0x0060, 0x0061, 0x0062,
    0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b,
    0x006c, 0x006d, 0x006e, 0x006f, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074,
    0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d,
    0x007e, 0x0000, 0x20ac, 0x0000, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020,
    0x2021, 0x02c6, 0x2030, 0x0160, 0x2039, 0x0152, 0x0000, 0x017d, 0x0000,
    0x0000, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, 0x02dc,
    0x2122, 0x0161, 0x203a, 0x0153, 0x0000, 0x017e, 0x0178, 0x00a0, 0x00a1,
    0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, 0x00a8, 0x00a9, 0x00aa,
    0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, 0x00b0, 0x00b1, 0x00b2, 0x00b3,
    0x00b4, 0x00b5, 0x00b6, 0x00b7, 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc,
    0x00bd, 0x00be, 0x00bf, 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5,
    0x00c6, 0x00c7, 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce,
    0x00cf, 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7,
    0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df, 0x00e0,
    0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, 0x00e8, 0x00e9,
    0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, 0x00f0, 0x00f1, 0x00f2,
    0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, 0x00f8, 0x00f9, 0x00fa, 0x00fb,
    0x00fc, 0x00fd, 0x00fe, 0x00ff },
  /* cp1252 to ascii. */
  { NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  "        ",
    "\n",  NULL,  NULL,  "\n",  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,
    NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,
    " ",   "!",   "\"",  "#",   "$",   "%",   "&",   "'",   "(",   ")",   "*",
    "+",   ",",   "-",   ".",   "/",   "0",   "1",   "2",   "3",   "4",   "5",
    "6",   "7",   "8",   "9",   ":",   ";",   "<",   "=",   ">",   "?",   "@",
    "A",   "B",   "C",   "D",   "E",   "F",   "G",   "H",   "I",   "J",   "K",
    "L",   "M",   "N",   "O",   "P",   "Q",   "R",   "S",   "T",   "U",   "V",
    "W",   "X",   "Y",   "Z",   "[",   "\\",  "]",   "^",   "_",   "`",   "a",
    "b",   "c",   "d",   "e",   "f",   "g",   "h",   "i",   "j",   "k",   "l",
    "m",   "n",   "o",   "p",   "q",   "r",   "s",   "t",   "u",   "v",   "w",
    "x",   "y",   "z",   "{",   "|",   "}",   "~",   NULL,  "E",   NULL,  ",",
    "f",   ",,",  "...", "+",   "#",   "^",   "%",   "S",   "<",   "OE",  NULL,
    "Z",   NULL,  NULL,  "'",   "'",   "\"",  "\"",  "*",   "-",   "-",   "~",
    "[TM]","s",   ">",   "oe",  NULL,  "z",   "Y",   " ",   "!",   "c",   "GBP",
    "*",   "Y",   "|",   "S",   "\"",  "(C)", "a",   "<<",  "-",   "-",   "(R)",
    "-",   "o",   "+/-", "2",   "3",   "'",   "u",   "P",   "*",   ",",   "1",
    "o",   ">>",  "1/4", "1/2", "3/4", "?",   "A",   "A",   "A",   "A",   "A",
    "A",   "AE",  "C",   "E",   "E",   "E",   "E",   "I",   "I",   "I",   "I",
    "D",   "N",   "O",   "O",   "O",   "O",   "O",   "x",   "O",   "U",   "U",
    "U",   "U",   "Y",   "p",   "ss",  "a",   "a",   "a",   "a",   "a",   "a",
    "ae",  "c",   "e",   "e",   "e",   "e",   "i",   "i",   "i",   "i",   "d",
    "n",   "o",   "o",   "o",   "o",   "o",   "/",   "o",   "u",   "u",   "u",
    "u",   "y",   "P",   "y" } },
  /* cp850 to unicode. */
{ { 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
    0x0000, 0x000a, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
    0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
    0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0020, 0x0021, 0x0022, 0x0023,
    0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c,
    0x002d, 0x002e, 0x002f, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035,
    0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e,
    0x003f, 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
    0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, 0x0050,
    0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059,
    0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, 0x0060, 0x0061, 0x0062,
    0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b,
    0x006c, 0x006d, 0x006e, 0x006f, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074,
    0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d,
    0x007e, 0x0000, 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5,
    0x00e7, 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5,
    0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, 0x00ff,
    0x00d6, 0x00dc, 0x00f8, 0x00a3, 0x00d8, 0x00d7, 0x0192, 0x00e1, 0x00ed,
    0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba, 0x00bf, 0x00ae, 0x00ac,
    0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb, 0x2591, 0x2592, 0x2593, 0x2502,
    0x2524, 0x00c1, 0x00c2, 0x00c0, 0x00a9, 0x2563, 0x2551, 0x2557, 0x255d,
    0x00a2, 0x00a5, 0x2510, 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c,
    0x00e3, 0x00c3, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c,
    0x00a4, 0x00f0, 0x00d0, 0x00ca, 0x00cb, 0x00c8, 0x0131, 0x00cd, 0x00ce,
    0x00cf, 0x2518, 0x250c, 0x2588, 0x2584, 0x00a6, 0x00cc, 0x2580, 0x00d3,
    0x00df, 0x00d4, 0x00d2, 0x00f5, 0x00d5, 0x00b5, 0x00fe, 0x00de, 0x00da,
    0x00db, 0x00d9, 0x00fd, 0x00dd, 0x00af, 0x00b4, 0x00ad, 0x00b1, 0x2017,
    0x00be, 0x00b6, 0x00a7, 0x00f7, 0x00b8, 0x00b0, 0x00a8, 0x00b7, 0x00b9,
    0x00b3, 0x00b2, 0x25a0, 0x00a0 },
  /* cp850 to ascii. */
  { NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  "        ",
    "\n",  NULL,  NULL,  "\n",  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,
    NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,
    " ",   "!",   "\"",  "#",   "$",   "%",   "&",   "'",   "(",   ")",   "*",
    "+",   ",",   "-",   ".",   "/",   "0",   "1",   "2",   "3",   "4",   "5",
    "6",   "7",   "8",   "9",   ":",   ";",   "<",   "=",   ">",   "?",   "@",
    "A",   "B",   "C",   "D",   "E",   "F",   "G",   "H",   "I",   "J",   "K",
    "L",   "M",   "N",   "O",   "P",   "Q",   "R",   "S",   "T",   "U",   "V",
    "W",   "X",   "Y",   "Z",   "[",   "\\",  "]",   "^",   "_",   "`",   "a",
    "b",   "c",   "d",   "e",   "f",   "g",   "h",   "i",   "j",   "k",   "l",
    "m",   "n",   "o",   "p",   "q",   "r",   "s",   "t",   "u",   "v",   "w",
    "x",   "y",   "z",   "{",   "|",   "}",   "~",   NULL,  "C",   "u",   "e",
    "a",   "a",   "a",   "a",   "c",   "e",   "e",   "e",   "i",   "i",   "i",
    "A",   "A",   "E",   "ae",  "AE",  "o",   "o",   "o",   "u",   "u",   "y",
    "O",   "U",   "o",   "GBP", "O",   "x",   "f",   "a",   "i",   "o",   "u",
    "n",   "N",   "a",   "o",   "?",   "(R)", "-",   "1/2", "1/4", "i",   "<<",
    ">>",  "#",   "#",   "#",   "|",   "+",   "A",   "A",   "A",   "(C)", "+",
    "|",   "+",   "+",   "c",   "Y",   "+",   "+",   "+",   "+",   "+",   "-",
    "+",   "a",   "A",   "+",   "+",   "+",   "+",   "+",   "=",   "+",   "*",
    "d",   "D",   "E",   "E",   "E",   "i",   "I",   "I",   "I",   "+",   "+",
    ".",   ".",   "|",   "I",   ".",   "O",   "ss",  "O",   "O",   "o",   "O",
    "u",   "p",   "P",   "U",   "U",   "U",   "y",   "Y",   "-",   "'",   "-",
    "+/-", "=",   "3/4", "P",   "S",   "/",   ",",   "deg", "\"",  "*",   "1",
    "3",   "2",   ".",   " " } }
};


/*
 * Locale for Cyrillic -- cp1251 and cp866.
 *
 * The ascii representations in this table, for alphabetic characters, follow
 * linguistic rather than appearance rules, the essence of gost 16876-71.
 * Capitalized cyrillic letters that translate to multiple ascii characters
 * have the first ascii character only of the sequence translated.  This gives
 * the best appearance in normal sentences, but is not optimal in a run of
 * all capitals (headings, for example).  For non-alphanumeric characters,
 * the general appearance and shape of the character being emulated is used.
 */
static const gsc_locale_t GSC_CYRILLIC_LOCALE = {
  "Cyrillic",
  /* cp1251 to unicode. */
{ { 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
    0x0000, 0x000a, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
    0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
    0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0020, 0x0021, 0x0022, 0x0023,
    0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c,
    0x002d, 0x002e, 0x002f, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035,
    0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e,
    0x003f, 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
    0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, 0x0050,
    0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059,
    0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, 0x0060, 0x0061, 0x0062,
    0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b,
    0x006c, 0x006d, 0x006e, 0x006f, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074,
    0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d,
    0x007e, 0x0000, 0x0402, 0x0403, 0x201a, 0x0453, 0x201e, 0x2026, 0x2020,
    0x2021, 0x20ac, 0x2030, 0x0409, 0x2039, 0x040a, 0x040c, 0x040b, 0x040f,
    0x0452, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, 0x0000,
    0x2122, 0x0459, 0x203a, 0x045a, 0x045c, 0x045b, 0x045f, 0x00a0, 0x040e,
    0x045e, 0x0408, 0x00a4, 0x0490, 0x00a6, 0x00a7, 0x0401, 0x00a9, 0x0404,
    0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x0407, 0x00b0, 0x00b1, 0x0406, 0x0456,
    0x0491, 0x00b5, 0x00b6, 0x00b7, 0x0451, 0x2116, 0x0454, 0x00bb, 0x0458,
    0x0405, 0x0455, 0x0457, 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415,
    0x0416, 0x0417, 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e,
    0x041f, 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427,
    0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f, 0x0430,
    0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, 0x0438, 0x0439,
    0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f, 0x0440, 0x0441, 0x0442,
    0x0443, 0x0444, 0x0445, 0x0446, 0x0447, 0x0448, 0x0449, 0x044a, 0x044b,
    0x044c, 0x044d, 0x044e, 0x044f },
  /* cp1251 to gost 16876-71 ascii. */
  { NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  "        ",
    "\n",  NULL,  NULL,  "\n",  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,
    NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,
    " ",   "!",   "\"",  "#",   "$",   "%",   "&",   "'",   "(",   ")",   "*",
    "+",   ",",   "-",   ".",   "/",   "0",   "1",   "2",   "3",   "4",   "5",
    "6",   "7",   "8",   "9",   ":",   ";",   "<",   "=",   ">",   "?",   "@",
    "A",   "B",   "C",   "D",   "E",   "F",   "G",   "H",   "I",   "J",   "K",
    "L",   "M",   "N",   "O",   "P",   "Q",   "R",   "S",   "T",   "U",   "V",
    "W",   "X",   "Y",   "Z",   "[",   "\\",  "]",   "^",   "_",   "`",   "a",
    "b",   "c",   "d",   "e",   "f",   "g",   "h",   "i",   "j",   "k",   "l",
    "m",   "n",   "o",   "p",   "q",   "r",   "s",   "t",   "u",   "v",   "w",
    "x",   "y",   "z",   "{",   "|",   "}",   "~",   NULL,  NULL,  NULL,  ",",
    NULL,  ",,",  "...", "+",   "#",   "E",   "%",   NULL,  "<",   NULL,  NULL,
    NULL,  NULL,  NULL,  "'",   "'",   "\"",  "\"",  "*",   "-",   "-",   NULL,
    "[TM]",NULL,  ">",   NULL,  NULL,  NULL,  NULL,  " ",   NULL,  NULL,  NULL,
    "*",   "G",   "|",   "S",   "Jo",  "(C)", "Je",  "<<",  "-",   "-",   "(R)",
    "Ji",  "o",   "+/-", "I",   "i",   "g",   "u",   "P",   "*",   "jo",  NULL,
    "je",  ">>",  "j",   "S",   "s",   "ji",  "A",   "B",   "V",   "G",   "D",
    "E",   "Zh",  "Z",   "I",   "Jj",  "K",   "L",   "M",   "N",   "O",   "P",
    "R",   "S",   "T",   "U",   "F",   "Kh",  "C",   "Ch",  "Sh",  "Shh", "\"",
    "Y",   "'",   "Eh",  "Ju",  "Ja",  "a",   "b",   "v",   "g",   "d",   "e",
    "zh",  "z",   "i",   "jj",  "k",   "l",   "m",   "n",   "o",   "p",   "r",
    "s",   "t",   "u",   "f",   "kh",  "c",   "ch",  "sh",  "shh", "\"",  "y",
    "'",   "eh",  "ju",  "ja" } },
  /* cp866 to unicode. */
{ { 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
    0x0000, 0x000a, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
    0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
    0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0020, 0x0021, 0x0022, 0x0023,
    0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c,
    0x002d, 0x002e, 0x002f, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035,
    0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e,
    0x003f, 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
    0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, 0x0050,
    0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059,
    0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, 0x0060, 0x0061, 0x0062,
    0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b,
    0x006c, 0x006d, 0x006e, 0x006f, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074,
    0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d,
    0x007e, 0x0000, 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416,
    0x0417, 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f,
    0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 0x0428,
    0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f, 0x0430, 0x0431,
    0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, 0x0438, 0x0439, 0x043a,
    0x043b, 0x043c, 0x043d, 0x043e, 0x043f, 0x2591, 0x2592, 0x2593, 0x2502,
    0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255d,
    0x255c, 0x255b, 0x2510, 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c,
    0x255e, 0x255f, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c,
    0x2567, 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b,
    0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580, 0x0440,
    0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, 0x0448, 0x0449,
    0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x044f, 0x0401, 0x0451, 0x0404,
    0x0454, 0x0407, 0x0457, 0x040e, 0x045e, 0x00b0, 0x2022, 0x00b7, 0x221a,
    0x2116, 0x00a4, 0x25a0, 0x00a0 },
  /* cp866 to gost 16876-71 ascii. */
  { NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  "        ",
    "\n",  NULL,  NULL,  "\n",  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,
    NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,
    " ",   "!",   "\"",  "#",   "$",   "%",   "&",   "'",   "(",   ")",   "*",
    "+",   ",",   "-",   ".",   "/",   "0",   "1",   "2",   "3",   "4",   "5",
    "6",   "7",   "8",   "9",   ":",   ";",   "<",   "=",   ">",   "?",   "@",
    "A",   "B",   "C",   "D",   "E",   "F",   "G",   "H",   "I",   "J",   "K",
    "L",   "M",   "N",   "O",   "P",   "Q",   "R",   "S",   "T",   "U",   "V",
    "W",   "X",   "Y",   "Z",   "[",   "\\",  "]",   "^",   "_",   "`",   "a",
    "b",   "c",   "d",   "e",   "f",   "g",   "h",   "i",   "j",   "k",   "l",
    "m",   "n",   "o",   "p",   "q",   "r",   "s",   "t",   "u",   "v",   "w",
    "x",   "y",   "z",   "{",   "|",   "}",   "~",   NULL,  "A",   "B",   "V",
    "G",   "D",   "E",   "Zh",  "Z",   "I",   "Jj",  "K",   "L",   "M",   "N",
    "O",   "P",   "R",   "S",   "T",   "U",   "F",   "Kh",  "C",   "Ch",  "Sh",
    "Shh", "\"",  "Y",   "'",   "Eh",  "Ju",  "Ja",  "a",   "b",   "v",   "g",
    "d",   "e",   "zh",  "z",   "i",   "jj",  "k",   "l",   "m",   "n",   "o",
    "p",   "#",   "#",   "#",   "|",   "+",   "+",   "+",   "+",   "+",   "+",
    "|",   "+",   "+",   "+",   "+",   "+",   "+",   "+",   "+",   "+",   "-",
    "+",   "+",   "+",   "+",   "+",   "+",   "+",   "+",   "|",   "+",   "+",
    "+",   "+",   "+",   "+",   "+",   "+",   "+",   "+",   "+",   "+",   "+",
    "+",   ".",   ".",   ".",   ".",   "r",   "s",   "t",   "u",   "f",   "kh",
    "c",   "ch",  "sh",  "shh", "\"",  "y",   "'",   "eh",  "ju",  "ja",  "Jo",
    "jo",  "Je",  "je",  "Ji",  "ji",  NULL,  NULL,  "deg", "*",    "*",  NULL,
    NULL,  "*",   ".",   " " } }
};


/*---------------------------------------------------------------------*/
/*  Glk port locale control and conversion functions                   */
/*---------------------------------------------------------------------*/

#ifdef GLK_MODULE_UNICODE
static const sc_bool gsc_has_unicode = TRUE;
#else
static const sc_bool gsc_has_unicode = FALSE;

/* Gestalt selector and stubs for non-unicode capable libraries. */
static const glui32 gestalt_Unicode = 15;

static void
glk_put_char_uni (glui32 ch)
{
  glui32 unused;
  unused = ch;
  gsc_fatal ("GLK: Stub unicode function called");
}

static void
glk_request_line_event_uni (winid_t win,
                            glui32 *buf, glui32 maxlen, glui32 initlen)
{
  winid_t unused1;
  glui32 *unused2;
  glui32 unused3, unused4;
  unused1 = win;
  unused2 = buf;
  unused3 = maxlen;
  unused4 = initlen;
  gsc_fatal ("GLK: Stub unicode function called");
}

#endif

/*
 * Known valid character printing range.  Some Glk libraries aren't accurate
 * about what will and what won't print when queried with glk_gestalt(), so
 * we also make an explicit range check against guaranteed to print chars.
 */
static const glui32 GSC_MIN_PRINTABLE = ' ',
                    GSC_MAX_PRINTABLE = '~';


/* List of pointers to supported and available locales, NULL terminated. */
static const gsc_locale_t *const GSC_AVAILABLE_LOCALES[] = {
  &GSC_LATIN1_LOCALE,
  &GSC_CYRILLIC_LOCALE,
  NULL
};

/*
 * The locale for the game, set below explicitly or on game startup, and
 * a fallback locale to use in case none has been set.
 */
static const gsc_locale_t *gsc_locale = NULL;
static const gsc_locale_t *const gsc_fallback_locale = &GSC_LATIN1_LOCALE;


/*
 * gsc_set_locale()
 *
 * Set a locale explicitly from the name passed in.
 */
static void
gsc_set_locale (const sc_char *name)
{
  const gsc_locale_t *matched = NULL;
  const gsc_locale_t *const *iterator;
  assert (name);

  /*
   * Search locales for a matching name, abbreviated if necessary.  Stop on
   * the first match found.
   */
  for (iterator = GSC_AVAILABLE_LOCALES; *iterator; iterator++)
    {
      const gsc_locale_t *const locale = *iterator;

      if (sc_strncasecmp (name, locale->name, strlen (name)) == 0)
        {
          matched = locale;
          break;
        }
    }

  /* If matched, set the global locale. */
  if (matched)
    gsc_locale = matched;
}


/*
 * gsc_put_char_uni()
 *
 * Wrapper around glk_put_char_uni().  Handles, inelegantly, the problem of
 * having to write transcripts as ascii.
 */
static void
gsc_put_char_uni (glui32 unicode, const char *ascii)
{
  /* If there is an transcript stream, temporarily disconnect it. */
  if (gsc_transcript_stream)
    glk_window_set_echo_stream (gsc_main_window, NULL);

  glk_put_char_uni (unicode);

  /* Print ascii to the transcript, then reattach it. */
  if (gsc_transcript_stream)
    {
      if (ascii)
        glk_put_string_stream (gsc_transcript_stream, (char *) ascii);
      else
        glk_put_char_stream (gsc_transcript_stream, '?');

      glk_window_set_echo_stream (gsc_main_window, gsc_transcript_stream);
    }
}


/*
 * gsc_put_char_locale()
 *
 * Write a single character using the supplied locale.  Select either the
 * main or the alternate codepage depending on the flag passed in.
 */
static void
gsc_put_char_locale (sc_char ch,
                     const gsc_locale_t *locale, sc_bool is_alternate)
{
  const gsc_codepages_t *codepage;
  unsigned char character;
  glui32 unicode;
  const char *ascii;

  /*
   * Select either the main or the alternate codepage for this locale, and
   * retrieve the unicode and ascii representations of the character.
   */
  codepage = is_alternate ? &locale->alternate : &locale->main;
  character = (unsigned char) ch;
  unicode = codepage->unicode[character];
  ascii = codepage->ascii[character];

  /*
   * If a unicode representation exists, use for either iso 8859-1 or, if
   * possible, direct unicode output.
   */
  if (unicode > 0)
    {
      /*
       * If unicode is in the range 1-255, this value is directly equivalent
       * to the iso 8859-1 representation; otherwise the character has no
       * direct iso 8859-1 glyph.
       */
      if (unicode < GSC_ISO_8859_EQUIVALENCE)
        {
          /*
           * If the iso 8859-1 character is one that this Glk library will
           * print exactly, print and return.  We add a check here for the
           * guaranteed printable characters, since some Glk libraries don't
           * return the correct values for gestalt_CharOutput for these.
           */
          if (unicode == '\n'
              || (unicode >= GSC_MIN_PRINTABLE && unicode <= GSC_MAX_PRINTABLE)
              || glk_gestalt (gestalt_CharOutput,
                              unicode) == gestalt_CharOutput_ExactPrint)
            {
              glk_put_char ((unsigned char) unicode);
              return;
            }
        }

      /*
       * If no usable iso 8859-1 representation, see if unicode is enabled and
       * if the Glk library can print the character exactly.  If yes, output
       * the character that way.
       *
       * TODO Using unicode output currently disrupts transcript output.  Any
       * echo stream connected for a transcript here will be a text rather than
       * a unicode stream, so probably won't output the character correctly.
       * For now, if there's a transcript, we try to write ascii output.
       */
      if (gsc_unicode_enabled)
        {
          if (glk_gestalt (gestalt_CharOutput,
                           unicode) == gestalt_CharOutput_ExactPrint)
            {
              gsc_put_char_uni (unicode, ascii);
              return;
            }
        }
    }

  /*
   * No success with iso 8859-1 or unicode, so try for an ascii substitute.
   * Substitute strings use only 7-bit ascii, and so all are safe to print
   * directly with Glk.
   */
  if (ascii)
    {
      glk_put_string ((char *) ascii);
      return;
    }

  /* No apparent way to output this character, so print a '?'. */
  glk_put_char ('?');
}


/*
 * gsc_put_char()
 * gsc_put_char_alternate()
 * gsc_put_buffer_using()
 * gsc_put_buffer()
 * gsc_put_string()
 * gsc_put_string_alternate()
 *
 * Public functions for writing using the current or fallback locale.
 */
static void
gsc_put_char (const sc_char character)
{
  const gsc_locale_t *locale;

  locale = gsc_locale ? gsc_locale : gsc_fallback_locale;
  gsc_put_char_locale (character, locale, FALSE);
}

static void
gsc_put_char_alternate (const sc_char character)
{
  const gsc_locale_t *locale;

  locale = gsc_locale ? gsc_locale : gsc_fallback_locale;
  gsc_put_char_locale (character, locale, TRUE);
}

static void
gsc_put_buffer_using (const sc_char *buffer,
                      sc_int length, void (*putchar_function) (sc_char))
{
  sc_int index_;

  for (index_ = 0; index_ < length; index_++)
    putchar_function (buffer[index_]);
}

static void
gsc_put_buffer (const sc_char *buffer, sc_int length)
{
  assert (buffer);

  gsc_put_buffer_using (buffer, length, gsc_put_char);
}

static void
gsc_put_string (const sc_char *string)
{
  assert (string);

  gsc_put_buffer_using (string, strlen (string), gsc_put_char);
}

static void
gsc_put_string_alternate (const sc_char *string)
{
  assert (string);

  gsc_put_buffer_using (string, strlen (string), gsc_put_char_alternate);
}


/*
 * gsc_unicode_to_locale()
 * gsc_unicode_buffer_to_locale()
 *
 * Convert a unicode character back to an sc_char through a locale.  Used for
 * reverse translations in line input.  Returns '?' if there is no translation
 * available.
 */
static sc_char
gsc_unicode_to_locale (glui32 unicode, const gsc_locale_t *locale)
{
  const gsc_codepages_t *codepage;
  sc_int character;

  /* Always use the main codepage for input. */
  codepage = &locale->main;

  /*
   * Search the unicode table sequentially for the input character.  This is
   * inefficient, but because game input is usually not copious, excusable.
   */
  for (character = 0; character < GSC_TABLE_SIZE; character++)
    {
      if (codepage->unicode[character] == unicode)
        break;
    }

  /* Return the character translation, or '?' if none. */
  return character < GSC_TABLE_SIZE ? (sc_char) character : '?';
}

static void
gsc_unicode_buffer_to_locale (const glui32 *unicode, sc_int length,
                              sc_char *buffer, const gsc_locale_t *locale)
{
  sc_int index_;

  for (index_ = 0; index_ < length; index_++)
    buffer[index_] = gsc_unicode_to_locale (unicode[index_], locale);
}


/*
 * gsc_read_line_locale()
 *
 * Read in a line and translate out of the given locale.  Returns the count
 * of characters placed in the buffer.
 */
static sc_int
gsc_read_line_locale (sc_char *buffer,
                      sc_int length, const gsc_locale_t *locale)
{
  event_t event;

  /*
   * If we have unicode, we have to use it to ensure that characters not in
   * the Latin1 locale are properly translated.
   */
  if (gsc_unicode_enabled)
    {
      glui32 *unicode;

      /*
       * Allocate a unicode buffer long enough to hold all the characters,
       * then read in a unicode line.
       */
      unicode = gsc_malloc (length * sizeof (*unicode));
      glk_request_line_event_uni (gsc_main_window, unicode, length, 0);
      gsc_event_wait (evtype_LineInput, &event);

      /* Convert the unicode buffer out, then free it. */
      gsc_unicode_buffer_to_locale (unicode, event.val1, buffer, locale);
      free (unicode);

      /* Return the count of characters placed in the buffer. */
      return event.val1;
    }

  /* No success with unicode, so fall back to standard line input. */
  glk_request_line_event (gsc_main_window, buffer, length, 0);
  gsc_event_wait (evtype_LineInput, &event);

  /* Return the count of characters placed in the buffer. */
  return event.val1;
}


/*
 * gsc_read_line()
 *
 * Public function for reading using the current or fallback locale.
 */
static sc_int
gsc_read_line (sc_char *buffer, sc_int length)
{
  const gsc_locale_t *locale;

  locale = gsc_locale ? gsc_locale : gsc_fallback_locale;
  return gsc_read_line_locale (buffer, length, locale);
}


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

/*
 * Slop for right-justification of status lines, as an attempt to compensate
 * for the fact that some characters in a games status line may use more than
 * one position when printed, a particular problem in gost 16876-71 Cyrillic.
 */
static const sc_int GSC_STATUS_SLOP = 10;

/* Size of saved status buffer used for non-windowing Glk status lines. */
enum { GSC_STATUS_BUFFER_LENGTH = 74 };

/* Whitespace characters, used to detect empty status elements. */
static const sc_char *const GSC_WHITESPACE = "\t\n\v\f\r ";


/*
 * gsc_is_string_usable()
 *
 * Return TRUE if string is non-null, not zero-length or contains characters
 * other than whitespace.
 */
static sc_bool
gsc_is_string_usable (const sc_char *string)
{
  /* If non-null, scan for any non-space character. */
  if (string)
    {
      sc_int index_;

      for (index_ = 0; string[index_] != '\0'; index_++)
        {
          if (!strchr (GSC_WHITESPACE, string[index_]))
            return TRUE;
        }
    }

  /* NULL, or no characters other than whitespace. */
  return FALSE;
}


/*
 * gsc_status_update()
 *
 * Update the status line from the current game state.  This is for windowing
 * Glk libraries.
 */
static void
gsc_status_update (void)
{
  glui32 width, height;
  int index;
  assert (gsc_status_window);

  glk_window_get_size (gsc_status_window, &width, &height);
  if (height > 0)
    {
      const sc_char *room;

      glk_window_clear (gsc_status_window);
      glk_window_move_cursor (gsc_status_window, 0, 0);
      glk_set_window (gsc_status_window);

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

      /* See if the game is indicating any current player room. */
      room = sc_get_game_room (gsc_game);
      if (!gsc_is_string_usable (room))
        {
          /*
           * Player location is indeterminate, so print out a generic status,
           * showing the game name and author.
           */
          glk_window_move_cursor (gsc_status_window, 1, 0);
          gsc_put_string (sc_get_game_name (gsc_game));
          glk_put_string (" | ");
          gsc_put_string (sc_get_game_author (gsc_game));
        }
      else
        {
          const sc_char *status;
          char score[64];

          /* Print the player location. */
          glk_window_move_cursor (gsc_status_window, 1, 0);
          gsc_put_string (room);

          /* Get the game's status line, or if none, format score. */
          status = sc_get_game_status_line (gsc_game);
          if (!gsc_is_string_usable (status))
            {
              sprintf (score, "Score: %ld", sc_get_game_score (gsc_game));
              status = score;
            }

          /* Print the status line or score at window right, if it fits. */
          if (width > strlen (status) + GSC_STATUS_SLOP + 1)
            {
              glui32 position;

              position = width - strlen (status) - GSC_STATUS_SLOP;
              glk_window_move_cursor (gsc_status_window, position - 1, 0);
              gsc_put_string (status);
            }
        }

      glk_set_window (gsc_main_window);
    }
}


/*
 * gsc_status_safe_strcat()
 *
 * Helper for gsc_status_print(), concatenates strings only up to the
 * available length.
 */
static void
gsc_status_safe_strcat (char *dest, size_t length, const char *src)
{
  size_t available, src_length;

  /* Append only as many characters as will fit. */
  src_length = strlen (src);
  available = length - strlen (dest) - 1;
  if (available > 0)
    strncat (dest, src, src_length < available ? src_length : available);
}


/*
 * gsc_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.
 */
static void
gsc_status_print (void)
{
  static char current_status[GSC_STATUS_BUFFER_LENGTH + 1];

  const sc_char *room;

  /* Do nothing if the game isn't indicating any current player room. */
  room = sc_get_game_room (gsc_game);
  if (gsc_is_string_usable (room))
    {
      char buffer[GSC_STATUS_BUFFER_LENGTH + 1];
      const sc_char *status;
      char score[64];

      /* Make an attempt at a status line, starting with player location. */
      strcpy (buffer, "");
      gsc_status_safe_strcat (buffer, sizeof (buffer), room);

      /* Get the game's status line, or if none, format score. */
      status = sc_get_game_status_line (gsc_game);
      if (!gsc_is_string_usable (status))
        {
          sprintf (score, "Score: %ld", sc_get_game_score (gsc_game));
          status = score;
        }

      /* Append the status line or score. */
      gsc_status_safe_strcat (buffer, sizeof (buffer), " | ");
      gsc_status_safe_strcat (buffer, sizeof (buffer), status);

      /* If this matches the current saved status line, do nothing more. */
      if (strcmp (buffer, current_status) != 0)
        {
          /* Bracket, and output the status line buffer. */
          glk_put_string ("[ ");
          gsc_put_string (buffer);
          glk_put_string (" ]\n");

          /* Save the details of the printed status buffer. */
          strcpy (current_status, buffer);
        }
    }
}


/*
 * gsc_status_notify()
 *
 * Front end function for updating status.  Either updates the status window
 * or prints the status line to the main window.
 */
static void
gsc_status_notify (void)
{
  if (gsc_status_window)
    gsc_status_update ();
  else
    gsc_status_print ();
}


/*
 * gsc_status_redraw()
 *
 * Redraw the contents of any status window with the constructed status string.
 * This function should be called on the appropriate Glk window resize and
 * arrange events.
 */
static void
gsc_status_redraw (void)
{
  if (gsc_status_window)
    {
      winid_t parent;

      /*
       * 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 (gsc_status_window);
      glk_window_set_arrangement (parent,
                                  winmethod_Above | winmethod_Fixed, 1, NULL);
      gsc_status_update ();
    }
}


/*---------------------------------------------------------------------*/
/*  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 gsc_help_requested = FALSE,
           gsc_help_hints_silenced = FALSE;

/* Font descriptor type, encapsulating size and monospaced boolean. */
typedef struct {
  sc_bool is_monospaced;
  sc_int size;
} gsc_font_size_t;

/* Font stack and attributes for nesting tags. */
enum { GSC_MAX_STYLE_NESTING = 32 };
static gsc_font_size_t gsc_font_stack[GSC_MAX_STYLE_NESTING];
static glui32 gsc_font_index = 0;
static glui32 gsc_attribute_bold = 0,
              gsc_attribute_italic = 0,
              gsc_attribute_underline = 0,
              gsc_attribute_secondary_color = 0;

/* Notional default font size, and limit font sizes. */
static const sc_int GSC_DEFAULT_FONT_SIZE = 12,
                    GSC_MEDIUM_FONT_SIZE = 14,
                    GSC_LARGE_FONT_SIZE = 16;

/* Milliseconds per second and timeouts count for delay tags. */
static const glui32 GSC_MILLISECONDS_PER_SECOND = 1000;
static const glui32 GSC_TIMEOUTS_COUNT = 10;

/* Number of hints to refuse before offering to end hint display. */
static const sc_int GSC_HINT_REFUSAL_LIMIT = 5;

/* The keypresses used to cancel any <wait x.x> early. */
static const glui32 GSC_CANCEL_WAIT_1 = ' ',
                    GSC_CANCEL_WAIT_2 = keycode_Return;


/*
 * gsc_output_register_help_request()
 * gsc_output_silence_help_hints()
 * gsc_output_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
gsc_output_register_help_request (void)
{
  gsc_help_requested = TRUE;
}

static void
gsc_output_silence_help_hints (void)
{
  gsc_help_hints_silenced = TRUE;
}

static void
gsc_output_provide_help_hint (void)
{
  if (gsc_help_requested && !gsc_help_hints_silenced)
    {
      glk_set_style (style_Emphasized);
      glk_put_string ("[Try 'glk help' for help on special interpreter"
                      " commands]\n");

      gsc_help_requested = FALSE;
      glk_set_style (style_Normal);
    }
}


/*
 * gsc_set_glk_style()
 *
 * Set a Glk style based on the top of the font stack and attributes.
 */
static void
gsc_set_glk_style (void)
{
  sc_bool is_monospaced;
  sc_int font_size;

  /* Get the current font stack top, or default value. */
  if (gsc_font_index > 0)
    {
      is_monospaced = gsc_font_stack[gsc_font_index - 1].is_monospaced;
      font_size = gsc_font_stack[gsc_font_index - 1].size;
    }
  else
    {
      is_monospaced = FALSE;
      font_size = GSC_DEFAULT_FONT_SIZE;
    }

  /*
   * Map the font and current attributes into a Glk style.  Because Glk styles
   * aren't cumulative this has to be done by precedences.
   */
  if (is_monospaced)
    {
      /*
       * No matter the size or attributes, if monospaced use Preformatted
       * style, as it's all we have.
       */
      glk_set_style (style_Preformatted);
    }
  else
    {
      /*
       * For large and medium point sizes, use Header or Subheader styles
       * respectively.
       */
      if (font_size >= GSC_LARGE_FONT_SIZE)
        glk_set_style (style_Header);
      else if (font_size >= GSC_MEDIUM_FONT_SIZE)
        glk_set_style (style_Subheader);
      else
        {
          /*
           * For bold, use Subheader; for italics, underline, or secondary
           * color, use Emphasized.
           */
          if (gsc_attribute_bold > 0)
            glk_set_style (style_Subheader);
          else if (gsc_attribute_italic > 0
                   || gsc_attribute_underline > 0
                   || gsc_attribute_secondary_color > 0)
            glk_set_style (style_Emphasized);
          else
            {
              /*
               * There's nothing special about this text, so drop down to
               * Normal style.
               */
              glk_set_style (style_Normal);
            }
        }
    }
}


/*
 * gsc_handle_font_tag()
 * gsc_handle_endfont_tag()
 *
 * Push the settings of a font tag onto the font stack, and pop on end of
 * font tag.  Set the appropriate Glk style.
 */
static void
gsc_handle_font_tag (const sc_char *argument)
{
  /* Ignore the call on stack overrun. */
  if (gsc_font_index < GSC_MAX_STYLE_NESTING)
    {
      sc_char *lower, *face, *size;
      sc_bool is_monospaced;
      sc_int index_, font_size;

      /* Get the current top of stack, or default on empty stack. */
      if (gsc_font_index > 0)
        {
          is_monospaced = gsc_font_stack[gsc_font_index - 1].is_monospaced;
          font_size = gsc_font_stack[gsc_font_index - 1].size;
        }
      else
        {
          is_monospaced = FALSE;
          font_size = GSC_DEFAULT_FONT_SIZE;
        }

      /* Copy and convert argument to all lowercase. */
      lower = gsc_malloc (strlen (argument) + 1);
      strcpy (lower, argument);
      for (index_ = 0; lower[index_] != '\0'; index_++)
        lower[index_] = glk_char_to_lower (lower[index_]);

      /* Find any face= portion of the tag argument. */
      face = strstr (lower, "face=");
      if (face)
        {
          /*
           * There may be plenty of monospaced fonts, but we do only courier
           * and terminal.
           */
          is_monospaced = strncmp (face, "face=\"courier\"", 14) == 0
                          || strncmp (face, "face=\"terminal\"", 15) == 0;
        }

      /* Find the size= portion of the tag argument. */
      size = strstr (lower, "size=");
      if (size)
        {
          sc_uint value;

          /* Deal with incremental and absolute sizes. */
          if (strncmp (size, "size=+", 6) == 0
              && sscanf (size, "size=+%lu", &value) == 1)
            font_size += value;
          else if (strncmp (size, "size=-", 6) == 0
                   && sscanf (size, "size=-%lu", &value) == 1)
            font_size -= value;
          else if (sscanf (size, "size=%lu", &value) == 1)
            font_size = value;
        }

      /* Done with tag argument copy. */
      free (lower);

      /*
       * Push the new font setting onto the font stack, and set Glk style.
       */
      gsc_font_stack[gsc_font_index].is_monospaced = is_monospaced;
      gsc_font_stack[gsc_font_index++].size = font_size;
      gsc_set_glk_style ();
    }
}

static void
gsc_handle_endfont_tag (void)
{
  /* Unless underrun, pop the font stack and set Glk style. */
  if (gsc_font_index > 0)
    {
      gsc_font_index--;
      gsc_set_glk_style ();
    }
}


/*
 * gsc_handle_attribute_tag()
 *
 * Increment the required attribute nesting counter, or decrement on end
 * tag.  Set the appropriate Glk style.
 */
static void
gsc_handle_attribute_tag (sc_int tag)
{
  /*
   * Increment the required attribute nesting counter, and set Glk style.
   */
  switch (tag)
    {
    case SC_TAG_BOLD:
      gsc_attribute_bold++;
      break;
    case SC_TAG_ITALICS:
      gsc_attribute_italic++;
      break;
    case SC_TAG_UNDERLINE:
      gsc_attribute_underline++;
      break;
    case SC_TAG_COLOR:
      gsc_attribute_secondary_color++;
      break;
    default:
      break;
    }
  gsc_set_glk_style ();
}

static void
gsc_handle_endattribute_tag (sc_int tag)
{
  /*
   * Decrement the required attribute nesting counter, unless underrun, and
   * set Glk style.
   */
  switch (tag)
    {
    case SC_TAG_ENDBOLD:
      if (gsc_attribute_bold > 0)
        gsc_attribute_bold--;
      break;
    case SC_TAG_ENDITALICS:
      if (gsc_attribute_italic > 0)
        gsc_attribute_italic--;
      break;
    case SC_TAG_ENDUNDERLINE:
      if (gsc_attribute_underline > 0)
        gsc_attribute_underline--;
      break;
    case SC_TAG_ENDCOLOR:
      if (gsc_attribute_secondary_color > 0)
        gsc_attribute_secondary_color--;
      break;
    default:
      break;
    }
  gsc_set_glk_style ();
}


/*
 * gsc_handle_wait_tag()
 *
 * If Glk offers timers, delay for the requested period.  Otherwise, this
 * function does nothing.
 */
static void
gsc_handle_wait_tag (const sc_char *argument)
{
  double delay = 0.0;

  /* Ignore the wait tag if the Glk doesn't have timers. */
  if (!glk_gestalt (gestalt_Timer, 0))
    return;

  /* Determine the delay time, and convert to milliseconds. */
  if (sscanf (argument, "%lf", &delay) == 1 && delay > 0.0)
    {
      glui32 milliseconds, timeout;

      /*
       * Work with timeouts at 1/10 of the wait period, to minimize Glk
       * timer jitter.  Allow the timeout to be canceled by keypress, as a
       * user convenience.
       */
      milliseconds = (glui32) (delay * GSC_MILLISECONDS_PER_SECOND);
      timeout = milliseconds / GSC_TIMEOUTS_COUNT;
      if (timeout > 0)
        {
          glui32 delayed;
          sc_bool is_completed;

          /* Request timer events, and let a keypress cancel the wait. */
          glk_request_char_event (gsc_main_window);
          glk_request_timer_events (timeout);

          /* Loop until delay completed or canceled by a keypress. */
          is_completed = TRUE;
          for (delayed = 0; delayed < milliseconds; delayed += timeout)
            {
              event_t event;

              gsc_event_wait_2 (evtype_CharInput, evtype_Timer, &event);
              if (event.type == evtype_CharInput)
                {
                  /* Cancel the delay, or reissue the input request. */
                  if (event.val1 == GSC_CANCEL_WAIT_1
                      || event.val1 == GSC_CANCEL_WAIT_2)
                    {
                      is_completed = FALSE;
                      break;
                    }
                  else
                    glk_request_char_event (gsc_main_window);
                }
             }

          /* Cancel any pending character input, and stop timers. */
          if (is_completed)
            glk_cancel_char_event (gsc_main_window);
          glk_request_timer_events (0);
        }
    }
}


/*
 * gsc_reset_glk_style()
 *
 * Drop all stacked fonts and nested attributes, and return to normal Glk
 * style.
 */
static void
gsc_reset_glk_style (void)
{
  /* Reset the font stack and attributes, and set a normal style. */
  gsc_font_index = 0;
  gsc_attribute_bold = 0;
  gsc_attribute_italic = 0;
  gsc_attribute_underline = 0;
  gsc_attribute_secondary_color = 0;
  gsc_set_glk_style ();
}


/*
 * os_print_tag()
 *
 * Interpret selected Adrift output control tags.  Not all are implemented
 * here; several are ignored.
 */
void
os_print_tag (sc_int tag, const sc_char *argument)
{
  event_t event;
  assert (argument);

  switch (tag)
    {
    case SC_TAG_CLS:
      /* Clear the main text display window. */
      glk_window_clear (gsc_main_window);
      break;

    case SC_TAG_FONT:
      /* Handle with specific tag handler function. */
      gsc_handle_font_tag (argument);
      break;

    case SC_TAG_ENDFONT:
      /* Handle with specific endtag handler function. */
      gsc_handle_endfont_tag ();
      break;

    case SC_TAG_BOLD:
    case SC_TAG_ITALICS:
    case SC_TAG_UNDERLINE:
    case SC_TAG_COLOR:
      /* Handle with common attribute tag handler function. */
      gsc_handle_attribute_tag (tag);
      break;

    case SC_TAG_ENDBOLD:
    case SC_TAG_ENDITALICS:
    case SC_TAG_ENDUNDERLINE:
    case SC_TAG_ENDCOLOR:
      /* Handle with common attribute endtag handler function. */
      gsc_handle_endattribute_tag (tag);
      break;

    case SC_TAG_CENTER:
    case SC_TAG_RIGHT:
    case SC_TAG_ENDCENTER:
    case SC_TAG_ENDRIGHT:
      /*
       * We don't center or justify text, but so that things look right we do
       * want a newline on starting or ending such a section.
       */
      glk_put_char ('\n');
      break;

    case SC_TAG_WAIT:
      /*
       * Update the status line now only if it has its own window, then
       * handle with a specialized handler.
       */
      if (gsc_status_window)
        gsc_status_notify ();
      gsc_handle_wait_tag (argument);
      break;

    case SC_TAG_WAITKEY:
      /*
       * If reading an input log, ignore; it disrupts replay.  Write a newline
       * to separate off any unterminated game output instead.
       */
      if (!gsc_readlog_stream)
        {
          /* Update the status line now only if it has its own window. */
          if (gsc_status_window)
            gsc_status_notify ();

          /* Request a character event, and wait for it to be filled. */
          glk_request_char_event (gsc_main_window);
          gsc_event_wait (evtype_CharInput, &event);
        }
      else
        glk_put_char ('\n');
      break;

    default:
      /* Ignore unimplemented and unknown tags. */
      break;
    }
}


/*
 * os_print_string()
 *
 * Print a text string to the main output window.
 */
void
os_print_string (const sc_char *string)
{
  sc_bool is_monospaced;
  assert (string);
  assert (glk_stream_get_current ());

  /*
   * Get the monospace font setting from the current top of stack, or
   * default on empty stack.  If set, we may need to use an alternative
   * function to write this string.
   */
  if (gsc_font_index > 0)
    is_monospaced = gsc_font_stack[gsc_font_index - 1].is_monospaced;
  else
    is_monospaced = FALSE;

  /*
   * The main window should always be the currently set window at this point,
   * so we never be attempting monospaced output to the status window.
   * Nevertheless, check anyway.
   */
  if (is_monospaced
      && glk_stream_get_current () == glk_window_get_stream (gsc_main_window))
    gsc_put_string_alternate (string);
  else
    gsc_put_string (string);
}


/*
 * os_print_string_debug()
 *
 * Debugging output goes to the main Glk window -- no special effects or
 * dedicated debugging window attempted.
 */
void
os_print_string_debug (const sc_char *string)
{
  assert (string);
  assert (glk_stream_get_current ());

  gsc_put_string (string);
}


/*
 * gsc_styled_string()
 * gsc_styled_char()
 * gsc_standout_string()
 * gsc_standout_char()
 * gsc_normal_string()
 * gsc_normal_char()
 * gsc_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
gsc_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
gsc_styled_char (glui32 style, char c)
{
  char buffer[2];

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

static void
gsc_standout_string (const char *message)
{
  gsc_styled_string (style_Emphasized, message);
}

static void
gsc_standout_char (char c)
{
  gsc_styled_char (style_Emphasized, c);
}

static void
gsc_normal_string (const char *message)
{
  gsc_styled_string (style_Normal, message);
}

static void
gsc_normal_char (char c)
{
  gsc_styled_char (style_Normal, c);
}

static void
gsc_header_string (const char *message)
{
  gsc_styled_string (style_Header, message);
}


/*
 * os_display_hints()
 *
 * This is a very basic hints display.  In mitigation, very few games use
 * hints at all, and those that do are usually sparse in what they hint at, so
 * it's sort of good enough for the moment.
 */
void
os_display_hints (sc_game game)
{
  sc_game_hint hint;
  sc_int refused;

  /* For each hint, print the question, and confirm hint display. */
  refused = 0;
  for (hint = sc_get_first_game_hint (game);
       hint; hint = sc_get_next_game_hint (game, hint))
    {
      const sc_char *hint_question, *hint_text;

      /* If enough refusals, offer a way out of the loop. */
      if (refused >= GSC_HINT_REFUSAL_LIMIT)
        {
          if (!os_confirm (GSC_CONF_CONTINUE_HINTS))
            break;
          refused = 0;
        }

      /* Pop the question. */
      hint_question = sc_get_game_hint_question (game, hint);
      gsc_normal_char ('\n');
      gsc_standout_string (hint_question);
      gsc_normal_char ('\n');

      /* Print the subtle hint, or on to the next hint. */
      hint_text = sc_get_game_subtle_hint (game, hint);
      if (hint_text)
        {
          if (!os_confirm (GSC_CONF_SUBTLE_HINT))
            {
              refused++;
              continue;
            }
          gsc_normal_char ('\n');
          gsc_standout_string (hint_text);
          gsc_normal_string ("\n\n");
        }

      /* Print the less than subtle hint, or on to the next hint. */
      hint_text = sc_get_game_unsubtle_hint (game, hint);
      if (hint_text)
        {
          if (!os_confirm (GSC_CONF_UNSUBTLE_HINT))
            {
              refused++;
              continue;
            }
          gsc_normal_char ('\n');
          gsc_standout_string (hint_text);
          gsc_normal_string ("\n\n");
        }
    }
}


/*---------------------------------------------------------------------*/
/*  Glk resource handling functions                                    */
/*---------------------------------------------------------------------*/

/*
 * os_play_sound()
 * os_stop_sound()
 *
 * Stub functions.  The unused variables defeat gcc warnings.
 */
void
os_play_sound (const sc_char *filepath,
               sc_int offset, sc_int length, sc_bool is_looping)
{
  const sc_char *unused1;
  sc_int unused2, unused3;
  sc_bool unused4;
  unused1 = filepath;
  unused2 = offset;
  unused3 = length;
  unused4 = is_looping;
}

void
os_stop_sound (void)
{
}


/*
 * os_show_graphic()
 *
 * For graphic-capable Glk libraries on Linux, attempt graphics using xv.  The
 * graphic capability test isn't really required, it's just a way of having
 * graphics behave without surprises; someone using a non-graphical Glk
 * probably won't expect graphics to pop up.
 *
 * For other cases, this is a stub function, with unused variables to defeat
 * gcc warnings.
 */
#ifdef LINUX_GRAPHICS
static int gsclinux_graphics_enabled = TRUE;
static char *gsclinux_game_file = NULL;
void
os_show_graphic (const sc_char *filepath, sc_int offset, sc_int length)
{
  const sc_char *unused1;
  unused1 = filepath;

  if (length > 0
      && gsclinux_graphics_enabled && glk_gestalt (gestalt_Graphics, 0))
    {
      sc_char *buffer;

      /*
       * Try to extract data with dd.  Assuming that works, background xv to
       * display the image, then background a job to delay ten seconds and
       * then delete the temporary file containing the image.  Systems lacking
       * xv can usually use a small script, named xv, to invoke eog or an
       * alternative image display binary.  Not exactly finessed.
       */
      assert (gsclinux_game_file);
      buffer = gsc_malloc (strlen (gsclinux_game_file) + 128);
      sprintf (buffer, "dd if=%s ibs=1c skip=%ld count=%ld obs=100k"
               " of=/tmp/scare.jpg 2>/dev/null",
               gsclinux_game_file, offset, length);
      system (buffer);
      free (buffer);
      system ("xv /tmp/scare.jpg >/dev/null 2>&1 &");
      system ("( sleep 10; rm /tmp/scare.jpg ) >/dev/null 2>&1 &");
    }
}
#else
void
os_show_graphic (const sc_char *filepath, sc_int offset, sc_int length)
{
  const sc_char *unused1;
  sc_int unused2, unused3;
  unused1 = filepath;
  unused2 = offset;
  unused3 = length;
}
#endif


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

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

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

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

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

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

      glk_window_set_echo_stream (gsc_main_window, gsc_transcript_stream);

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

  else if (sc_strcasecmp (argument, "off") == 0)
    {
      if (!gsc_transcript_stream)
        {
          gsc_normal_string ("Glk transcript is already off.\n");
          return;
        }

      glk_stream_close (gsc_transcript_stream, NULL);
      gsc_transcript_stream = NULL;

      glk_window_set_echo_stream (gsc_main_window, NULL);

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

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

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


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

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

      if (gsc_inputlog_stream)
        {
          gsc_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)
        {
          gsc_standout_string ("Glk input logging failed.\n");
          return;
        }

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

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

  else if (sc_strcasecmp (argument, "off") == 0)
    {
      if (!gsc_inputlog_stream)
        {
          gsc_normal_string ("Glk input logging is already off.\n");
          return;
        }

      glk_stream_close (gsc_inputlog_stream, NULL);
      gsc_inputlog_stream = NULL;

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

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

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


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

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

      if (gsc_readlog_stream)
        {
          gsc_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)
        {
          gsc_standout_string ("Glk read log failed.\n");
          return;
        }

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

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

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

  else if (sc_strcasecmp (argument, "off") == 0)
    {
      if (!gsc_readlog_stream)
        {
          gsc_normal_string ("Glk read log is already off.\n");
          return;
        }

      glk_stream_close (gsc_readlog_stream, NULL);
      gsc_readlog_stream = NULL;

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

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

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


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

  if (sc_strcasecmp (argument, "on") == 0)
    {
      if (gsc_abbreviations_enabled)
        {
          gsc_normal_string ("Glk abbreviation expansions are already on.\n");
          return;
        }

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

  else if (sc_strcasecmp (argument, "off") == 0)
    {
      if (!gsc_abbreviations_enabled)
        {
          gsc_normal_string ("Glk abbreviation expansions are already off.\n");
          return;
        }

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

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

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


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

  sprintf (buffer, "%lu.%lu.%lu",
          (unsigned long) version >> 16,
          (unsigned long) (version >> 8) & 0xff,
          (unsigned long) version & 0xff);
  gsc_normal_string (buffer);
}

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

  gsc_normal_string ("This is version ");
  gsc_command_print_version_number (GSC_PORT_VERSION);
  gsc_normal_string (" of the Glk SCARE port.\n");

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


/*
 * gsc_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
gsc_command_commands (const char *argument)
{
  assert (argument);

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

  else if (sc_strcasecmp (argument, "off") == 0)
    {
      gsc_commands_enabled = FALSE;
      gsc_normal_string ("Glk commands are now off.\n");
    }

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

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


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

  gsc_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");

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

  gsc_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");

  gsc_normal_string ("Please report any bugs, omissions, or misfeatures to ");
  gsc_standout_string ("simon_baldwin@yahoo.com");
  gsc_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. */
} gsc_command_t;
typedef gsc_command_t *gsc_commandref_t;

static void gsc_command_summary (const char *argument);
static void gsc_command_help (const char *argument);

static gsc_command_t GSC_COMMAND_TABLE[] = {
  {"summary",        gsc_command_summary,        FALSE},
  {"script",         gsc_command_script,         TRUE},
  {"inputlog",       gsc_command_inputlog,       TRUE},
  {"readlog",        gsc_command_readlog,        TRUE},
  {"abbreviations",  gsc_command_abbreviations,  TRUE},
  {"version",        gsc_command_version,        FALSE},
  {"commands",       gsc_command_commands,       TRUE},
  {"license",        gsc_command_license,        FALSE},
  {"help",           gsc_command_help,           TRUE},
  {NULL, NULL, FALSE}
};


/*
 * gsc_command_summary()
 *
 * Report all current Glk settings.
 */
static void
gsc_command_summary (const char *argument)
{
  gsc_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 = GSC_COMMAND_TABLE; entry->command; entry++)
    {
      if (entry->handler == gsc_command_summary
            || entry->handler == gsc_command_license
            || entry->handler == gsc_command_help)
        continue;

      entry->handler ("");
    }
}


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

  if (strlen (command) == 0)
    {
      gsc_normal_string ("Glk commands are");
      for (entry = GSC_COMMAND_TABLE; entry->command; entry++)
        {
          gsc_commandref_t next;

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

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

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

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

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

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

  else if (matched->handler == gsc_command_readlog)
    {
      gsc_normal_string ("Plays back commands recorded with ");
      gsc_standout_string ("glk inputlog on");
      gsc_normal_string (".\n\nUse ");
      gsc_standout_string ("glk readlog on");
      gsc_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 == gsc_command_abbreviations)
    {
      gsc_normal_string ("Controls abbreviation expansion.\n\nGlk SCARE"
                         " automatically expands several standard single"
                         " letter abbreviations for you; for example, \"x\""
                         " becomes \"examine\".  Use ");
      gsc_standout_string ("glk abbreviations on");
      gsc_normal_string (" to turn this feature on, and ");
      gsc_standout_string ("glk abbreviations off");
      gsc_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 == gsc_command_version)
    {
      gsc_normal_string ("Prints the version numbers of the Glk library"
                         " and the Glk SCARE port.\n");
    }

  else if (matched->handler == gsc_command_commands)
    {
      gsc_normal_string ("Turn off Glk commands.\n\nUse ");
      gsc_standout_string ("glk commands off");
      gsc_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 == gsc_command_license)
    {
      gsc_normal_string ("Prints Glk SCARE's software license.\n");
    }

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

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


/*
 * gsc_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
gsc_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 (sc_strncasecmp (string + posn, "glk", strlen ("glk")) != 0)
    return FALSE;

  /* Take a copy of the string, without any leading space or introducer. */
  string_copy = gsc_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)
    {
      gsc_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 = GSC_COMMAND_TABLE; entry->command; entry++)
        {
          if (sc_strncasecmp (command, entry->command, strlen (command)) == 0)
            {
              matches++;
              matched = entry;
            }
        }

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

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

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

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


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

/* Quote used to suppress abbreviation expansion and local commands. */
static const char GSC_QUOTED_INPUT = '\'';


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

static gsc_abbreviation_t GSC_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}
};


/*
 * gsc_expand_abbreviations()
 *
 * Expand a few common one-character abbreviations commonly found in other
 * game systems.
 */
static void
gsc_expand_abbreviations (char *buffer, int size)
{
  char *command, abbreviation;
  const char *expansion;
  gsc_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 = GSC_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 >= (unsigned int) size)
        return;

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

      gsc_standout_string ("[");
      gsc_standout_char (abbreviation);
      gsc_standout_string (" -> ");
      gsc_standout_string (expansion);
      gsc_standout_string ("]\n");
    }
}


/*
 * os_read_line()
 *
 * Read and return a line of player input.
 */
sc_bool
os_read_line (sc_char *buffer, sc_int length)
{
  sc_int characters;
  assert (buffer && length > 0);

  /* If a help request is pending, provide a user hint. */
  gsc_output_provide_help_hint ();

  /*
   * Ensure normal style, update the status line, and issue an input prompt.
   */
  gsc_reset_glk_style ();
  gsc_status_notify ();
  glk_put_string (">");

  /*
   * 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 (gsc_readlog_stream)
    {
      glui32 chars;

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

          /* Return this line as player input. */
          return TRUE;
        }

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

  /*
   * No input log being read, or we just hit the end of file on one.  Revert
   * to normal line input; start by getting a new line from Glk.
   */
  characters = gsc_read_line (buffer, length - 1);
  assert (characters <= length);
  buffer[characters] = '\0';

  /*
   * If neither abbreviations nor local commands are enabled, use the data
   * read above without further massaging.
   */
  if (gsc_abbreviations_enabled || gsc_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] == GSC_QUOTED_INPUT)
        {
          /* Delete the quote with memmove(). */
          memmove (command, command + 1, strlen (command));
        }
      else
        {
          /* Check for, and expand, and abbreviated commands. */
          if (gsc_abbreviations_enabled)
            gsc_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 by
           * returning FALSE.
           */
          if (gsc_commands_enabled)
            {
              int posn;

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

              if (gsc_command_escape (buffer))
                {
                  gsc_output_silence_help_hints ();
                  return FALSE;
                }
            }
        }
    }

  /*
   * 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 (gsc_inputlog_stream)
    {
      glk_put_string_stream (gsc_inputlog_stream, buffer);
      glk_put_char_stream (gsc_inputlog_stream, '\n');
    }

  return TRUE;
}


/*
 * os_read_line_debug()
 *
 * Read and return a debugger command line.  There's no dedicated debugging
 * window, so this is just a call to the normal readline, with an additional
 * prompt.
 */
sc_bool
os_read_line_debug (sc_char *buffer, sc_int length)
{
  gsc_output_silence_help_hints ();
  gsc_reset_glk_style ();
  glk_put_string ("[SCARE debug]");
  return os_read_line (buffer, length);
}


/*
 * os_confirm()
 *
 * Confirm a game action with a yes/no prompt.
 */
sc_bool
os_confirm (sc_int type)
{
  sc_char response;

  /*
   * Always allow game saves and hint display, and if we're reading from an
   * input log, allow everything no matter what, on the assumption that the
   * user knows what they are doing.
   */
  if (gsc_readlog_stream
      || type == SC_CONF_SAVE || type == SC_CONF_VIEW_HINTS)
    return TRUE;

  /* Ensure back to normal style, and update status. */
  gsc_reset_glk_style ();
  gsc_status_notify ();

  /* Prompt for the confirmation, based on the type. */
  if (type == GSC_CONF_SUBTLE_HINT)
    glk_put_string ("View the subtle hint for this topic");
  else if (type == GSC_CONF_UNSUBTLE_HINT)
    glk_put_string ("View the unsubtle hint for this topic");
  else if (type == GSC_CONF_CONTINUE_HINTS)
    glk_put_string ("Continue with hints");
  else
    {
      glk_put_string ("Do you really want to ");
      switch (type)
        {
        case SC_CONF_QUIT:
          glk_put_string ("quit");
          break;
        case SC_CONF_RESTART:
          glk_put_string ("restart");
          break;
        case SC_CONF_SAVE:
          glk_put_string ("save");
          break;
        case SC_CONF_RESTORE:
          glk_put_string ("restore");
          break;
        case SC_CONF_VIEW_HINTS:
          glk_put_string ("view hints");
          break;
        default:
          glk_put_string ("do that");
          break;
        }
    }
  glk_put_string ("? ");

  /* Loop until 'yes' or 'no' returned. */
  do
    {
      event_t event;

      /* Wait for a standard key, ignoring Glk special keys. */
      do
        {
          glk_request_char_event (gsc_main_window);
          gsc_event_wait (evtype_CharInput, &event);
        }
      while (event.val1 > UCHAR_MAX);
      response = glk_char_to_upper (event.val1);
    }
  while (response != 'Y' && response != 'N');

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

  /* Use a short delay on restarts, if confirmed. */
  if (type == SC_CONF_RESTART && response == 'Y')
    gsc_short_delay ();

  /* Return TRUE if 'Y' was entered. */
  return (response == 'Y');
}


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

/* Short delay before restarts; 1s, in 100ms segments. */
static const glui32 GSC_DELAY_TIMEOUT = 100;
static const glui32 GSC_DELAY_TIMEOUTS_COUNT = 10;

/*
 * gsc_short_delay()
 *
 * Delay for a short period; used before restarting a completed game, to
 * improve the display where 'r', or confirming restart, triggers an otherwise
 * immediate, and abrupt, restart.
 */
static void
gsc_short_delay (void)
{
  /* Ignore the call if the Glk doesn't have timers. */
  if (glk_gestalt (gestalt_Timer, 0))
    {
      glui32 timeout;

      /* Timeout in small chunks to minimize Glk jitter. */
      glk_request_timer_events (GSC_DELAY_TIMEOUT);
      for (timeout = 0; timeout < GSC_DELAY_TIMEOUTS_COUNT; timeout++)
        {
          event_t event;

          gsc_event_wait (evtype_Timer, &event);
        }
      glk_request_timer_events (0);
    }
}


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

  do
    {
      glk_select (event);

      switch (event->type)
        {
        case evtype_Arrange:
        case evtype_Redraw:
          /* Refresh any sensitive windows on size events. */
          gsc_status_redraw ();
          break;
        }
    }
  while (!(event->type == wait_type_1 || event->type == wait_type_2));
}

static void
gsc_event_wait (glui32 wait_type, event_t * event)
{
  assert (event);

  gsc_event_wait_2 (wait_type, evtype_None, event);
}


/*---------------------------------------------------------------------*/
/*  Glk port file functions                                            */
/*---------------------------------------------------------------------*/

/*
 * os_open_file()
 *
 * Open a file for save or restore, and return a Glk stream for the opened
 * file.
 */
void *
os_open_file (sc_bool is_save)
{
  glui32 usage, fmode;
  frefid_t fileref;
  strid_t stream;

  usage = fileusage_SavedGame | fileusage_BinaryMode;
  fmode = is_save ? filemode_Write : filemode_Read;

  fileref = glk_fileref_create_by_prompt (usage, fmode, 0);
  if (!fileref)
    return NULL;

  if (!is_save && !glk_fileref_does_file_exist (fileref))
    {
      glk_fileref_destroy (fileref);
      return NULL;
    }

  stream = glk_stream_open_file (fileref, fmode, 0);
  glk_fileref_destroy (fileref);

  return stream;
}


/*
 * os_write_file()
 * os_read_file()
 *
 * Write/read the given buffered data to/from the open Glk stream.
 */
void
os_write_file (void *opaque, const sc_byte *buffer, sc_int length)
{
  strid_t stream = (strid_t) opaque;
  assert (opaque && buffer);

  glk_put_buffer_stream (stream, (char *) buffer, length);
}

sc_int
os_read_file (void *opaque, sc_byte *buffer, sc_int length)
{
  strid_t stream = (strid_t) opaque;
  assert (opaque && buffer);

  return glk_get_buffer_stream (stream, (char *) buffer, length);
}


/*
 * os_close_file()
 *
 * Close the opened Glk stream.
 */
void
os_close_file (void *opaque)
{
  strid_t stream = (strid_t) opaque;
  assert (opaque);

  glk_stream_close (stream, NULL);
}


/*---------------------------------------------------------------------*/
/*  main() and options parsing                                         */
/*---------------------------------------------------------------------*/

/* Loading message flush delay timeout. */
static const glui32 GSC_LOADING_TIMEOUT = 100;

/* Enumerated game end options. */
enum gsc_end_option { GAME_RESTART, GAME_UNDO, GAME_QUIT };

/*
 * The following value needs to be passed between the startup_code and main
 * functions.
 */
static char *gsc_game_message = NULL;


/*
 * gsc_callback()
 *
 * Callback function for reading in game and restore file data; fills a
 * buffer with TAF or TAS file data from a Glk stream, and returns the byte
 * count.
 */
static sc_int
gsc_callback (void *opaque, sc_byte *buffer, sc_int length)
{
  strid_t stream = (strid_t) opaque;
  assert (stream);

  return glk_get_buffer_stream (stream, (char *) buffer, length);
}


/*
 * gsc_get_ending_option()
 *
 * Offer the option to restart, undo, or quit.  Returns the selected game
 * end option.  Called on game completion.
 */
static enum gsc_end_option
gsc_get_ending_option (void)
{
  sc_char response;

  /* Ensure back to normal style, and update status. */
  gsc_reset_glk_style ();
  gsc_status_notify ();

  /* Prompt for restart, undo, or quit. */
  glk_put_string ("\nWould you like to RESTART, UNDO a turn, or QUIT? ");

  /* Loop until 'restart', 'undo' or 'quit'. */
  do
    {
      event_t event;

      do
        {
          glk_request_char_event (gsc_main_window);
          gsc_event_wait (evtype_CharInput, &event);
        }
      while (event.val1 > UCHAR_MAX);
      response = glk_char_to_upper (event.val1);
    }
  while (response != 'R' && response != 'U' && response != 'Q');

  /* Echo the confirmation response, and a new line. */
  glk_set_style (style_Input);
  switch (response)
    {
    case 'R':
      glk_put_string ("Restart");
      break;
    case 'U':
      glk_put_string ("Undo");
      break;
    case 'Q':
      glk_put_string ("Quit");
      break;
    default:
      gsc_fatal ("GLK: Invalid response encountered");
      glk_exit ();
    }
  glk_set_style (style_Normal);
  glk_put_char ('\n');

  /* Return the appropriate value for response. */
  switch (response)
    {
    case 'R':
      return GAME_RESTART;
    case 'U':
      return GAME_UNDO;
    case 'Q':
      return GAME_QUIT;
    default:
      gsc_fatal ("GLK: Invalid response encountered");
      glk_exit ();
    }

  /* Unreachable; supplied to suppress compiler warning. */
  return GAME_QUIT;
}


/*
 * gsc_startup_code()
 * gsc_main
 *
 * Together, these functions take the place of the original main().  The
 * first one is called from the platform-specific 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
gsc_startup_code (strid_t game_stream, strid_t restore_stream,
                  sc_uint trace_flags, sc_bool enable_debugger,
                  sc_bool stable_random, const sc_char *locale)
{
  winid_t window;
  assert (game_stream);

  /* Open a temporary Glk main window. */
  window = glk_window_open (0, 0, 0, wintype_TextBuffer, 0);
  if (window)
    {
      /* Clear and initialize the temporary window. */
      glk_window_clear (window);
      glk_set_window (window);
      glk_set_style (style_Normal);

      /*
       * Display a brief loading game message; here we have to use a timeout
       * to ensure that the text is flushed to Glk.
       */
      glk_put_string ("Loading game...\n");
      if (glk_gestalt (gestalt_Timer, 0))
        {
          event_t event;

          glk_request_timer_events (GSC_LOADING_TIMEOUT);
          do
            {
              glk_select (&event);
            }
          while (event.type != evtype_Timer);
          glk_request_timer_events (0);
        }
    }

  /* If the Glk libarary does not support unicode, disable it. */
  if (!gsc_has_unicode || !glk_gestalt (gestalt_Unicode, 0))
    gsc_unicode_enabled = FALSE;

  /*
   * If a locale was requested, set it in the core interpreter now.  This
   * locale will preempt any auto-detected one found from inspecting the
   * game on creation.  After game creation, the Glk locale is synchronized
   * to the core interpreter's locale.
   */
  if (locale)
    sc_set_locale (locale);

  /*
   * Set tracing flags, then try to create a SCARE game reference from the
   * TAF file.  Since we need this in our call from glk_main, we have to keep
   * it in a module static variable.  If we can't open the TAF file, then
   * we'll set the pointer to NULL, and complain about it later in main.
   * Passing the message string around like this is a nuisance...
   */
  sc_set_trace_flags (trace_flags);
  gsc_game = sc_game_from_callback (gsc_callback, game_stream);
  if (!gsc_game)
    {
      gsc_game = NULL;
      gsc_game_message = "Unable to load an Adrift game from the"
                         " requested file.";
    }
  else
    gsc_game_message = NULL;
  glk_stream_close (game_stream, NULL);

  /*
   * If the game was created successfully and there is a restore stream, try
   * to immediately restore the game from that stream.
   */
  if (gsc_game && restore_stream)
    {
      if (!sc_load_game_from_callback (gsc_game, gsc_callback, restore_stream))
        {
          sc_free_game (gsc_game);
          gsc_game = NULL;
          gsc_game_message = "Unable to restore this Adrift game from the"
                             " requested file.";
        }
      else
        gsc_game_message = NULL;
    }
  if (restore_stream)
    glk_stream_close (restore_stream, NULL);

  /* If successful, set game debugging and synchronize to the core's locale. */
  if (gsc_game)
    {
      sc_set_game_debugger_enabled (gsc_game, enable_debugger);
      gsc_set_locale (sc_get_locale ());
    }

  /* Set portable and predictable random number generation if requested. */
  if (stable_random)
    {
      sc_set_portable_random (TRUE);
      sc_reseed_random_sequence (1);
    }

  /* Close the temporary window. */
  if (window)
    glk_window_close (window, NULL);

  /* Set title of game */
#ifdef GARGLK
    garglk_set_story_name(sc_get_game_name(gsc_game));
#endif

  /* Game set up, perhaps successfully. */
  return TRUE;
}

static void
gsc_main (void)
{
  sc_bool is_running;

  /* Ensure SCARE internal types have the right sizes. */
  if (!(sizeof (sc_byte) == 1 && sizeof (sc_char) == 1
        && sizeof (sc_uint) >= 4 && sizeof (sc_int) >= 4
        && sizeof (sc_uint) <= 8 && sizeof (sc_int) <= 8))
    {
      gsc_fatal ("GLK: Types sized incorrectly, recompilation is needed");
      glk_exit ();
    }

  /* Create the Glk window, and set its stream as the current one. */
  gsc_main_window = glk_window_open (0, 0, 0, wintype_TextBuffer, 0);
  if (!gsc_main_window)
    {
      gsc_fatal ("GLK: Can't open main window");
      glk_exit ();
    }
  glk_window_clear (gsc_main_window);
  glk_set_window (gsc_main_window);
  glk_set_style (style_Normal);

  /* If there's a problem with the game file, complain now. */
  if (!gsc_game)
    {
      assert (gsc_game_message);
      gsc_header_string ("Glk SCARE Error\n\n");
      gsc_normal_string (gsc_game_message);
      gsc_normal_char ('\n');
      glk_exit ();
    }

  /* Try to create a one-line status window.  We can live without it. */
  glk_stylehint_set (wintype_TextGrid, style_User1, stylehint_ReverseColor, 1);
  gsc_status_window = glk_window_open (gsc_main_window,
                                       winmethod_Above | winmethod_Fixed,
                                       1, wintype_TextGrid, 0);

  /* Repeat the game until no more restarts requested. */
  is_running = TRUE;
  while (is_running)
    {
      /* Run the game until it ends, or the user quits. */
      gsc_status_notify ();
      sc_interpret_game (gsc_game);

      /*
       * If the game did not complete, the user quit explicitly, so leave the
       * game repeat loop.
       */
      if (!sc_has_game_completed (gsc_game))
        {
          is_running = FALSE;
          break;
        }

      /*
       * If reading from an input log, close it now.  We need to request a
       * user selection, probably modal, and after that we probably don't
       * want the follow-on readlog data being used as game input.
       */
      if (gsc_readlog_stream)
        {
          glk_stream_close (gsc_readlog_stream, NULL);
          gsc_readlog_stream = NULL;
        }

      /*
       * Get user selection of restart, undo a turn, or quit completed game.
       * If undo is unavailable (this should not be possible), degrade to
       * restart.
       */
      switch (gsc_get_ending_option ())
        {
        case GAME_RESTART:
          gsc_short_delay ();
          sc_restart_game (gsc_game);
          break;

        case GAME_UNDO:
          if (sc_is_game_undo_available (gsc_game))
            {
              sc_undo_game_turn (gsc_game);
              gsc_normal_string ("The previous turn has been undone.\n");
            }
          else
            {
              gsc_normal_string ("Sorry, no undo is available.\n");
              gsc_short_delay ();
              sc_restart_game (gsc_game);
            }
          break;

        case GAME_QUIT:
          is_running = FALSE;
          break;
        }
    }

  /* All done -- release game resources. */
  sc_free_game (gsc_game);

  /* Close any open transcript, input log, and/or read log. */
  if (gsc_transcript_stream)
    {
      glk_stream_close (gsc_transcript_stream, NULL);
      gsc_transcript_stream = NULL;
    }
  if (gsc_inputlog_stream)
    {
      glk_stream_close (gsc_inputlog_stream, NULL);
      gsc_inputlog_stream = NULL;
    }
  if (gsc_readlog_stream)
    {
      glk_stream_close (gsc_readlog_stream, NULL);
      gsc_readlog_stream = NULL;
    }
}


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

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

/*
 * glk_main()
 *
 * Main entry point for Glk.  Here, all startup is done, and we call our
 * function to run the game, or to report errors if gsc_game_message is set.
 */
void
glk_main (void)
{
  assert (gsc_startup_called && !gsc_main_called);
  gsc_main_called = TRUE;

  /* Call the generic interpreter main function. */
  gsc_main ();
}


/*---------------------------------------------------------------------*/
/*  Glk linkage relevant only to the UNIX platform                     */
/*---------------------------------------------------------------------*/
#ifdef TRUE

#include "glkstart.h"

/*
 * Glk arguments for UNIX versions of the Glk interpreter.
 */
glkunix_argumentlist_t glkunix_arguments[] = {
  {(char *) "-nc", glkunix_arg_NoValue,
   (char *) "-nc        No local handling for Glk special commands"},
  {(char *) "-na", glkunix_arg_NoValue,
   (char *) "-na        Turn off abbreviation expansions"},
  {(char *) "-nu", glkunix_arg_NoValue,
   (char *) "-nu        Turn off any use of Unicode output"},
#ifdef LINUX_GRAPHICS
  {(char *) "-ng", glkunix_arg_NoValue,
   (char *) "-ng        Turn off attempts at game graphics"},
#endif
  {(char *) "-r", glkunix_arg_ValueFollows,
   (char *) "-r FILE    Restore from FILE on starting the game"},
  {(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 interpreter.  Glk will call
 * glkunix_startup_code() to pass in arguments.  On startup, parse arguments
 * and open a Glk stream to the game, then call the generic gsc_startup_code()
 * to build a game from the stream.  On error, set the message in
 * gsc_game_message; the core gsc_main() will report it when it's called.
 */
int
glkunix_startup_code (glkunix_startup_t * data)
{
  int argc = data->argc;
  sc_char **argv = data->argv;
  int argv_index;
  sc_char *restore_from;
  const sc_char *locale;
  strid_t game_stream, restore_stream;
  sc_uint trace_flags;
  sc_bool enable_debugger, stable_random;
  assert (!gsc_startup_called);
  gsc_startup_called = TRUE;

#ifdef GARGLK
  garglk_set_program_name("SCARE " SCARE_VERSION);
  garglk_set_program_info("SCARE " SCARE_VERSION
      " by Simon Baldwin and Mark J. Tilford");
#endif

  /* Handle command line arguments. */
  restore_from = NULL;
  for (argv_index = 1;
       argv_index < argc && argv[argv_index][0] == '-'; argv_index++)
    {
      if (strcmp (argv[argv_index], "-nc") == 0)
        {
          gsc_commands_enabled = FALSE;
          continue;
        }
      if (strcmp (argv[argv_index], "-na") == 0)
        {
          gsc_abbreviations_enabled = FALSE;
          continue;
        }
      if (strcmp (argv[argv_index], "-nu") == 0)
        {
          gsc_unicode_enabled = FALSE;
          continue;
        }
#ifdef LINUX_GRAPHICS
      if (strcmp (argv[argv_index], "-ng") == 0)
        {
          gsclinux_graphics_enabled = FALSE;
          continue;
        }
#endif
      if (strcmp (argv[argv_index], "-r") == 0)
        {
          restore_from = argv[++argv_index];
          continue;
        }
      return FALSE;
    }

  /* On invalid usage, set a complaint message and return. */
  if (argv_index != argc - 1)
    {
      gsc_game = NULL;
      if (argv_index < argc - 1)
        gsc_game_message = "More than one game file"
                           " was given on the command line.";
      else
        gsc_game_message = "No game file was given on the command line.";
      return TRUE;
    }

  /* Open a stream to the TAF file, complain if this fails. */
  game_stream = glkunix_stream_open_pathname (argv[argv_index], FALSE, 0);
  if (!game_stream)
    {
      gsc_game = NULL;
      gsc_game_message = "Unable to open the requested game file.";
      return TRUE;
    }
  else
    gsc_game_message = NULL;

  /*
   * If a restore requested, open a stream to the TAF (TAS) file, and
   * again, complain if this fails.
   */
  if (restore_from)
    {
      restore_stream = glkunix_stream_open_pathname (restore_from, FALSE, 0);
      if (!restore_stream)
        {
          glk_stream_close (game_stream, NULL);
          gsc_game = NULL;
          gsc_game_message = "Unable to open the requested restore file.";
          return TRUE;
        }
      else
        gsc_game_message = NULL;
    }
  else
    restore_stream = NULL;

  /* Set SCARE trace flags and other general setup from the environment. */
  if (getenv ("SC_TRACE_FLAGS"))
    trace_flags = strtoul (getenv ("SC_TRACE_FLAGS"), NULL, 0);
  else
    trace_flags = 0;
  enable_debugger = (getenv ("SC_DEBUGGER_ENABLED") != NULL);
  stable_random = (getenv ("SC_STABLE_RANDOM_ENABLED") != NULL);
  locale = getenv ("SC_LOCALE");

#ifdef LINUX_GRAPHICS
  /* Note the path to the game file for graphics extraction. */
  gsclinux_game_file = argv[argv_index];
#endif

  /* Use the generic startup code to complete startup. */
  return gsc_startup_code (game_stream, restore_stream, trace_flags,
                           enable_debugger, stable_random, locale);
}
#endif /* __unix */


/*---------------------------------------------------------------------*/
/*  Glk linkage relevant only to the Windows platform                  */
/*---------------------------------------------------------------------*/
#ifdef _WIN32

#include <windows.h>

#include "WinGlk.h"
#include "resource.h"

/* Windows constants and external definitions. */
static const unsigned int GSCWIN_GLK_INIT_VERSION = 0x601;
extern int InitGlk (unsigned int iVersion);

/*
 * WinMain()
 *
 * Entry point for all Glk applications.
 */
int WINAPI
WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
         LPSTR lpCmdLine, int nCmdShow)
{
  /* Attempt to initialize both the Glk library and SCARE. */
  if (!(InitGlk (GSCWIN_GLK_INIT_VERSION) && winglk_startup_code (lpCmdLine)))
    return 0;

  /* Run the application; no return from this routine. */
  glk_main ();
  glk_exit ();
  return 0;
}


/*
 * winglk_startup_code()
 *
 * Startup entry point for Windows versions of Glk interpreter.
 */
int
winglk_startup_code (const char *cmdline)
{
  const char *filename, *locale;
  frefid_t fileref;
  strid_t game_stream;
  sc_uint trace_flags;
  sc_bool enable_debugger, stable_random;
  assert (!gsc_startup_called);
  gsc_startup_called = TRUE;

  /* Set up application and window. */
  winglk_app_set_name ("Scare");
  winglk_set_menu_name ("&Scare");
  winglk_window_set_title ("Scare Adrift Interpreter");
  winglk_set_about_text ("Windows Scare 1.3.10");
  winglk_set_gui (IDI_SCARE);

  /* Open a stream to the game. */
  filename = winglk_get_initial_filename (cmdline,
                             "Select an Adrift game to run",
                             "Adrift Files (.taf)|*.taf;All Files (*.*)|*.*||");
  if (!filename)
    return 0;

  fileref = winglk_fileref_create_by_name (fileusage_BinaryMode
                                           | fileusage_Data,
                                           (char *) filename, 0, 0);
  if (!fileref)
    return 0;

  game_stream = glk_stream_open_file (fileref, filemode_Read, 0);
  glk_fileref_destroy (fileref);
  if (!game_stream)
    return 0;

  /* Set trace, debugger, and portable random flags. */
  if (getenv ("SC_TRACE_FLAGS"))
    trace_flags = strtoul (getenv ("SC_TRACE_FLAGS"), NULL, 0);
  else
    trace_flags = 0;
  enable_debugger = (getenv ("SC_DEBUGGER_ENABLED") != NULL);
  stable_random = (getenv ("SC_STABLE_RANDOM_ENABLED") != NULL);
  locale = getenv ("SC_LOCALE");

  /* Use the generic startup code to complete startup. */
  return gsc_startup_code (game_stream, NULL, trace_flags,
                           enable_debugger, stable_random, locale);
}
#endif /* _WIN32 */
