/*
** The functions in this file handle redisplay. There are two halves, the
** ones that update the virtual display screen, and the ones that make the
** physical display screen the same as the virtual display screen. These
** functions use hints that are left in the windows by the commands.
**
 **************************************************************************/

#include "Include.h"

char *lastptr = NULL;         /* ptr to lastmesg[]    */

int DNEAR eolexist = TRUE;    /* does clear to EOL exist?   */
int DNEAR vtrow;              /* Row location of SW cursor  */
int DNEAR vtcol;              /* Column location of SW cursor */
int DNEAR ttrow = HUGE;       /* Row location of HW cursor  */
int DNEAR taboff;             /* tab offset for display  */
int DNEAR currow;             /* Cursor row        */

short mode_color = -1;
short ml_color = -1;

/// struct VIDEO typedef
typedef struct VIDEO
{
   int   v_flag;        // Flags (see defines below)
   int   v_fcolor;      // current forground color
   int   v_bcolor;      // current background color
   int   v_rfcolor;     // requested forground color
   int   v_rbcolor;     // requested background color
   char  v_text[1];     // Screen data.
}  VIDEO;
//-

#define VFCHG  0x0001         /* Changed flag      */
#define VFEXT  0x0002         /* extended (beyond column 80)   */
#define VFREV  0x0004         /* reverse video status    */
#define VFREQ  0x0008         /* reverse video request   */
#define VFCOL  0x0010         /* color change requested  */

static VIDEO   **vscreen;            /* Virtual screen. */
static VIDEO   **pscreen;            /* Physical screen. */

// some local function declarations
VIDEO   *alloc_cline(int, char *);
REG      mlputf(int);
REG      updateline(int, struct VIDEO *, struct VIDEO *);
REG      mlputi(int, int);
REG      mlputli(long, int);
REG      mlputs(char *);
REG      modeline(WINDOW *);
REG      reframe(WINDOW *);
REG      updall(WINDOW *);
REG      upddex(void);
REG      updext(void);
REG      updgar(void);
REG      updone(WINDOW *);
REG      updpos(void);
REG      updupd(int);
REG      vteeol(void);
REG      vtmove(int, int);
REG      vtputc(int);


/*
** Initialize the data structures used by the display code. The edge vectors
** used to access the screens are set up. The operating system's terminal I/O
** channel is set up. All the other things get initialized at compile time.
** The original window has "WFCHG" set, so that it will get completely
** redrawn on the first call to "update".
**
 *******************************************************************************/

//First MAX_CACHELINES lines are static, rest are malloc
#define MAX_CACHELINES 27
//NOTE: assume NCOL is 80 in ansi.c
#define STATIC_NCOL 100
char vcache[(STATIC_NCOL+sizeof(VIDEO))*MAX_CACHELINES];
char pcache[(STATIC_NCOL+sizeof(VIDEO))*MAX_CACHELINES];

/// vtinit()
REG vtinit(void)
{
   register int i;
   register VIDEO *vp;

   TTopen();      /* open the screen */
   TTkopen();     /* open the keyboard */
   TTrev(FALSE);


   /* allocate the virtual screen pointer array */
   vscreen = (VIDEO **) malloc(term.t_mrow*sizeof(VIDEO *));
   if (vscreen == NULL)
      meexit(1);

   /* allocate the physical shadow screen array */
   pscreen = (VIDEO **) malloc(term.t_mrow*sizeof(VIDEO *));
   if (pscreen == NULL)
      meexit(1);

   /* for every line in the display */
   for (i = 0; i < term.t_mrow; ++i) {

      vp = alloc_cline(i, vcache);

      vp->v_flag = 0;      /* init change clags */
      vp->v_rfcolor = 7;   /* init fore/background colors */
      vp->v_rbcolor = 0;
      /* connect virtual line to line array */
      vscreen[i] = vp;

      vp = alloc_cline(i, pcache);

      vp->v_flag = 0;
      pscreen[i] = vp;
   }

   return(1);
}
//-

/// upscreen()
/*
** upscreen:   user routine to force a screen update
** always finishes complete update
 ***********************************************************************/
STD upscreen(int f, int n)
{
   update(TRUE);
   return(TRUE);
}
//-

/// update()
/*
** Make sure that the display is right. This is a three part process. First,
** scan through all of the windows looking for dirty ones. Check the framing,
** and refresh the screen. Second, make sure that "currow" and "curcol" are
** correct for the current window. Third, make the virtual and physical
** screens the same.
**
** int force;     force update past type ahead?
 ***********************************************************************/
