/*  Nitfol - z-machine interpreter using Glk for output.
    Copyright (C) 1999  Evin Robertson

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

    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.

    The author can be reached at nitfol@deja.com
*/
#include "nitfol.h"
#include "nio.h"

#ifdef HEADER

typedef struct z_window *zwinid;

#endif

BOOL is_fixed;       /* If we are forcing output to be fixed-width */


/* descriptions of z-machine colors, in glk 0x00rrggbb style.
   -1 means to call glk_stylehint_clear instead of glk_stylehint_set.
   The 'current color' will be overwritten on calls to set_colour.

   Go ahead and customize these (making background colors lighter than
   foreground colors might be interesting)
*/

glsi32 bgcolortable[] = {
  -1L,          /* current color */
  -1L,          /* defualt setting */
  0x00000000L,  /* black */
  0x00ff0000L,  /* red */
  0x00008000L,  /* green */
  0x00ffff00L,  /* yellow */
  0x000000ffL,  /* blue */
  0x00ff00ffL,  /* magenta */
  0x0000ffffL,  /* cyan */
  0x00ffffffL,  /* white */
  0x00c0c0c0L,  /* light grey */
  0x00808080L,  /* medium grey */
  0x00404040L   /* dark grey */
};

glsi32 fgcolortable[] = {
  -1L,          /* current color */
  -1L,          /* defualt setting */
  0x00000000L,  /* black */
  0x00ff0000L,  /* red */
  0x00008000L,  /* green */
  0x00ffff00L,  /* yellow */
  0x000000ffL,  /* blue */
  0x00ff00ffL,  /* magenta */
  0x0000ffffL,  /* cyan */
  0x00ffffffL,  /* white */
  0x00c0c0c0L,  /* light grey */
  0x00808080L,  /* medium grey */
  0x00404040L   /* dark grey */
};


static void killglkwithcolor(glui32 styl, int fore, int back)
{
  if(fgcolortable[fore] == -1)
    glk_stylehint_clear(wintype_AllTypes, styl,
			stylehint_TextColor);
  else
    glk_stylehint_set(wintype_AllTypes, styl,
		      stylehint_TextColor, fgcolortable[fore]);

  if(bgcolortable[back] == -1)
    glk_stylehint_clear(wintype_AllTypes, styl,
			stylehint_BackColor);
  else
    glk_stylehint_set(wintype_AllTypes, styl,
		      stylehint_BackColor, bgcolortable[back]);
}


static void set_stylehints(char fore, char back)
{
  glui32 n;
  for(n = 0; n < style_NUMSTYLES; n++)
    killglkwithcolor(n, fore, back);

  /* Subheader will be used for bold */
  glk_stylehint_set(wintype_TextBuffer, style_Subheader,
		    stylehint_Weight, 1);

  /* BlockQuote will be used for reverse proportional text */
  glk_stylehint_set(wintype_TextBuffer, style_BlockQuote,
		    stylehint_Proportional, 0);
  glk_stylehint_set(wintype_TextBuffer, style_BlockQuote,
		    stylehint_Justification, stylehint_just_Centered);
#ifdef stylehint_ReverseColor
  glk_stylehint_set(wintype_TextBuffer, style_BlockQuote,
		    stylehint_ReverseColor, 1);
#endif

  /* User1 will be used for bold italics */
  glk_stylehint_set(wintype_TextBuffer, style_User1,
		    stylehint_Weight, 1);
  glk_stylehint_set(wintype_TextBuffer, style_User1,
		    stylehint_Oblique, 1);

  /* User2 will be used for proportional bold/italic */
  glk_stylehint_set(wintype_TextBuffer, style_User2,
		    stylehint_Proportional, 0);
  glk_stylehint_set(wintype_TextBuffer, style_User2,
		    stylehint_Weight, 1);
}

#define sBOLD 1
#define sITAL 2
#define sFIXE 4
#define sREVE 8

#if 0

static glui32 bitmap_to_style[16] = {
  style_Normal,
  style_Subheader,   /* sBOLD */
  style_Emphasized,  /* sITAL */
  style_User1,       /* sBOLD | sITAL */
  style_Preformatted,/* sFIXE */
  style_User2,       /* sFIXE | sBOLD */
  style_User2,       /* sFIXE | sITAL */
  style_User2,       /* sFIXE | sBOLD | sITAL*/
  style_BlockQuote,  /* sREVE */
  style_BlockQuote,  /* sREVE | sBOLD */
  style_BlockQuote,  /* sREVE | sITAL */
  style_BlockQuote,  /* sREVE | sBOLD | sITAL */
  style_BlockQuote,  /* sFIXE | sREVE */
  style_BlockQuote,  /* sFIXE | sREVE | sBOLD */
  style_BlockQuote,  /* sFIXE | sREVE | sITAL */
  style_BlockQuote   /* sFIXE | sREVE | sBOLD | sITAL */
};

#else

static glui32 bitmap_to_style[16] = {
  style_Normal,
  style_Subheader,   /* sBOLD */
  style_Emphasized,  /* sITAL */
  style_Subheader,   /* sBOLD | sITAL */
  style_Preformatted,/* sFIXE */
  style_Subheader,   /* sFIXE | sBOLD */
  style_Emphasized,  /* sFIXE | sITAL */
  style_Subheader,   /* sFIXE | sBOLD | sITAL*/
  style_Normal,
  style_Subheader,   /* sBOLD */
  style_Emphasized,  /* sITAL */
  style_Subheader,   /* sBOLD | sITAL */
  style_Preformatted,/* sFIXE */
  style_Subheader,   /* sFIXE | sBOLD */
  style_Emphasized,  /* sFIXE | sITAL */
  style_Subheader    /* sFIXE | sBOLD | sITAL*/
};

