#include <R_ext/Boolean.h>
#include <R_ext/Error.h>

/*
 * Copyright (C) 1991, 1992, 1993 by Chris Thewalt (thewalt@ce.berkeley.edu)
 *
 * Permission to use, copy, modify, and distribute this software 
 * for any purpose and without fee is hereby granted, provided
 * that the above copyright notices appear in all copies and that both the
 * copyright notice and this permission notice appear in supporting
 * documentation.  This software is provided "as is" without express or
 * implied warranty.
 *
 * Thanks to the following people who have provided enhancements and fixes:
 *   Ron Ueberschaer, Christoph Keller, Scott Schwartz, Steven List,
 *   DaviD W. Sanderson, Goran Bostrom, Michael Gleason, Glenn Kasten,
 *   Edin Hodzic, Eric J Bivona, Kai Uwe Rommel, Danny Quah, Ulrich Betzler
 */

 /* Copyright (C) 2018-2022 The R Core Team */

#include       "getline.h"

static int      gl_tab();  /* forward reference needed for gl_tab_hook */
int 		(*gl_in_hook)() = 0;
int 		(*gl_out_hook)() = 0;
int 		(*gl_tab_hook)() = gl_tab;

#include <Rconfig.h>
#include <R_ext/Riconv.h>
#include <errno.h>

#include <rlocale.h>
extern Rboolean mbcslocale;
#define mbs_init(x) memset(x, 0, sizeof(mbstate_t))

/* NB:  this define must match the one in src/main/scan.c */
#define CONSOLE_PROMPT_SIZE	256

#include <string.h>
#include <ctype.h>
#include <setjmp.h>
#include <stdlib.h>
#include <stdio.h>
#include <io.h>

/******************** internal interface *********************************/

/* Note for multi-byte support. The original getline only worked on single-byte
   characters all of print width 1, such as ASCII. This R version has been
   extended by R Core to support multi-byte characters of varying print widths,
   with some preparation for edit units composed of multiple (Unicode)
   characters. The present code uses an approximation where an edit unit is
   a sequence of a printable character of width greater than one, followed
   by a sequence of printable characters of width zero.

   Symbols starting with "w_" return/hold offsets in "widths" relative to the
   edit buffer gl_buf. As in the original version, symbols not starting with
   "w_" hold offsets in bytes.
 */
static int      BUF_SIZE;               /* dimension of the buffer received*/
static int      gl_init_done = -1;	/* terminal mode flag  */
static int      gl_w_termw = 80;	/* actual terminal width */
static int      gl_w_width = 0;		/* net size available for input */
static int      gl_extent = 0;		/* how far to redraw, 0 means all */
static int      gl_overwrite = 0;	/* overwrite mode */
static int      gl_pos, gl_cnt = 0;     /* position and size of input */
static int      gl_w_pos;
static int      gl_w_cnt = 0;
static char    *gl_buf;                 /* input buffer */
static char    *gl_killbuf = NULL;      /* killed text */
static const char    *gl_prompt;	/* to save the prompt string */
static int      gl_search_mode = 0;	/* search mode flag */

static jmp_buf  gl_jmp;

static void     gl_init(void);		/* prepare to edit a line */
static void     gl_cleanup(void);	/* to undo gl_init */
static void     gl_char_init(void);	/* get ready for no echo input */
static void     gl_char_cleanup(void);	/* undo gl_char_init */
static size_t   gl_w_strlen(const char *); /* width of a string */
static size_t   gl_e_strlen(const char *); /* edit units in a string */
static size_t 	(*gl_w_promptlen)() = (size_t(*)())gl_w_strlen; 
					/* returns printable prompt width */

static void     gl_addchar(int);	/* install specified char */
static void     gl_del(int);		/* del, either left (-1) or cur (0) */
static void     gl_error(const char *); /* write error msg and die */
static void     gl_fixup(const char *, int, int); /* fixup state variables and screen */
static int      gl_getc(void);		/* read one char from terminal */
static void     gl_kill(int);		/* delete to EOL */
static void     gl_newline(void);	/* handle \n or \r */
static void     gl_putc(int);		/* write one char to terminal */
static void     gl_puts(const char *);	/* write a line to terminal */
static void     gl_redraw(void);	/* issue \n and redraw all */
static void     gl_transpose(void);	/* transpose two chars */
static void     gl_yank(void);		/* yank killed text */
static void     gl_word(int);		/* move a word */
static void     gl_killword(int);

void     gl_hist_init(int, int);	/* initializes hist pointers */
char    *gl_hist_next();		/* return ptr to next item */
char    *gl_hist_prev();		/* return ptr to prev item */
static char    *hist_save();		/* makes copy of a string, without NL */

static void     search_addchar(int);	/* increment search string */
static void     search_term(void);	/* reset with current contents */
static void     search_back(int);	/* look back for current string */
static void     search_forw(int);	/* look forw for current string */
static void     gl_beep(void);          /* try to play a system beep sound */

static size_t   gl_w_from_b(size_t);    /* translate gl_buff offset from bytes to widths */
static size_t   gl_b_from_w(size_t);    /* translate gl_buff offset from widths to bytes */
static size_t   gl_w_align_left(size_t);   /* reduce width offset looking for start of edit unit */
static size_t   gl_w_align_right(size_t);  /* increase width offset looking of start of edit unit */

static void    *gl_nat_to_ucs = NULL;   /* iconv conversion descriptor to UCS-4 */
static void    *gl_nat_to_utf16 = NULL; /* iconv conversion descriptor for UTF-16 */
static void    *gl_ucs_to_nat = NULL;   /* iconv conversion descriptor from UCS-4 */
static void    *gl_oem_to_ucs = NULL;   /* iconv conversion descriptor OEM CP -> UCS-4 */
static size_t  *gl_b2w_map = NULL;      /* map gl_buff offset from bytes to widths */
static size_t  *gl_w2b_map = NULL;      /* map gl_buff offset from widths to bytes */
static size_t  *gl_w2e_map = NULL;      /* map gl_buff offset from widths to edit units */

/************************ nonportable part *********************************/

#define WIN32_LEAN_AND_MEAN 1
#include <windows.h>
static HANDLE Win32OutputStream, Win32InputStream = NULL;
static DWORD OldWin32Mode, AltIsDown;

static void
gl_char_init()			/* turn off input echo */
{
   if (!Win32InputStream) {
       Win32InputStream = GetStdHandle(STD_INPUT_HANDLE);
       Win32OutputStream = GetStdHandle(STD_OUTPUT_HANDLE);	
   }
   GetConsoleMode(Win32InputStream,&OldWin32Mode);
   SetConsoleMode(Win32InputStream, ENABLE_PROCESSED_INPUT); /* So ^C works */
   AltIsDown = 0;
}

static void
gl_char_cleanup(void)		/* undo effects of w_gl_char_init */
{
   SetConsoleMode(Win32InputStream,OldWin32Mode);
   AltIsDown = 0;
}

