/******************************************************************************
 *                                                                            *
 * Copyright (C) 2006-2009 by Tor Andersson.                                  *
 * Copyright (C) 2010-2011 by Ben Cressey.                                    *
 *                                                                            *
 * This file is part of Gargoyle.                                             *
 *                                                                            *
 * Gargoyle 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.                                        *
 *                                                                            *
 * Gargoyle 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 Gargoyle; if not, write to the Free Software                    *
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA *
 *                                                                            *
 *****************************************************************************/

/* osglk.c -- glk os adapter */

#include <assert.h>

#include "os.h"
#include "glk.h"

/* for version strings */
#include "trd.h"
#include "vmvsn.h"

static void redraw_windows(void);
static void os_status_redraw(void);
extern void os_banners_redraw(void);

static char lbuf[256], rbuf[256];
static int curwin = 0;
static int curattr = 0;

winid_t mainwin;
winid_t statuswin;

glui32 mainfg;
glui32 mainbg;

glui32 statusfg;
glui32 statusbg;


/* ------------------------------------------------------------------------ */
/*
 *   Get system information.  'code' is a SYSINFO_xxx code, which
 *   specifies what type of information to get.  The 'param' argument's
 *   meaning depends on which code is selected.  'result' is a pointer to
 *   an integer that is to be filled in with the result value.  If the
 *   code is not known, this function should return FALSE.  If the code is
 *   known, the function should fill in *result and return TRUE.
 */
int os_get_sysinfo(int code, void *param, long *result)
{
    switch (code)
    {
        case SYSINFO_TEXT_HILITE:
            *result = 1;
            return TRUE;
        case SYSINFO_BANNERS:
            *result = 1;
            return TRUE;
        case SYSINFO_TEXT_COLORS:
            *result = SYSINFO_TXC_RGB;
            return TRUE;

#ifdef USE_HTML
        case SYSINFO_INTERP_CLASS:
            *result = SYSINFO_ICLASS_HTML;
            return TRUE;
        case SYSINFO_HTML:
            *result = 1;
            return TRUE;
#else
        case SYSINFO_INTERP_CLASS:
            *result = SYSINFO_ICLASS_TEXTGUI;
            return TRUE;
        case SYSINFO_HTML:
            *result = 0;
            return TRUE;
#endif

        case SYSINFO_JPEG:
        case SYSINFO_PNG:
        case SYSINFO_WAV:
        case SYSINFO_MIDI:
        case SYSINFO_WAV_MIDI_OVL:
        case SYSINFO_WAV_OVL:
        case SYSINFO_PREF_IMAGES:
        case SYSINFO_PREF_SOUNDS:
        case SYSINFO_PREF_MUSIC:
        case SYSINFO_PREF_LINKS:
        case SYSINFO_MPEG:
        case SYSINFO_MPEG1:
        case SYSINFO_MPEG2:
        case SYSINFO_MPEG3:
        case SYSINFO_LINKS_HTTP:
        case SYSINFO_LINKS_FTP:
        case SYSINFO_LINKS_NEWS:
        case SYSINFO_LINKS_MAILTO:
        case SYSINFO_LINKS_TELNET:
        case SYSINFO_PNG_TRANS:
        case SYSINFO_PNG_ALPHA:
        case SYSINFO_OGG:
            *result = 0;
            return TRUE;

        default:
            return FALSE;
    }
}


/* ------------------------------------------------------------------------ */

/* 
 *   Initialize.  This should be called during program startup to
 *   initialize the OS layer and check OS-specific command-line arguments.
 *   
 *   If 'prompt' and 'buf' are non-null, and there are no arguments on the
 *   given command line, the OS code can use the prompt to ask the user to
 *   supply a filename, then store the filename in 'buf' and set up
 *   argc/argv to give a one-argument command string.  (This mechanism for
 *   prompting for a filename is obsolescent, and is retained for
 *   compatibility with a small number of existing implementations only;
 *   new implementations should ignore this mechanism and leave the
 *   argc/argv values unchanged.)  
 */
