/***********************************
  (C) Copyright 1992-1993; dit/upm
  Distributed under the conditions stated in the
  TOPO General Public License (see file LICENSE)
  ***********************************/

/***********************************

 *
 * Copyright (C) 1991 by Chris Thewalt
 *
 * 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.
 *

 1991/11/09 20:27:48


 * Fairly portable (ANSI C),  emacs style line editing input package.
 * This package uses \b to move, and \007 to ring the bell.
 * It uses a fixed screen width, as initialized in the gl_init() call,
 * and does not draw in the last location to avoid line wraps.
 * The only non-portable part is how to turn off character echoing.
 * This code works for *NIX of BSD or SYSV flavor, as well as MSDOS (MSC6.0).
 * No TERMCAP features are used, so long lines are scrolled on one line
 * rather than extending over several lines.  The function getline2
 * returns a pointer to a static buffer area which holds the input string,
 * including the newline. On EOF the first character is set equal to '\0'.
 * The caller supplies a desired prompt, as shown in the prototype:
 *
 *     char *getline2(char *prompt)
 *
 * Getline is different from GNU readline in that:
 *   - getline2 has much more limited editing capabilities, but it
 *     is also much smaller and doesn't need termcap.
 *   - you don't free the buffer when done, since it is static
 *     (make a copy yourself if you want one)
 *   - the newline is appended to the buffer
 *   - you don't add lines to history, it is done automatically.
 *
 * The function gl_init(int screen_width) should be called before
 * calling getline2(char *prompt), and gl_cleanup(void) should be
 * called before before exiting.  The function gl_redraw(void) may also
 * be called to redraw the screen (as is done when ^L or ^R are read).
 *
 * The editing keys are:
 * ^A, ^E   - move to beginnning or end of line, respectively.
 * ^F, ^B   - nondestructive move forward or back one location, respectively.
 * ^D       - delete the character currently under the cursor, or
 *            send EOF if no characters in the buffer.
 * ^H, DEL  - delete character left of the cursor.
 * ^K       - delete from cursor to end of line.
 * ^P, ^N   - move through history, previous and next, respectively.
 * ^Q, ^W   - move first or last history position, respectively.
 * ^L, ^R   - redraw the current line.
 * TAB      - call user defined function if bound, or insert spaces
 *            to get to next TAB stop (just past every 8th column).
 * NL, CR   - places line on history list if nonblank, calls output
 *            cleanup function if bound, appends newline and returns
 *            to the caller.
 *
 * In addition, the caller can modify the buffer in certain ways, which
 * may be useful for things like auto-indent modes.  There are three
 * function pointers which can be bound to user functions.
 * Each of these functions must return the index of the first location
 * at which the buffer was modified, or -1 if the buffer wasn't modified.
 * Each of the functions receive the current input buffer as the first
 * argument.  The screen is automatically cleaned up if the buffer is changed.
 * The user is responsible for not writing beyond the end of the static
 * buffer.  The function pointer prototypes are:
 *
 *  int (*gl_in_hook)(char *buf)
 *      - called on entry to getline2, and each time a new history
 *        string is loaded, from a ^P or ^N. Initally NULL.
 *  int (*gl_out_hook)(char *buf)
 *      - called when a \n or \r is read, before appending \n and
 *        returning to caller. Initally NULL.
 *  int (*gl_tab_hook)(char *buf, int offset, int *loc)
 *      - called whenever a TAB is read.  The second argument is
 *        the current line offset due to the width of the prompt.
 *        The third argument is a pointer to the variable holding the
 *        current location in the buffer.  The location may be reset
 *        by the user to move the cursor when the call returns.
 *        Initially a built in tabbing function is bound.
 *
 * Please send bug reports, fixes and enhancements to Chris Thewalt,
 * thewalt@ce.berkeley.edu
 *
 * Modifications for LoLa : successive calls to gl_init() and gl_cleanup()
 * have the effect of resuming/suspending line edition keyboard ioctl
 * configuration.
 *
 ************************************/

