/*
 * pgputs (paging module)
 */

#include <stdio.h>
#include <ctype.h>
#include <setjmp.h>
#include <signal.h>
#include <termio.h>

/*
 *   Following four globals are user accessible.
 */
int      lpp   = 22;         /* lines per page */
int      cpl   = 79;         /* chars per line */
char      *prompt   = "-- more -- ";   /* prompt string */
char      quit   = 'q';         /* quit character */

void      rest(), reset();
char      *malloc(), *bbuf;
int      bfrlns   = 0;         /* cur # of lines buffered */
static int   totmem;
static int   curmem;
static struct   termio tb, stb;
static int   tfd;
static int   eraselen;         /* how much to erase */
static int   plen;            /* length of prompt */
static int   exitcode;
static int   intr = 0;
static jmp_buf   jmpbuf;

/*
 *   paging subroutine.
 *   Returns:   -1   -- error (see errno)
 *          0   -- user requested exit
 *          1   -- success
 */
pgputs(buf)
register char *buf;
{
   register int lns;      /* lines occupied by current buffer */
   static int first = 1;

   if (first == 1) {
      pginit();
      first = 0;
   }
   exitcode = 1;
   if (!*buf)
      return(exitcode);
   lns = (((screenlen(buf)-1) - 1) / cpl) + 1;
   if (bfrlns+lns > lpp)
      rest("");
   if (exitcode == 1) {
      apnd(buf);
      bfrlns += lns;
   }
   return(exitcode);
}

static
apnd(lbuf)
char *lbuf;
{
   register char *p;
   register int len;

   p = lbuf;
   if ((curmem + (len=strlen(p))) > totmem)
      pgflsh(1);
   strcpy(&bbuf[curmem], p);
   curmem += len;
}

static void
rest(str)
char *str;
{
   char lbuf[260], *cp;
   static int lppsave;
   int oldsig;

   pgflsh(1);
   bfrlns = 0;
   if (exitcode != 1)
      return;
   if (lppsave) {
      lpp = lppsave;
      lppsave = 0;
   }
   if ((tfd = open("/dev/tty", 2)) == -1) {
      exitcode = -1;
      return;
   }
   getty();
   if (setjmp(jmpbuf)) {
      pgflsh(0);
      goto out;
   }
   intr = 0;
   oldsig = (int)signal(SIGINT, reset);
   for (;intr == 0;) {
      rawmode();
      write(tfd, prompt, plen);
      cp = lbuf;
      eraselen = plen;
      lbuf[0] = '\0';
      while (read(tfd, cp, 1) == 1) {
         if (*cp == ' ' && cp == lbuf) {
            lbuf[0] = '\0';
            break;
         }
         else if (*cp == '\n') {
            *cp = '\0';
            eraselen += (cp - &lbuf[0]);
            lppsave = lpp;
            lpp = 1;
            break;
         }
         else if (lbuf[0] == quit) {
            erasep();
            write(tfd, "\n", 1);
            exitcode = 0;
            lbuf[0] = '\0';
            break;
         }
         else if (*cp == tb.c_cc[VERASE]) {
            if (tb.c_lflag&ECHOE)
               write(tfd, "\b \b", 3);
            else
               write(tfd, cp, 1);
            if (cp > lbuf)
               --cp;
         }
         else if (*cp == tb.c_cc[VKILL]) {
            write(tfd, cp, 1);
            if (tb.c_lflag&ECHOK) {
               eraselen += (cp-&lbuf[0]);
               erasep();
            }
            write(tfd, prompt, plen);
            eraselen = plen;
            cp = lbuf;
         }
         else
            write(tfd, "\07", 1);
      }
      if (!lbuf[0])
         break;
   }
   erasep();
out:
   resetty();
   signal(SIGINT, oldsig);
   close(tfd);
   return;
}

static void
reset()
{
   intr++;
   exitcode = 0;
   fprintf(stderr, "\r\n");
   longjmp(jmpbuf,1);
}

static
screenlen(s)
register char *s;
{
   register n;

   n = 0;
   while (*s) {
      switch(*s++) {
      case '\t':
         n += 8 - n%8;
         break;
      case '\b':
         n--;
         break;
      default:
         n++;
      }
   }
   return(n);
}

static
erasep()      /* erase prompt */
{
   register int i;

   write(tfd, "\r", 1);
   for (i = 0; i < eraselen; i++)
      write(tfd, " ", 1);
   write(tfd, "\r", 1);
}

static
pginit()
{
   plen = strlen(prompt);
   bbuf = malloc((totmem=(cpl+1)*(24)+20));
   if (bbuf == NULL) {
      fputs("pg: malloc error\n", stderr);
      exit(2);
   }
   *bbuf = '\0';
}

pgflsh(flg)
{
   /*
    *   Initialized yet?
    */
   if (bbuf) {
      if (flg && curmem) {
         if (write(1, bbuf, curmem) != curmem)
            exitcode = -1;
      }
      *bbuf = '\0';
      curmem = 0;
   }
}

static
rawmode()
{
   tb.c_lflag &= ~(ECHONL|ECHO|ICANON);
   tb.c_cc[VMIN] = 1;
   tb.c_cc[VTIME] = 0;
   ioctl(tfd, TCSETA, &tb);
}

static
resetty()
{
   ioctl(tfd, TCSETA, &stb);
}

static
getty()
{
   ioctl(tfd, TCGETA, &tb);
   stb = tb;
}