int os_init(int *argc, char *argv[], const char *prompt,
            char *buf, int bufsiz)
{
    mainwin = glk_window_open(0, 0, 0, wintype_TextBuffer, 0);

    if (!mainwin)
    {
        fprintf(stderr, "fatal: could not open window!\n");
        glk_exit();
    }

    /* get default colors for main window */
    if (!glk_style_measure(mainwin, style_Normal, stylehint_TextColor, &mainfg))
        mainfg = 0;

    if (!glk_style_measure(mainwin, style_Normal, stylehint_BackColor, &mainbg))
        mainbg = 0;

    /* get default colors for status window */
    statuswin = glk_window_open(mainwin,
            winmethod_Above | winmethod_Fixed, 1,
            wintype_TextGrid, 0);

    if (!glk_style_measure(statuswin, style_Normal, stylehint_TextColor, &statusfg))
        statusfg = 0;

    if (!glk_style_measure(statuswin, style_Normal, stylehint_BackColor, &statusbg))
        statusbg = 0;

    /* close statuswin; reopened on request */
    glk_window_close(statuswin, 0);

    statuswin = NULL;

    glk_set_window(mainwin);

    strcpy(rbuf, "");

    return 0;
}

/*
 *   Uninitialize.  This is called prior to progam termination to reverse
 *   the effect of any changes made in os_init().  For example, if
 *   os_init() put the terminal in raw mode, this should restore the
 *   previous terminal mode.  This routine should not terminate the
 *   program (so don't call exit() here) - the caller might have more
 *   processing to perform after this routine returns.
 */
void os_uninit(void)
{
}

void os_term(int status)
{
    glk_exit();
}

/* ------------------------------------------------------------------------ */
/*
 *   Display routines.
 *   
 *   Our display model is a simple stdio-style character stream.
 *   
 *   In addition, we provide an optional "status line," which is a
 *   non-scrolling area where a line of text can be displayed.  If the status
 *   line is supported, text should only be displayed in this area when
 *   os_status() is used to enter status-line mode (mode 1); while in status
 *   line mode, text is written to the status line area, otherwise (mode 0)
 *   it's written to the normal main text area.  The status line is normally
 *   shown in a different color to set it off from the rest of the text.
 *   
 *   The OS layer can provide its own formatting (word wrapping in
 *   particular) if it wants, in which case it should also provide pagination
 *   using os_more_prompt().  
 */

/*
 *   Print a string on the console.  These routines come in two varieties:
 *   
 *   os_printz - write a NULL-TERMINATED string
 *.  os_print - write a COUNTED-LENGTH string, which may not end with a null
 *   
 *   These two routines are identical except that os_printz() takes a string
 *   which is terminated by a null byte, and os_print() instead takes an
 *   explicit length, and a string that may not end with a null byte.
 *   
 *   os_printz(str) may be implemented as simply os_print(str, strlen(str)).
 *   
 *   The string is written in one of three ways, depending on the status mode
 *   set by os_status():
 *   
 *   status mode == 0 -> write to main text window
 *.  status mode == 1 -> write to status line
 *.  anything else -> do not display the text at all
 *   
 *   Implementations are free to omit any status line support, in which case
 *   they should simply suppress all output when the status mode is anything
 *   other than zero.
 *   
 *   The following special characters must be recognized in the displayed
 *   text:
 *   
 *   '\n' - newline: end the current line and move the cursor to the start of
 *   the next line.  If the status line is supported, and the current status
 *   mode is 1 (i.e., displaying in the status line), then two special rules
 *   apply to newline handling: newlines preceding any other text should be
 *   ignored, and a newline following any other text should set the status
 *   mode to 2, so that all subsequent output is suppressed until the status
 *   mode is changed with an explicit call by the client program to
 *   os_status().
 *   
 *   '\r' - carriage return: end the current line and move the cursor back to
 *   the beginning of the current line.  Subsequent output is expected to
 *   overwrite the text previously on this same line.  The implementation
 *   may, if desired, IMMEDIATELY clear the previous text when the '\r' is
 *   written, rather than waiting for subsequent text to be displayed.
 *   
 *   All other characters may be assumed to be ordinary printing characters.
 *   The routine need not check for any other special characters.
 *   
 */

