/*
      Generalized console input/output handler
      A maintanable replacement for readline()

         Updated for Bacula, Kern Sibbald, December MMIII

      This code is in part derived from code that I wrote in
      1981, so some of it is a bit old and could use a cleanup.

*/
/*
   Copyright (C) 1981-2006 Kern Sibbald
             Yes, that is 1981 no error.

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   version 2 as amended with additional clauses defined in the
   file LICENSE in the main source directory.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
   the file LICENSE for additional details.

 */

/*
 * UTF-8
 *  If the top bit of a UTF-8 string is 0 (8 bits), then it
 *    is a normal ASCII character.
 *  If the top two bits are 11 (i.e. (c & 0xC0) == 0xC0 then
 *    it is the start of a series of chars (up to 5)
 *  Each subsequent character starts with 10 (i.e. (c & 0xC0) == 0x80)
 */


#ifdef  TEST_PROGRAM
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <ctype.h>
#else

/* We are in Bacula */
#include "bacula.h"

#endif

#include <termios.h>
#ifdef HAVE_SUN_OS
extern "C" int tgetent(void *, const char *);
extern "C" int tgetnum(const char *);
extern "C" char *tgetstr (const char*, char**);
extern "C" char *tgoto (const char *, int, int);
#elif HAVE_HPUX_OS
#include <term.h>
#elif defined (__digital__) && defined (__unix__)
extern "C" int tgetent(void *, const char *);
extern "C" int tgetnum(const char *);
extern "C" char *tgetstr (const char*, char**);
extern "C" char *tgoto (const char *, int, int);

#else
#include <termcap.h>
#endif
#include "func.h"


/* From termios library */
#ifdef HAVE_HPUX_OS
static char *BC;
static char *UP;
#else
extern char *BC;
extern char *UP;
#endif

/* Forward referenced functions */
extern "C" {
static void sigintcatcher(int);
}

static void add_smap(char *str, int func);


/* Global variables */

static const char *t_up = "\n";                    /* scroll up character */
static const char *t_honk = "\007";                /* sound beep */
static char *t_il;                           /* insert line */
static char *t_dl;                           /* delete line */
static char *t_cs;                           /* clear screen */
static char *t_cl;                           /* clear line */
static int t_width = 79;                     /* terminal width */
static int t_height = 24;                    /* terminal height */
static int linsdel_ok = 0;              /* set if term has line insert & delete fncs */

static char *t_cm;                           /* cursor positioning */
static char *t_ti;                           /* init sequence */
static char *t_te;                           /* end sequence */
static char *t_do;                           /* down one line */
static char *t_sf;                           /* scroll screen one line up */

/* Keypad and Function Keys */
static char *kl;                             /* left key */
static char *kr;                             /* right */
static char *ku;                             /* up */
static char *kd;                             /* down */
static char *kh;                             /* home */
static char *kb;                             /* backspace */
static char *kD;                             /* delete key */
static char *kI;                             /* insert */
static char *kN;                             /* next page */
static char *kP;                             /* previous page */
static char *kH;                             /* home */
static char *kE;                             /* end */

#ifndef EOS
#define EOS  '\0'                     /* end of string terminator */
#endif

#define TRUE  1
#define FALSE 0
/*
 * Stab entry. Input chars (str), the length, and the desired
 *  func code.
 */
typedef struct s_stab {
   struct s_stab *next;
   int len;
   int func;
   char *str;
} stab_t;

#define MAX_STAB 30

static stab_t **stab = NULL;                 /* array of stabs by length */
static int num_stab;                         /* size of stab array */

static bool old_term_params_set = false;
static struct termios old_term_params;

/* Maintain lines in a doubly linked circular pool of lines. Each line is
   preceded by a header defined by the lstr structure */


struct lstr {                         /* line pool structure */
   struct lstr *prevl;                /* link to previous line */
   struct lstr *nextl;                /* link to next line */
   long len;                          /* length of line+header */
   char used;                         /* set if line valid */
   char line;                         /* line is actually varying length */
};

#ifdef unix
#define POOLEN 128000                 /* bytes in line pool */
#else
#define POOLEN 500                    /* bytes in line pool */
#endif
char pool[POOLEN];                    /* line pool */
#define PHDRL ((int)sizeof(struct lstr))  /* length of line header */