/* KJT 08/08/06: changes for compatibility with CygWin 1.5.18-1/gcc 3.4.4-1 */

/* LINTLIBRARY */

#include "listdh.h"
#include <stdlib.h>
#include "ligetlin.h"
#include "limisc.h"

/*
   static char *copyright = "Copyright (C) 1991, Chris Thewalt";
   */
extern int      isatty();

static int      gl_init_done = 0;       /* -1 is terminal, 1 is batch  */
static int      gl_screen = 80;         /* screen width */
static int      gl_width = 0;           /* net size available for input */
static int      gl_pos, gl_cnt = 0;     /* position and size of input */
static char     gl_buf[BUF_SIZE];       /* input buffer */
static char    *gl_prompt;              /* to save the prompt string */
static boolean  gl_eof = FALSE;         /* TRUE when EOF */

void            gl_init(/*int*/);
void            gl_cleanup(/* void */); /* to undo gl_init */
void            gl_redraw(/* void */);  /* issue \n and redraw all */
static void     gl_char_init(/* void */); /* get ready for no echo input */
static void     gl_char_cleanup(/* void */);    /* undo gl_char_init */
static int      gl_getchar(/* void */);       /* read one char from stdin */
static void     gl_addchar(/*int*/);    /* install specified char */
static void     gl_newline(/* void */); /* handle \n or \r */
static void     gl_fixup(/*int, int*/); /* fixup state variables and screen */
static void     gl_del(/*int*/);        /* del, either left (-1) or cur (0) */
static void     gl_kill(/* void */);            /* delete to EOL */
static int      gl_tab(/*char *, int, int **/); /* default TAB handler */

static void     hist_add(/* void */);   /* adds nonblank entries to hist */
static void     hist_init(/* void */);  /* initializes hist pointers */
static void     hist_next(/* void */);  /* copies next entry to input buf */
static void     hist_prev(/* void */);  /* copies prev entry to input buf */
static void     hist_top(/* void */);   /* copies first entry into input buf */
static void     hist_bottom(/* void */); /* last entry into input buf */
static char    *hist_save(/*char **/);  /* makes copy of a string */

int             (*gl_in_hook)(/*char **/) = 0;
     int             (*gl_out_hook)(/*char **/) = 0;
     int             (*gl_tab_hook)(/*char *, int, int **/) = gl_tab;

     /************************ nonportable part *********************************/
     /*
	# ifdef MSDOS
	# include <bios.h>
	# endif
	*/

#ifdef MSDOS
#include <pc.h>
#else
#ifdef unix
#include <sys/ioctl.h>
#if !defined(TIOCGETP)
#include <termio.h>
#undef TIOCGETP
     struct termio tty, old_tty;
#else
#include <sgtty.h>
     struct sgttyb   tty, old_tty;
#endif /* TIOCGETP */
/* KJT 08/08/06: commented out as incompatible with standard definition */
/*      extern int      ioctl(); */
#endif  /* unix */
#endif  /* MSDOS */

     static void
     gl_char_init()
     /* turn off input echo */
{
#ifndef MSDOS
#ifdef unix
#if defined(TIOCGETP)
  (void)ioctl(0, TIOCGETP, &old_tty);
  tty = old_tty;
  tty.sg_flags |= CBREAK;
  tty.sg_flags &= ~ECHO;
  (void)ioctl(0, TIOCSETP, &tty);
#else
  (void)ioctl(0, TCGETA, &old_tty);
  tty = old_tty;
  tty.c_lflag &= ~(ICANON|ECHO|ECHOE|ECHOK|ECHONL);
  tty.c_cc[VMIN] = 1;
  (void)ioctl(0, TCSETA, &tty);
#endif
#endif /* unix */
#endif /* MSDOS */
}