REG update(int force)
{
   register WINDOW *wp;

   if (force == FALSE && typahead())
      return(TRUE);
   if (force == FALSE && kbdmode == PLAY)
      return(TRUE);

   /* update any windows that need refreshing */
   wp = wheadp;
   while (wp != NULL) {
      if (wp->w_flag) {
         /* if the window has changed, service it */
         reframe(wp);   /* check the framing */
         if ((wp->w_flag & ~WFMODE) == WFEDIT)
            updone(wp); /* update EDITed line */
         else if (wp->w_flag & ~WFMOVE)
            updall(wp); /* update all lines */
         if (wp->w_flag & WFMODE)
            modeline(wp);  /* update modeline */
         wp->w_flag = 0;
         wp->w_force = 0;
      }

      /* on to the next window */
      wp = wp->w_wndp;
   }

   /* recalc the current hardware cursor location */
   updpos();


   /* check for lines to de-extend */
   upddex();

   /* if screen is garbage, re-plot it */
   if (sgarbf != FALSE)
      if (gflags & GFSDRAW)
         sgarbf = FALSE;
      else
         updgar();

   /* update the virtual screen to the physical screen */
   updupd(force);

   /* update the cursor and flush the buffers */
   movecursor(currow, curcol - lbound);
   TTflush();

   return(TRUE);
}
//-

/// upmode()
/* update all the mode lines */
REG upmode(void)
{
   register WINDOW *wp;

   wp = wheadp;

   while (wp != NULL)
   {
      wp->w_flag |= WFMODE;
      wp = wp->w_wndp;
   }

   return(1);
}
//-

/// upwind()
/* force hard updates on all windows */
REG upwind(void)
{
   register WINDOW *wp;

   wp = wheadp;
   while (wp != NULL)
   {
      wp->w_flag |= WFHARD|WFMODE;
      wp = wp->w_wndp;
   }

   return(1);
}
//-

/// movecursor()
/*
** Send a command to the terminal to move the hardware cursor to row "row"
** and column "col". The row and column arguments are origin 0. Optimize out
** random calls. Update "ttrow" and "ttcol".
 ***********************************************************************/
REG movecursor(int row, int col)
{
   if (row!=ttrow || col!=ttcol) {
      if (row == ttrow) {
         if (col == 0) {
            TTputc('\r');
         } else if (col == ttcol-1) {
            ttputs("\033[D");
         } else if (col == ttcol+1) {
            ttputs("\033[C");
         } else {
            TTmove(row, col);
         }
      } else {
         TTmove(row, col);
      }
      ttrow = row;
      ttcol = col;
   }

   return(1);
}
//-

/// mlerase()
REG mlerase(void)
{
   int i;
    
   movecursor(term.t_nrow, 0);
   if (discmd == FALSE) return(0);

   TTforg(7);
   TTbacg(gbcolor);

   if (eolexist == TRUE)
      TTeeol();
   else {
      for (i = 0; i < term.t_ncol - 1; i++)
         TTputc(' ');

      /* force the move! */
      movecursor(term.t_nrow, 0);
   }
   TTflush();
   mpresf = FALSE;
   bc_displayed = FALSE;

   return(1);
}
//-

/// mlout()
/*
** Write a message into the message line. Keep track of the physical cursor
** position. A small class of printf like format items is handled. Assumes the
** stack grows down; this assumption is made by the "+=" in the argument scan
** loop. If  STACK_GROWS_UP  is set in estruct.h, then we'll assume that the
** stack grows up and use "-=" instead of "+=". Set the "message line"
**  flag TRUE.  Don't write beyond the end of the current terminal width.
**
** int c;      character to write
**
 ***********************************************************************/
REG mlout(c)
{
   if (ttcol + 1 < term.t_ncol)
      TTputc(c);
   if (c != '\b')
      *lastptr++ = c;
   else if (lastptr > &lastmesg[0])
      --lastptr;

   return(1);
}
//-

/// mlwrite
/*
** variable argument list
**    arg1 = format string
**    arg2+ = arguments in that string
 ***********************************************************************/