void os_printz(const char *str)
{
    os_print(str, strlen(str));
}

void os_print(const char *str, size_t len)
{
    if (curwin == 0 && str)
        os_put_buffer((unsigned char *)str, len);

    if (curwin == 1)
    {
        const char *p;
        size_t      rem, max;

        /* The string requires some fiddling for the status window */
        for (p = str, rem = len ; rem != 0 && *p == '\n'; p++, --rem)
            ;
        if (rem != 0 && p[rem-1] == '\n')
            --rem;

        /* if that leaves anything, update the statusline */
        if (rem != 0)
        {
            max = sizeof(lbuf) - strlen(lbuf) - 1;
            strncat(lbuf, p, rem > max ? max : rem);
            os_status_redraw();
        }
    }
}


/* 
 *   Set the status line mode.  There are three possible settings:
 *   
 *   0 -> main text mode.  In this mode, all subsequent text written with
 *   os_print() and os_printz() is to be displayed to the main text area.
 *   This is the normal mode that should be in effect initially.  This mode
 *   stays in effect until an explicit call to os_status().
 *   
 *   1 -> statusline mode.  In this mode, text written with os_print() and
 *   os_printz() is written to the status line, which is usually rendered as
 *   a one-line area across the top of the terminal screen or application
 *   window.  In statusline mode, leading newlines ('\n' characters) are to
 *   be ignored, and any newline following any other character must change
 *   the mode to 2, as though os_status(2) had been called.
 *   
 *   2 -> suppress mode.  In this mode, all text written with os_print() and
 *   os_printz() must simply be ignored, and not displayed at all.  This mode
 *   stays in effect until an explicit call to os_status().  
 */

void os_status(int stat)
{
    curwin = stat;

    if (stat == 1)
    {
        if (statuswin == NULL)
        {
            glk_stylehint_set(wintype_TextGrid, style_User1, stylehint_ReverseColor, 1);
            statuswin = glk_window_open(mainwin,
                                        winmethod_Above | winmethod_Fixed, 1,
                                        wintype_TextGrid, 0);
        }
        strcpy(lbuf, "");
    }
}

/* get the status line mode */
int os_get_status()
{
    return curwin;
}

/* 
 *   Set the score value.  This displays the given score and turn counts on
 *   the status line.  In most cases, these values are displayed at the right
 *   edge of the status line, in the format "score/turns", but the format is
 *   up to the implementation to determine.  In most cases, this can simply
 *   be implemented as follows:
 *   
 */
void os_score(int score, int turncount)
{
    char buf[40];
    sprintf(buf, "%d/%d", score, turncount);
    os_strsc(buf);
}

/* display a string in the score area in the status line */
void os_strsc(const char *p)
{
    snprintf(rbuf, sizeof rbuf, "%s", p);
    os_status_redraw();
}

static void os_status_redraw(void)
{
    char fmt[32];
    char buf[256];
    glui32 wid;
    glui32 div;
    int i;

    if (!statuswin)
        return;

    glk_window_get_size(statuswin, &wid, NULL);
    div = wid - strlen(rbuf) - 3;

    sprintf(fmt, " %%%ds %%s ", - (int)div);
    sprintf(buf, fmt, lbuf, rbuf);

    glk_window_clear(statuswin);
    glk_set_window(statuswin);
    glk_set_style(style_User1);
    os_put_buffer(buf, strlen(buf));
    glk_set_window(mainwin);
}

static void redraw_windows(void)
{
    os_status_redraw();
    os_banners_redraw();
}

/* clear the screen */
void oscls(void)
{
    glk_window_clear(mainwin);
}