static struct lstr *lptr;             /* current line pointer */
static struct lstr *slptr;            /* store line pointer */
static int cl, cp;
static char *getnext(), *getprev();
static int first = 1;
static int mode_insert = 0;
static int mode_wspace = 1;           /* words separated by spaces */


static short char_map[600]= {
   0,                                  F_SOL,    /* ^a Line start */
   F_PRVWRD, /* ^b Previous word */    F_BREAK,  /* ^C break */
   F_DELCHR, /* ^D Delete character */ F_EOL,    /* ^e End of line */
   F_CSRRGT, /* ^f Right */            F_TABBAK, /* ^G Back tab */
   F_CSRLFT, /* ^H Left */             F_TAB,    /* ^I Tab */
   F_CSRDWN, /* ^J Down */             F_DELEOL, /* ^K kill to eol */
   F_CLRSCRN,/* ^L clear screen */     F_RETURN, /* ^M Carriage return */
   F_RETURN, /* ^N enter line  */      F_CONCAT, /* ^O Concatenate lines */
   F_CSRUP,  /* ^P cursor up */        F_TINS,   /* ^Q Insert character mode */
   F_PAGUP,  /* ^R Page up */          F_CENTER, /* ^S Center text */
   F_PAGDWN, /* ^T Page down */        F_DELSOL, /* ^U delete to start of line */
   F_DELWRD, /* ^V Delete word */      F_PRVWRD, /* ^W Previous word */
   F_NXTMCH, /* ^X Next match */       F_DELEOL, /* ^Y Delete to end of line */
   F_BACKGND,/* ^Z Background */       0x1B,     /* ^[=ESC escape */
   F_TENTRY, /* ^\ Entry mode */       F_PASTECB,/* ^]=paste clipboard */
   F_HOME,   /* ^^ Home */             F_ERSLIN, /* ^_ Erase line */

   ' ','!','"','#','$','%','&','\047',
   '(',')','*','+','\054','-','.','/',
   '0','1','2','3','4','5','6','7',
   '8','9',':',';','<','=','>','?',
   '@','A','B','C','D','E','F','G',
   'H','I','J','K','L','M','N','O',
   'P','Q','R','S','T','U','V','W',
   'X','Y','Z','[','\\',']','^','_',
   '\140','a','b','c','d','e','f','g',
   'h','i','j','k','l','m','n','o',
   'p','q','r','s','t','u','v','w',
   'x','y','z','{','|','}','\176',F_ERSCHR  /* erase character */

  };


/* Local variables */

#define CR '\r'                       /* carriage return */


/* Function Prototypes */

static unsigned int input_char(void);
static unsigned int t_gnc(void);
static void insert_space(char *curline, int line_len);
static void insert_hole(char *curline, int line_len);
static void forward(char *str, int str_len);
static void backup(char *curline);
static void delchr(int cnt, char *curline, int line_len);
static int iswordc(char c);
static int  next_word(char *ldb_buf);
static int  prev_word(char *ldb_buf);
static void prtcur(char *str);
static void poolinit(void);
static char * getnext(void);
static char * getprev(void);
static void putline(char *newl, int newlen);
static void t_honk_horn(void);
static void t_insert_line(void);
static void t_delete_line(void);
static void t_clrline(int pos, int width);
void t_sendl(const char *msg, int len);
void t_send(const char *msg);
void t_char(char c);
static void asclrs();
static void ascurs(int y, int x);

static void rawmode(FILE *input);
static void normode(void);
static unsigned t_getch();
static void asclrl(int pos, int width);
static void asinsl();
static void asdell();

int input_line(char *string, int length);
extern "C" {
void con_term();
}
void trapctlc();
int usrbrk();
void clrbrk();

void con_init(FILE *input)
{
   atexit(con_term);
   rawmode(input);
   trapctlc();
}

/*
 * Zed control keys
 */