#endif

typedef struct {
  char fore;
  char back;
  unsigned char style;
} colorstyle;

typedef struct {
  glui32 image_num;
  glui32 x, y;
  glui32 width, height;
} z_image;


struct z_window {
  winid_t win;
  strid_t str;
  glui32 wintype;
  glui32 method;

  strid_t transcript;

  BOOL glk_input_pending;
  glui32 pending_input_type;
  glui32 pending_input_length;

  /* for upper window of v3 - returns # of lines drawn */
  glui32 (*draw_callback)(winid_t win, glui32 width, glui32 height);
  BOOL (*mouse_callback)(BOOL is_char_event, winid_t win, glui32 x, glui32 y);
  
  glui32 width, height;
  glui32 x1, y1, x2, y2;

  glui32 last_height;   /* What the height was last time we got input */
  glui32 biggest_height;/* The biggest it's been since */

  glui32 curr_offset;   /* offset into text_buffer/color_buffer */
  glui32 max_offset;    /* curr_offset must stay < max_offset */
  glui32 buffer_size;   /* max_offset must stay < buffer_size */

  BOOL dirty;           /* Has window been changed since last redraw? */
  BOOL defined;         /* Is our location well defined? */

  unsigned char *text_buffer;  /* whole window for grid, current line for buffer */
  colorstyle *color_buffer;

  z_image images[12];

  colorstyle current;
  colorstyle actual;
};


#define num_z_windows 16

static struct z_window game_windows[num_z_windows];

static glui32 upper_width, upper_height;


static int waitforinput(zwinid window, glui32 *val,
			BOOL (*timer_callback)(zword), zword timer_arg);


void set_glk_stream_current(void)
{
  z_flush_text(&game_windows[0]);
  glk_stream_set_current(game_windows[0].str);
}

void draw_intext_picture(zwinid window, glui32 picture, glui32 alignment)
{
  z_flush_text(window);
  wrap_glk_image_draw(window->win, picture, alignment, 0);
}

void draw_picture(zwinid window, glui32 picture, glui32 x, glui32 y)
{
  int i;
  int useimage = -1;
  glui32 width, height;

  wrap_glk_image_get_info(operand[0], &width, &height);

  for(i = 0; i < 12; i++) {
    if(is_in_bounds(window->images[i].x, window->images[i].y,
		    window->images[i].width, window->images[i].height,
		    x, y, width, height))
      useimage = i;
  }
  if(useimage == -1)
    for(i = 0; i < 12; i++)
      if(window->images[i].image_num == 0)
	useimage = i;
  if(useimage == -1)
    return;

  window->images[useimage].image_num = picture;
  window->images[useimage].x = x;
  window->images[useimage].y = y;
  window->images[useimage].width = width;
  window->images[useimage].height = height;
}


static int showstuffcount = 0;

/* Show an interpreter message */
void showstuff(const char *title, const char *type, const char *message, offset number)
{
  static BOOL loopy = FALSE;
  if(loopy) {
    loopy = FALSE;
    n_show_fatal(E_SYSTEM, "loopy message reporting", 0);
  }
  loopy = TRUE;

  z_pause_timed_input(&game_windows[0]);
  z_flush_text(&game_windows[0]);
  glk_stream_set_current(game_windows[0].str);

  glk_set_style(style_Alert);
  w_glk_put_string("\n[");
  w_glk_put_string(title);
  w_glk_put_string(": ");
  w_glk_put_string(type);
  w_glk_put_string("]: ");
  w_glk_put_string(message);
  w_glk_put_string(" (");
  g_print_snumber(number);
  w_glk_put_string(") ");

#ifdef DEBUGGING
  infix_gprint_loc(stack_get_depth(), 0);
#else
  w_glk_put_string("PC=");
  g_print_number(oldPC);
  glk_put_char('\n');
#endif

  if(++showstuffcount == 100) {
    w_glk_put_string("[pausing every 100 errors]\n");
    z_wait_for_key(&game_windows[0]);
  }

  glk_set_style(style_Normal);

  loopy = FALSE;
}


void init_lower(zwinid *lower)
{
  zwinid lower_win = &game_windows[0];
  glui32 i;

  if(lower)
    *lower = lower_win;

  if(lower_win->win) {
    z_pause_timed_input(lower_win);
    glk_window_close(lower_win->win, NULL);
  }

  set_stylehints(lower_win->current.fore,
		 lower_win->current.back);


  lower_win->dirty = TRUE;
  lower_win->wintype = wintype_TextBuffer;
  lower_win->method = winmethod_Below;
  lower_win->curr_offset = 0;
  lower_win->max_offset = 80 * 24;
  lower_win->buffer_size = lower_win->max_offset;

  if(!lower_win->text_buffer)
    lower_win->text_buffer = (unsigned char *) n_malloc(lower_win->buffer_size);
  if(!lower_win->color_buffer)
    lower_win->color_buffer = (colorstyle *) n_malloc(lower_win->buffer_size * sizeof(colorstyle));

  for(i = 0; i < lower_win->buffer_size; i++) {
    lower_win->text_buffer[i] = ' ';
    lower_win->color_buffer[i] = lower_win->current;
  }

  lower_win->actual = lower_win->current;

  lower_win->win = glk_window_open(game_windows[1].win,
				    winmethod_Below | winmethod_Proportional,
				    50,  /* Percent doesn't matter */
				    wintype_TextBuffer, 0);


  if(lower_win->win == 0) {
    n_show_fatal(E_OUTPUT, "cannot open lower window", 0);
    return;
  }

  lower_win->str = glk_window_get_stream(lower_win->win);

  if(lower_win->transcript)
    glk_window_set_echo_stream(lower_win->win, lower_win->transcript);
}