CDECL mlwrite(char *fmt, ...)
{
   register int c;   /* current char in format string */
   va_list ap;    /* ptr to current data field */
   int arg_int;      /* integer argument */
   long arg_long;    /* long argument */
   char *arg_str;    /* string argument */

   /* if we are not currently echoing on the command line, abort this */
   if (discmd == FALSE)
      return(0);

   /* set up the proper colors for the command line */
   if (ml_color < 0) {
      ml_color = lookup_color(Xmlcolor);
   }
   TTforg(ml_color);
   TTbacg(gbcolor);

   /* point to the first argument */
   va_start(ap, fmt);

   /* if we can not erase to end-of-line, do it manually */
   if (eolexist == FALSE) {
      mlerase();
      TTflush();
   }

   movecursor(term.t_nrow, 0);
   lastptr = &lastmesg[0];    /* setup to record message */
   while ((c = *fmt++) != 0) {
      if (c != '%') {
         mlout(c);
         ++ttcol;
      } else {
         c = *fmt++;
         switch (c) {
            case 'd':
               arg_int = va_arg(ap, int);
               mlputi(arg_int, 10);
               break;

            case 'o':
               arg_int = va_arg(ap, int);
               mlputi(arg_int, 8);
               break;

            case 'x':
               arg_int = va_arg(ap, int);
               mlputi(arg_int, 16);
               break;

            case 'D':
               arg_long = va_arg(ap, long);
               mlputli(arg_long, 10);
               break;

            case 's':
               arg_str = va_arg(ap, char *);
               mlputs(arg_str);
               break;

            case 'f':
               arg_int = va_arg(ap, int);
               mlputf(arg_int);
               break;

            default:
               mlout(c);
               ++ttcol;
         }
      }
   }

   /* if we can, erase to the end of screen */
   if (eolexist == TRUE)
      TTeeol();
   TTflush();
   mpresf = TRUE;
   bc_displayed = FALSE;
   *lastptr = 0;  /* terminate lastmesg[] */
   va_end(ap);
}
//-

/// mlforce()
/*
** Force a string out to the message line regardless of the
** current $discmd setting. This is needed when $debug is TRUE
** and for the write-message and clear-message-line commands
**
** char *s;    string to force out
**
 ***********************************************************************/
REG mlforce(char *s)
{
   register int oldcmd; /* original command display flag */

   oldcmd = discmd;  /* save the discmd value */
   discmd = TRUE;    /* and turn display on */
   mlwrite(s);    /* write the string out */
   discmd = oldcmd;  /* and restore the original setting */

   return(1);
}
//-

/// timeset()
/*
** return a system dependant string with the current time
***/
char *STD timeset(void)
{
    struct ATime ts;
    static char timestring[9]; /* "hh:mm pm" */
    char *p;
    char *pm;

    UnpackTime(AmigaTime(), &ts);
    if (ts.hour < 12) {
   pm = " am";
    } else {
   ts.hour -= 12;
   pm = " pm";
    }
    if (ts.hour == 0) ts.hour = 12;

    strcpy(timestring, int_asc(ts.hour));
    if (ts.min < 10) {
   p = ":0";
    } else {
   p = ":";
    }
    strcat(timestring, p);
    strcat(timestring, int_asc(ts.min));
    strcat(timestring, pm);
    return timestring;
}
//-

// Local functions

/// mlputf()
/*
** write out a scaled integer with two decimal places
**
** int s;      scaled integer to output
**
 ***********************************************************************/
REG mlputf(int s)
{
   int i;   /* integer portion of number */
   int f;   /* fractional portion of number */

   /* break it up */
   i = s / 100;
   f = s % 100;

   /* send out the integer portion */
   mlputi(i, 10);
   mlout('.');
   mlout((f / 10) + '0');
   mlout((f % 10) + '0');
   ttcol += 3;

   return(1);
}
//-

/// updateline()
/*
** Update a single line. This does not know how to use insert or delete
** character sequences; we are using VT52 functionality. Update the physical
** row and column variables. It does try an exploit erase to end of line.
**
** int row;          row of screen to update
** struct VIDEO *vp; virtual screen image
** struct VIDEO *pp; physical screen image
**
 ****************************************************************************/