void con_set_zed_keys(void)
{
   char_map[1]  = F_NXTWRD; /* ^A Next Word */
   char_map[2]  = F_SPLIT;  /* ^B Split line */
   char_map[3]  = F_EOI;    /* ^C Quit */
   char_map[4]  = F_DELCHR; /* ^D Delete character */
   char_map[5]  = F_EOF;    /* ^E End of file */
   char_map[6]  = F_INSCHR; /* ^F Insert character */
   char_map[7]  = F_TABBAK; /* ^G Back tab */
   char_map[8]  = F_CSRLFT; /* ^H Left */
   char_map[9]  = F_TAB;    /* ^I Tab */
   char_map[10] = F_CSRDWN; /* ^J Down */
   char_map[11] = F_CSRUP;  /* ^K Up */
   char_map[12] = F_CSRRGT; /* ^L Right */
   char_map[13] = F_RETURN; /* ^M Carriage return */
   char_map[14] = F_EOL;    /* ^N End of line */
   char_map[15] = F_CONCAT; /* ^O Concatenate lines */
   char_map[16] = F_MARK;   /* ^P Set marker */
   char_map[17] = F_TINS;   /* ^Q Insert character mode */
   char_map[18] = F_PAGUP;  /* ^R Page up */
   char_map[19] = F_CENTER; /* ^S Center text */
   char_map[20] = F_PAGDWN; /* ^T Page down */
   char_map[21] = F_SOL;    /* ^U Line start */
   char_map[22] = F_DELWRD; /* ^V Delete word */
   char_map[23] = F_PRVWRD; /* ^W Previous word */
   char_map[24] = F_NXTMCH; /* ^X Next match */
   char_map[25] = F_DELEOL; /* ^Y Delete to end of line */
   char_map[26] = F_DELLIN; /* ^Z Delete line */
   /* 27 = ESC */
   char_map[28] = F_TENTRY; /* ^\ Entry mode */
   char_map[29] = F_PASTECB;/* ^]=paste clipboard */
   char_map[30] = F_HOME;   /* ^^ Home */
   char_map[31] = F_ERSLIN; /* ^_ Erase line */

}

void con_term()
{
   normode();
}

#ifdef TEST_PROGRAM
/*
 * Guarantee that the string is properly terminated */
char *bstrncpy(char *dest, const char *src, int maxlen)
{
   strncpy(dest, src, maxlen-1);
   dest[maxlen-1] = 0;
   return dest;
}
#endif


/*
 * New style string mapping to function code
 */
static unsigned do_smap(unsigned c)
{
    char str[MAX_STAB];
    int len = 0;
    stab_t *tstab;
    int i, found;
    unsigned cm;

    len = 1;
    str[0] = c;
    str[1] = 0;

    cm = char_map[c];
    if (cm == 0) {
       return c;
    } else {
       c = cm;
    }
    for ( ;; ) {
       found = 0;
       for (i=len-1; i<MAX_STAB; i++) {
          for (tstab=stab[i]; tstab; tstab=tstab->next) {
             if (strncmp(str, tstab->str, len) == 0) {
                if (len == tstab->len) {
                   return tstab->func;
                }
                found = 1;
                break;                /* found possibility continue searching */
             }
          }
       }
       if (!found) {
          return len==1?c:0;
       }
       /* found partial match, so get next character and retry */
       str[len++] = t_gnc();
       str[len] = 0;
    }
}

#ifdef DEBUG_x
static void dump_stab()
{
    int i, j, c;
    stab_t *tstab;
    char buf[100];

    for (i=0; i<MAX_STAB; i++) {
       for (tstab=stab[i]; tstab; tstab=tstab->next) {
          for (j=0; j<tstab->len; j++) {
             c = tstab->str[j];
             if (c < 0x20 || c > 0x7F) {
                sprintf(buf, " 0x%x ", c);
                t_send(buf);
             } else {
                buf[0] = c;
                buf[1] = 0;
                t_sendl(buf, 1);
             }
          }
          sprintf(buf, " func=%d len=%d\n\r", tstab->func, tstab->len);
          t_send(buf);
       }
    }
}
#endif

/*
 * New routine. Add string to string->func mapping table.
 */
