/* ------------------------------------------------------------------------ */
/* You have just entered the world of inp.c                                 */
/* In this file we will strive for excellence in keyboard input. That is    */
/* all this file is for. I have adopted some ideas from the makers of IRCII */
/* ------------------------------------------------------------------------ */
/* All rights reserved. Do not distribute this code without permission of   */
/* the programmer(s). (Douglas Lewis)                                       */
/* ------------------------------------------------------------------------ */
#define NEW_INP

#include <stdio.h>
#include <ssdef.h>
#include <descrip.h>
#ifdef __GNUC__
#define PSL$C_USER 3

#define SMG$K_TRM_UP    274
#define SMG$K_TRM_DOWN  275
#define SMG$K_TRM_LEFT  276
#define SMG$K_TRM_RIGHT 277
#else
#include <smgdef.h>
#include <psldef.h>
#endif
#include <iodef.h>
#include <ttdef.h>
#include <tt2def.h>
#include <ssdef.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <ctype.h>
#include <lib$routines.h>

#include "base_includes.h"
#include "binding.h"
#include "qio.h"
#include "list.h"
#include "inp.h"
#include "system.h"

extern server_ptr gsrv;
extern int DUMB;

#define NUMKEYS  26     /* Number of special keys defined. */

static unsigned in_chan = 0;

#define INPUT_QIO  2
#define INPUT_GETS 1
#define INPUT_NONE 0
int INPUT_DEFINED = INPUT_QIO;

static unsigned long keybd_efn;
static unsigned char new_keyget(void);
unsigned char (*input_routine)(void) = new_keyget;
int last_keyhit;
static int dont_restart_qio = 1;

static struct iosb_type ast_iosb;
static char inp_ast, buff_ast[256];
static int buff_left = 0, buff_start = 0, buff_curr = 0;

/* ------------------------------------------------------------------------ */
static void ctrlc_ast(void)
{
  yell("received a control-c");
  tcp_connect_cancel();
}

/* ------------------------------------------------------------------------ */
static void new_keyget_ast(void)
{
  unsigned status;
  if (!(ast_iosb.status & 1)) {
    /* 2104 is a data overrun */
    if (ast_iosb.status && ast_iosb.status != 2104) {
      if (dont_restart_qio)
      {
        /* abort - called by spawn */
        if (ast_iosb.status != 44)
          my_sig(ast_iosb.status, "keyget not restarting");
        return;
      }
    }
  } else {
    last_keyhit = time(NULL);
    if (inp_ast==3) ctrlc_ast();
    if (buff_start==256) buff_start = 0;
    if (buff_left==256)
      yell("input buffer is full");
    else {
      buff_ast[buff_start++] = inp_ast;
      buff_left++;
      status = sys$setef(KEYGET_EF);
    }
  }
  status = sys$qio(keybd_efn, in_chan, IO$_READVBLK|IO$M_NOECHO, &ast_iosb,
                   new_keyget_ast, 0, &inp_ast, sizeof(inp_ast), 0,
                   0, 0, 0);
}
/* ------------------------------------------------------------------------ */
static void start_keyget_ast(void)
{
  unsigned status, loop;

  buff_curr = buff_left = buff_start = 0;
  status = sys$clref(KEYGET_EF);
  status = sys$qio(keybd_efn, in_chan, IO$_READVBLK|IO$M_NOECHO, &ast_iosb,
                   new_keyget_ast, 0, &inp_ast, sizeof(inp_ast), 0, 0, 0, 0);
}
/* ------------------------------------------------------------------------ */
static unsigned char new_keyget(void)
{
  extern void wait_routine(int);
  int old;
  old = sys$setast(0);
  if (!buff_left) {
    if (old==SS$_WASSET) sys$setast(1);
    wait_routine(KEYGET_EF);
    old = sys$setast(0);
  }
  if (buff_curr==256) buff_curr = 0;
  buff_left--;
  if (!buff_left) sys$clref(KEYGET_EF);
  if (old==SS$_WASSET) sys$setast(1);
  return buff_ast[buff_curr++];
}
/* ------------------------------------------------------------------------ */
/* ------------------------------------------------------------------------ */
/* Initialize the keyboard information.                                     */
/* ------------------------------------------------------------------------ */

struct devchars {
  char class;
  char ttype;
  unsigned short width;
  unsigned long basic;
  union tt2def exten;
};