/* ------------------------------------------------------------------------ */
/*
 *   Set text attributes.  Text subsequently displayed through os_print() and
 *   os_printz() are to be displayed with the given attributes.
 *   
 *   'attr' is a (bitwise-OR'd) combination of OS_ATTR_xxx values.  A value
 *   of zero indicates normal text, with no extra attributes.  
 */
void os_set_text_attr(int attr)
{
    curattr = attr;
    if (curattr & OS_ATTR_BOLD && curattr & OS_ATTR_ITALIC)
        glk_set_style(style_Alert);
    else if (curattr & OS_ATTR_BOLD)
        glk_set_style(style_Subheader);
    else if (curattr & OS_ATTR_ITALIC)
        glk_set_style(style_Emphasized);
    else
        glk_set_style(style_Normal);
}

/*
 *   Set the text foreground and background colors.  This sets the text
 *   color for subsequent os_printf() and os_vprintf() calls.
 *   
 *   The background color can be OS_COLOR_TRANSPARENT, in which case the
 *   background color is "inherited" from the current screen background.
 *   Note that if the platform is capable of keeping old text for
 *   "scrollback," then the transparency should be a permanent attribute of
 *   the character - in other words, it should not be mapped to the current
 *   screen color in the scrollback buffer, because doing so would keep the
 *   current screen color even if the screen color changes in the future. 
 *   
 *   Text color support is optional.  If the platform doesn't support text
 *   colors, this can simply do nothing.  If the platform supports text
 *   colors, but the requested color or attributes cannot be displayed, the
 *   implementation should use the best available approximation.  
 */
void os_set_text_color(os_color_t fg, os_color_t bg)
{
}

/*
 *   Set the screen background color.  This sets the text color for the
 *   background of the screen.  If possible, this should immediately redraw
 *   the main text area with this background color.  The color is given as an
 *   OS_COLOR_xxx value.
 *   
 *   If the platform is capable of redisplaying the existing text, then any
 *   existing text that was originally displayed with 'transparent'
 *   background color should be redisplayed with the new screen background
 *   color.  In other words, the 'transparent' background color of previously
 *   drawn text should be a permanent attribute of the character - the color
 *   should not be mapped on display to the then-current background color,
 *   because doing so would lose the transparency and thus retain the old
 *   screen color on a screen color change.  
 */
void os_set_screen_color(os_color_t color)
{
}

/*
 *   Set the game title.  The output layer calls this routine when a game
 *   sets its title (via an HTML <title> tag, for example).  If it's
 *   convenient to do so, the OS layer can use this string to set a window
 *   caption, or whatever else makes sense on each system.  Most
 *   character-mode implementations will provide an empty implementation,
 *   since there's not usually any standard way to show the current
 *   application title on a character-mode display.  
 */
void os_set_title(const char *title)
{
#ifdef GARGLK
    garglk_set_story_title(title);
#endif
}

/*
 *   Show the system-specific MORE prompt, and wait for the user to respond.
 *   Before returning, remove the MORE prompt from the screen.
 *   
 *   This routine is only used and only needs to be implemented when the OS
 *   layer takes responsibility for pagination; this will be the case on
 *   most systems that use proportionally-spaced (variable-pitch) fonts or
 *   variable-sized windows, since on such platforms the OS layer must do
 *   most of the formatting work, leaving the standard output layer unable
 *   to guess where pagination should occur.
 *   
 *   If the portable output formatter handles the MORE prompt, which is the
 *   usual case for character-mode or terminal-style implementations, this
 *   routine is not used and you don't need to provide an implementation.
 *   Note that HTML TADS provides an implementation of this routine, because
 *   the HTML renderer handles line breaking and thus must handle
 *   pagination.  
 */
void os_more_prompt()
{
    os_printz("\n[more]\n");
    os_waitc();
}

/* ------------------------------------------------------------------------ */
/*
 *   User Input Routines
 */