static void add_smap(char *str, int func)
{
   stab_t *tstab;
   int len;

   if (!str) {
      return;
   }
   len = strlen(str);
   if (len == 0) {
/*    errmsg("String for func %d is zero length\n", func); */
      return;
   }
   tstab = (stab_t *)malloc(sizeof(stab_t));
   memset(tstab, 0, sizeof(stab_t));
   tstab->len = len;
   tstab->str = (char *)malloc(tstab->len + 1);
   bstrncpy(tstab->str, str, tstab->len + 1);
   tstab->func = func;
   if (tstab->len > num_stab) {
      printf("stab string too long %d. Max is %d\n", tstab->len, num_stab);
      exit(1);
   }
   tstab->next = stab[tstab->len-1];
   stab[tstab->len-1] = tstab;
/* printf("Add_smap tstab=%x len=%d func=%d tstab->next=%x\n\r", tstab, len,
          func, tstab->next); */

}


/* Get the next character from the terminal - performs table lookup on
   the character to do the desired translation */
static unsigned int
input_char()
{
    unsigned c;

    if ((c=t_gnc()) <= 599) {         /* IBM generates codes up to 260 */
          c = do_smap(c);
    } else if (c > 1000) {            /* stuffed function */
       c -= 1000;                     /* convert back to function code */
    }
    if (c <= 0) {
        t_honk_horn();
    }
    /* if we got a screen size escape sequence, read height, width */
    if (c == F_SCRSIZ) {
       int y, x;
       y = t_gnc() - 0x20;        /* y */
       x = t_gnc() - 0x20;        /* x */
       c = input_char();
    }
    return c;
}


/* Get a complete input line */

int
input_line(char *string, int length)
{
   char curline[2000];                /* edit buffer */
   int noline;
   unsigned c;
   int more;
   int i;

    if (first) {
       poolinit();                   /* build line pool */
       first = 0;
    }
    noline = 1;                       /* no line fetched yet */
    for (cl=cp=0; cl<length && cl<(int)sizeof(curline); ) {
       if (usrbrk()) {
          clrbrk();
          break;
       }
       switch (c=input_char()) {
       case F_RETURN:                /* CR */
           t_sendl("\r\n", 2);       /* yes, print it and */
           goto done;                /* get out */
       case F_CLRSCRN:               /* clear screen */
          asclrs();
          t_sendl(curline, cl);
          ascurs(0, cp);
          break;
       case F_CSRUP:
           if (noline) {             /* no line fetched yet */
               getnext();            /* getnext so getprev gets current */
               noline = 0;           /* we now have line */
           }
           bstrncpy(curline, getprev(), sizeof(curline));
           prtcur(curline);
           break;
       case F_CSRDWN:
           noline = 0;               /* mark line fetched */
           bstrncpy(curline, getnext(), sizeof(curline));
           prtcur(curline);
           break;
       case F_INSCHR:
           insert_space(curline, sizeof(curline));
           break;
       case F_DELCHR:
           delchr(1, curline, sizeof(curline));       /* delete one character */
           break;
       case F_CSRLFT:                /* Backspace */
           backup(curline);
           break;
       case F_CSRRGT:
           forward(curline, sizeof(curline));
           break;
       case F_ERSCHR:                /* Rubout */
           backup(curline);
           delchr(1, curline, sizeof(curline));
           if (cp == 0) {
              t_char(' ');
              t_char(0x8);
           }
           break;
       case F_DELEOL:
           t_clrline(0, t_width);
           if (cl > cp)
               cl = cp;
           break;
       case F_NXTWRD:
           i = next_word(curline);
           while (i--) {
              forward(curline, sizeof(curline));
           }
           break;
       case F_PRVWRD:
           i = prev_word(curline);
           while (i--) {
              backup(curline);
           }
           break;
       case F_DELWRD:
           delchr(next_word(curline), curline, sizeof(curline)); /* delete word */
           break;
       case F_NXTMCH:                /* Ctl-X */
           if (cl==0) {
               *string = EOS;        /* terminate string */
               return(c);            /* give it to him */
           }
           /* Note fall through */
       case F_DELLIN:
       case F_ERSLIN:
           while (cp > 0) {
              backup(curline);      /* backup to beginning of line */
           }
           t_clrline(0, t_width);     /* erase line */
           cp = 0;
           cl = 0;                   /* reset cursor counter */
           t_char(' ');
           t_char(0x8);
           break;
       case F_SOL:
           while (cp > 0) {
              backup(curline);
           }
           break;
       case F_EOL:
           while (cp < cl) {
               forward(curline, sizeof(curline));
           }
           while (cp > cl) {
               backup(curline);
           }
           break;
       case F_TINS:                  /* toggle insert mode */
           mode_insert = !mode_insert;  /* flip bit */
           break;
       default:
           if (c > 255) {            /* function key hit */
               if (cl==0) {          /* if first character then */
                  *string = EOS;     /* terminate string */
                  return c;          /* return it */
               }
               t_honk_horn();        /* complain */
           } else {
               if ((c & 0xC0) == 0xC0) {
                  if ((c & 0xFC) == 0xFC) {
                     more = 5;
                  } else if ((c & 0xF8) == 0xF8) {
                     more = 4;
                  } else if ((c & 0xF0) == 0xF0) {
                     more = 3;
                  } else if ((c & 0xE0) == 0xE0) {
                     more = 2;
                  } else {
                     more = 1;
                  }
               } else {
                  more = 0;
               }
               if (mode_insert) {
                  insert_space(curline, sizeof(curline));
               }
               curline[cp++] = c;    /* store character in line being built */
               t_char(c);      /* echo character to terminal */
               while (more--) {
                  c= input_char();
                  insert_hole(curline, sizeof(curline));
                  curline[cp++] = c;    /* store character in line being built */
                  t_char(c);      /* echo character to terminal */
               }
               if (cp > cl) {
                  cl = cp;           /* keep current length */
                  curline[cp] = 0;
               }
           }
           break;
       }                             /* end switch */
    }
/* If we fall through here rather than goto done, the line is too long
   simply return what we have now. */
done:
   curline[cl++] = EOS;              /* terminate */
   bstrncpy(string,curline,length);           /* return line to caller */
   /* Note, put line zaps curline */
   putline(curline,cl);              /* save line for posterity */
   return 0;                         /* give it to him/her */
}