static int oldpasthru = -1;

void set_pasthru(int reset)
{
  int status;
  struct devchars dc;
  struct iosb_type iosb;

  if (in_chan==0) return;
  status = sys$qiow(0, in_chan, IO$_SENSEMODE, &iosb, 0, 0,
                    &dc, sizeof(dc),0,0,0,0);
  if (!ODD(status) || !ODD(iosb.status))
    yell("Error in sensemode pasthru: %d", ODD(status)?iosb.status:status);

#ifdef __GNUC__
  if (reset) {
    if (oldpasthru != -1)
      dc.exten.tt2$r_tt2def_bits.tt2$v_pasthru = oldpasthru;
  } else {
    if (oldpasthru == -1) oldpasthru = dc.exten.tt2$r_tt2def_bits.tt2$v_pasthru;
    dc.exten.tt2$r_tt2def_bits.tt2$v_pasthru = 1; /**/
  }
#else
  if (reset) {
    if (oldpasthru != -1) dc.exten.tt2$v_pasthru = oldpasthru;
  } else {
    if (oldpasthru == -1) oldpasthru = dc.exten.tt2$v_pasthru;
    dc.exten.tt2$v_pasthru = 1; /**/
  }
#endif
  status = sys$qiow(0, in_chan, IO$_SETMODE, &iosb, 0, 0,
                    &dc, sizeof(dc),0,0,0,0);
  if (!ODD(status) || !ODD(iosb.status))
    yell("Error in setmode pasthru: %d", ODD(status)?iosb.status:status);
}

void stop_input(void)
{
  if (INPUT_DEFINED!=INPUT_QIO) return;
  dont_restart_qio = TRUE;
  sys$cancel(in_chan);
}
void cleanup_input(void)
{
  int loop;
  unsigned status;
  if (INPUT_DEFINED!=INPUT_QIO) return;
  set_pasthru(TRUE);
}

void init_keybdio(void)
{
  unsigned status, loop;
  char input[10] = "SYS$INPUT";
  $DESCRIPTOR(input_d, "TT");
  unsigned prcpri;
  struct devchars dc;
  struct iosb_type iosb;

  last_keyhit = time(NULL);
  if (DUMB==1) INPUT_DEFINED = INPUT_GETS;
  if (DUMB==2) INPUT_DEFINED = INPUT_NONE;
  if (INPUT_DEFINED!=INPUT_QIO) return;
  status = sys$setpri(0, 0, 4, &prcpri);
  if (!ODD(status)) my_sig(status, "keybdio");

  status = sys$assign(&input_d, &in_chan, PSL$C_USER, 0, 0);
  if (!ODD(status)) my_sig(status, "keybdio2");

  status = sys$qiow(0, in_chan, IO$_SENSEMODE, &iosb, 0, 0,
                    &dc, sizeof(dc),0,0,0,0);
  if (!ODD(status) || !ODD(iosb.status))
    say("Error in sensemode2 pasthru: %d", ODD(status)?iosb.status:status);
  if (!((dc.ttype == TT$_VT100) || (dc.ttype == TT$_VT200_SERIES) ||
      (dc.ttype == TT$_VT300_SERIES) || (dc.ttype == TT$_VT102))) {
    (void)printf("Please use a VT100/VT200/VT300 compatible terminal.\n");
    exit(0);
  }

  set_pasthru(FALSE);
  lib$get_ef(&keybd_efn);
}

void start_keybdio(void)
{
  if (dont_restart_qio == FALSE) return;
  dont_restart_qio = FALSE;
  start_keyget_ast();
}

/* ------------------------------------------------------------------------ */
/* Offset is used to determine when the input buffer should be "scrolled"   */
/* to the next line of input. When the current screen position falls out of */
/* the range, the input buffer will be scrolled. I.E. (curr<OFFSET or       */
/* Curr> (screen_width-OFFSET))                                             */
/* ------------------------------------------------------------------------ */

#define OFFSET 10

/* ------------------------------------------------------------------------ */
/* Global variables used to handle the input line. Determines cursor        */
/* position, the "visible window", line length, actual position in the      */
/* string and various limits.                                               */
/* ------------------------------------------------------------------------ */

static int wstart = OFFSET, wcurs=0, wend, line_len = 0;
static int actual = 0;
static int pl = 0, max;
static char *work = NULL;
static int screen_width = 80;
static int global_maxlen;
static char prompt[80];