void init_upper(zwinid *upper)
{
  zwinid upper_win = &game_windows[1];
  glui32 i;

  if(upper)
    *upper = upper_win;

  if(upper_win->win) {
    z_pause_timed_input(upper_win);
    glk_window_close(upper_win->win, NULL);
  }

  upper_win->dirty = TRUE;
  upper_win->wintype = wintype_TextGrid;
  upper_win->method = winmethod_Above | winmethod_Fixed;
  upper_win->height = 0;
  upper_win->width = upper_width;
  upper_win->curr_offset = 0;
  upper_win->max_offset = upper_height * upper_width;
  upper_win->buffer_size = upper_win->max_offset;

  if(!upper_win->text_buffer)
    upper_win->text_buffer = (unsigned char *) n_malloc(upper_win->buffer_size);
  if(!upper_win->color_buffer)
    upper_win->color_buffer = (colorstyle *) n_malloc(upper_win->buffer_size * sizeof(colorstyle));

  for(i = 0; i < upper_win->buffer_size; i++) {
    upper_win->text_buffer[i] = ' ';
    upper_win->color_buffer[i] = upper_win->current;
  }

  upper_win->actual = upper_win->current;

  upper_win->win = glk_window_open(game_windows[0].win,
				    winmethod_Above | winmethod_Fixed,
				    1, /* XXX huh? upper_height, */
				    wintype_TextGrid, 1);

  if(upper_win->win == 0) {
    upper_win->str = 0;
    return;
  }

  upper_win->str = glk_window_get_stream(upper_win->win);

  if(upper_win->str == 0) {
    glk_window_close(upper_win->win, NULL);
    upper_win->win = 0;
    return;
  }
}


void z_init_windows(BOOL dofixed,
		    glui32 (*draw_callback)(winid_t, glui32, glui32),
		    BOOL (*mouse_callback)(BOOL, winid_t, glui32, glui32),
		    glui32 maxwidth, glui32 maxheight,
		    zwinid *upper, zwinid *lower)
{
  colorstyle defaultstyle;
  defaultstyle.fore = 1; defaultstyle.back = 1; defaultstyle.style = 0;

  is_fixed = dofixed;

  kill_windows();

  upper_width = maxwidth; upper_height = maxheight;

  game_windows[0].current = game_windows[1].current = defaultstyle;
  game_windows[1].draw_callback  = draw_callback;
  game_windows[1].mouse_callback = mouse_callback;

  init_lower(lower);
  init_upper(upper);
}


zwinid z_split_screen(glui32 wintype, glui32 method,
		      glui32 (*draw_callback)(winid_t, glui32, glui32),
		      BOOL (*mouse_callback)(BOOL, winid_t, glui32, glui32))
{
  int i;
  for(i = 0; i < num_z_windows; i++) {
    if(!game_windows[i].win) {
      winid_t root = glk_window_get_root();
      game_windows[i].win = glk_window_open(root, method, 0, wintype, 0);
      game_windows[i].str = glk_window_get_stream(game_windows[i].win);
      game_windows[i].wintype = wintype;
      game_windows[i].method = method;
      game_windows[i].transcript = NULL;
      game_windows[i].glk_input_pending = FALSE;
      game_windows[i].draw_callback = draw_callback;
      game_windows[i].mouse_callback = mouse_callback;
      game_windows[i].width = 0;
      game_windows[i].height = 0;
      game_windows[i].curr_offset = 0;
      game_windows[i].max_offset = 1;
      game_windows[i].buffer_size = 2;
      game_windows[i].text_buffer = n_malloc(2);
      game_windows[i].color_buffer = n_malloc(2);
      game_windows[i].dirty = TRUE;
      return &game_windows[i];
    }
  }
  return NULL;
}


void z_kill_window(zwinid win)
{
  if(!win)
    return;
  n_free(win->text_buffer);
  win->text_buffer = NULL;
  n_free(win->color_buffer);
  win->color_buffer = NULL;
  win->transcript = NULL;
  glk_window_close(win->win, NULL);
  win->win = NULL;
  win->str = NULL;
}


/* close any open windows */
void kill_windows(void)
{
  int i;

  for(i = 0; i < num_z_windows; i++)
    z_clear_window(&game_windows[i]);

  free_windows();
  for(i = 0; i < num_z_windows; i++) {
    if(game_windows[i].win) {
      game_windows[i].transcript = NULL;

      glk_window_close(game_windows[i].win, NULL);
      game_windows[i].win = NULL;
      game_windows[i].str = NULL;
    }
  }
  showstuffcount = 0;
}


/* free memory space used by windows, but don't close them */
void free_windows(void)
{
  int i;

  z_flush_all_windows();

  for(i = 0; i < num_z_windows; i++) {
    if(game_windows[i].win) {
      n_free(game_windows[i].text_buffer);
      game_windows[i].text_buffer = NULL;

      n_free(game_windows[i].color_buffer);
      game_windows[i].color_buffer = NULL;
    }
  }
}