/* Insert a space at the current cursor position */
static void
insert_space(char *curline, int curline_len)
{
   int i;

   if (cp > cl || cl+1 > curline_len) {
      return;
   }
   /* Note! source and destination overlap */
   memmove(&curline[cp+1],&curline[cp],i=cl-cp);
   cl++;
   curline[cp] = ' ';
   i = 0;
   while (cl > cp) {
      forward(curline, curline_len);
      i++;
   }
   while (i--) {
      backup(curline);
   }
}


static void
insert_hole(char *curline, int curline_len)
{
   int i;

   if (cp > cl || cl+1 > curline_len) {
      return;
   }
   /* Note! source and destination overlap */
   memmove(&curline[cp+1], &curline[cp], i=cl-cp);
   cl++;
   curline[cl] = 0;
}


/* Move cursor forward keeping characters under it */
static void
forward(char *str, int str_len)
{
   if (cp > str_len) {
      return;
   }
   if (cp >= cl) {
       t_char(' ');
       str[cp+1] = ' ';
       str[cp+2] = 0;
   } else {
       t_char(str[cp]);
       if ((str[cp] & 0xC0) == 0xC0) {
          cp++;
          while ((str[cp] & 0xC0) == 0x80) {
             t_char(str[cp]);
             cp++;
          }
          cp--;
       }
   }
   cp++;
}

/* How many characters under the cursor */
static int
char_count(int cptr, char *str)
{
   int cnt = 1;
   if (cptr > cl) {
      return 0;
   }
   if ((str[cptr] & 0xC0) == 0xC0) {
      cptr++;
      while ((str[cptr] & 0xC0) == 0x80) {
         cnt++;
         cptr++;
      }
   }
   return cnt;
}

/* Backup cursor keeping characters under it */
static void
backup(char *str)
{
    if (cp == 0) {
       return;
    }
    while ((str[cp] & 0xC0) == 0x80) {
       cp--;
    }
    t_char('\010');
    cp--;
}

/* Delete the character under the cursor */
static void
delchr(int del, char *curline, int line_len)
{
   int i, cnt;

   if (cp > cl || del == 0) {
      return;
   }
   while (del-- && cp > 0) {
      cnt = char_count(cp, curline);
      if ((i=cl-cp-cnt) > 0) {
         memcpy(&curline[cp], &curline[cp+cnt], i);
      }
      cl -= cnt;
      curline[cl] = EOS;
      t_clrline(0, t_width);
      i = 0;
      while (cl > cp) {
         forward(curline, line_len);
         i++;
      }
      while (i--) {
         backup(curline);
      }
   }
}