REG updateline(int row, struct VIDEO *vp, struct VIDEO *pp)
{
   register char *cp1;
   register char *cp2;
   register char *cp3;
   register char *cp4;
   register char *cp5;
   register int nbflag; /* non-blanks to the right flag? */
   int rev;    /* reverse video flag */
   int req;    /* reverse video request flag */
   int upcol;     /* update column (KRS) */

   /* set up pointers to virtual and physical lines */
   cp1 = &vp->v_text[0];
   cp2 = &pp->v_text[0];

   TTforg(vp->v_rfcolor);
   TTbacg(vp->v_rbcolor);

   /* if we need to change the reverse video status of the
      current line, we need to re-write the entire line   */
   rev = (vp->v_flag & VFREV) == VFREV;
   req = (vp->v_flag & VFREQ) == VFREQ;
   if ((rev != req)
       || (vp->v_fcolor != vp->v_rfcolor) || (vp->v_bcolor != vp->v_rbcolor)
         ) {
/* added moveit for XENO_ */
      int moveit = 1;      /* optimize... move cursor only if necessary */

      /* set rev video if needed */
      if (rev != req)
         (*term.t_rev)(req);

      /* scan through the line and dump it to the screen and
         the virtual screen array            */
      cp3 = &vp->v_text[term.t_ncol];
      if (!req) {
         while (cp3 > cp1 && *(cp3-1) == ' ') --cp3;
      }
      while (cp1 < cp3) {
         if (moveit) {
            movecursor(row, 0);
            moveit = 0;
         }
         TTputc(*cp1);
         ++ttcol;
         *cp2++ = *cp1++;
      }
      /* turn rev video off */
      if (rev != req)
         (*term.t_rev)(FALSE);
      if (!req) {
         cp3 = &vp->v_text[term.t_ncol];
         nbflag = FALSE;
         if (cp1 < cp3) {
            while (cp1 < cp3) {
               if (*cp2 != ' ')
                  nbflag = TRUE;
               *cp2++ = *cp1++;
            }
            /* no need to eeol if already blank */
            if (nbflag || rev != req) {
               if (moveit) {
                  movecursor(row, 0);
                  moveit = 0;
               }
               TTeeol();
            }
         }
      }
      /* update the needed flags */
      vp->v_flag &= ~VFCHG;
      if (req)
         vp->v_flag |= VFREV;
      else
         vp->v_flag &= ~VFREV;
      vp->v_fcolor = vp->v_rfcolor;
      vp->v_bcolor = vp->v_rbcolor;
      return(TRUE);
   }

   upcol = 0;

   /* advance past any common chars at the left */
   while (cp1 != &vp->v_text[term.t_ncol] && cp1[0] == cp2[0]) {
      ++cp1;
      ++upcol;
      ++cp2;
   }

   /* This can still happen, even though we only call this routine on changed
   ** lines. A hard update is always done when a line splits, a massive
   ** change is done, or a buffer is displayed twice. This optimizes out most
   ** of the excess updating. A lot of computes are used, but these tend to
   ** be hard operations that do a lot of update, so I don't really care.
    **************************************************************************
    */
   /* if both lines are the same, no update needs to be done */
   if (cp1 == &vp->v_text[term.t_ncol]) {
      vp->v_flag &= ~VFCHG;      /* flag this line is changed */
      return(TRUE);
   }

   /* find out if there is a match on the right */
   nbflag = FALSE;
   cp3 = &vp->v_text[term.t_ncol];
   cp4 = &pp->v_text[term.t_ncol];

   while (cp3[-1] == cp4[-1]) {
      --cp3;
      --cp4;
      if (cp3[0] != ' ')      /* Note if any nonblank */
         nbflag = TRUE;    /* in right match. */
   }

   cp5 = cp3;

   /* Erase to EOL ? */
   if (nbflag == FALSE && eolexist == TRUE && (req != TRUE)) {
      while (cp5!=cp1 && cp5[-1]==' ')
         --cp5;

      if (cp3-cp5 <= 3)    /* Use only if erase is */
         cp5 = cp3;     /* fewer characters. */
   }

   /* move to the begining of the text to update */
   movecursor(row, upcol);

   if (rev)
      TTrev(TRUE);

   //Here's the EmacsDLG 1.3 attempt to fix bugs at end of line

   if (id_ok && !req && cp5-cp1 == 1 && cp4-cp2 == 2) {
      ttputs("\033[P");
      while (cp1 != cp3) {
         *cp2++ = *cp1++;
      }
   }

   //Overwrite a single character -- added in EmacsDLG 1.3
        else if (!req && cp5-cp1 == 1 && cp4-cp2 == 1) {

      TTputc(*cp1);
      ++ttcol;
      *cp2 = *cp1;
   }
   //Insert a single character
   else if (id_ok && !req && cp4-cp2 > 0 && strncmp(cp1+1, cp2, cp4-cp2) == 0 &&
      cp4 < &pp->v_text[term.t_ncol]) {

      ttputs("\033[@");
      TTputc(*cp1);
      ++ttcol;
      while (cp1 != cp3) {
         *cp2++ = *cp1++;
      }
   }
   else {
      while (cp1 != cp5) {    /* Ordinary. */
         TTputc(*cp1);
         ++ttcol;
         *cp2++ = *cp1++;
      }

      if (cp5 != cp3) {    /* Erase. */
         TTeeol();
         while (cp1 != cp3)
            *cp2++ = *cp1++;
      }
   }

   if (rev)
      TTrev(FALSE);
   vp->v_flag &= ~VFCHG;      /* flag this line as updated */
   return(TRUE);
}
//-