/* Convert the number (xxx) entered via ALT+xxx to UCS-4. */
static int
gl_alt_to_ucs(int alt)
{
    if (alt <= 0 || alt > 255)
	return 0;

    /* TODO: this is how it worked before, but it would be better to treat
       the input as (decoded) index of the character and to support more
       than a single byte. */
    R_wchar_t uc = 0;
    const char *inbuf = (char *)&alt;
    char *outbuf = (char *)&uc;
    size_t inbytesleft = sizeof(int);  /* the ALT code only uses 1 byte */
    size_t outbytesleft = 4;
    size_t status;

    Riconv(gl_oem_to_ucs, NULL, NULL, NULL, NULL);
    status = Riconv(gl_oem_to_ucs, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
    if (status == (size_t)-1 && errno != E2BIG) {
	gl_putc('\a');
	return 0;
    }
    return uc;
}

/* Get a UCS-4 character without echoing it to screen. */
static int
gl_getc(void)
{
    int             c;

    /* Initial version by Guido Masarotto (3/12/98):
         "get Ansi char code from a Win32 console" */
    DWORD a;
    INPUT_RECORD r;
    DWORD st;
    WORD vk;
    CONSOLE_SCREEN_BUFFER_INFO csb;
    wchar_t high = 0;
    int bbb = 0, nAlt=0, n, hex = 0;
    static int debug_codes = 0;

    c = 0; 
    while (!c) {
      /* Following two lines seem to be needed under Win2k to reshow the
         cursor. */
      GetConsoleScreenBufferInfo(Win32OutputStream, &csb);
      SetConsoleCursorPosition(Win32OutputStream, csb.dwCursorPosition);
      /* Originally uChar.AsciiChar was used and for MBCS characters
         ReadConsoleInput returned as many events as bytes in the character.
         As of Windows 8 this reportedly no longer works, ReadConsoleInput
         would only generate one event with the first byte in AsciiChar.
         The bug still exists in Windows 10, and thus we now call
         GetConsoleInputW to get uchar.UnicodeChar. */
      ReadConsoleInputW(Win32InputStream, &r, 1, &a);
      if (!(r.EventType == KEY_EVENT)) break;
      st = r.Event.KeyEvent.dwControlKeyState;
      vk = r.Event.KeyEvent.wVirtualKeyCode;
      if (debug_codes)
        fprintf(stderr, "st %x vk %x down %x char %x\n",
                        (unsigned int)st, (unsigned int)vk,
                        (unsigned int)r.Event.KeyEvent.bKeyDown,
                        (unsigned int)r.Event.KeyEvent.uChar.UnicodeChar);
      if (r.Event.KeyEvent.bKeyDown) {
        AltIsDown = (st & LEFT_ALT_PRESSED);
	if (vk == VK_MENU && AltIsDown) { /* VK_MENU is Alt or AltGr */
	  nAlt = 0;
	  bbb  = 0;
          hex  = 0;
	}
        else if (AltIsDown && vk == 0x49)  /* Alt+I */
          debug_codes = !debug_codes;
	else if (AltIsDown) { /* Interpret Alt+xxx entries */
	  /* Alt+xxx entries may be given directly by user or may
	     result from pasting a character that does not map to
	     a key on the current keyboard (in that case the numbers
	     are with numlock on at least on Windows 10), which has
	     been observed with tilde on Italian keyboard (PR17679). */
	  switch (vk) {
	  case VK_NUMPAD0: case VK_INSERT: case 0x30: n = 0; break;
	  case VK_NUMPAD1: case VK_END: case 0x31: n = 1; break;
	  case VK_NUMPAD2: case 0x32: n = 2; break;
	  case VK_NUMPAD3: case VK_NEXT: case 0x33: n = 3;break;
	  case VK_NUMPAD4: case 0x34: n = 4; break;
	  case VK_NUMPAD5: case VK_CLEAR: case 0x35: n = 5; break;
	  case VK_NUMPAD6: case 0x36: n = 6; break;
	  case VK_NUMPAD7: case VK_HOME: case 0x37: n = 7; break;
	  case VK_NUMPAD8: case 0x38: n = 8; break;
	  case VK_NUMPAD9: case VK_PRIOR: case 0x39: n = 9; break;
          case VK_ADD: /* + on NumPad */ case VK_OEM_PLUS:
            if (nAlt == 0)
              hex = 1;
            n = -1;
            break;
          case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46:
            if (hex)
              n = vk - 0x41 + 10; /* A B C D E F */
            else
              n = -1;
            break;
	  default: n = -1;
	  }
	  if (n >= 0) {
            if (hex)
              bbb = 16 * bbb + n;
            else
              bbb = 10 * bbb + n;
	    nAlt += 1;
            if (debug_codes)
	      fprintf(stderr, "Alt+ [%d] down: %x\n", nAlt, bbb);
          }
	  if (!hex && nAlt==3) {
	    c = gl_alt_to_ucs(bbb);
	    bbb = 0;
	    nAlt = 0;
	    AltIsDown = 0;
	  } else if (hex && nAlt==8) {
            c = bbb;
            bbb = 0;
            nAlt = 0;
            hex = 0;
	    AltIsDown = 0;
          }
	}
	/* Originally, these (LEFT, RIGHT, HOME, END, UP, DOWN, DELETE) were
	   accepted only with ENHANCED_KEY state and other keys with that state
	   were ignored, but with conPTY on Windows 10, the ENHANCED_KEY state is
	   not set. */
	else if (vk == VK_LEFT)
	    c = '\002';
	else if (vk == VK_RIGHT)
	    c = '\006';
	else if (vk == VK_HOME)
	    c = '\001';
	else if (vk == VK_END)
	    c = '\005';
	else if (vk == VK_UP)
	    c = '\020';
	else if (vk == VK_DOWN)
	    c = '\016';
	else if (vk == VK_DELETE)
	    c = '\004';
	else 
            /* Only characters from BMP obtained this way. */
            c = r.Event.KeyEvent.uChar.UnicodeChar;
      }
      else if (vk == VK_MENU && AltIsDown) { 
       /* Alt key up event: could be AltGr, but let's hope users 
	  only press one of them at a time. */

	wchar_t wc = r.Event.KeyEvent.uChar.UnicodeChar;
	if (IS_HIGH_SURROGATE(wc))
	    high = wc;
	else if (IS_LOW_SURROGATE(wc)) {
	    /* Only supplementary characters obtained this way. */
	    c = 0x10000 + ((int) (high & 0x3FF) << 10 ) + 
                           (int) (wc & 0x3FF);
	    high = 0;
	} else if (hex && wc==0) {
	    c = bbb;
	} else if (bbb) {
	    /* Handle Alt+xxx (Alt+xx).
	       Console implementations differ in whether and how they interpret
	       Alt+xxx sequences. Some translate internally and do not send
	       Alt+xxx to R. Some send the Alt+xxx to R but also intepret and
	       send the result in Alt key up event (wc). See PR#18391. */
	    c = gl_alt_to_ucs(bbb);
	} else
	    c = wc;
	/* This may have to be re-visited when extending support for combining
	   marks, which have been seen arriving in Alt key up event as well. */
	AltIsDown = 0;
	nAlt = 0;
	bbb = 0;
        hex = 0;
      }
      else if (AltIsDown) {
	/* NumPad cursor keys now come without the key-down event, so
	   handle Alt+xxx for them here (when NumLock is disabled) */
        switch (vk) {
        case VK_DOWN: n = 2; break;
        case VK_LEFT: n = 4; break;
        case VK_RIGHT: n = 6; break;
        case VK_UP: n = 8; break;
        default: n = -1;
        }
        if (n >= 0) {
  	  if (hex)
	    bbb = 16 * bbb + n;
	  else
	    bbb = 10 * bbb + n;
          nAlt += 1;
          if (debug_codes)
	    fprintf(stderr, "Alt+ [%d] up: %x\n", nAlt, bbb);
        }
        if (!hex && nAlt==3) {
	  c = gl_alt_to_ucs(bbb);
	  bbb = 0;
	  nAlt = 0;
	  AltIsDown = 0;
        } else if (hex && nAlt==8) {
	  c = bbb;
	  bbb = 0;
	  nAlt = 0;
	  hex = 0;
	  AltIsDown = 0;
        }
      } 
    }
    return c;
}

static void
gl_putc(int c)
{
   int ch = c;

    write(1, &ch, 1);
    if (ch == '\n') {
	ch = '\r';
        write(1, &ch, 1);	/* RAW mode needs '\r', does not hurt */
    }
}

/* Print bytes to console (via wchar_t API). On Windows 10 running in a DBCS
   locale (not UTF-8), printing using write() is not reliable, sometimes
   there are extra spaces in the output, depending on timing (probably a race
   condition in the console host). Printing via wchar_t interface seems to be
   more reliable. */
static void gl_write(char *s, int len)
{
    wchar_t buf[len + 1]; /* bigger than needed */
    size_t status, inbytesleft, outbytesleft, wchars;
    const char *inbuf;
    char *outbuf;
    static HANDLE Win32OutputStream;

    if (len == 0)
	return;

    Riconv(gl_nat_to_utf16, NULL, NULL, NULL, NULL);
    inbuf = s;
    inbytesleft = len;
    outbuf = (char *)buf;
    outbytesleft = sizeof(buf);
    status = Riconv(gl_nat_to_utf16, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
    if (status == (size_t)-1) 
	gl_error("\n*** Error: getline(): invalid multi-byte character.\n");

    Win32OutputStream = GetStdHandle(STD_OUTPUT_HANDLE);
    wchars = (sizeof(buf) - outbytesleft)/sizeof(wchar_t);
    WriteConsoleW(Win32OutputStream, buf, wchars, NULL, NULL);
}

/********************* fairly portable part *********************************/

static void
gl_puts(const char *const buf)
{
    int len; 
    
    if (buf) {
        len = strlen(buf);
        write(1, buf, len);
    }
}

void gl_error(const char *const buf)
{
    int len = strlen(buf);

    gl_cleanup();
    write(2, buf, len);
    longjmp(gl_jmp,1);
}

static void
gl_init(void)
/* set up variables and terminal */
{
    char oemname[256];

    if (gl_init_done < 0) {		/* -1 only on startup */
        gl_hist_init(512, 1);
    }
    if (isatty(0) == 0 || isatty(1) == 0)
	gl_error("\n*** Error: getline(): not interactive, use stdio.\n");
    if (!(gl_killbuf=calloc(BUF_SIZE,sizeof(char))))
        gl_error("\n*** Error: getline(): not enough memory.\n");

    gl_nat_to_ucs = Riconv_open("UCS-4LE", "");
    if (gl_nat_to_ucs == (void *)-1) 
	gl_error("\n*** Error: getline(): unable to convert to UCS-4.\n");
    gl_nat_to_utf16 = Riconv_open("UTF-16LE", "");
    if (gl_nat_to_utf16 == (void *)-1) 
	gl_error("\n*** Error: getline(): unable to convert to UTF-16.\n");
    gl_ucs_to_nat = Riconv_open("", "UCS-4LE");
    if (gl_ucs_to_nat == (void *)-1)
	gl_error("\n*** Error: getline(): unable to convert to UCS-4.\n");
    snprintf(oemname, sizeof(oemname), "CP%d", (int)GetOEMCP());
    gl_oem_to_ucs = Riconv_open(oemname, "UCS-4LE");
    if (gl_oem_to_ucs == (void *)-1)
	gl_error("\n*** Error: getline(): unable to convert from OEM CP.\n"); 
    if (!(gl_b2w_map = calloc(BUF_SIZE, sizeof(size_t))))
	gl_error("\n*** Error: getline(): not enough memory.\n");
    if (!(gl_w2b_map = calloc(BUF_SIZE, sizeof(size_t))))
	gl_error("\n*** Error: getline(): not enough memory.\n");
    if (!(gl_w2e_map = calloc(BUF_SIZE, sizeof(size_t))))
	gl_error("\n*** Error: getline(): not enough memory.\n");

    gl_char_init();
    gl_init_done = 1;
}

static void
gl_cleanup(void)
/* undo effects of gl_init, as necessary */
{
    if (gl_init_done > 0)
        gl_char_cleanup();
    if (gl_killbuf)
	free(gl_killbuf);
    if (gl_nat_to_ucs && (gl_nat_to_ucs != (void *)-1))
        Riconv_close(gl_nat_to_ucs);
    if (gl_nat_to_utf16 && (gl_nat_to_utf16 != (void *)-1))
        Riconv_close(gl_nat_to_utf16);
    if (gl_ucs_to_nat && (gl_ucs_to_nat != (void *)-1))
        Riconv_close(gl_ucs_to_nat);
    if (gl_oem_to_ucs && (gl_oem_to_ucs != (void *)-1))
        Riconv_close(gl_oem_to_ucs);
    if (gl_b2w_map)
	free(gl_b2w_map);
    if (gl_w2b_map)
        free(gl_w2b_map);
    if (gl_w2e_map)
        free(gl_w2e_map);
    gl_init_done = 0;
}

void
gl_setwidth(int w)
{
    /* not used in R; should arrange for redraw */
    if (w > 20) 
	gl_w_termw = w;
    else 
	gl_error("\n*** Error: minimum screen width is 21\n");
}

/* Number of bytes of the edit unit left of the cursor (loc = -1) or
   right of the cursor (loc=0). */
static int
gl_edit_unit_size(int loc, int cursor)
{
    size_t w = gl_w_from_b(cursor);

    if (loc == -1) {
	/* left */
	if (w == 0)
	    return 0;
	return gl_b_from_w(w) - gl_b_from_w(gl_w_align_left(w-1));
    } else {
	/* right */
	if (w == gl_w_cnt)
	    return 0;
	return gl_b_from_w(gl_w_align_right(w+1)) - gl_b_from_w(w);
    }
}

static int
gl_edit_unit_size_left()
{
    return gl_edit_unit_size(-1 /* left */, gl_pos);
}

static int
gl_edit_unit_size_right()
{
    return gl_edit_unit_size(0 /* right */, gl_pos);
}

static size_t
gl_w_from_b(size_t b)
{
    if (b >= gl_cnt)
	return gl_w_cnt;
    return gl_b2w_map[b];
}

static size_t
gl_b_from_w(size_t w)
{
    if (w >= gl_w_cnt)
	return gl_cnt;
    return gl_w2b_map[w];
}

static size_t
gl_w_align_left(size_t w)
{
    size_t e;
    
    if (w >= gl_w_cnt)
	return w;
    for(e = gl_w2e_map[w]; (w>0) && (gl_w2e_map[w-1] == e); w--);
    return w;
}

static size_t
gl_w_align_right(size_t w)
{
    if (w == 0)
	return w;

    size_t e;
    for(e = gl_w2e_map[w-1]; (w < gl_w_cnt) && (gl_w2e_map[w] == e); w++);
    return w;
}

/* Update map of characters, widths and edit units to reflect changes in gl_buf,
   given changes in the interval [change, change+gl_extent>. Returns true when
   the (print) width of this interval remains unchanged, false otherwise.

   This also updates gl_cnt and gl_w_cnt. */
static int
update_map(size_t change)
{
    int consider_extent = 1;
    size_t w_old_extent;

    if (gl_extent == 0)
	consider_extent = 0;

    if (gl_extent && consider_extent)
	w_old_extent = gl_w_from_b(change + gl_extent);

    gl_cnt = strlen(gl_buf);
    if (gl_cnt == 0) {
	gl_w_cnt = 0;
	return 0;
    }

    size_t w, b, e, iw, ib, width;
    size_t last_w, last_b, last_e;
    R_wchar_t uc;
    size_t inbytesleft, last_inbytesleft, outbytesleft, status;
    const char *inbuf;
    char *outbuf;

    inbytesleft = gl_cnt - change;
    inbuf = gl_buf + change;
    Riconv(gl_nat_to_ucs, NULL, NULL, NULL, NULL);

    if (change == 0) {
	gl_b2w_map[0] = 0;
	gl_w2b_map[0] = 0;
	gl_w2e_map[0] = 0;
	w = b = e = 0;
    } else {
	b = change;
	w = gl_b2w_map[b];
	e = gl_w2e_map[w];
    }
    ib = b + 1; 
    iw = w + 1;

    while(inbytesleft > 0) {
	outbytesleft = 4;
	outbuf = (char *)&uc;
	last_inbytesleft = inbytesleft;
	status = Riconv(gl_nat_to_ucs, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
	if (status == (size_t)-1 && errno != E2BIG)
	    gl_error("\n*** Error: getline(): invalid multi-byte character.\n");

	width = iswprint(uc) ? Ri18n_wcwidth(uc) : 0;

	last_b = b;
	last_w = w;
	last_e = e;
	/* tab should not appear here */
	w += width;
	b += last_inbytesleft - inbytesleft;
	if (width > 0)
	    /* this is an approximation, ideally use complete grapheme here */
	    e++;

	for(; ib < b; ib++)
	    gl_b2w_map[ib] = last_w;
	gl_b2w_map[ib] = w;
	for(; iw < w; iw++) {
	    gl_w2b_map[iw] = last_b;
	    gl_w2e_map[iw] = last_e;
	}
	gl_w2b_map[iw] = b;
	gl_w2e_map[iw] = e;

	if (consider_extent && (b == change + gl_extent) && (w == w_old_extent))
	    /* gl_w_cnt is unchanged */
	    return 1;
    }
    gl_w_cnt = w;
    return 0;
}

/* Returns 1 on EOF */
int
getline(const char *prompt, char *buf, int buflen)
{
    int c, loc, tmp;

    BUF_SIZE = buflen;
    gl_buf = buf;
    gl_buf[0] = '\0';
    if (setjmp(gl_jmp)) {
	if (gl_init_done > 0) {
	    gl_newline();
	    gl_cleanup();
	    return 0;
	}
	/* predictable error in gl_cleanup() leads to infinite loop when R asks
	   whether the image should be saved */
	gl_cleanup();
	return 1;
    }
    gl_init();	
    gl_pos = 0;
    gl_w_pos = 0;
    gl_prompt = (prompt)? prompt : "";
    if (gl_in_hook)
	gl_in_hook(gl_buf);
    gl_fixup(gl_prompt, -2, BUF_SIZE);
    while ((c = gl_getc()) >= 0) {
	gl_extent = 0;  	/* reset to full extent */
	if (!iswcntrl(c)) {
	    if (gl_search_mode)
	       search_addchar(c);
	    else
	       gl_addchar(c);
	} else {
	    if (gl_search_mode) {
	        if (c == '\033' || c == '\016' || c == '\020') {
	            search_term();
	            c = 0;     		/* ignore the character */
		} else if (c == '\010' || c == '\177') {
		    search_addchar(-1); /* unwind search string */
		    c = 0;
		} else if (c != '\022' && c != '\023') {
		    search_term();	/* terminate and handle char */
		}
	    }
	    switch (c) {
	      case '\n': case '\r': 			/* newline */
		gl_newline();
		gl_cleanup();
		return 0;
	      case '\001': gl_fixup(gl_prompt, -1, 0);		/* ^A, VK_HOME */
		break;
	      case '\002': 	/* ^B, VK_LEFT */
		gl_fixup(gl_prompt, -1, gl_pos - gl_edit_unit_size_left());
		break;
	      case '\003':                                      /* ^C */
		  gl_fixup(gl_prompt, -1, gl_cnt);
		  gl_puts("^C\n");
		  gl_kill(0);
		  gl_fixup(gl_prompt, -2, BUF_SIZE);
		break;
	      case '\004':					/* ^D, VK_DELETE */
		if (gl_cnt == 0) {
		    gl_buf[0] = 0;
		    gl_cleanup();
		    gl_putc('\n');
		    return 0;
		} else {
		    gl_del(0);
		}
		break;
	      case '\005': gl_fixup(gl_prompt, -1, gl_cnt);	/* ^E, VK_END */
		break;
		case '\006': /* ^F */
		gl_fixup(gl_prompt, -1, gl_pos + gl_edit_unit_size_right());
		break;
	      case '\010': case '\177': gl_del(-1);	/* ^H and DEL */
		break;
	      case '\t':        				/* TAB */
                if (gl_tab_hook) {
		    tmp = gl_pos;
	            loc = gl_tab_hook(gl_buf, gl_w_promptlen(gl_prompt), &tmp);
	            if (loc != -1 || tmp != gl_pos)
	                gl_fixup(gl_prompt, loc, tmp);
                }
		break;
	      case '\013': gl_kill(gl_pos);			/* ^K */
		break;
	      case '\014': gl_redraw();				/* ^L */
		break;
	      case '\016': 					/* ^N, VK_DOWN */
		strncpy(gl_buf, gl_hist_next(), BUF_SIZE-2);
		gl_buf[BUF_SIZE-2] = '\0';
                if (gl_in_hook)
	            gl_in_hook(gl_buf);
		gl_fixup(gl_prompt, 0, BUF_SIZE);
		break;
	      case '\017': gl_overwrite = !gl_overwrite;       	/* ^O */
		break;
	      case '\020': 					/* ^P, VK_UP */
		strncpy(gl_buf, gl_hist_prev(),BUF_SIZE-2);
		gl_buf[BUF_SIZE-2] = '\0';
                if (gl_in_hook)
	            gl_in_hook(gl_buf);
		gl_fixup(gl_prompt, 0, BUF_SIZE);
		break;
	      case '\022': search_back(1);			/* ^R */
		break;
	      case '\023': search_forw(1);			/* ^S */
		break;
	      case '\024': gl_transpose();			/* ^T */
		break;
              case '\025': gl_kill(0);				/* ^U */
		break;
              case '\027': gl_killword(-1);			/* ^W */
		break;
	      case '\031': gl_yank();				/* ^Y */
		break;
	      case '\032': 					/* ^Z */
		gl_newline();
		gl_cleanup();
		return 1;
	      case '\033':				/* ansi arrow keys */
		c = gl_getc();
		if (c == '[') {
		    switch(c = gl_getc()) {
		      case 'A':             			/* up */
		        strncpy(gl_buf, gl_hist_prev(), BUF_SIZE-2);
		        gl_buf[BUF_SIZE-2] = '\0';
		        if (gl_in_hook)
	                    gl_in_hook(gl_buf);
		        gl_fixup(gl_prompt, 0, BUF_SIZE);
		        break;
		      case 'B':                         	/* down */
		        strncpy(gl_buf, gl_hist_next(), BUF_SIZE-2);
		        gl_buf[BUF_SIZE-2] = '\0';
                        if (gl_in_hook)
	                    gl_in_hook(gl_buf);
		        gl_fixup(gl_prompt, 0, BUF_SIZE);
		        break;
		    case 'C':                                  /* right */
			gl_fixup(gl_prompt, -1, gl_pos + gl_edit_unit_size_right());
		        break;
		    case 'D':                                  /* left */
			gl_fixup(gl_prompt, -1, gl_pos - gl_edit_unit_size_left());
			break;
		    default: gl_putc('\007');                  /* who knows */
		        break;
		    }
		} else if (c == 'f' || c == 'F') {
		    gl_word(1);
		} else if (c == 'b' || c == 'B') {
		    gl_word(-1);
		} else
		    gl_putc('\007');
		break;
	      default:		/* check for a terminal signal */
                if (c > 0)
		    gl_putc('\007');
		break;
	    }
	}
    }
    gl_newline();
    gl_cleanup();
    return 0;
}

/* Adds bytes from s to the current position of the buffer. The input
   needs to only include complete edit units. */
static void
gl_addbytes(const char *s)
{
    int e, del = 0, size, len, i;

    len = strlen(s);
    if (gl_overwrite == 1) {
	e = gl_e_strlen(s);
	for(i = 0; i < e; i++) {
	    size = gl_edit_unit_size(0 /* right */, gl_pos + del);
	    if (size == 0)
		/* no more edit units */
		break;
	    del += size;
	}
    }
    if (len > del) {
	/* expanding buffer */
	if (gl_cnt + len - del >= BUF_SIZE - 1) 
	    gl_error("\n*** Error: getline(): input buffer overflow\n");
	for (i = gl_cnt; i >= gl_pos + del; i--)
	    gl_buf[i + len - del] = gl_buf[i];
    } else if (len < del) {
	/* reducing buffer */
	for (i = gl_pos + del; i <= gl_cnt; i++)
	    gl_buf[i - (del - len)] = gl_buf[i];
    } else {
	/*  clen == del */
	gl_extent = len;
    }
    for (i=0; i < len; i++)
	gl_buf[gl_pos + i] = s[i];
    gl_fixup(gl_prompt, gl_pos, gl_pos+len);
}

/* Adds character c (UCS-4) to the current position. Normally it would add
   a new edit unit, but eventually this may append to the current edit unit. */
static void
gl_addchar(int c)  
{
    char buf[MB_CUR_MAX + 1];
    size_t status, inbytesleft, outbytesleft, clen, left;
    const char *inbuf;
    char *outbuf;
    int i;

    if (gl_cnt >= BUF_SIZE - 2)
	gl_putc('\a');
    else if (iswprint(c)) {
	Riconv(gl_ucs_to_nat, NULL, NULL, NULL, NULL);
	inbuf = (char *)&c;
	inbytesleft = 4;
	outbuf = buf;
	outbytesleft = MB_CUR_MAX;
	status = Riconv(gl_ucs_to_nat, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
	if (status == (size_t)-1)
	    gl_error("\n*** Error: getline(): invalid multi-byte character.\n");
	clen = MB_CUR_MAX - outbytesleft;
	buf[clen] = '\0';
	
	if (Ri18n_wcwidth(c) > 0) {
	    gl_addbytes(buf);
	    return;
	} else if (GetACP() == 65001 && gl_pos > 0) {
	    /* This is an approximation, ideally we would allow building of
	       arbitrary Unicode sequences (graphemes), including ZWJ. Also,
	       this is experimental and little tested: it seems that currently
	       neither RTerm nor Windows Terminal properly support character
	       composition. */
	    left = gl_edit_unit_size_left();
	  
	    if (left > 0) { 
		if (gl_cnt + clen >= BUF_SIZE - 1)
		    gl_error("\n*** Error: getline(): input buffer overflow\n");

		for (i = gl_cnt; i >= gl_pos; i--)
		    gl_buf[i + clen] = gl_buf[i];
		for (i = 0; i < clen; i++)
		    gl_buf[gl_pos + i] = buf[i];

		gl_fixup(gl_prompt, gl_pos - left, gl_pos + clen);
		return;
	    }
	}
    }
    gl_putc('\a');
}

static void
gl_yank(void)
/* adds the kill buffer to the input buffer at current location */
{
    int  len;

    len = strlen(gl_killbuf);
    if (len > 0)
	gl_addbytes(gl_killbuf);
    else
	gl_beep();
}

static void
gl_transpose(void)
/* switch character under cursor and to left of cursor */
{
    int    c;

    if (gl_pos > 0 && gl_cnt > gl_pos) {
	if(mbcslocale) {
	    int l_len = 0;
	    int r_len = 0;
	    int i = 0;
	    int j = 0;
	    mbstate_t mb_st;

	    mbs_init(&mb_st);
	    for (i = 0; i < gl_pos;) {
		l_len = mbrlen(gl_buf+i, MB_CUR_MAX, &mb_st);
		i += l_len;
	    }
	    mbs_init(&mb_st);
	    r_len = mbrlen(gl_buf+gl_pos, MB_CUR_MAX, &mb_st);
	    for (i = 0; i < r_len; i++) {
		for(j = 0; j < l_len; j++) {
		    c = gl_buf[gl_pos+i-j];
		    gl_buf[gl_pos+i-j] = gl_buf[gl_pos+i-j-1];
		    gl_buf[gl_pos+i-j-1] = (char)c;
		}
	    }
	    gl_extent = l_len + r_len;
	    gl_fixup(gl_prompt, gl_pos - l_len, gl_pos + (r_len - l_len)); 
	} else {
	    c = gl_buf[gl_pos-1];
	    gl_buf[gl_pos-1] = gl_buf[gl_pos];
	    gl_buf[gl_pos] = (char) c;
	    gl_extent = 2;
	    gl_fixup(gl_prompt, gl_pos-1, gl_pos);
	}
    } else
	gl_beep();
}

static void
gl_newline(void)
/*
 * Cleans up entire line before returning to caller. A \n is appended.
 * If line longer than screen, we redraw starting at beginning
 */
{
    int change = gl_cnt;
    int len = gl_cnt;
    /* shifts line back to start position */
    int loc = gl_b_from_w(gl_w_align_left(gl_w_width - 5));

    if (gl_cnt >= BUF_SIZE - 1) { 
        gl_error("\n*** Error: getline(): input buffer overflow\n");
    }
    if (gl_out_hook) {
	change = gl_out_hook(gl_buf);
        len = strlen(gl_buf);
    } 
    if (loc > len)
	loc = len;
    gl_fixup(gl_prompt, change, loc);	/* must do this before appending \n */
    gl_buf[len] = '\n';
    gl_buf[len+1] = '\0';
    gl_putc('\n');
}

static void
gl_del(int loc)
/*
 * Delete a character.  The loc variable can be:
 *    -1 : delete character to left of cursor
 *     0 : delete character under cursor
 */
{
   int i, len;

   if ((loc == -1 && gl_pos > 0) || (loc == 0 && gl_pos < gl_cnt)) {
       len = gl_edit_unit_size(loc, gl_pos);
       for (i = gl_pos+(loc*len); i <= gl_cnt - len; i++)
	   gl_buf[i] = gl_buf[i + len];
       gl_fixup(gl_prompt,gl_pos+(loc * len) , gl_pos+(loc * len));
   } else
       gl_beep();
}

static void
gl_kill(int pos)
        
/* delete from pos to the end of line */
{
    if (pos < gl_cnt) {
	strcpy(gl_killbuf, gl_buf + pos);
	gl_buf[pos] = '\0';
	gl_fixup(gl_prompt, pos, pos);
    } else
	gl_beep();
}

static void
gl_killword(int direction)
{
    int pos = gl_pos;
    int startpos = gl_pos;
    int tmp;
    int i;

    if (direction > 0) {		/* forward */
        while (!isspace(gl_buf[pos]) && pos < gl_cnt) 
	    pos++;
	while (isspace(gl_buf[pos]) && pos < gl_cnt)
	    pos++;
    } else {				/* backward */
	if (pos > 0)
	    pos--;
	while (isspace(gl_buf[pos]) && pos > 0)
	    pos--;
        while (!isspace(gl_buf[pos]) && pos > 0) 
	    pos--;
	if (pos < gl_cnt && isspace(gl_buf[pos]))   /* move onto word */
	    pos++;
    }
    if (pos < startpos) {
    	tmp = pos;
	pos = startpos;
	startpos = tmp;
    }
    memcpy(gl_killbuf, gl_buf + startpos, (size_t) (pos - startpos));
    gl_killbuf[pos - startpos] = '\0';
    if (isspace(gl_killbuf[pos - startpos - 1]))
    	gl_killbuf[pos - startpos - 1] = '\0';
    gl_fixup(gl_prompt, -1, startpos);
    for (i=0, tmp=pos - startpos; i<tmp; i++)
    	gl_del(0);
}	/* gl_killword */

static void
gl_word(int direction)
              
/* move forward or backword one word */
{
    int pos = gl_pos;

    if (direction > 0) {		/* forward */
        while (!isspace(gl_buf[pos]) && pos < gl_cnt) 
	    pos++;
	while (isspace(gl_buf[pos]) && pos < gl_cnt)
	    pos++;
    } else {				/* backword */
	if (pos > 0)
	    pos--;
	while (isspace(gl_buf[pos]) && pos > 0)
	    pos--;
        while (!isspace(gl_buf[pos]) && pos > 0) 
	    pos--;
	if (pos < gl_cnt && isspace(gl_buf[pos]))   /* move onto word */
	    pos++;
    }
    gl_fixup(gl_prompt, -1, pos);
}

static void
gl_redraw(void)
/* emit a newline, reset and redraw prompt and current input line */
{
    if (gl_init_done > 0) {
        gl_putc('\n');
        gl_fixup(gl_prompt, -2, gl_pos);
    }
}

static void
gl_fixup(const char *prompt, int change, int cursor)
              
                      
/*
 * This function is used both for redrawing when input changes or for
 * moving within the input line.  The parameters are:
 *   prompt:  compared to last_prompt[] for changes;
 *   change : the index of the start of changes in the input buffer,
 *            with -1 indicating no changes, -2 indicating we're on
 *            a new line, redraw everything assuming clean line.
 *   cursor : the desired location of the cursor after the call.
 *            A value of BUF_SIZE can be used  to indicate the cursor should
 *            move just past the end of the input line.
 */

/* when change >= 0, change must be aligned to edit units and
                     change+gl_extent must be as well */
/* when cursor != BUF_SIZE, it must be aligned to edit units */

{
    static int   gl_shift;	 /* index of first on screen byte */
    static int   gl_w_shift;
    static int   off_right;	 /* true if more text right of screen */
    static int   off_left;	 /* true if more text left of screen */
    static char  last_prompt[CONSOLE_PROMPT_SIZE] = "";
    int          w_change = -1;
    int          w_cursor;                            
    int          left = 0;     /* index of first byte to print */
    int          right = -1;   /* index of first byte not to print */
    int          w_right;
    int          w_rightmost_printable;
    int          w_pad;        /* how much to erase at end of line */
    int          w_dollar_pad = 0;
    int          w_backup;       /* how far to backup before fixing */
    int          new_shift = 0;  /* value of shift based on cursor */
    int          w_new_shift;
    int          consider_extent = 1;
    int          print_left_dollar = 0;
    int          print_right_dollar = 0;
    int          i;
    int          gl_w_scroll;    /* width of EOL scrolling region */

    gl_w_scroll = gl_w_termw / 3;

    if (change == -2) {   /* reset, initialization, redraw */
	gl_putc('\r');
        gl_pos = gl_cnt = gl_shift = off_right = off_left = 0;
	gl_w_pos = gl_w_cnt = gl_w_shift = 0;

	gl_puts(prompt);
	strncpy(last_prompt, prompt, CONSOLE_PROMPT_SIZE-1);
        gl_w_width = gl_w_termw - gl_w_promptlen(prompt);
	change = 0;
	consider_extent = 0;
    } else if (strcmp(prompt, last_prompt) != 0) {
	gl_putc('\r');
	gl_w_pos = gl_w_shift;
	gl_pos = gl_shift;
	/* temporarily updated gl_w_cnt is only used to include the change in prompt
	   width into calculation of pad, right after that the gl_*cnt values are
	   recomputed (the prompt is never included otherwise into gl_*cnt) */
	gl_w_cnt = gl_w_cnt + gl_w_promptlen(last_prompt) - gl_w_promptlen(prompt);

	gl_puts(prompt);
	strncpy(last_prompt, prompt, CONSOLE_PROMPT_SIZE-1);
        gl_w_width = gl_w_termw - gl_w_promptlen(prompt);
	change = 0;
	consider_extent = 0;
    }

    w_pad = (off_right)? gl_w_width - 1 : gl_w_cnt - gl_w_shift + off_left;   /* old width */
    if (change >= 0) {
	w_change = gl_w_from_b(change);  /* old map or initialization */
	consider_extent = update_map(change) && consider_extent;
	                  /* map_update updates also gl_cnt, gl_w_cnt */
	if (change > gl_cnt) {
	    change = gl_cnt;
	    w_change = gl_w_cnt;
	}
    }
    if (cursor > gl_cnt) {
	if (cursor != BUF_SIZE)		/* BUF_SIZE means end of line */
	    gl_putc('\007');
	cursor = gl_cnt;
    }
    if (cursor < 0) {
	gl_putc('\007');
	cursor = 0;
    }
    w_cursor = gl_w_from_b(cursor);
    w_new_shift = w_cursor - (gl_w_width - 1 - gl_w_scroll);
    if (w_new_shift > 0)
	w_new_shift++; /* adjust if newly off left */
    if (w_new_shift > 0 && gl_w_cnt > w_new_shift  + gl_w_width - 2)
	w_new_shift++; /* adjust if newly off right */
    if (w_new_shift > 0) {
	w_new_shift /= gl_w_scroll;
	w_new_shift *= gl_w_scroll;
	w_new_shift = gl_w_align_right(w_new_shift); 
	new_shift = gl_b_from_w(w_new_shift);
    } else
	w_new_shift = 0;
    w_backup = gl_w_pos - gl_w_shift + off_left;

    if (new_shift != gl_shift) {	/* scroll or redraw/init occurs */
	gl_shift = new_shift;
	gl_w_shift = w_new_shift;
	off_left = print_left_dollar = (gl_shift)? 1 : 0;
        left = gl_shift;
	w_rightmost_printable = gl_w_shift + gl_w_width - 2 - off_left;
	off_right = (gl_w_cnt > w_rightmost_printable + 1)? 1 : 0;

	if (off_right) {
	    /* right needs to account for right-$, but, right is the
	       first byte _not_ to print, while w_rightmost_printable
	       is the last width to print */
	    print_right_dollar = 1;
	    w_right = gl_w_align_left(w_rightmost_printable);
	    right = gl_b_from_w(w_right); 
	    /* there may be something right off the right-$ */
	    w_dollar_pad = w_rightmost_printable - w_right; 
	} else
	    right = gl_cnt;

    } else if (change >= 0) {		/* no scroll, but text changed */
	if (off_left && (change <= gl_shift)) {
	    left = gl_shift;
	    w_backup ++;  /* left-$ present */
	} else {
	    left = change;
	    w_backup = gl_w_pos - w_change;
	}
	w_rightmost_printable = gl_w_shift + gl_w_width - 2 - off_left;
	off_right = (gl_w_cnt > w_rightmost_printable + 1)? 1 : 0;

	if (off_right) {
	    w_right = gl_w_align_left(w_rightmost_printable);
	    right = gl_b_from_w(w_right);
	    if (consider_extent && (left + gl_extent < right))
		right = left + gl_extent;
	    else {
		print_right_dollar = 1;
		/* there may be something right off the right-$ */
		w_dollar_pad = w_rightmost_printable - w_right;
	    }
	} else if (consider_extent && (left + gl_extent < gl_cnt))
	    right = left + gl_extent; 
	else
	    right = gl_cnt;
    }
    w_pad -= (off_right)? gl_w_width - 1 : gl_w_cnt - gl_w_shift + off_left; /* new width */
    w_pad = (w_pad < 0)? 0 : w_pad;

    if (left <= right) {               /* clean up screen */
	for (i=0; i < w_backup; i++) 
	    gl_putc('\b');
	if (print_left_dollar)
	    gl_putc('$');
	if (right > left)
	    gl_write(gl_buf + left, right - left); /* print changed characters */
	gl_pos = right;
	gl_w_pos = gl_w_from_b(gl_pos); 
	if (print_right_dollar)
	    gl_putc('$');
	for(i = 0; i < w_pad + w_dollar_pad; i++)
	    /* erase remains of prev line or right-$ */
	    gl_putc(' ');
	gl_w_pos += print_right_dollar + w_pad + w_dollar_pad;
    }
    i = gl_w_pos - w_cursor;		/* move to final cursor location */
    if (i > 0) {
	while (i--) 
	   gl_putc('\b');
    } else {
	    if (gl_pos < cursor)        /* only to move the cursor on terminal */
		gl_write(gl_buf + gl_pos, cursor - gl_pos);
    }
    gl_w_pos = w_cursor;
    gl_pos = cursor;
}

static int
gl_tab(char *buf, int offset, int *loc)
/* default tab handler, acts like tabstops every 8 cols */
{
    int i, count, len;

    len = gl_w_strlen(buf);
    count = 8 - (offset + *loc) % 8;
    for (i=len; i >= *loc; i--)
        buf[i+count] = buf[i];
    for (i=0; i < count; i++)
        buf[*loc+i] = ' ';
    i = *loc;
    *loc = i + count;
    return i;
}

/******************* strlen stuff **************************************/

/* hook to install a custom gl_w_promptlen, used _only_ for the prompt  */
void gl_strwidth(func)
size_t (*func)();
{
    if (func != 0) {
	gl_w_promptlen = func;
    }
}

/* lenght of string in widths */
static size_t
gl_w_strlen(const char *s)
{
    size_t inbytesleft, outbytesleft, width = 0, status;
    R_wchar_t uc;
    char *outbuf;

    inbytesleft = strlen(s);
    Riconv(gl_nat_to_ucs, NULL, NULL, NULL, NULL);
    while(inbytesleft) {
	outbytesleft = 4;
	outbuf = (char *)&uc;
	status = Riconv(gl_nat_to_ucs, &s, &inbytesleft, &outbuf, &outbytesleft);
	if (status == (size_t)-1 && errno != E2BIG)
	    gl_error("\n*** Error: getline(): invalid multi-byte character.\n");

	if (iswprint(uc))
	    width += Ri18n_wcwidth(uc);
    }
    return width;
}

/* length of string in edit units */
static size_t
gl_e_strlen(const char *s)
{
    size_t inbytesleft, outbytesleft, status, e = 0;
    R_wchar_t uc;
    char *outbuf;

    inbytesleft = strlen(s);
    Riconv(gl_nat_to_ucs, NULL, NULL, NULL, NULL);
    while(inbytesleft) {
	outbytesleft = 4;
	outbuf = (char *)&uc;
	status = Riconv(gl_nat_to_ucs, &s, &inbytesleft, &outbuf, &outbytesleft);
	if (status == (size_t)-1 && errno != E2BIG)
	    gl_error("\n*** Error: getline(): invalid multi-byte character.\n");

	if (iswprint(uc) && Ri18n_wcwidth(uc) > 0)
	    /* this is an approximation, ideally use complete grapheme here */
	    e++;
    }
    return e;
}


/******************* History stuff **************************************/

static int	HIST_SIZE = 512;
static int      hist_pos = 0, hist_last = 0, gl_beep_on = 1;
static char     **hist_buf;

void
gl_hist_init(int size, int beep)
{
    int i;

    HIST_SIZE = size;
    hist_buf = (char **) malloc(size * sizeof(char *));
    if(!hist_buf)
	gl_error("\n*** Error: gl_hist_init() failed on malloc\n");
    hist_buf[0] = "";
    for (i = 1; i < HIST_SIZE; i++)
	hist_buf[i] = (char *)0;
    hist_pos = hist_last = 0;
    gl_init_done = 0;
    gl_beep_on = beep;
}

void
gl_histadd(const char *buf)
{
    const char *p = buf;

    /* in case we call gl_histadd() before we call getline() */
    if (gl_init_done < 0) {		/* -1 only on startup */
        gl_hist_init(512, 1);
        gl_init_done = 0;
    }
    while (*p == ' ' || *p == '\t' || *p == '\n') 
	p++;
    if (*p) {
	hist_buf[hist_last] = hist_save(buf);
	hist_last = hist_last + 1;
	if(hist_last > HIST_SIZE - 1) {
	    int i, size = HIST_SIZE + 512;
	    hist_buf = (char **) realloc(hist_buf, size * sizeof(char *));
	    if(!hist_buf)
		gl_error("\n*** Error: gl_histadd() failed on realloc\n");
	    for(i = HIST_SIZE; i < size; i++)
		hist_buf[i] = (char *)0;
	    HIST_SIZE = size;
	}
	hist_buf[hist_last] = "";
    }
    hist_pos = hist_last;
}

char *
gl_hist_prev(void)
/* loads previous hist entry into input buffer, sticks on first */
{
    char *p = 0;
    int   next = hist_pos - 1;

    if (hist_buf[hist_pos] != 0 && next >= 0) {
        hist_pos = next;
        p = hist_buf[hist_pos];
    } 
    if (p == 0) {
	p = "";
	gl_beep();
    }
    return p;
}

char *
gl_hist_next(void)
/* loads next hist entry into input buffer, clears on last */
{
    char *p = 0;

    if (hist_pos != hist_last) {
        hist_pos = hist_pos+1;
	p = hist_buf[hist_pos];
    } 
    if (p == 0) {
	p = "";
	gl_beep();
    }
    return p;
}

static char *
hist_save(const char *p)
        
/* makes a copy of the string */
{
    char *s = 0;
    int   len = strlen(p);
    char *nl = strchr(p, '\n');

    if (nl) {
        if ((s = (char *) malloc(len)) != 0) {
            memcpy(s, p, len-1);
	    s[len-1] = 0;
	}
    } else {
        if ((s = (char *) malloc(len+1)) != 0) {
            strcpy(s, p);
        }
    }
    if (s == 0) 
	gl_error("\n*** Error: hist_save() failed on malloc\n");
    return s;
}

void gl_savehistory(const char *file, int size)
{
    FILE *fp;
    int i, init;

    if (!file || !hist_last) return;
    fp = fopen(file, "w");
    if (!fp) {
       char msg[256];
       sprintf(msg, "Unable to open %s", file);
       R_ShowMessage(msg);
       return;
    }
    init = hist_last - size;
    init = (init < 0) ? 0 : init;
    for (i = init; i < hist_last; i++)
       fprintf(fp, "%s\n", hist_buf[i]);
    fclose(fp); 
}

void gl_loadhistory(const char *file)
{
    FILE *fp;
    int i;
    char buf[1000];

    if (!file) return;
    fp = fopen(file, "r");
    if (!fp) {
       return;
    }
    for(i = 0;; i++) {
	if(!fgets(buf, 1000, fp)) break;
	gl_histadd(buf);
    }
    fclose(fp); 
}


/******************* Search stuff **************************************/

static char  search_prompt[101];  /* prompt includes search string */
static char  search_string[100];
static int   search_pos = 0;      /* current location in search_string */
static int   search_forw_flg = 0; /* search direction flag */
static int   search_last = 0;	  /* last match found */

static void  
search_update(int c)
{
    if (c == 0) {
	search_pos = 0;
        search_string[0] = 0;
        search_prompt[0] = '?';
        search_prompt[1] = ' ';
        search_prompt[2] = 0;
    } else if (c > 0) {
        search_string[search_pos] = (char) c;
        search_string[search_pos+1] = (char) 0;
        search_prompt[search_pos] = (char) c;
        search_prompt[search_pos+1] = (char) '?';
        search_prompt[search_pos+2] = (char) ' ';
        search_prompt[search_pos+3] = (char) 0;
	search_pos++;
    } else {
	if (search_pos > 0) {
	    search_pos--;
            search_string[search_pos] = (char) 0;
            search_prompt[search_pos] = (char) '?';
            search_prompt[search_pos+1] = (char) ' ';
            search_prompt[search_pos+2] = (char) 0;
	} else {
	    gl_beep();
	    hist_pos = hist_last;
	}
    }
}

static void 
search_addchar(int c)
{
    char *loc;

    search_update(c);
    if (c < 0) {
	if (search_pos > 0) {
	    hist_pos = search_last;
	} else {
	    gl_buf[0] = 0;
	    hist_pos = hist_last;
	}
	strncpy(gl_buf, hist_buf[hist_pos],BUF_SIZE-2);
        gl_buf[BUF_SIZE-2] = '\0' ;
    }
    if ((loc = strstr(gl_buf, search_string)) != 0) {
	gl_fixup(search_prompt, 0, loc - gl_buf);
    } else if (search_pos > 0) {
        if (search_forw_flg) {
	    search_forw(0);
        } else {
	    search_back(0);
        }
    } else {
	gl_fixup(search_prompt, 0, 0);
    }
}

static void     
search_term(void)
{
    gl_search_mode = 0;
    if (gl_buf[0] == 0)		/* not found, reset hist list */
        hist_pos = hist_last;
    if (gl_in_hook)
	gl_in_hook(gl_buf);
    gl_fixup(gl_prompt, 0, gl_pos);
}

static void     
search_back(int new_search)
{
    int    found = 0;
    char  *p, *loc;

    search_forw_flg = 0;
    if (gl_search_mode == 0) {
	search_last = hist_pos = hist_last;	
	search_update(0);	
	gl_search_mode = 1;
        gl_buf[0] = 0;
	gl_fixup(search_prompt, 0, 0);
    } else if (search_pos > 0) {
	while (!found) {
	    p = gl_hist_prev();
	    if (*p == 0) {		/* not found, done looking */
	       gl_buf[0] = 0;
	       gl_fixup(search_prompt, 0, 0);
	       found = 1;
	    } else if ((loc = strstr(p, search_string)) != 0) {
	       strncpy(gl_buf, p, BUF_SIZE-2);
               gl_buf[BUF_SIZE-2] = '\0';
	       gl_fixup(search_prompt, 0, loc - p);
	       if (new_search)
		   search_last = hist_pos;
	       found = 1;
	    } 
	}
    } else {
        gl_beep();
    }
}

static void     
search_forw(int new_search)
{
    int    found = 0;
    char  *p, *loc;

    search_forw_flg = 1;
    if (gl_search_mode == 0) {
	search_last = hist_pos = hist_last;	
	search_update(0);	
	gl_search_mode = 1;
        gl_buf[0] = 0;
	gl_fixup(search_prompt, 0, 0);
    } else if (search_pos > 0) {
	while (!found) {
	    p = gl_hist_next();
	    if (*p == 0) {		/* not found, done looking */
	       gl_buf[0] = 0;
	       gl_fixup(search_prompt, 0, 0);
	       found = 1;
	    } else if ((loc = strstr(p, search_string)) != 0) {
	       strncpy(gl_buf, p, BUF_SIZE-2);
               gl_buf[BUF_SIZE-2] = '\0';
	       gl_fixup(search_prompt, 0, loc - p);
	       if (new_search)
		   search_last = hist_pos;
	       found = 1;
	    } 
	}
    } else {
        gl_beep();
    }
}

static void
gl_beep(void)
{
	if(gl_beep_on) MessageBeep(MB_OK);
}	/* gl_beep */