/*
 *   Ask the user for a filename, using a system-dependent dialog or other
 *   mechanism.  Returns one of the OS_AFE_xxx status codes (see below).
 *   
 *   prompt_type is the type of prompt to provide -- this is one of the
 *   OS_AFP_xxx codes (see below).  The OS implementation doesn't need to
 *   pay any attention to this parameter, but it can be used if desired to
 *   determine the type of dialog to present if the system provides
 *   different types of dialogs for different types of operations.
 *   
 *   file_type is one of the OSFTxxx codes for system file type.  The OS
 *   implementation is free to ignore this information, but can use it to
 *   filter the list of files displayed if desired; this can also be used
 *   to apply a default suffix on systems that use suffixes to indicate
 *   file type.  If OSFTUNK is specified, it means that no filtering
 *   should be performed, and no default suffix should be applied.  
 */
int os_askfile(const char *prompt, char *fname_buf, int fname_buf_len,
               int prompt_type, os_filetype_t file_type)
{
    frefid_t fileref;
    glui32 gprompt, gusage;

    if (prompt_type == OS_AFP_OPEN)
        gprompt = filemode_Read;
    else
        gprompt = filemode_ReadWrite;

    if (file_type == OSFTSAVE || file_type == OSFTT3SAV)
        gusage = fileusage_SavedGame;
    else if (file_type == OSFTLOG || file_type == OSFTTEXT)
        gusage = fileusage_Transcript;
    else
        gusage = fileusage_Data;

    fileref = glk_fileref_create_by_prompt(gusage, gprompt, 0);
    if (fileref == NULL)
        return OS_AFE_CANCEL;

    strcpy(fname_buf, garglk_fileref_get_name(fileref));

    glk_fileref_destroy(fileref);

    return OS_AFE_SUCCESS;
}

/* 
 *   Read a string of input.  Fills in the buffer with a null-terminated
 *   string containing a line of text read from the standard input.  The
 *   returned string should NOT contain a trailing newline sequence.  On
 *   success, returns 'buf'; on failure, including end of file, returns a
 *   null pointer.  
 */
unsigned char *os_gets(unsigned char *buf, size_t buflen)
{
    event_t event;

    os_get_buffer(buf, buflen, 0);

    do
    {
        glk_select(&event);
        if (event.type == evtype_Arrange)
            redraw_windows();
    }
    while (event.type != evtype_LineInput);

    return os_fill_buffer(buf, event.val1);
}