/// mlputi()
/*
** Write out an integer, in the specified radix. Update the physical cursor
** position.
 ***********************************************************************/
REG mlputi(int i, int r)
{
   register int q;
   static char hexdigits[] = "0123456789ABCDEF";

   if (i < 0)
   {
      i = -i;
      mlout('-');
   }

   q = i/r;

   if (q != 0) mlputi(q, r);

   mlout(hexdigits[i%r]);
   ++ttcol;

   return(1);
}
//-

/// mlputli()
/*
** do the same except as a long integer.
 ***********************************************************************/
REG mlputli(long l, int r)
{
   register long q;

   if (l < 0)
   {
      l = -l;
      mlout('-');
   }

   q = l/r;

   if (q != 0) mlputli(q, r);

   mlout((int)(l%r)+'0');
   ++ttcol;

   return(1);
}
//-

/// mlputs()
/*
 * Write out a string. Update the physical cursor position. This assumes that
 * the characters in the string all have width "1"; if this is not the case
 * things will get screwed up a little.
 */

REG mlputs(char *s)
{
   register int c;

   while ((c = *s++) != 0)
   {
      mlout(c);
      ++ttcol;
   }

   return(1);
}
//-

/// modeline()
/*
** Redisplay the mode line for the window pointed to by the "wp". This is the
** only routine that has any idea of how the modeline is formatted. You can
** change the modeline format by hacking at this routine. Called by "update"
** any time there is a dirty window.
**
** WINDOW *wp;    window to update modeline for
 ***********************************************************************/
REG modeline(WINDOW *wp)
{
   register char *cp;
   register int c;
   register int n;      /* cursor position count */
   register BUFFER *bp;
   register int i;      /* loop index */
   register int lchar;  /* character to draw line in buffer with */

   /* don't bother if there is none! */
   if (modeflag == FALSE)  return(0);

   n = wp->w_toprow+wp->w_ntrows;      /* Location. */

   /*
   ** Note that we assume that setting REVERSE will cause the terminal
   ** driver to draw with the inverted relationship of fcolor and
   ** bcolor, so that when we say to set the foreground color to "white"
   ** and background color to "black", the fact that "reverse" is
   ** enabled means that the terminal driver actually draws "black" on a
   ** background of "white".  Makes sense, no?  This way, devices for
   ** which the color controls are optional will still get the "reverse"
   ** signals.
   **
    **********************************************************************/

   // LOTS OF XENO_ CHANGES

   vscreen[n]->v_flag |= VFCHG | VFCOL;   /* Redraw next time. */

   // blank must be the first kool char
   if (kool == 0) {
      vscreen[n]->v_flag |= VFREQ;  /* reverse... */
   }

   if (mode_color < 0) {
      mode_color = lookup_color(Xwbcolor);
   }

   if (kool == 0) {
      vscreen[n]->v_rbcolor = mode_color;
      // kludge foreground color to look good on all bitplanes
      if (mode_color == 7 || mode_color == 15) {
         vscreen[n]->v_rfcolor = 12;   /* light blue */
      } else {
         vscreen[n]->v_rfcolor = 7; /* white */
      }
   } else {
      vscreen[n]->v_rfcolor = mode_color;    /* black on */
      vscreen[n]->v_rbcolor = 0;       /* white.....*/
   }
   vtmove(n, 0);           /* Seek to right line. */
   lchar = kool_array[kool];

   bp = wp->w_bufp;
   n = 0;

   vtputc(lchar);
   vtputc(lchar);
   vtputc(' ');
   n += 3;
   cp = &bp->b_bname[0];

   // multiple blanks are replaced by lchar
   // first char is special case
   if (*cp) {
      vtputc(*cp);
      ++n;
      ++cp;
   }
   while (*cp) {
      if (*(cp-1) == ' ' && *cp == ' ' && *(cp+1) == ' ') {
         vtputc(lchar);
      } else {
         vtputc(*cp);
      }
      ++n;
      ++cp;
   }
   vtputc(' ');
   vtputc(lchar);
   vtputc(lchar);
   n += 3;

   if (bp->b_mode & MDTIME) {
       cp = timeset();
       i = term.t_ncol - strlen(cp) - 6;
       while (n < i) {              /* Pad to time position. */
      vtputc(lchar);
      ++n;
       }

       vtputc(' ');
       while ((c = *cp++) != 0) {
      vtputc(c);
      ++n;
       }
       vtputc(' ');
       n += 2; // blanks
   }

   while (n < term.t_ncol) {  /* Pad to full width. */
      vtputc(lchar);
      ++n;
   }
}
//-