zwinid z_find_win(winid_t win)
{
  int i;
  for(i = 0; i < num_z_windows; i++) {
    if(game_windows[i].win == win)
      return &game_windows[i];
  }
  return NULL;
}


static BOOL coloreq(colorstyle a, colorstyle b) /* return true if colors are equivalent */
{
  return (fgcolortable[(int) a.fore] == fgcolortable[(int) b.fore]) &&
         (bgcolortable[(int) a.back] == bgcolortable[(int) b.back]);
}


static void checkforblockquote(zwinid window, zwinid dest_win)
{
  if(window->biggest_height > window->last_height &&
     window->biggest_height > window->height) {
    /* find borders of the blockquote */
    unsigned leftx = window->width, rightx = 0;
    unsigned topy = window->biggest_height;
    unsigned bottomy = window->height;
    unsigned x, y, i;

    i = window->height * window->width;
    for(y = window->height; y < window->biggest_height; y++)
      for(x = 0; x < window->width; x++)
	if(window->text_buffer[i++] != ' ') {
	  if(x < leftx)
	    leftx = x;
	  if(x > rightx)
	    rightx = x;
	  if(y < topy)
	    topy = y;
	  if(y > bottomy)
	    bottomy = y;
	}
    
    z_pause_timed_input(dest_win);
    glk_stream_set_current(game_windows[1].str);
    glk_put_char(10);
    glk_set_style(style_BlockQuote);
    
    /* draw the blockquote */
    for(y = topy; y <= bottomy; y++) {
      i = y * window->width + leftx;
      for(x = leftx; x <= rightx; x++)
	glk_put_char(window->text_buffer[i++]);
      glk_put_char(10);
    }
  }
}


void z_pause_timed_input(zwinid window)
{
  event_t eep;
  if(window->glk_input_pending) {
    window->glk_input_pending = FALSE;

    switch(window->pending_input_type) {
    case evtype_CharInput:
      glk_cancel_char_event(window->win);
      break;
    case evtype_LineInput:
      glk_cancel_line_event(window->win, &eep);
      window->pending_input_length = eep.val1;
    }
  }
}


void z_flush_all_windows(void)
{
  int window;
  for(window = 0; window < num_z_windows; window++) {
    if(game_windows[window].dirty) {
      z_pause_timed_input(&game_windows[window]);
      
      switch(game_windows[window].wintype) {
      case wintype_TextBuffer:
	z_flush_text(&game_windows[window]);
	break;
      case wintype_TextGrid:
	z_flush_fixed(&game_windows[window]);
	break;
      case wintype_Graphics:
	z_flush_graphics(&game_windows[window]);
	break;
      }
    }
  }
}

void z_draw_all_windows(void)
{
  int window;
  for(window = 0; window < num_z_windows; window++) {
    if(game_windows[window].wintype == wintype_TextGrid) {
      game_windows[window].dirty = TRUE;
      z_flush_fixed(&game_windows[window]);
    }
  }
}


static void z_put_styled_string(zwinid window, unsigned char *text,
				colorstyle *color, glui32 length)
{
  glui32 n;
  colorstyle laststyle = color[0];

  if(!length)
    return;

  glk_set_style_stream(window->str, bitmap_to_style[laststyle.style]);

  for(n = 0; n < length; n++) {
    if(color[n].style != laststyle.style)
      glk_set_style_stream(window->str, bitmap_to_style[color[n].style]);
    glk_put_char_stream(window->str, text[n]);
    laststyle = color[n];
  }
}