/*
 *   Read a string of input with an optional timeout.  This behaves like
 *   os_gets(), in that it allows the user to edit a line of text (ideally
 *   using the same editing keys that os_gets() does), showing the line of
 *   text under construction during editing.  This routine differs from
 *   os_gets() in that it returns if the given timeout interval expires
 *   before the user presses Return (or the local equivalent).
 *   
 *   If the user presses Return before the timeout expires, we store the
 *   command line in the given buffer, just as os_gets() would, and we
 *   return OS_EVT_LINE.  We also update the display in the same manner that
 *   os_gets() would, by moving the cursor to a new line and scrolling the
 *   displayed text as needed.
 *   
 *   If a timeout occurs before the user presses Return, we store the
 *   command line so far in the given buffer, statically store the cursor
 *   position, insert mode, buffer text, and anything else relevant to the
 *   editing state, and we return OS_EVT_TIMEOUT.
 *   
 *   If the implementation does not support the timeout operation, this
 *   routine should simply return OS_EVT_NOTIMEOUT immediately when called;
 *   the routine should not allow the user to perform any editing if the
 *   timeout is not supported.  Callers must use the ordinary os_gets()
 *   routine, which has no timeout capabilities, if the timeout is not
 *   supported.
 *   
 *   When we return OS_EVT_TIMEOUT, the caller is responsible for doing one
 *   of two things.
 *   
 *   The first possibility is that the caller performs some work that
 *   doesn't require any display operations (in other words, the caller
 *   doesn't invoke os_printf, os_getc, or anything else that would update
 *   the display), and then calls os_gets_timeout() again.  In this case, we
 *   will use the editing state that we statically stored before we returned
 *   OS_EVT_TIMEOUT to continue editing where we left off.  This allows the
 *   caller to perform some computation in the middle of user command
 *   editing without interrupting the user - the extra computation is
 *   transparent to the user, because we act as though we were still in the
 *   midst of the original editing.
 *   
 *   The second possibility is that the caller wants to update the display.
 *   In this case, the caller must call os_gets_cancel() BEFORE making any
 *   display changes.  Then, the caller must do any post-input work of its
 *   own, such as updating the display mode (for example, closing HTML font
 *   tags that were opened at the start of the input).  The caller is now
 *   free to do any display work it wants.
 *   
 *   If we have information stored from a previous call that was interrupted
 *   by a timeout, and os_gets_cancel(TRUE) was never called, we will resume
 *   editing where we left off when the cancelled call returned; this means
 *   that we'll restore the cursor position, insertion state, and anything
 *   else relevant.  Note that if os_gets_cancel(FALSE) was called, we must
 *   re-display the command line under construction, but if os_gets_cancel()
 *   was never called, we will not have to make any changes to the display
 *   at all.
 *   
 *   Note that when resuming an interrupted editing session (interrupted via
 *   os_gets_cancel()), the caller must re-display the prompt prior to
 *   invoking this routine.
 *   
 *   Note that we can return OS_EVT_EOF in addition to the other codes
 *   mentioned above.  OS_EVT_EOF indicates that an error occurred reading,
 *   which usually indicates that the application is being terminated or
 *   that some hardware error occurred reading the keyboard.  
 *   
 *   If 'use_timeout' is false, the timeout should be ignored.  Without a
 *   timeout, the function behaves the same as os_gets(), except that it
 *   will resume editing of a previously-interrupted command line if
 *   appropriate.  (This difference is why the timeout is optional: a caller
 *   might not need a timeout, but might still want to resume a previous
 *   input that did time out, in which case the caller would invoke this
 *   routine with use_timeout==FALSE.  The regular os_gets() would not
 *   satisfy this need, because it cannot resume an interrupted input.)  
 */
static char * timebuf = NULL;
static size_t timelen = 0;

int os_gets_timeout(unsigned char *buf, size_t bufl,
                    unsigned long timeout_in_milliseconds, int use_timeout)
{
#if defined GLK_TIMERS && defined GLK_MODULE_LINE_ECHO
    int timer = use_timeout ? timeout_in_milliseconds : 0;
    int timeout = 0;
    int initlen = 0;
    event_t event;

    /* restore saved buffer contents */
    if (timebuf)
    {
        assert(timelen && timelen <= bufl);
        memcpy(buf, timebuf, timelen);
        initlen = timelen - 1;
        buf[initlen] = 0;
        free(timebuf);
        timebuf = 0;
    }

    /* start timer and turn off line echo */
    if (timer)
    {
        glk_request_timer_events(timer);
        glk_set_echo_line_event(mainwin, 0);
    }

    os_get_buffer(buf, bufl, initlen);

    do
    {
        glk_select(&event);
        if (event.type == evtype_Arrange)
            redraw_windows();
        else if (event.type == evtype_Timer && (timeout = 1))
            glk_cancel_line_event(mainwin, &event);
    }
    while (event.type != evtype_LineInput);

    char *res = os_fill_buffer(buf, event.val1);

    /* stop timer and turn on line echo */
    if (timer)
    {
        glk_request_timer_events(0);
        glk_set_echo_line_event(mainwin, 1);
    }

    /* save or print buffer contents */
    if (res && timer)
    {
        if (timeout)
        {
            timelen = strlen(buf) + 1;
            timebuf = malloc(timelen);
            memcpy(timebuf, buf, timelen);
        }
        else
        {
            glk_set_style(style_Input);
            os_print(buf, strlen(buf));
            os_print("\n", 1);
            glk_set_style(style_Normal);
        }
    }

    return timeout ? OS_EVT_TIMEOUT : res ? OS_EVT_LINE : OS_EVT_EOF;
#else
    return OS_EVT_NOTIMEOUT;
#endif
}

