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

#include <stdio.h>
#include <trmdef.h>
#include <ssdef.h>
#include <smgdef.h>
#include <descrip.h>
#include <psldef.h>
#include <iodef.h>
#include <ttdef.h>
#include <tt2def.h>
#include <ssdef.h>
#include <string.h>
#include <stdlib.h>

#include "global.h"
#include "constants.h"
#include "binding.h"
#include "window.h"

extern server_ptr gsrv;

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

extern int insert_mode, sys$qiow(), sys$setpri(), sys$assign();
extern int print_at(), putcursor(), sys$setast(), del_char_from_input();
extern int get_prev_input(), redraw(), insert_char(), rep_char_in_input();
extern int lib$get_ef(), DUMB;
struct itemlistcell list[6];
unsigned in_chan = 0;
char init = '\0';

int INPUT_DEFINED = TRUE;

#define MAXKEYS 512
int bindings[MAXKEYS];

extern struct binds META[MAX_META+1][128];
unsigned long keybd_efn;
unsigned char keyget();
unsigned char (*input_routine)() = keyget;

/* ------------------------------------------------------------------------ */
/* Read a keypress from channel in_chan and return the value of the key     */
/* that is found. KeyGet is blocking.                                       */
/* ------------------------------------------------------------------------ */

unsigned char keyget(void)
{
  int status;
  int i;
  unsigned char inp[5];
  unsigned char pic = '\0';
  struct iosb_type iosb;

  if (!INPUT_DEFINED) return 0;
  list[5].buffer_length = 1;
  list[5].item_code = TRM$_PICSTRNG;
  list[5].buffer_addr = (int)(&pic);
  list[5].return_addr = 0;

  do {
    status = sys$qiow(keybd_efn ,in_chan, IO$_READVBLK+IO$M_EXTEND, &iosb,
                      0, 0, inp, sizeof(inp), 0, 0, list, sizeof(list));
  } while ((int)iosb.status == SS$_OPINCOMPL);
  if (!ODD(status)) my_sig(status, "keyget");
  return inp[1];
}

/* ------------------------------------------------------------------------ */
/* Intialize the item list for the qio call to read data from in_chan.      */
/* ------------------------------------------------------------------------ */

void init_keybd_list(void)
{

#ifdef __GNUC__
  unsigned tfunc;
#else
  unsigned short tfunc;
#endif
  int loop;
  (void)lib$get_ef(&keybd_efn);
  for (loop=0;loop<MAXKEYS;loop++)
    bindings[loop] = loop;

  list[0].buffer_length = 0;
  list[0].item_code = TRM$_EDITMODE;
  list[0].buffer_addr = TRM$K_EM_RDVERIFY;
  list[0].return_addr = 0;

  list[1].buffer_length = 0;
  list[1].item_code = TRM$_ESCTRMOVR;
  list[1].buffer_addr = 0;
  list[1].return_addr = 0;

  list[2].buffer_length = 0;
  list[2].item_code = TRM$_INIOFFSET;
  list[2].buffer_addr = 0;
  list[2].return_addr = 0;

  tfunc = TRM$M_TM_NOEDIT + TRM$M_TM_NOFILTR /*+ TRM$M_TM_ESCAPE*/;

  list[3].buffer_length = 0;
  list[3].item_code = TRM$_MODIFIERS;
  list[3].buffer_addr = tfunc;
  list[3].return_addr = 0;

  list[4].buffer_length = 1;
  list[4].item_code = TRM$_INISTRNG;
  list[4].buffer_addr = (int)&init;
  list[4].return_addr = 0;
}

/* ------------------------------------------------------------------------ */
/* Initialize the keyboard information.                                     */
/* ------------------------------------------------------------------------ */

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

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))
    say("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 {
    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 {
    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))
    say("Error in setmode pasthru: %d", ODD(status)?iosb.status:status);
}

void cleanup(void)
{
  if (!INPUT_DEFINED) return;
  set_pasthru(TRUE);
}

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

  if (DUMB) INPUT_DEFINED = FALSE;
  if (!INPUT_DEFINED) 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);
  init_keybd_list();
}

/* ------------------------------------------------------------------------ */
/* 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.                                               */
/* ------------------------------------------------------------------------ */

int wstart = OFFSET, wcurs=0, wend, line_len = 0;
int actual = 0;
int pl = 0, max;
char *work = NULL;
int screen_width = 0;
int global_maxlen;
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.        */
/* ------------------------------------------------------------------------ */

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

  if (!INPUT_DEFINED) 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);
}

char prompt_format[80] = "";
char real_prompt[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;

  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 *)malloc(global_maxlen + temppl + 1);
    (void)strcpy(temp, prompt);
    if (!INPUT_DEFINED) {
      (void)strcpy(real_prompt, prompt);
      return;
    }

    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;
    screen_width = print_at(temp, 0, pl);  /* Was line_len */
    if (work) free(work);
    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);
  }
}

void inp_history_line(int flag)
{
  char str[MAXLEN];
  (void)get_prev_input(str, flag);
  (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);
  }
}

void 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);
  }
}

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

char *input_line;
int global_done = FALSE;

void handle_key(unsigned char real)
{
  char str[MAXLEN];
  int loop, count, key, done;
  int bind_loop, bind_indx = -1, meta_key = MAX_META;

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

  if (key<128)
    if ((int)META[meta_key][key].func>0) {
      (void)META[meta_key][key].func(META[meta_key][key].param, FALSE, 0);
      return;
    }
  switch (key) {
  case SMG$K_TRM_DOWN:
    inp_forward_history();
  break;
  case SMG$K_TRM_UP:
    inp_backward_history();
  break;
  case SMG$K_TRM_LEFT :
    inp_backward_character();
  break;
  case SMG$K_TRM_RIGHT :
    inp_forward_character();
  break;
  case 127:
    inp_backspace();
  break;
  case  1:
    insert_mode = !(insert_mode);
  break;
  case  4:
    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, 1);
    }
  break;
  case  5:
    actual = line_len;
    wcurs = line_len;
    (void)putcursor(wcurs+pl);
  break;
  case 12:
    (void)redraw();
  break;
  case 13:
    global_done = TRUE;
  break;
  case 21:
    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);
  break;
  default:
    if (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++;
      }
    }
  break;
  } /* case statement */
}

char *dumb_input(char *input)
{
  (void)printf(real_prompt);
  (void)gets(input);
  return input;
}

extern char input_prompt[];

char *read_input(input, maxlen, pr)
  char input[MAXLEN];
  int maxlen;
  char *pr;
{
  int count, num;
  char str[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, input_prompt);
  global_maxlen = maxlen;
  if (!INPUT_DEFINED) return dumb_input(input);

  line_len = 0;
  actual = 0;

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

  (void)putcursor(pl);
  global_done = FALSE;
  do {
    check_input(FALSE);
    handle_key(input_routine());
  } while (!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.                                                            */
/* ----------------------------------------------------------------------- */

void remap(void)
{
  int ack;
  say("+++ Enter the key to change:");
  ack = input_routine();
  say("+++ Hit the new key:");
  bindings[ack] = input_routine();
}