static void
gl_char_cleanup()
     /* undo effects of gl_char_init, as necessary */
{
#ifndef MSDOS
#ifdef unix
#ifdef TIOCSETP
  (void)ioctl(0, TIOCSETP, &old_tty);
#else
  (void)ioctl(0, TCSETA, &old_tty);
#endif
#endif /* unix */
#endif /* MSDOS */
}

static int
gl_getchar()
     /* get a character without echoing it to screen */
{
  int             c;

#ifndef MSDOS
#ifdef unix
  c = fgetc(stdin);
#endif
#else
  /* c = _bios_keybrd(_NKEYBRD_READ) & 0177;*/   /* only using 7 bit ASCII */
  c = getxkey() ;
  switch(c) {
  case 327: case 583: /* HOME */
    c = '\001';
    break;
  case 328: case 584: /* UP */
    c = '\020';
    break;
  case 329: case 585: /* PGUP */
    c = '\021';
    break;
  case 331: case 587: /* LEFT  */
    c = '\002';
    break;
  case 333: case 589: /* RIGHT */
    c = '\006';
    break;
  case 335: case 591: /* END */
    c = '\005';
    break;
  case 336: case 592: /* DOWN */
    c = '\016';
    break;
  case 337: case 593: /* PGDOWN */
    c = '\027';
    break;
    /*    case :
	  case : / * INS * /
	  c = '\020';
	  break;
	  */
  case 339: case 595: /* DEL */
    c = '\177';
    break;

  default:
    /*       c = c & 0177;  */
  }
#endif /* MSDOS */

  return c;
}
/******************** fairly portable part *********************************/

void gl_init( scrn_wdth )
     int scrn_wdth;
     /* set up variables and terminal */
{
  gl_eof = FALSE;
  gl_screen = scrn_wdth;
  if (gl_init_done == 0) {
    hist_init();
    if (isatty(1))
      (void)setvbuf(stdout, (char *)0, _IONBF, 0);
    if (isatty(0) && isatty(1)) {
      (void)setvbuf(stdin, (char *)0, _IONBF, 0);
      gl_char_init();
      gl_init_done = -1;            /* -1 means terminal */
    } else {
      gl_init_done = 1;             /* 1 means batch */
    }
  }
  /* dlarra : just resume keyboard configuration */
  else if (gl_init_done == -1 ) {
    if (isatty(1))
      (void)setvbuf(stdout, (char *)0, _IONBF, 0);
    if (isatty(0) && isatty(1)) {
      (void)setvbuf(stdin, (char *)0, _IONBF, 0);
      gl_char_init();
    }
  }
  /****/
  gl_pos = gl_cnt = 0;
}


void gl_cleanup()
     /* undo effects of gl_init, as necessary */
{
  if (gl_init_done == -1)
    gl_char_cleanup();
  /*
  gl_init_done = 0;
  */
  gl_eof = TRUE;
}