void z_flush_fixed(zwinid window)
{
  glui32 winx, winy;
  winid_t o;
  glui32 start_line, end_line;

  /* If there's no such window, give up */
  if(!window->win || !window->str ||
     !window->text_buffer || !window->color_buffer)
    return;

  /* glk doesn't allow writing to a window while input is pending */
  z_pause_timed_input(window);

  end_line = window->height;

  /* Has the window grown and shrunk?  If so, probably because someone wants
     to draw a box quote - don't let them shrink the window quite so fast */
  if(window->biggest_height > window->last_height &&
     window->biggest_height > window->height)
    end_line = window->biggest_height;

  /* For v3 games, there's a callback function to draw the room name and
     score; if this is present, we start drawing at a lower position */
  start_line = 0;
  if(window->draw_callback)
    start_line = window->draw_callback(NULL, 0, 0);
  end_line += start_line;

  o = glk_window_get_parent(window->win);
#if 0
  glk_window_get_size(window->win, &winx, &winy);
  if (!(window->method & winmethod_Above || window->method & winmethod_Below)
      || winy != end_line)
    glk_window_set_arrangement(o, window->method,
			       end_line, window->win);
#else
  glk_window_set_arrangement(o, window->method, end_line, window->win);
#endif
  glk_window_get_size(window->win, &winx, &winy);

  if(window->draw_callback) {
    glk_stream_set_current(window->str);
    glk_window_clear(window->win);
    glk_window_move_cursor(window->win, 0, 0);
    window->draw_callback(window->win, winx, winy);
  }

  if(end_line > start_line && window->dirty) {

    unsigned padleft = 0, padmiddle = 0, padright = 0;
    unsigned skipleft = 0, skipmiddle = 0, skipright = 0;
    unsigned width;
    unsigned firstwidth, lastwidth;

    unsigned x, y, i;

    /* Calculate how much space is used for margins */

    unsigned left_margin = window->width, right_margin = window->width;

    i = 0;
    for(y = start_line; y < end_line; y++) {
      
      for(x = 0; x < window->width; x++)
	if(window->text_buffer[i + x] != ' ') {
	  if(x < left_margin)
	    left_margin = x;
	  break;
	}
      
      for(x = 0; x < window->width; x++)
	if(window->text_buffer[i + window->width - x - 1] != ' ') {
	  if(x < right_margin)
	    right_margin = x;
	  break;
	}
      
      i += window->width;
    }
    
    firstwidth = window->width; lastwidth = 0;
    
    if(start_line + 1 == end_line) {
      unsigned longestx = 0;
      unsigned longestlen = 0;
      unsigned thisx = 0;
      unsigned thislen = 0;
      colorstyle lastcolor;
      width = window->width;
      
      for(x = skipleft; x < width + skipleft; x++) {
	if(window->text_buffer[x] == ' '
	   && (!thislen || coloreq(window->color_buffer[x], lastcolor))) {
	  if(!thislen)
	    thisx = x;
	  thislen++;
	  lastcolor = window->color_buffer[x];
	} else {
	  if(thislen > longestlen) {
	    longestx = thisx;
	    longestlen = thislen;
	  }
	  thislen = 0;
	}
      }
      if(longestlen > 4) {
	firstwidth = longestx - skipleft;
	skipmiddle = longestlen - 1;
	lastwidth = width - firstwidth - skipmiddle;
      }
    }
    
    if(skipmiddle && winx < firstwidth + 2 + lastwidth)
      padmiddle = 2;
    
    if(lastwidth && winx >= firstwidth + padmiddle + lastwidth) {
      padmiddle = winx - firstwidth - lastwidth;
    } else {
      if(winx >= window->width)
	width = window->width;
      else
	width = winx;
      
      if(right_margin + left_margin) {
	if(winx > window->width)
	  padleft = (unsigned) ((winx - window->width) * (((float) left_margin) / (right_margin + left_margin)));
	else
	  skipleft = (unsigned) ((window->width - winx) * (((float) left_margin) / (right_margin + left_margin)));
      }
      else {
	padleft = winx - window->width;
      }
      
      if(skipleft > left_margin)
	skipleft = left_margin;
      
      if(winx > window->width)
	padright = winx - window->width - padleft;
      else
	skipright = window->width - winx - skipleft;
      
      if(width < firstwidth + padmiddle) {
	firstwidth = width;
	padmiddle = 0;
	lastwidth = 0;
      } else if(width < firstwidth + padmiddle + lastwidth) {
	lastwidth = width - firstwidth - padmiddle;
      }
    }

    
    glk_stream_set_current(window->str);
    glk_window_move_cursor(window->win, 0, start_line);

    /* draw to the upper window */
    i = 0;
    for(y = start_line; y < end_line; y++) {

      for(x = 0; x < padleft; x++)
	glk_put_char(' ');

      i += skipleft;

      z_put_styled_string(window, window->text_buffer + i,
			  window->color_buffer + i, firstwidth);
      i += firstwidth;

      for(x = 0; x < padmiddle; x++)
	glk_put_char(' ');
      i += skipmiddle;
      
      z_put_styled_string(window, window->text_buffer + i,
			  window->color_buffer + i, lastwidth);
      i += lastwidth;

      for(x = 0; x < padright; x++)
	glk_put_char(' ');

      i += skipright;
    }

    /* Bureaucracy needs the cursor positioned and visible in upper window. */
    glk_window_move_cursor(window->win,
			   window->curr_offset % window->width,
			   window->curr_offset / window->width);
    window->dirty = FALSE;
  }
}


void z_flush_text(zwinid window)
{
  z_pause_timed_input(window);

  if(!window->win || !window->str
     || !window->text_buffer || !window->color_buffer
     || window->curr_offset == 0) {
    window->curr_offset = 0;
    return;
  }
  
  z_put_styled_string(window, window->text_buffer, window->color_buffer,
		      window->curr_offset);

  window->curr_offset = 0;
  window->dirty = FALSE;
}


void z_flush_graphics(zwinid window)
{
  int i;
  glui32 winx, winy;
  float xratio, yratio;
  winid_t parent;

  if(!window->win)
    return;

  glk_window_get_size(window->win, &winx, &winy);
  xratio = ((float) winx) / window->width;
  yratio = ((float) winy) / window->height;

  parent = glk_window_get_parent(window->win);

  /* We want the window to maintain its original height/width ratio */
  switch(window->method & winmethod_DirMask) {
  case winmethod_Left:   /* Left and right splits mean height is fixed - */
  case winmethod_Right:  /* adjust width to the yratio */
    glk_window_set_arrangement(parent, window->method,
			       (glui32) (window->width * yratio), 0);
    break;
  case winmethod_Above:  /* Above and below splits mean width is fixed - */
  case winmethod_Below:  /* adjust height to the xratio */
    glk_window_set_arrangement(parent, window->method,
			       (glui32) (window->height * xratio), 0);
    break;
  }

  /* Check to see what it became, and if it's still off, don't worry */
  glk_window_get_size(window->win, &winx, &winy);
  xratio = ((float) winx) / window->width;
  yratio = ((float) winy) / window->height;

  for(i = 0; i < 12; i++) {
    if(window->images[i].image_num) {
      wrap_glk_image_draw_scaled(window->win, window->images[i].image_num,
				 (glui32) (window->images[i].x * xratio),
				 (glui32) (window->images[i].y * yratio),
				 (glui32) (window->images[i].width * xratio),
				 (glui32) (window->images[i].height * yratio));
    }
  }
}