/// reframe()
/*
** reframe: check to see if the cursor is on in the window
** and re-frame it if needed or wanted
 ***********************************************************************/
REG reframe(WINDOW *wp)
{
   register LINE *lp;   /* search pointer */
   register int i;      /* general index/# lines to scroll */
   register int nlines; /* number of lines in current window */

   /* figure out our window size */
   nlines = wp->w_ntrows;
   if (modeflag == FALSE)
      nlines++;

   /* if not a requested reframe, check for a needed one */
   if ((wp->w_flag & WFFORCE) == 0) {
      lp = wp->w_linep;
      for (i = 0; i < nlines; i++) {

         /* if the line is in the window, no reframe */
         if (lp == wp->w_dotp)
            return(TRUE);

         /* if we are at the end of the file, reframe */
         if (lp == wp->w_bufp->b_linep)
            break;

         /* on to the next line */
         lp = lforw(lp);
      }
   }

   /* reaching here, we need a window refresh */
   i = wp->w_force;

   if (0) {
   /* how far back to reframe? */
   } else if (i > 0) {  /* only one screen worth of lines max */
      if (--i >= nlines)
         i = nlines - 1;
   } else if (i < 0) {  /* negative update???? */
      i += nlines;
      if (i < 0)
         i = 0;
   } else
      i = nlines / 2;

   /* backup to new line at top of window */
   lp = wp->w_dotp;
   while (i != 0 && lback(lp) != wp->w_bufp->b_linep) {
      --i;
      lp = lback(lp);
   }

   /* and reset the current line at top of window */
   wp->w_linep = lp;
   wp->w_flag |= WFHARD;
   wp->w_flag &= ~WFFORCE;
   return(TRUE);
}
//-

/// updall()
/*
** updall: update all the lines in a window on the virtual screen
**
** WINDOW *wp;    window to update lines in
 ***********************************************************************/
REG updall(WINDOW *wp)
{
   register LINE *lp;   /* line to update */
   register int sline;  /* physical screen line to update */
   register int i;
   register int nlines; /* number of lines in the current window */

   /* search down the lines, updating them */
   lp = wp->w_linep;
   sline = wp->w_toprow;
   nlines = wp->w_ntrows;
   if (modeflag == FALSE)
      nlines++;
   taboff = wp->w_fcol;
   while (sline < wp->w_toprow + nlines) {

      /* and update the virtual line */
      vscreen[sline]->v_flag |= VFCHG;
      vscreen[sline]->v_flag &= ~VFREQ;
      vtmove(sline, -taboff);
      if (lp != wp->w_bufp->b_linep) {
         /* if we are not at the end */
         for (i=0; i < llength(lp); ++i)
            vtputc(lgetc(lp, i));
         lp = lforw(lp);
      }

      /* make sure we are on screen */
      if (vtcol < 0)
         vtcol = 0;

      /* on to the next one */
      vscreen[sline]->v_rfcolor = wp->w_fcolor;
      vscreen[sline]->v_rbcolor = wp->w_bcolor;
      vteeol();
      ++sline;
   }
   taboff = 0;

   return(1);
}
//-

/// upddex()
/*
** upddex: de-extend any line that derserves it
 ***********************************************************************/