/* Determine if character is part of a word */
static int
iswordc(char c)
{
   if (mode_wspace)
      return !isspace(c);
   if (c >= '0' && c <= '9')
      return true;
   if (c == '$' || c == '%')
      return true;
   return isalpha(c);
}

/* Return number of characters to get to next word */
static int
next_word(char *ldb_buf)
{
   int ncp;

   if (cp > cl)
      return 0;
   ncp = cp;
   for ( ; ncp<cl && iswordc(*(ldb_buf+ncp)); ncp++) ;
   for ( ; ncp<cl && !iswordc(*(ldb_buf+ncp)); ncp++) ;
   return ncp-cp;
}

/* Return number of characters to get to previous word */
static int
prev_word(char *ldb_buf)
{
    int ncp, i;

    if (cp == 0)                      /* if at begin of line stop now */
        return 0;
    if (cp > cl)                      /* if past eol start at eol */
        ncp=cl+1;
    else
        ncp = cp;
    /* backup to end of previous word - i.e. skip special chars */
    for (i=ncp-1; i && !iswordc(*(ldb_buf+i)); i--) ;
    if (i == 0) {                     /* at beginning of line? */
        return cp;                    /* backup to beginning */
    }
    /* now move back through word to beginning of word */
    for ( ; i && iswordc(*(ldb_buf+i)); i--) ;
    ncp = i+1;                        /* position to first char of word */
    if (i==0 && iswordc(*ldb_buf))    /* check for beginning of line */
        ncp = 0;
    return cp-ncp;                    /* return count */
}

/* Display new current line */
static void
prtcur(char *str)
{
    while (cp > 0) {
       backup(str);
    }
    t_clrline(0,t_width);
    cp = cl = strlen(str);
    t_sendl(str, cl);
}


/* Initialize line pool. Split pool into two pieces. */
static void
poolinit()
{
   slptr = lptr = (struct lstr *)pool;
   lptr->nextl = lptr;
   lptr->prevl = lptr;
   lptr->used = 1;
   lptr->line = 0;
   lptr->len = POOLEN;
}


/* Return pointer to next line in the pool and advance current line pointer */
static char *
getnext()
{
   do {                              /* find next used line */
      lptr = lptr->nextl;
   } while (!lptr->used);
   return (char *)&lptr->line;
}

/* Return pointer to previous line in the pool */
static char *
getprev()
{
   do {                              /* find previous used line */
      lptr = lptr->prevl;
   } while (!lptr->used);
   return (char *)&lptr->line;
}

static void
putline(char *newl, int newlen)
{
   struct lstr *nptr;                /* points to next line */
   char *p;

   lptr = slptr;                     /* get ptr to last line stored */
   lptr = lptr->nextl;               /* advance pointer */
   if ((char *)lptr-pool+newlen+PHDRL > POOLEN) { /* not enough room */
       lptr->used = 0;               /* delete line */
       lptr = (struct lstr *)pool;   /* start at beginning of buffer */
   }
   while (lptr->len < newlen+PHDRL) { /* concatenate buffers */
       nptr = lptr->nextl;           /* point to next line */
       lptr->nextl = nptr->nextl;    /* unlink it from list */
       nptr->nextl->prevl = lptr;
       lptr->len += nptr->len;
   }
   if (lptr->len > newlen + 2 * PHDRL) { /* split buffer */
       nptr = (struct lstr *)((char *)lptr + newlen + PHDRL);
       /* Appropriate byte alignment - normally 2 byte, but on
          sparc we need 4 byte alignment, so we always do 4 */
       if (((long unsigned)nptr & 3) != 0) { /* test four byte alignment */
           p = (char *)nptr;
           nptr = (struct lstr *)((((long unsigned) p) & ~3) + 4);
       }
       nptr->len = lptr->len - ((char *)nptr - (char *)lptr);
       lptr->len -= nptr->len;
       nptr->nextl = lptr->nextl;    /* link in new buffer */
       lptr->nextl->prevl = nptr;
       lptr->nextl = nptr;
       nptr->prevl = lptr;
       nptr->used = 0;
   }
   memcpy(&lptr->line,newl,newlen);
   lptr->used = 1;                   /* mark line used */
   slptr = lptr;                     /* save as stored line */
}