/*
 *   Cancel an interrupted editing session.  This MUST be called if any
 *   output is to be displayed after a call to os_gets_timeout() returns
 *   OS_EVT_TIMEOUT.
 *   
 *   'reset' indicates whether or not we will forget the input state saved
 *   by os_gets_timeout() when it last returned.  If 'reset' is true, we'll
 *   clear the input state, so that the next call to os_gets_timeout() will
 *   start with an empty input buffer.  If 'reset' is false, we will retain
 *   the previous input state, if any; this means that the next call to
 *   os_gets_timeout() will re-display the same input buffer that was under
 *   construction when it last returned.
 *   
 *   This routine need not be called if os_gets_timeout() is to be called
 *   again with no other output operations between the previous
 *   os_gets_timeout() call and the next one.
 *   
 *   Note that this routine needs only a trivial implementation when
 *   os_gets_timeout() is not supported (i.e., the function always returns
 *   OS_EVT_NOTIMEOUT).  
 */
void os_gets_cancel(int reset)
{
#if defined GLK_TIMERS && defined GLK_MODULE_LINE_ECHO
    if (timebuf)
    {
        glk_set_style(style_Input);
        os_print(timebuf, strlen(timebuf));
        os_print("\n", 1);
        glk_set_style(style_Normal);

        if (reset)
        {
            free(timebuf);
            timebuf = 0;
        }
    }
#endif
}

/* 
 *   Read a character from the keyboard.  For extended keystrokes, this
 *   function returns zero, and then returns the CMD_xxx code for the
 *   extended keystroke on the next call.  For example, if the user
 *   presses the up-arrow key, the first call to os_getc() should return
 *   0, and the next call should return CMD_UP.  Refer to the CMD_xxx
 *   codes below.
 *   
 *   os_getc() should return a high-level, translated command code for
 *   command editing.  This means that, where a functional interpretation
 *   of a key and the raw key-cap interpretation both exist as CMD_xxx
 *   codes, the functional interpretation should be returned.  For
 *   example, on Unix, Ctrl-E is conventionally used in command editing to
 *   move to the end of the line, following Emacs key bindings.  Hence,
 *   os_getc() should return CMD_END for this keystroke, rather than
 *   (CMD_CTRL + 'E' - 'A'), because CMD_END is the high-level command
 *   code for the operation.
 *   
 *   The translation ability of this function allows for system-dependent
 *   key mappings to functional meanings.  
 */
static int glktotads(unsigned int key)
{
    if (key < 256)
        return key;
    switch (key)
    {
        case keycode_Up:
            return CMD_UP;
        case keycode_Down:
            return CMD_DOWN;
        case keycode_Left:
            return CMD_LEFT;
        case keycode_Right:
            return CMD_RIGHT;
        case keycode_PageUp:
            return CMD_PGUP;
        case keycode_PageDown:
            return CMD_PGDN;
        case keycode_Home:
            return CMD_HOME;
        case keycode_End:
            return CMD_END;
        case keycode_Func1:
            return CMD_F1;
        case keycode_Func2:
            return CMD_F2;
        case keycode_Func3:
            return CMD_F3;
        case keycode_Func4:
            return CMD_F4;
        case keycode_Func5:
            return CMD_F5;
        case keycode_Func6:
            return CMD_F6;
        case keycode_Func7:
            return CMD_F7;
        case keycode_Func8:
            return CMD_F8;
        case keycode_Func9:
            return CMD_F9;
        case keycode_Func10:
            return CMD_F10;
        default:
            return 0;
    }
}

static int bufchar = 0;
static int waitchar = 0;
static int timechar = 0;