/* KJT 15/02/12: renamed "getline2" to avoid clash with standard "getline" */
char *getline2( prompt )
     char *prompt;
{
  int c, loc, tmp;

  if (gl_eof) {
    gl_buf[0] = '\0'; /* means EOF */
    gl_buf[1] = '\n';
    gl_buf[2] = '\0';
    return gl_buf;
  }
  if (!gl_init_done)
    gl_init(80);
  gl_buf[0] = 0;                /* used as end of input indicator */
  if (gl_init_done == 1) {
    /* no input editing, and prompt and command output */
    if (fgets(gl_buf, BUF_SIZE, stdin)==NULL) {
      gl_eof = TRUE;
      gl_cleanup();
    }
    (void)fputs(prompt, stdout);
    (void)fputs(gl_buf, stdout);
    return gl_buf;
  }
  gl_fixup(-1, 0);                      /* this resets gl_fixup */
  gl_width = gl_screen - strlen(prompt);
  if (prompt == NULL)
    prompt = "";
  gl_prompt = prompt;
  gl_pos = gl_cnt = 0;
  (void)fputs(prompt, stdout);
  if (gl_in_hook) {
    loc = gl_in_hook(gl_buf);
    if (loc >= 0)
      gl_fixup(0, BUF_SIZE);
  }
  while((c = gl_getchar()) != EOF ) {
    if (isprint(c)) {
      gl_addchar(c);
    } else {
      switch (c) {
      case '\n': case '\r':                         /* newline */
	gl_newline();
	return gl_buf;
      case '\001': gl_fixup(-1, 0);         /* ^A */
	break;
      case '\002': gl_fixup(-1, gl_pos-1);  /* ^B */
	break;
/* KJT 11/03/98: Backspace/Delete swapped for NS/OS */
#ifdef __NeXT__
      case '\010':
#else
      case '\177':
#endif
	gl_del(0);
	break;
      case '\004':                          /* ^D */
      case '\032':                          /* ^Z */
	if ((gl_cnt == 0) && !isatty(0)) {
	  gl_buf[0] = 0;
	  gl_buf[1] = '\n';
	  gl_buf[2] = '\0';
	  gl_cleanup();
	  (void)fputc('\n', stdout);
	  return gl_buf;
	} else {
	  gl_del(0);
	}
	break;
      case '\005': gl_fixup(-1, gl_cnt);    /* ^E */
	break;
      case '\006': gl_fixup(-1, gl_pos+1);  /* ^F */
	break;
/* KJT 11/03/98: Backspace/Delete swapped for NS/OS */
#ifdef __NeXT__
      case '\177': gl_del(-1);
#else
      case '\010': gl_del(-1);
#endif
	break;
      case '\t':                            /* TAB */
	if (gl_tab_hook) {
	  tmp = gl_pos;
	  loc = gl_tab_hook(gl_buf, strlen(gl_prompt), &tmp);
	  if (loc >= 0 || tmp != gl_pos)
	    gl_fixup(loc, tmp);
	}
	break;
      case '\013': gl_kill();                       /* ^K */
	break;
      case '\014': case '\022':     gl_redraw();    /* ^L and ^R */
	break;
      case '\016': hist_next();                     /* ^N */
	break;
      case '\020': hist_prev();                     /* ^P */
	break;
      case '\021': hist_top();                      /* ^Q */
	break;
      case '\027': hist_bottom();           /* ^W */
	break;
      default:
	(void)fputc('\007', stdout);
	break;
      }
    }
  }
  if (!gl_eof) {
    gl_newline();
  }
  gl_cleanup();
  return gl_buf;
}

static void
gl_addchar( c )
     int c;
     /* adds the character c to the input buffer at current location */
{
  int  i;

  if (gl_cnt >= BUF_SIZE - 1) {
    Error("getline2: input buffer overflow");
  }
  for (i=gl_cnt; i >= gl_pos; i--)
    gl_buf[i+1] = gl_buf[i];
  gl_buf[gl_pos] = c;
  gl_fixup(gl_pos, gl_pos+1);
}

static void
gl_newline()
     /*
      * 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;
  int loc = gl_width - 5;     /* shifts line back to start position */

  if (gl_cnt >= BUF_SIZE - 1) {
    Error("getline2: input buffer overflow");
  }
  hist_add();                 /* only adds if nonblank */
  if (gl_out_hook) {
    change = gl_out_hook(gl_buf);
    len = strlen(gl_buf);
  }
  if (loc > len)
    loc = len;
  gl_fixup(change, loc);      /* must do this before appending \n */
  gl_buf[len] = '\n';
  gl_buf[len+1] = '\0';
  (void)fputc('\n', stdout);
}

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

  if (loc == -1 && gl_pos > 0 || loc == 0 && gl_pos < gl_cnt) {
    for (i=gl_pos+loc; i < gl_cnt; i++)
      gl_buf[i] = gl_buf[i+1];
    gl_fixup(gl_pos+loc, gl_pos+loc);
  } else
    (void)fputc('\007', stdout);
}