#ifdef  DEBUGOUT
static void
dump(struct lstr *ptr, char *msg)
{
    printf("%s buf=%x nextl=%x prevl=%x len=%d used=%d\n",
        msg,ptr,ptr->nextl,ptr->prevl,ptr->len,ptr->used);
    if (ptr->used)
        printf("line=%s\n",&ptr->line);
}
#endif  /* DEBUGOUT */


/* Honk horn on terminal */
static void
t_honk_horn()
{
   t_send(t_honk);
}

/* Insert line on terminal */
static void
t_insert_line()
{
   asinsl();
}

/* Delete line from terminal */
static void
t_delete_line()
{
   asdell();
}

/* clear line from pos to width */
static void
t_clrline(int pos, int width)
{
    asclrl(pos, width);           /* clear to end of line */
}

/* Helper function to add string preceded by
 *  ESC to smap table */
static void add_esc_smap(const char *str, int func)
{
   char buf[1000];
   buf[0] = 0x1B;                     /* esc */
   bstrncpy(buf+1, str, sizeof(buf)-1);
   add_smap(buf, func);
}

/* Set raw mode on terminal file.  Basically, get the terminal into a
   mode in which all characters can be read as they are entered.  CBREAK
   mode is not sufficient.
 */
static void rawmode(FILE *input)
{
   struct termios t;
   static char term_buf[2048];
   static char *term_buffer = term_buf;
   char *termtype = (char *)getenv("TERM");

   /* Make sure we are dealing with a terminal */
   if (!isatty(fileno(input))) {
      return;
   }
   if (tcgetattr(0, &old_term_params) != 0) {
      printf("conio: Cannot tcgetattr()\n");
      exit(1);
   }
   old_term_params_set = true;
   t = old_term_params;
   t.c_cc[VMIN] = 1; /* satisfy read after 1 char */
   t.c_cc[VTIME] = 0;
   t.c_iflag &= ~(BRKINT | IGNPAR | PARMRK | INPCK |
                  ISTRIP | ICRNL | IXON | IXOFF | INLCR | IGNCR);
   t.c_iflag |= IGNBRK;
   t.c_oflag |= ONLCR;
   t.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL | ICANON |
                  NOFLSH | TOSTOP);
   tcflush(0, TCIFLUSH);
   if (tcsetattr(0, TCSANOW, &t) == -1) {
      printf("Cannot tcsetattr()\n");
   }

   /* Defaults, the main program can override these */
   signal(SIGQUIT, SIG_IGN);
   signal(SIGHUP, SIG_IGN);
   signal(SIGINT, sigintcatcher);
   signal(SIGWINCH, SIG_IGN);

   if (!termtype) {
      printf("Cannot get terminal type.\n");
      normode();
      exit(1);
   }
   if (tgetent(term_buffer, termtype) < 0) {
      printf("Cannot get terminal termcap entry.\n");
      normode();
      exit(1);
   }
   t_width = t_height = -1;
   t_width = tgetnum("co") - 1;
   t_height = tgetnum("li");
   BC = NULL;
   UP = NULL;
   t_cm = (char *)tgetstr("cm", &term_buffer);
   t_cs = (char *)tgetstr("cl", &term_buffer); /* clear screen */
   t_cl = (char *)tgetstr("ce", &term_buffer); /* clear line */
   t_dl = (char *)tgetstr("dl", &term_buffer); /* delete line */
   t_il = (char *)tgetstr("al", &term_buffer); /* insert line */
   t_honk = (char *)tgetstr("bl", &term_buffer); /* beep */
   t_ti = (char *)tgetstr("ti", &term_buffer);
   t_te = (char *)tgetstr("te", &term_buffer);
   t_up = (char *)tgetstr("up", &term_buffer);
   t_do = (char *)tgetstr("do", &term_buffer);
   t_sf = (char *)tgetstr("sf", &term_buffer);

   num_stab = MAX_STAB;                  /* get default stab size */
   stab = (stab_t **)malloc(sizeof(stab_t *) * num_stab);
   memset(stab, 0, sizeof(stab_t *) * num_stab);

   /* Key bindings */
   kl = (char *)tgetstr("kl", &term_buffer);
   kr = (char *)tgetstr("kr", &term_buffer);
   ku = (char *)tgetstr("ku", &term_buffer);
   kd = (char *)tgetstr("kd", &term_buffer);
   kh = (char *)tgetstr("kh", &term_buffer);
   kb = (char *)tgetstr("kb", &term_buffer);
   kD = (char *)tgetstr("kD", &term_buffer);
   kI = (char *)tgetstr("kI", &term_buffer);
   kN = (char *)tgetstr("kN", &term_buffer);
   kP = (char *)tgetstr("kP", &term_buffer);
   kH = (char *)tgetstr("kH", &term_buffer);
   kE = (char *)tgetstr("kE", &term_buffer);

   add_smap(kl, F_CSRLFT);
   add_smap(kr, F_CSRRGT);
   add_smap(ku, F_CSRUP);
   add_smap(kd, F_CSRDWN);
   add_smap(kI, F_TINS);
   add_smap(kN, F_PAGDWN);
   add_smap(kP, F_PAGUP);
   add_smap(kH, F_HOME);
   add_smap(kE, F_EOF);


   add_esc_smap("[A",   F_CSRUP);
   add_esc_smap("[B",   F_CSRDWN);
   add_esc_smap("[C",   F_CSRRGT);
   add_esc_smap("[D",   F_CSRLFT);
   add_esc_smap("[1~",  F_HOME);
   add_esc_smap("[2~",  F_TINS);
   add_esc_smap("[3~",  F_DELCHR);
   add_esc_smap("[4~",  F_EOF);
   add_esc_smap("f",    F_NXTWRD);
   add_esc_smap("b",    F_PRVWRD);
}