void z_print_number(zwinid window, int number)
{
  int i;
  char buffer[12];
  int length = n_to_decimal(buffer, number);

  for(i = length - 1; i >= 0; i--)
    z_put_char(window, buffer[i]);
}

void z_put_char(zwinid window, unsigned c)
{
  colorstyle color = window->current;
  if(is_fixed)
    color.style |= sFIXE;

  if(c == 0)     /* Section 3.8.2.1 */
    return;

  window->dirty = TRUE;

  if((c < 32 && c != 13) || (c >= 127 && c <= 159)) {  /*Undefined in latin-1*/
    switch(window->wintype) {
    case wintype_TextBuffer:
      z_put_char(window, '[');
      z_print_number(window, c);
      z_put_char(window, ']');
      return;
    case wintype_TextGrid:
      c = '?';
    }
  }

  switch(c) {
  case 0x152:
    z_put_char(window, 'O');
    c = 'E';
    break;
  case 0x153:
    z_put_char(window, 'o');
    c = 'e';
    break;
  }


  if(c > 255)    /* Section 3.8.5.4.3 */
    c = '?';

  if(c == 13) {  /* Section 7.1.2.2.1 */
    switch(window->wintype) {
    case wintype_TextBuffer:
      window->text_buffer[window->curr_offset] = 10;
      window->curr_offset++;
      z_flush_text(window);
      break;
    case wintype_TextGrid:
      window->curr_offset += window->width;
      window->curr_offset -= window->curr_offset % window->width;
    }
  } else {
    window->text_buffer[window->curr_offset] = c;
    window->color_buffer[window->curr_offset] = color;
    window->curr_offset++;
  }

  if(window->curr_offset >= window->max_offset) {
    switch(window->wintype) {
    case wintype_TextBuffer:
      z_flush_text(window);
      break;
    case wintype_TextGrid:
      if(!window->defined)                  /* Section 8.6.2 */
	n_show_port(E_OUTPUT, "writing past end of window", c);
      
      if(window->max_offset)
	window->curr_offset = window->max_offset - 1;
      else
	window->curr_offset = 0;

      window->defined = FALSE;
    }
  }
}

void z_setxy(zwinid window, zword x, zword y)
{
  window->curr_offset = (y - 1) * window->width + (x - 1);
  window->defined = TRUE;
}

void z_getxy(zwinid window, zword *x, zword *y)
{
  if(window->width) {
    *x = (window->curr_offset % window->width) + 1;
    *y = (window->curr_offset / window->width) + 1;
  } else {
    *x = window->curr_offset + 1;
    *y = 1;
  }
}

void z_getsize(zwinid window, unsigned *width, unsigned *height)
{
  *width = window->width;
  *height = window->height;
}

void z_find_size(glui32 *wid, glui32 *hei)
{
  glui32 oldwid, oldhei;
  zwinid upper = &game_windows[1];
  winid_t o = glk_window_get_parent(upper->win);
  glk_window_get_size(upper->win, &oldwid, &oldhei);
  glk_window_set_arrangement(o, (upper->method & ~winmethod_Fixed) |
			     winmethod_Proportional, 100, upper->win);
  glk_window_get_size(upper->win, wid, hei);
  glk_window_set_arrangement(o, upper->method, oldhei, upper->win);

  upper_width = *wid; upper_height = *hei;
  init_upper(&upper);
}

void z_set_height(zwinid window, unsigned height)
{
  unsigned x;
  if(height * window->width > window->buffer_size) {
    n_show_error(E_OUTPUT, "height too large", height);
    return;
  }

  window->height = height;
  if(height > window->biggest_height)
    window->biggest_height = height;

  x = window->max_offset;
  window->max_offset = height * window->width;

  for(; x < window->max_offset; x++) {
    window->text_buffer[x] = ' ';
    window->color_buffer[x] = window->current;
  }

  window->dirty = TRUE;
}

void z_set_color(zwinid window, unsigned fore, unsigned back)
{
  if(fore >= sizeof(fgcolortable) / sizeof(*fgcolortable)) {
    n_show_error(E_OUTPUT, "illegal foreground color", fore);
    return;
  }
  if(back >= sizeof(bgcolortable) / sizeof(*bgcolortable)) {
    n_show_error(E_OUTPUT, "illegal background color", back);
    return;
  }

  fgcolortable[0] = fgcolortable[fore];
  bgcolortable[0] = bgcolortable[back];
  
  window->current.fore = fore;
  window->current.back = back;
}

void z_set_style(zwinid window, int style)
{
  switch(style) {
  case 0: window->current.style = 0; break;
  case 1: window->current.style |= sREVE; break;
  case 2: window->current.style |= sBOLD; break;
  case 4: window->current.style |= sITAL; break;
  case 8: window->current.style |= sFIXE; break;
  default: n_show_error(E_OUTPUT, "undefined style", style);
  }
}

void set_fixed(BOOL p)
{
  if(!p) {
    is_fixed = FALSE;
  } else {
    is_fixed = TRUE;
  }
}

void z_set_transcript(zwinid window, strid_t stream)
{
  window->transcript = stream;
  glk_window_set_echo_stream(window->win, stream);
}