/* ------------------------------------------------------------------------ */
/* Check to see if the position where we are in the input string is actual- */
/* ly in the current window. If it is not, scroll the window to get it in.  */
/* If we had to scroll, or all is true, then reprint the input line.        */
/* ------------------------------------------------------------------------ */

static void check_input(int all)
{
  int oldstart, tot, old;

  if (INPUT_DEFINED != INPUT_QIO) return;
  old = sys$setast(0);
  oldstart = wstart;
  wend = wstart + screen_width- 2 * OFFSET;

  while ((actual < wstart) && (wstart > OFFSET))
  {
    wend = wstart;
    wstart = wstart - max;
  }
  while (actual >= wend)
  {
    wstart = wend;
    wend = wend + max;
  }
  wcurs = actual-wstart+OFFSET+1;
  if ((tot = (line_len-wstart+OFFSET)) > screen_width)
    tot = screen_width; 
  if ((wstart!=oldstart) || (all)) {
    (void)print_at(&work[wstart-OFFSET], tot, pl);
  }
  (void)putcursor(wcurs);
  if (old==SS$_WASSET) (void)sys$setast(1);
}

static char prompt_format[80] = "";

/* ----------------------------------------------------------------------- */
/* "Rebuild" the prompt and compare it to the old one. If there is a       */
/* change, rewrite the input line to correct for a different prompt.       */
/* flag is FALSE if is the begginning of a new input line, else it is TRUE */
/* ----------------------------------------------------------------------- */

void find_input_prompt(flag)
  int flag;
{
  char *temp;
  int temppl, loop, num = 0, added;

  if (INPUT_DEFINED != INPUT_QIO) return;
  expand_alias(prompt_format, "", &added, prompt, sizeof(prompt));
  temppl = strlen(prompt);

  /* Check if the prompt has changed */
  if (flag || (strncasecmp(prompt, work, temppl) != 0) || (temppl!=pl))
  {
    temp = (char *)mymalloc(global_maxlen + temppl + 1);
    (void)strcpy(temp, prompt);

    if (line_len == 0) num = 0;
    else num = line_len - pl;

    for (loop=0; loop<num; loop++) temp[temppl+loop] = work[pl+loop];

    line_len = num + temppl;
    if (!actual) actual = temppl;
    else actual = actual + temppl - pl;

    pl = temppl;
    if (work) myfree(work);
/*    yell("printing [%s] at len 0, pl %d", temp, pl); */
    screen_width = print_at(temp, 0, pl);  /* Was line_len */
    work = temp;
    wstart = OFFSET;
    wend = wstart + screen_width- 2 * OFFSET;
    max = screen_width - 2 * OFFSET;
    check_input(TRUE);
  }
}

/* ----------------------------------------------------------------------- */
/* Go left a character in the input string.                                */
/* ----------------------------------------------------------------------- */
void inp_backward_character(void)
{
  if (actual > pl) {
    actual--;
    wcurs--;
    (void)putcursor(wcurs);
  }
}

static void inp_history_line(int flag)
{
  char *str;
  str = get_prev_input(flag, TRUE);
  if (!str) return;
  (void)sprintf(work+pl, "%s\0", str);
  line_len = pl+strlen(str);
  wcurs = line_len;
  actual = line_len;
  check_input(TRUE);
}

void inp_forward_history(void)
 { inp_history_line(0); }

void inp_backward_history(void)
  { inp_history_line(1); }

void inp_beginning_of_line(void)
{
  actual = pl;
  check_input(TRUE);
}

void inp_forward_character(void)
{
  if (actual < line_len) {
    actual++;
    wcurs++;
    (void)putcursor(wcurs);
  }
}

int inp_backspace(void)
{
  int loop;
  if (actual > pl) {   /* Are we at the start of the line when we delete */
    for (loop=actual-1; loop<line_len; loop++)
      work[loop] = work[loop+1];
    actual--; line_len--; wcurs--;
    (void)del_char_from_input(wcurs, 1);
  }
  return 1;
}

int inp_toggle_insert_mode(void)
  { setivar(VAR_INSERT_MODE, !getivar(VAR_INSERT_MODE)); return 1; }