/* Restore tty mode */
static void normode()
{
   if (old_term_params_set) {
      tcsetattr(0, TCSANOW, &old_term_params);
      old_term_params_set = false;
   }
}

/* Get next character from terminal/script file/unget buffer */
static unsigned
t_gnc()
{
    return t_getch();
}


/* Get next character from OS */
static unsigned t_getch(void)
{
   unsigned char c;

   if (read(0, &c, 1) != 1) {
      c = 0;
   }
   return (unsigned)c;
}

/* Send message to terminal - primitive routine */
void
t_sendl(const char *msg, int len)
{
   write(1, msg, len);
}

void
t_send(const char *msg)
{
   if (msg == NULL) {
      return;
   }
   t_sendl(msg, strlen(msg));    /* faster than one char at time */
}

/* Send single character to terminal - primitive routine - */
void
t_char(char c)
{
   (void)write(1, &c, 1);
}


static int brkflg = 0;              /* set on user break */

/* Routine to return true if user types break */
int usrbrk()
{
   return brkflg;
}

/* Clear break flag */
void clrbrk()
{
   brkflg = 0;

}

/* Interrupt caught here */
static void sigintcatcher(int sig)
{
   brkflg++;
   if (brkflg > 3) {
      normode();
      exit(1);
   }
   signal(SIGINT, sigintcatcher);
}


/* Trap Ctl-C */
void trapctlc()
{
   signal(SIGINT, sigintcatcher);
}


/* ASCLRL() -- Clear to end of line from current position */
static void asclrl(int pos, int width)
{
   int i;

   if (t_cl) {
       t_send(t_cl);                 /* use clear to eol function */
       return;
   }
   if (pos==1 && linsdel_ok) {
       t_delete_line();              /* delete line */
       t_insert_line();              /* reinsert it */
       return;
   }
   for (i=1; i<=width-pos+1; i++)
       t_char(' ');                  /* last resort, blank it out */
   for (i=1; i<=width-pos+1; i++)    /* backspace to original position */
       t_char(0x8);
   return;

}


/* ASCURS -- Set cursor position */
static void ascurs(int y, int x)
{
   t_send((char *)tgoto(t_cm, x, y));
}


/* ASCLRS -- Clear whole screen */
static void asclrs()
{
   ascurs(0,0);
   t_send(t_cs);
}



/* ASINSL -- insert new line after cursor */
static void asinsl()
{
   t_clrline(0, t_width);
   t_send(t_il);                      /* insert before */
}

/* ASDELL -- Delete line at cursor */
static void asdell()
{
   t_send(t_dl);
}
