/******************************************************************************
 *                                                                            *
 * Copyright (C) 2006-2009 by Tor Andersson.                                  *
 *                                                                            *
 * 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 "os.h"
#include "glk.h"

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

static inline int max(int a, int b)
{
	return a > b ? a : b;
}

static void redrawstatus(void);

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

winid_t mainwin;
winid_t statuswin;

/* ------------------------------------------------------------------------ */
/*
 *   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_INTERP_CLASS:
			*result = SYSINFO_ICLASS_TEXTGUI;
			return TRUE;
		case SYSINFO_BANNERS:
			*result = 0;	/* as if anyone actually listens... */
			return TRUE;

		case SYSINFO_HTML:
		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();
	}

	statuswin = NULL;

	glk_set_window(mainwin);

	strcat(lbuf, "");
	strcat(rbuf, "");

	return 0;
}

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)
	{
		while (*str)
			glk_put_char((unsigned char)*str++);
	}
	if (curwin == 1)
	{
		const char *p;
		size_t      rem;

		/* 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)
		{
			memcpy(lbuf, p, max(rem, sizeof(lbuf) - 1));
			lbuf[rem] = 0;
			redrawstatus();
		}
	}
}


/* 
 *   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 && 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);
	}
}

/* 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);
	redrawstatus();
}

static void redrawstatus(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);
	glk_put_string(buf);
	glk_set_window(mainwin);
}

/* 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_name(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;

	glk_request_line_event(mainwin, buf, buflen - 1, 0);

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

	buf[event.val1] = 0;

	return buf;
}

/*
 *   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.)  
 */
int os_gets_timeout(unsigned char *buf, size_t bufl,
                    unsigned long timeout_in_milliseconds, int use_timeout)
{
	if (use_timeout)
		return OS_EVT_TIMEOUT;
	else
		return os_gets(buf, bufl) == 0 ? OS_EVT_EOF : OS_EVT_LINE;
}

/*
 *   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)
{
}

/* 
 *   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(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 getglkchar(void)
{
	event_t event;

	glk_request_char_event(mainwin);

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

	/* TODO: turn keycode_ into CMD_ */

	return event.val1;
}

int os_getc(void)
{
	static int bufchar = 0;
	int c;

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

	c = getglkchar();

	if (c == keycode_Return)
		c = '\n';
	if (c == keycode_Tab)
		c = '\t';
	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)
{
	os_getc();
}

/*
 *   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)
{
    /* we can't handle timeouts */
    if (use_timeout)
        return OS_EVT_NOTIMEOUT;

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

    /* return the keyboard event */
    return OS_EVT_KEY;
}