void z_clear_window(zwinid window)
{
  glui32 i;

  if(window == &game_windows[0] && showstuffcount) {
    z_pause_timed_input(&game_windows[0]);
    z_flush_text(&game_windows[0]);
    glk_stream_set_current(game_windows[0].str);
    w_glk_put_string("[pausing to show unread error message]\n");
    z_wait_for_key(&game_windows[0]);
  }

  window->dirty = TRUE;
  window->curr_offset = 0;

  if(window->win && window->text_buffer && window->color_buffer) {
    switch(window->wintype) {
    case wintype_TextGrid:
      for(i = 0; i < window->max_offset; i++) {
	window->text_buffer[i] = ' ';
	window->color_buffer[i] = window->current;
      }
      window->curr_offset = 0;
      window->dirty = TRUE;
      break;
    case wintype_TextBuffer:
      z_pause_timed_input(window);
      z_flush_text(window);
      if(coloreq(window->actual, window->current)) {
	glk_window_clear(window->win);
      } else {
	init_lower(NULL); /* **FIXME** This is wrong, but deal with it later */
      }
    }
  }
}

void z_erase_line(zwinid window)
{
  if(window->wintype == wintype_TextGrid) {
    int i;
    int x = window->curr_offset % window->width;
    int endoffset = window->curr_offset + (window->width - x);
    
    window->dirty = TRUE;
    for(i = window->curr_offset; i < endoffset; i++) {
      window->text_buffer[i] = ' ';
      window->color_buffer[i] = window->current;
    }
  }
}


/* Waits for input or timeout
 * Returns:
 *   0 - output during wait; may need to redraw or somesuch
 *  -1 - callback routine said to stop
 *  10 - read input
 * 254 - mouse input
 * char and line events will be canceled by the time it exits
 */
static int waitforinput(zwinid window, glui32 *val,
			BOOL (*timer_callback)(zword), zword timer_arg)
{
  int i;
  event_t moo;
  zwinid t;
  
  showstuffcount = 0;

  for(i = 0; i < num_z_windows; i++)
    if(game_windows[i].mouse_callback && game_windows[i].win)
      glk_request_mouse_event(game_windows[i].win);
  
  window->glk_input_pending = TRUE;

  while(window->glk_input_pending) {
    glk_select(&moo);

    check_sound(moo);

    switch(moo.type) {
    case evtype_Timer:
      if(timer_callback && timer_callback(timer_arg)) {
	if(window->pending_input_type == evtype_CharInput) {
	  glk_cancel_char_event(window->win);
	  *val = 0;
	} else {
	  glk_cancel_line_event(window->win, &moo);
	  *val = moo.val1;
	}
	window->glk_input_pending = FALSE;
	return -1;
      }
      break;

    case evtype_CharInput:
      *val = moo.val1;
      window->glk_input_pending = FALSE;
      return 10;

    case evtype_LineInput:
      *val = moo.val1;
      window->glk_input_pending = FALSE;
      return 10;

    case evtype_MouseInput:
      t = z_find_win(moo.win);
      if(t && t->mouse_callback &&
	 t->mouse_callback(window->pending_input_type == evtype_CharInput,
			   moo.win, moo.val1, moo.val2)) {
	if(window->pending_input_type == evtype_CharInput) {
	  glk_cancel_char_event(window->win);
	  *val = 254;
	} else {
	  glk_cancel_line_event(window->win, &moo);
	  *val = moo.val1;
	}
	window->glk_input_pending = FALSE;
	return 254;
      }
      glk_request_mouse_event(moo.win);
      break;
    
    case evtype_Arrange:
      z_draw_all_windows();
    }

    z_flush_all_windows();
  }

  if(window->pending_input_type == evtype_LineInput)
    *val = window->pending_input_length;
  else
    *val = 0;

  return 0;
}


void z_wait_for_key(zwinid window)
{
  glui32 ch;
  do {
    z_draw_all_windows();
    glk_request_char_event(window->win);
    window->pending_input_type = evtype_CharInput;
  } while(waitforinput(window, &ch, NULL, 0) == 0);
  window->pending_input_type = 0;
}


zwinid check_valid_for_input(zwinid window)
{  
  glui32 y, i;
  if(!window->win) {
    zwinid newwin = NULL;
    for(i = 0; i < num_z_windows; i++) {
      if(game_windows[i].win) {
	newwin = &game_windows[i];
	break;
      }
    }
    if(!newwin)
      return NULL;

    if(window->wintype == wintype_TextGrid) {
      i = 0;
      for(y = 0; y < window->height; y++) {
	z_put_char(newwin, 13);
	z_put_styled_string(newwin, window->text_buffer + i,
			    window->color_buffer + i, window->width);
	i += window->width;
      }
      z_put_char(newwin, 13);
    }

    window = newwin;
  }
  return window;
}