void inp_insert_char(char key)
{
  int loop;
  if (getivar(VAR_INSERT_MODE)) {
    if ((line_len-pl) < global_maxlen) {
      for (loop=line_len; (loop>actual); loop--)
         work[loop] = work[loop-1];
      line_len++;
      (void)insert_char((char)key, wcurs);
      work[actual++] = (char)key; wcurs++;
    }
  } else {
    if ((actual-pl) < global_maxlen) {
      (void)rep_char_in_input((char)key, wcurs);
      if (line_len==actual) line_len++;
      work[actual++] = (char)key; wcurs++;
    }
  }
}

int inp_eol(void)
{
  actual = line_len;
  wcurs = line_len;
  (void)putcursor(wcurs+pl);
  return 1;
}

static int global_done = FALSE;
int inp_send_line(void)
{
  global_done = TRUE;
  return 1;
}

int inp_delete_current_char(void)
{
  int loop;
  if (actual<line_len) {
    line_len--;
    for (loop=actual; loop<line_len; loop++)
      work[loop] = work[loop+1];
    (void)del_char_from_input(wcurs, 1);
  }
  return 1;
}

int inp_delete_to_bol(void)
{
  int loop, count;
  for (loop=actual, count = pl; loop<line_len; loop++)
    work[count++] = work[loop];
  line_len=(line_len-actual+pl);
  wcurs = pl; actual = pl;
  check_input(TRUE);
  return 1;
}

/* ----------------------------------------------------------------------- */
/* Read input from the input device and handle any characters as they come */
/* up.  Read up to maxlen characters for the input.                        */
/* ----------------------------------------------------------------------- */

static char *input_line;

static void handle_key(unsigned char real)
{
  int key, done, num = 0, ac_loop, meta_key = MAX_META;
  u_char saved_bind[10]; /* Assume there are no sequences over 10 characters */

  key = real;
  if (key<128)
    done = (int)(*META)[meta_key][key].func_bind >= 0;
    while (!done) {
      saved_bind[num++] = key;
      meta_key = (int)(*META)[meta_key][key].func_bind;
      if (meta_key<0) {
        key = input_routine();
        if (key>127) done = TRUE;
        meta_key += MAX_META;
        if (!done) done = (int)(*META)[meta_key][key].func_bind >= 0;
      } else done = TRUE;
    }

  if (key<128)
    if ((int)(*META)[meta_key][key].func_bind>0) {
      (void)(*META)[meta_key][key].func_bind((*META)[meta_key][key].param, FALSE, 0);
      return;
    }
  saved_bind[num++] = key;
  for (ac_loop=0;ac_loop<num;ac_loop++) {
    key = saved_bind[ac_loop];
    inp_insert_char(key);
  } /* for loop */
}

static char *dumb_input(char *input)
{
  gets(input);
  return input;
}

char *read_input(char input[MAXLEN], int maxlen, char *pr)
{
  int count, num, old_global_done, old_global_maxlen;
  char oldprompt[80];
  int oldline_len;

  oldline_len = line_len;
  (void)strcpy(oldprompt, prompt_format);
  if (pr)
    (void)strcpy(prompt_format, pr);
  else
    (void)strcpy(prompt_format, getvar(VAR_INPUT_PROMPT));
  old_global_maxlen = global_maxlen;
  global_maxlen = maxlen;
  if (INPUT_DEFINED == INPUT_GETS) return dumb_input(input);
  if (INPUT_DEFINED == INPUT_NONE) {
    yell("Oh shit, we ended up in a hiber loop!");
    *input = 0;
    sys$hiber();
    return NULL;
  }

  line_len = 0;
  actual = 0;

  num = sys$setast(0);
  find_input_prompt(TRUE);
  if (num == SS$_WASSET) (void)sys$setast(1);

  (void)putcursor(pl);
  old_global_done = global_done;
  global_done = FALSE;
  do {
    check_input(FALSE);
    handle_key(input_routine());
  } while (!global_done);
  global_maxlen = old_global_maxlen;
  global_done = old_global_done;
  (void)strncpy(input, &work[pl], line_len-pl);
  work[pl] = 0;
  input[line_len-pl] = 0;
  line_len=pl;
  actual = line_len;
  strcpy(prompt_format, oldprompt);
  return input;
}

/* ----------------------------------------------------------------------- */
/* End of file.                                                            */
/* ----------------------------------------------------------------------- */