REG upddex(void)
{
   register WINDOW *wp;
   register LINE *lp;
   register int i,j;
   register int nlines; /* number of lines in the current window */

   wp = wheadp;

   while (wp != NULL) {
      lp = wp->w_linep;
      i = wp->w_toprow;
      nlines = wp->w_ntrows;
      if (modeflag == FALSE)
         nlines++;

      while (i < wp->w_toprow + nlines) {
         if (vscreen[i]->v_flag & VFEXT) {
            if ((wp != curwp) || (lp != wp->w_dotp) ||
               (curcol < term.t_ncol - 1)) {
               taboff = wp->w_fcol;
               vtmove(i, -taboff);
               for (j = 0; j < llength(lp); ++j)
                  vtputc(lgetc(lp, j));
               vteeol();
               taboff = 0;

               /* this line no longer is extended */
               vscreen[i]->v_flag &= ~VFEXT;
               vscreen[i]->v_flag |= VFCHG;
            }
         }
         lp = lforw(lp);
         ++i;
      }
      /* and onward to the next window */
      wp = wp->w_wndp;
   }

   return(1);
}
//-

/// updext()
/*
** updext: update the extended line which the cursor is currently
** on at a column greater than the terminal width. The line
** will be scrolled right or left to let the user see where
** the cursor is
 ***********************************************************************/
REG updext(void)
{
   register int rcursor;   /* real cursor location */
   register LINE *lp;   /* pointer to current line */
   register int j;   /* index into line */

   /* calculate what column the real cursor will end up in */
   rcursor = ((curcol - term.t_ncol) % term.t_scrsiz)
         + term.t_margin;
   lbound = curcol - rcursor + 1;
   taboff = lbound + curwp->w_fcol;

   /* scan through the line outputing characters to the virtual screen */
   /* once we reach the left edge               */
   vtmove(currow, -taboff); /* start scanning offscreen */
   lp = curwp->w_dotp;     /* line to output */
   for (j=0; j<llength(lp); ++j) /* until the end-of-line */
      vtputc(lgetc(lp, j));

   /* truncate the virtual line, restore tab offset */
   vteeol();
   taboff = 0;

   /* and put a '$' in column 1 */
   vscreen[currow]->v_text[0] = '$';

   return(1);
}
//-

/// updgar()
/*
** updgar: if the screen is garbage, clear the physical screen and
** the virtual screen and force a full update
 ***********************************************************************/
REG updgar(void)
{
   register int i;
   register int j;
   register char *txt;

   for (i = 0; i < term.t_nrow; ++i) {
      vscreen[i]->v_flag |= VFCHG;
      vscreen[i]->v_flag &= ~VFREV;
      vscreen[i]->v_fcolor = gfcolor;
      vscreen[i]->v_bcolor = gbcolor;
      txt = pscreen[i]->v_text;
      for (j = 0; j < term.t_ncol; ++j)
         txt[j] = ' ';
   }

   movecursor(0, 0);     /* Erase the screen. */
   (*term.t_eeop)();
   sgarbf = FALSE;       /* Erase-page clears */
   mpresf = FALSE;       /* the message area. */
   mlerase();        /* needs to be cleared if colored */

   return(1);
}
//-

/// updone()
/*
** updone: update the current line to the virtual screen
**
** WINDOW *wp;    window to update current line in
 ***********************************************************************/
REG updone(WINDOW *wp)
{
   register LINE *lp;   /* line to update */
   register int sline;  /* physical screen line to update */
   register int i;

   /* search down the line we want */
   lp = wp->w_linep;
   sline = wp->w_toprow;
   while (lp != wp->w_dotp) {
      ++sline;
      lp = lforw(lp);
   }

   /* and update the virtual line */
   vscreen[sline]->v_flag |= VFCHG;
   vscreen[sline]->v_flag &= ~VFREQ;
   taboff = wp->w_fcol;
   vtmove(sline, -taboff);
   for (i=0; i < llength(lp); ++i)
      vtputc(lgetc(lp, i));
   vscreen[sline]->v_rfcolor = wp->w_fcolor;
   vscreen[sline]->v_rbcolor = wp->w_bcolor;
   vteeol();
   taboff = 0;

   return(1);
}
//-

/// updpos()
/*
** updpos: update the position of the hardware cursor and handle extended
** lines. This is the only update for simple moves.
 ***********************************************************************/