static int getglkchar(void)
{
    event_t event;

    timechar = 0;

    glk_request_char_event(mainwin);

    do
    {
        glk_select(&event);
        if (event.type == evtype_Arrange)
            redraw_windows();
        else if (event.type == evtype_Timer)
            timechar = 1;
    }
    while (event.type != evtype_CharInput && event.type != evtype_Timer);

    glk_cancel_char_event(mainwin);

    return timechar ? 0 : event.val1;
}

int os_getc(void)
{
    unsigned int c;

    if (bufchar)
    {
        c = bufchar;
        bufchar = 0;
        return c;
    }

    c = waitchar ? waitchar : getglkchar();
    waitchar = 0;

    if (c == keycode_Return)
        c = '\n';
    else if (c == keycode_Tab)
        c = '\t';
    else if (c == keycode_Escape)
        c = '\e';

    if (c < 256)
        return c;

    bufchar = glktotads(c);

    return 0;
}

/*
 *   Read a character from the keyboard, following the same protocol as
 *   os_getc() for CMD_xxx codes (i.e., when an extended keystroke is
 *   encountered, os_getc_raw() returns zero, then returns the CMD_xxx code
 *   on the subsequent call).
 *   
 *   This function differs from os_getc() in that this function returns the
 *   low-level, untranslated key code whenever possible.  This means that,
 *   when a functional interpretation of a key and the raw key-cap
 *   interpretation both exist as CMD_xxx codes, this function returns the
 *   key-cap interpretation.  For the Unix Ctrl-E example in the comments
 *   describing os_getc() above, this function should return 5 (the ASCII
 *   code for Ctrl-E), because the CMD_CTRL interpretation is the low-level
 *   key code.
 *   
 *   This function should return all control keys using their ASCII control
 *   codes, whenever possible.  Similarly, this function should return ASCII
 *   27 for the Escape key, if possible.  
 *   
 *   For keys for which there is no portable ASCII representation, this
 *   should return the CMD_xxx sequence.  So, this function acts exactly the
 *   same as os_getc() for arrow keys, function keys, and other special keys
 *   that have no ASCII representation.  This function returns a
 *   non-translated version ONLY when an ASCII representation exists - in
 *   practice, this means that this function and os_getc() vary only for
 *   CTRL keys and Escape.
 */
int os_getc_raw(void)
{
    return os_getc();
}

/* wait for a character to become available from the keyboard */
void os_waitc(void)
{
    waitchar = getglkchar();
}

/*
 *   Get an input event.  The event types are shown above.  If use_timeout
 *   is false, this routine should simply wait until one of the events it
 *   recognizes occurs, then return the appropriate information on the
 *   event.  If use_timeout is true, this routine should return
 *   OS_EVT_TIMEOUT after the given number of milliseconds elapses if no
 *   event occurs first.
 *   
 *   This function is not obligated to obey the timeout.  If a timeout is
 *   specified and it is not possible to obey the timeout, the function
 *   should simply return OS_EVT_NOTIMEOUT.  The trivial implementation
 *   thus checks for a timeout, returns an error if specified, and
 *   otherwise simply waits for the user to press a key.  
 */
int os_get_event(unsigned long timeout_in_milliseconds, int use_timeout,
                 os_event_info_t *info)
{
#ifdef GLK_TIMERS
    /* start timer */
    int timer = use_timeout ? timeout_in_milliseconds : 0;
    if (timer)
        glk_request_timer_events(timer);
#else
    /* we can't handle timeouts */
    if (use_timeout)
        return OS_EVT_NOTIMEOUT;
#endif

    /* get a key */
    info->key[0] = os_getc_raw();
    if (info->key[0] == 0 && timechar == 0)
        info->key[1] = os_getc_raw();

#ifdef GLK_TIMERS
    /* stop timer */
    if (timer)
        glk_request_timer_events(0);
#endif

    /* return the event */
    return timechar ? OS_EVT_TIMEOUT : OS_EVT_KEY;
}