static void
gl_kill()
     /* delete from current position to the end of line */
{
  if (gl_pos < gl_cnt) {
    gl_buf[gl_pos] = '\0';
    gl_fixup(gl_pos, gl_pos);
  } else
    (void)fputc('\007', stdout);
}

void
gl_redraw()
     /* emit a newline, reset and redraw prompt and current input line */
{
  if (gl_init_done == -1) {
    (void)fputc('\n', stdout);
    (void)fputs(gl_prompt, stdout);
    gl_pos = 0;
    gl_fixup(0, BUF_SIZE);
  }
}

static void
gl_fixup( change, cursor )
     int change;
     int cursor;
     /*
      * This function is used both for redrawing when input changes or for
      * moving within the input line.  The parameters are:
      *   change : the index of the start of changes in the input buffer,
      *            with -1 indicating no changes.
      *   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.
      */
{
  static int   gl_shift;      /* index of first on screen character */
  static int   off_right;     /* true if more text right of screen */
  static int   off_left;      /* true if more text left of screen */
  int          left = 0, right = -1;          /* bounds for redraw */
  int          pad;           /* how much to erase at end of line */
  int          backup;        /* how far to backup before fixing */
  int          new_shift;     /* value of shift based on cursor */
  int          extra;         /* adjusts when shift (scroll) happens */
  int          i;

  if (change == -1 && cursor == 0 && gl_buf[0] == 0) {   /* reset */
    gl_shift = off_right = off_left = 0;
    return;
  }
  pad = (off_right)? gl_width - 1 : gl_cnt - gl_shift;   /* old length */
  backup = gl_pos - gl_shift;
  if (change >= 0) {
    gl_cnt = strlen(gl_buf);
    if (change > gl_cnt)
      change = gl_cnt;
  }
  if (cursor > gl_cnt) {
    if (cursor != BUF_SIZE)         /* BUF_SIZE means end of line */
      (void)fputc('\007', stdout);
    cursor = gl_cnt;
  }
  if (cursor < 0) {
    (void)fputc('\007', stdout);
    cursor = 0;
  }
  if (off_right || off_left && cursor < gl_shift + gl_width - SCROLL / 2)
    extra = 2;                      /* shift the scrolling boundary */
  else
    extra = 0;
  new_shift = cursor + extra + SCROLL - gl_width;
  if (new_shift > 0) {
    new_shift /= SCROLL;
    new_shift *= SCROLL;
  } else
    new_shift = 0;
  if (new_shift != gl_shift) {        /* scroll occurs */
    gl_shift = new_shift;
    off_left = (gl_shift)? 1 : 0;
    off_right = (gl_cnt > gl_shift + gl_width - 1)? 1 : 0;
    left = gl_shift;
    right = (off_right)? gl_shift + gl_width - 2 : gl_cnt;
  } else if (change >= 0) {           /* no scroll, but text changed */
    if (change < gl_shift + off_left) {
      left = gl_shift;
    } else {
      left = change;
      backup = gl_pos - change;
    }
    off_right = (gl_cnt > gl_shift + gl_width - 1)? 1 : 0;
    right = (off_right)? gl_shift + gl_width - 2 : gl_cnt;
  }
  pad -= (off_right)? gl_width - 1 : gl_cnt - gl_shift;
  pad = (pad < 0)? 0 : pad;
  if (left <= right) {                /* clean up screen */
    for (i=0; i < backup; i++)
      (void)fputc('\b', stdout);
    if (left == gl_shift && off_left) {
      (void)fputc('$', stdout);
      left++;
    }
    for (i=left; i < right; i++)
      (void)fputc(gl_buf[i], stdout);
    if (off_right) {
      (void)fputc('$', stdout);
      gl_pos = right + 1;
    } else {
      for (i=0; i < pad; i++)     /* erase remains of prev line */
	(void)fputc(' ', stdout);
      gl_pos = right + pad;
    }
  }
  i = gl_pos - cursor;                /* move to final cursor location */
  if (i > 0) {
    while (i--)
      (void)fputc('\b', stdout);
  } else {
    for (i=gl_pos; i < cursor; i++)
      (void)fputc(gl_buf[i], stdout);
  }
  gl_pos = cursor;
}

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

  len = 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;
}

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