/* returns number of characters read */
int z_read(zwinid window, char *dest, unsigned maxlen, unsigned initlen,
	   zword timer, BOOL (*timer_callback)(zword), zword timer_arg,
	   unsigned char *terminator)
{
  /* FIXME: support terminating characters when (if) glk gets support for
     them */
  unsigned i;
  unsigned ux, uy;
  glui32 length;
  BOOL done;

  if(automap_unexplore()) {
    read_abort = TRUE;
    return 0;
  }
  
  read_abort = FALSE;

  if(initlen > maxlen) {
    n_show_error(E_OUTPUT, "initlen > maxlen", initlen);
    return 0;
  }

  if(window == 0)
    window = &game_windows[0];
  
  if(window->pending_input_type != 0) {
    n_show_error(E_OUTPUT, "nested input attempted", 0);
    return 0;
  }

#ifdef DEBUGGING

  if(do_automap) {
    const char *dir = automap_explore();
    if(dir) {
      length = n_strlen(dir);
      if(length > maxlen)
	length = maxlen;
      n_strncpy(dest, dir, length);
      return length;
    }
  }
#endif

  glk_request_timer_events(timer * 100);  /* if time is zero, does nothing */
    
  if(initlen != 0 && window->wintype == wintype_TextBuffer) {
    BOOL good = FALSE;
    if(initlen <= window->curr_offset) {
      good = TRUE;
      for(i = 0; i < initlen; i++)  /* check the end of the linebuffer */
	if(window->text_buffer[window->curr_offset - initlen + i] != dest[i]) {
	  good = FALSE;
	  break;
	}
    }
    if(!good) {
      /* bleah */
      /* argh */
      /* oof */
    } else {
      window->curr_offset -= initlen; /* Remove initial text from linebuffer */
    }
  }
  
  if(window->wintype == wintype_TextGrid) {
    ux = window->curr_offset % window->width;
    uy = window->curr_offset / window->width;
  }

  z_flush_all_windows();
  window = check_valid_for_input(window);

  done = FALSE;
  length = initlen;
  while(!done) {
    int t;

    if(window->wintype == wintype_TextGrid)
      glk_window_move_cursor(window->win, ux, uy);

    if(input_stream1) {
      glui32 len = maxlen;
      *terminator = transcript_getline(dest, &len);
      length = len;
    }
    if(input_stream1) { /* If didn't EOF, input_stream1 will be non-zero */
      glk_stream_set_current(window->str);
      set_glk_stream_current();
      glk_set_style(style_Input);
      glk_put_buffer(dest, length);
      glk_put_char(10);
      done = TRUE;
    } else {
      glk_request_line_event(window->win, dest, maxlen, length);
      window->pending_input_type = evtype_LineInput;
    
      t = waitforinput(window, &length, timer_callback, timer_arg);
      if(t != 0) {
	if(t == -1)
	  *terminator = 0;
	else
	  *terminator = t;
	done = TRUE;
      }
    }

    if(done)
      stream4line(dest, length, *terminator);

#ifdef DEBUGGING
    if(done && length >= 2 && dest[0] == '/') {
      if(dest[1] == '/') {  /* "//" means no command, but start with "/" */
	for(i = 1; i < length; i++)
	  dest[i-1] = dest[i];
	length--;
      } else {
	done = FALSE;
	dest[length] = 0;
	
	process_debug_command(dest+1);

	if(read_abort)
	  done = TRUE;

	length = 0;
      }
    }
#endif
  }
  glk_request_timer_events(0);  /* stop timer */

  window->pending_input_type = 0;

  for(i = 0; i < num_z_windows; i++)
    game_windows[i].biggest_height = game_windows[i].last_height = game_windows[i].height;

  return length;
}

zword z_read_char(zwinid window,
		  zword timer, BOOL (*timer_callback)(zword), zword timer_arg)
{
  unsigned i;
  glui32 ch;
  zword validch = 0;

  if(automap_unexplore()) {
    read_abort = TRUE;
    return 0;
  }

  if(input_stream1) {
    unsigned num;
    validch = transcript_getchar(&num);
    if(!validch)
      validch = num;
  }
  if(input_stream1) {
    return validch;
  }
  
  read_abort = FALSE;

  glk_request_timer_events(timer * 100);

  z_flush_all_windows();
  window = check_valid_for_input(window);

  do {
    do {
      z_draw_all_windows();
      glk_request_char_event(window->win);
      window->pending_input_type = evtype_CharInput;
    } while(waitforinput(window, &ch, timer_callback, timer_arg) == 0);
    
    if(' ' <= ch && ch <= '~')
      validch = ch;
    
    switch(ch) {
    case 8:
    case keycode_Delete: validch = 8; break;
    case 9:
    case keycode_Tab:    validch = 9; break;
    case 13:
    case keycode_Return: validch = 13; break;
/*  case 21:
      if(restoreundo()) {
	read_abort = TRUE;
	return 0;
      }
      break; */
    case 27:
    case keycode_Escape: validch = 27; break;
    case 16:
    case keycode_Up:     validch = 129; break;
    case 14:
    case keycode_Down:   validch = 130; break;
    case 2:
    case keycode_Left:   validch = 131; break;
    case 6:
    case keycode_Right:  validch = 132; break;
    case keycode_Func1:  validch = 133; break;
    case keycode_Func2:  validch = 134; break;
    case keycode_Func3:  validch = 135; break;
    case keycode_Func4:  validch = 136; break;
    case keycode_Func5:  validch = 137; break;
    case keycode_Func6:  validch = 138; break;
    case keycode_Func7:  validch = 139; break;
    case keycode_Func8:  validch = 140; break;
    case keycode_Func9:  validch = 141; break;
    case keycode_Func10: validch = 142; break;
    case keycode_Func11: validch = 143; break;
    case keycode_Func12: validch = 144; break;
    }
  } while(!(validch || ch == 0));

  glk_request_timer_events(0);     /* stop timer */

  window->pending_input_type = 0;

  for(i = 0; i < num_z_windows; i++)
    game_windows[i].biggest_height = game_windows[i].last_height = game_windows[i].height;

  return validch;
}


/*
void zwin_init(int number, glui32 wintype,
	       glui32 x_coord, glui32 y_coord, glui32 x_size, glui32 y_size)
{
  zwinid self = game_windows + number;

  if(x_coord == self->x1) {
    if(y_coord == self->y1) {
      

  if(game_windows[number].win) {
    z_pause_timed_input(game_windows[number].win);
    glk_window_close(game_windows[number].win, NULL);
  }
  set_style_hints();
  game_windows[number].win = glk_window_open(
}
*/