REG updpos(void)
{
   register LINE *lp;
   register int c;
   register int i;

   /* find the current row */
   lp = curwp->w_linep;
   currow = curwp->w_toprow;
   while (lp != curwp->w_dotp) {
      ++currow;
      lp = lforw(lp);
   }

   /* find the current column */
   curcol = 0;
   i = 0;
   while (i < curwp->w_doto) {
      c = lgetc(lp, i++);
      if (c == '\t')
         curcol += - (curcol % tabsize) + (tabsize - 1);
      else {
         if (disphigh && c > 0x7f) {
            curcol += 2;
            c -= 0x80;
         }
         if (c < 0x20 || c == 0x7f)
            ++curcol;
      }
      ++curcol;
   }

   /* adjust by the current first column position */
   curcol -= curwp->w_fcol;

   /* make sure it is not off the left side of the screen */
   while (curcol < 0) {
      if (curwp->w_fcol >= hjump) {
         curcol += hjump;
         curwp->w_fcol -= hjump;
      } else {
         curcol += curwp->w_fcol;
         curwp->w_fcol = 0;
      }
      curwp->w_flag |= WFHARD | WFMODE;
   }

   /* if horizontall scrolling is enabled, shift if needed */
   if (hscroll) {
      while (curcol >= term.t_ncol - 1) {
         curcol -= hjump;
         curwp->w_fcol += hjump;
         curwp->w_flag |= WFHARD | WFMODE;
      }
   } else {
   /* if extended, flag so and update the virtual line image */
      if (curcol >=  term.t_ncol - 1) {
         vscreen[currow]->v_flag |= (VFEXT | VFCHG);
         updext();
      } else
         lbound = 0;
   }

   /* update the current window if we have to move it around */
   if (curwp->w_flag & WFHARD)
      updall(curwp);
   if (curwp->w_flag & WFMODE)
      modeline(curwp);
   curwp->w_flag = 0;

   return(1);
}
//-

/// updupd()
/*
** updupd: update the physical screen from the virtual screen
**
** int force;     forced update flag
 ***********************************************************************/
REG updupd(int force)
{
   register VIDEO *vp1;
   register int i;

   for (i = 0; i < term.t_nrow; ++i) {
      vp1 = vscreen[i];

      /* for each line that needs to be updated*/
      if ((vp1->v_flag & VFCHG) != 0) {
         if (force == FALSE && typahead())
            return(TRUE);
         updateline(i, vp1, pscreen[i]);
      }
   }
   return(TRUE);
}
//-

/// vteeol()
/*
** Erase from the end of the software cursor to the end of the line on which
** the software cursor is located.
 ***********************************************************************/
REG vteeol(void)
{
    register VIDEO   *vp;

    vp = vscreen[vtrow];
   while (vtcol < term.t_ncol)
   {
      if (vtcol >= 0)   vp->v_text[vtcol] = ' ';
      vtcol++;
   }

   return(1);
}
//-

/// vtmove()
/*
** Set the virtual cursor to the specified row and column on the virtual
** screen. There is no checking for nonsense values; this might be a good
** idea during the early stages.
 ***********************************************************************/
REG vtmove(int row, int col)
{
    vtrow = row;
    vtcol = col;
   return(1);
}
//-

/// vtputc()
/*
** Write a character to the virtual screen. The virtual row and
** column are updated. If we are not yet on left edge, don't print
** it yet. If the line is too long put a "$" in the last column.
** This routine only puts printing characters into the virtual
** terminal buffers. Only column overflow is checked.
 ***********************************************************************/
REG vtputc(int c)
{
   register VIDEO *vp;  /* ptr to line being updated */

   vp = vscreen[vtrow];

   if (c == '\t') {
      do {
         vtputc(' ');
      } while (((vtcol + taboff) % (tabsize)) != 0);
   } else if (vtcol >= term.t_ncol) {
      ++vtcol;
      vp->v_text[term.t_ncol - 1] = '$';
   } else if (disphigh && c > 0x7f) {
      vtputc('^');
      vtputc('!');
      c -= 0x80;
      if (c == '\t') {
         vtputc('^');
         vtputc('I');
      } else
         vtputc(c);
   } else if (c < 0x20 || c == 0x7F) {
      vtputc('^');
      vtputc(c ^ 0x40);
   } else {
      if (vtcol >= 0)
         vp->v_text[vtcol] = c;
      ++vtcol;
   }

   return(1);
}
//-

/// alloc_cline()
VIDEO *alloc_cline(int i, char *cache)
{
   VIDEO *vp;

   if (i < MAX_CACHELINES) {
      // use static mem
      vp = (VIDEO *) (cache + i*(STATIC_NCOL+sizeof(VIDEO)));
   } else {
      /* allocate a virtual screen line */
      vp = (VIDEO *) malloc(sizeof(VIDEO)+term.t_mcol);
      if (vp == NULL)
         meexit(1);
   }
   return vp;
}
//-