#ifndef HIST_SIZE
#define HIST_SIZE 100
#endif

int             hist_pos, hist_last;
char           *hist_buf[HIST_SIZE];
char            hist_current_line[BUF_SIZE];

static void
hist_init()
{
  int i;

  for (i=0; i < HIST_SIZE; i++)
    hist_buf[i] = (char *)NULL;
  hist_current_line[0]=0;
}

static void
hist_add()
{
  char *p = gl_buf;

  while (*p == ' ' || *p == '\t')     /* only save nonblank line */
    p++;
  if (*p) {
    hist_buf[hist_last] = hist_save(gl_buf);
    hist_last = (hist_last + 1) % HIST_SIZE;
    if (hist_buf[hist_last]) {      /* erase next location */
      free(hist_buf[hist_last]);
      hist_buf[hist_last] = NULL;
    }
  }
  hist_pos = hist_last;
}

static void
hist_prev()
     /* loads previous hist entry into input buffer, sticks on first */
{
  int   next;

  if (hist_pos==hist_last)
    (void)sprintf(hist_current_line,"%s",gl_buf);
  next = (hist_pos - 1 + HIST_SIZE) % HIST_SIZE;
  if (next != hist_last) {
    if (hist_buf[next]) {
      hist_pos = next;
      (void)strcpy(gl_buf, hist_buf[hist_pos]);
    } else
      (void)fputc('\007', stdout);
  } else
    (void)fputc('\007', stdout);
  if (gl_in_hook)
    gl_in_hook(gl_buf);
  gl_fixup(0, BUF_SIZE);
}

static void
hist_top()
     /* loads the first hist entry into input buffer, sticks on first */
{
  int   next;

  if (hist_pos==hist_last)
    (void)sprintf(hist_current_line,"%s",gl_buf);
  for (;;) {
    next = (hist_pos - 1 + HIST_SIZE) % HIST_SIZE;
    if (next != hist_last) {
      if (hist_buf[next]) {
	hist_pos = next;
      } else
	break;
    } else
      break;
  }
  (void)strcpy(gl_buf, hist_buf[hist_pos]);
  if (gl_in_hook)
    gl_in_hook(gl_buf);
  gl_fixup(0, BUF_SIZE);
}

static void
hist_next()
     /* loads next hist entry into input buffer, clears on last */
{
  if (hist_pos != hist_last) {
    hist_pos = (hist_pos + 1) % HIST_SIZE;
    if (hist_buf[hist_pos]) {
      (void)strcpy(gl_buf, hist_buf[hist_pos]);
    } else {
      (void)sprintf(gl_buf,"%s",hist_current_line);
      hist_current_line[0] = 0;
    }
  } else
    (void)fputc('\007', stdout);
  if (gl_in_hook)
    gl_in_hook(gl_buf);
  gl_fixup(0, BUF_SIZE);
}

static void
hist_bottom()
     /* loads last hist entry into input buffer, clears on last */
{
  if (hist_pos != hist_last) {
    hist_pos = hist_last;
    if (hist_buf[hist_pos]) {
      (void)strcpy(gl_buf, hist_buf[hist_pos]);
    } else {
      (void)sprintf(gl_buf,"%s",hist_current_line);
      hist_current_line[0] = 0;
    }
  } else
    (void)fputc('\007', stdout);
  if (gl_in_hook)
    gl_in_hook(gl_buf);
  gl_fixup(0, BUF_SIZE);
}

static char *
hist_save( p )
     char *p;
     /* makes a copy of the string */
{
  char *s = NULL;

  if (p && ((s = malloc((unsigned)strlen(p)+1)) != NULL)) {
    (void)strcpy(s, p);
  }
  return s;
}

