/* Copyright (c) 1993
 *      Juergen Weigert (jnweiger@immd4.informatik.uni-erlangen.de)
 *      Michael Schroeder (mlschroe@immd4.informatik.uni-erlangen.de)
 * Copyright (c) 1987 Oliver Laumann
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * 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
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program (see the file COPYING); if not, write to the
 * Free Software Foundation, Inc.,
 * 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
 *
 ****************************************************************
 */

#include "rcs.h"
RCS_ID("$Id: display.c,v 1.16 1994/05/31 12:31:50 mlschroe Exp $ FAU")


#include <sys/types.h>
#include <fcntl.h>

#include "config.h"
#include "screen.h"
#include "extern.h"

static void CountChars __P((int));
static void PutChar __P((int));
static int  BlankResize __P((int, int));


extern struct win *windows;

extern int  use_hardstatus;
extern int  MsgMinWait;
extern int  Z0width, Z1width;
extern char *blank, *null;
extern struct mline mline_blank, mline_null;
extern struct mchar mchar_null, mchar_blank, mchar_so;

/*
 * tputs needs this to calculate the padding
 */
#ifndef NEED_OSPEED
extern
#endif /* NEED_OSPEED */
short ospeed;


struct display *display, *displays;

#ifndef MULTI
struct display TheDisplay;
#endif

/*
 *  The default values
 */
int defobuflimit = OBUF_MAX;
#ifdef AUTO_NUKE
int defautonuke = 0;
#endif

/*
 *  Default layer management
 */

void
DefProcess(bufp, lenp)
char **bufp;
int *lenp;
{
  *bufp += *lenp;
  *lenp = 0;
}

void
DefRedisplayLine(y, xs, xe, isblank)
int y, xs, xe, isblank;
{
  if (isblank == 0 && y >= 0)
    DefClearLine(y, xs, xe);
}

void
DefClearLine(y, xs, xe)
int y, xs, xe;
{
  DisplayLine(&mline_null, &mline_blank, y, xs, xe);
}

/*ARGSUSED*/
int
DefRewrite(y, xs, xe, doit)
int y, xs, xe, doit;
{
  return EXPENSIVE;
}

void
DefSetCursor()
{
  GotoPos(0, 0);
}

/*ARGSUSED*/
int
DefResize(wi, he)
int wi, he;
{
  return -1;
}

void
DefRestore()
{
  InsertMode(0);
  ChangeScrollRegion(0, D_height - 1);
  KeypadMode(0);
  CursorkeysMode(0);
  CursorVisibility(0);
  SetRendition(&mchar_null);
  SetFlow(FLOW_NOW);
}

/*
 *  Blank layer management
 */

struct LayFuncs BlankLf =
{
  DefProcess,
  0,
  DefRedisplayLine,
  DefClearLine,
  DefRewrite,
  DefSetCursor,
  BlankResize,
  DefRestore
};

struct layer BlankLayer =
{
  0,
  0,
  &BlankLf,
  0
};

/*ARGSUSED*/
static int
BlankResize(wi, he)
int wi, he;
{
  return 0;
}


/*
 *  Generate new display
 */

struct display *
MakeDisplay(uname, utty, term, fd, pid, Mode)
char *uname, *utty, *term;
int fd, pid;
struct mode *Mode;
{
  struct user **u;

  if (!*(u = FindUserPtr(uname)) && UserAdd(uname, (char *)0, u))
    return 0;	/* could not find or add user */

#ifdef MULTI
  if ((display = (struct display *)malloc(sizeof(*display))) == 0)
    return 0;
  bzero((char *) display, sizeof(*display));
#else
  if (displays)
    return 0;
  display = &TheDisplay;
#endif
  display->d_next = displays;
  displays = display;
  D_flow = 1;
  D_nonblock = 0;
  D_userfd = fd;
  D_OldMode = *Mode;
  Resize_obuf();  /* Allocate memory for buffer */
  D_obufmax = defobuflimit;
#ifdef AUTO_NUKE
  D_auto_nuke = defautonuke;
#endif
  D_obufp = D_obuf;
  D_printfd = -1;
  D_userpid = pid;
#if defined(POSIX) || defined(TERMIO)
# ifdef POSIX
  switch (cfgetospeed(&D_OldMode.tio))
# else
  switch (D_OldMode.tio.c_cflag & CBAUD)
# endif
    {
#ifdef B0
    case B0: D_dospeed = 0; break;
#endif
#ifdef B50
    case B50: D_dospeed = 1; break;
#endif
#ifdef B75
    case B75: D_dospeed = 2; break;
#endif
#ifdef B110
    case B110: D_dospeed = 3; break;
#endif
#ifdef B134
    case B134: D_dospeed = 4; break;
#endif
#ifdef B150
    case B150: D_dospeed = 5; break;
#endif
#ifdef B200
    case B200: D_dospeed = 6; break;
#endif
#ifdef B300
    case B300: D_dospeed = 7; break;
#endif
#ifdef B600
    case B600: D_dospeed = 8; break;
#endif
#ifdef B1200
    case B1200: D_dospeed = 9; break;
#endif
#ifdef B1800
    case B1800: D_dospeed = 10; break;
#endif
#ifdef B2400
    case B2400: D_dospeed = 11; break;
#endif
#ifdef B4800
    case B4800: D_dospeed = 12; break;
#endif
#ifdef B9600
    case B9600: D_dospeed = 13; break;
#endif
#ifdef EXTA
    case EXTA: D_dospeed = 14; break;
#endif
#ifdef EXTB
    case EXTB: D_dospeed = 15; break;
#endif
#ifdef B57600
    case B57600: D_dospeed = 16; break;
#endif
#ifdef B115200
    case B115200: D_dospeed = 17; break;
#endif
    default: ;
    }
#else	/* POSIX || TERMIO */
  D_dospeed = (short) D_OldMode.m_ttyb.sg_ospeed;
#endif	/* POSIX || TERMIO */
  debug1("New displays ospeed = %d\n", D_dospeed);
  strcpy(D_usertty, utty);
  strcpy(D_termname, term);

  D_user = *u;
  D_lay = &BlankLayer;
  D_layfn = BlankLayer.l_layfn;
  return display;
}

void
FreeDisplay()
{
  struct win *p;
#ifdef MULTI
  struct display *d, **dp;
#endif

  FreeTransTable();
  freetty();
  if (D_tentry)
    free(D_tentry);
  D_tentry = 0;
  D_tcinited = 0;
#ifdef MULTI
  for (dp = &displays; (d = *dp) ; dp = &d->d_next)
    if (d == display)
      break;
  ASSERT(d);
  if (D_status_lastmsg)
    free(D_status_lastmsg);
  if (D_obuf)
    free(D_obuf);
  *dp = display->d_next;
  free((char *)display);
#else /* MULTI */
  ASSERT(display == displays);
  ASSERT(display == &TheDisplay);
  displays = 0;
#endif /* MULTI */
  for (p = windows; p; p = p->w_next)
    {
      if (p->w_display == display)
	p->w_display = 0;
      if (p->w_pdisplay == display)
	p->w_pdisplay = 0;
    }
  display = 0;
}

/*
 * if the adaptflag is on, we keep the size of this display, else
 * we may try to restore our old window sizes.
 */
void
InitTerm(adapt)
int adapt;
{
  ASSERT(display);
  ASSERT(D_tcinited);
  D_top = D_bot = -1;
  PutStr(D_TI);
  PutStr(D_IS);
  /* Check for toggle */
  if (D_IM && strcmp(D_IM, D_EI))
    PutStr(D_EI);
  D_insert = 0;
  /* Check for toggle */
#ifdef MAPKEYS
  PutStr(D_KS);
  PutStr(D_CCS);
#else
  if (D_KS && strcmp(D_KS, D_KE))
    PutStr(D_KE);
  if (D_CCS && strcmp(D_CCS, D_CCE))
    PutStr(D_CCE);
#endif
  D_keypad = 0;
  D_cursorkeys = 0;
  PutStr(D_ME);
  PutStr(D_EA);
  PutStr(D_CE0);
  D_rend = mchar_null;
  D_atyp = 0;
  if (adapt == 0)
    ResizeDisplay(D_defwidth, D_defheight);
  ChangeScrollRegion(0, D_height - 1);
  D_x = D_y = 0;
  Flush();
  ClearDisplay();
  debug1("we %swant to adapt all our windows to the display\n", 
	 (adapt) ? "" : "don't ");
  /* In case the size was changed by a init sequence */
  CheckScreenSize((adapt) ? 2 : 0);
}

void
FinitTerm()
{
  ASSERT(display);
  if (D_tcinited)
    {
      ResizeDisplay(D_defwidth, D_defheight);
      DefRestore();
      SetRendition(&mchar_null);
#ifdef MAPKEYS
      PutStr(D_KE);
      PutStr(D_CCE);
#endif
      if (D_hstatus)
	PutStr(D_DS);
      D_x = D_y = -1;
      GotoPos(0, D_height - 1);
      AddChar('\n');
      PutStr(D_TE);
    }
  Flush();
}


void
INSERTCHAR(c)
int c;
{
  ASSERT(display);
  if (!D_insert && D_x < D_width - 1)
    {
      if (D_IC || D_CIC)
	{
	  if (D_IC)
	    PutStr(D_IC);
	  else
	    CPutStr(D_CIC, 1);
	  RAW_PUTCHAR(c);
	  return;
	}
      InsertMode(1);
      if (!D_insert)
	{
          RefreshLine(D_y, D_x, D_width-1, 0);
	  return;
	}
    }
  RAW_PUTCHAR(c);
}

void
PUTCHAR(c)
int c;
{
  ASSERT(display);
  if (D_insert && D_x < D_width - 1)
    InsertMode(0);
  RAW_PUTCHAR(c);
}

void
PUTCHARLP(c)
int c;
{
  if (D_x < D_width - 1)
    {
      if (D_insert)
	InsertMode(0);
      RAW_PUTCHAR(c);
      return;
    }
  if (D_CLP || D_y != D_bot)
    {
      RAW_PUTCHAR(c);
      return;
    }
  D_lp_missing = 1;
  D_rend.image = c;
  D_lpchar = D_rend;
}

/*
 * RAW_PUTCHAR() is for all text that will be displayed.
 * NOTE: charset Nr. 0 has a conversion table, but c1, c2, ... don't.
 */

void
RAW_PUTCHAR(c)
int c;
{
  ASSERT(display);
#ifdef KANJI
  if (D_rend.font == KANJI)
    {
      int t = c;
      if (D_mbcs == 0)
	{
	  D_mbcs = c;
	  return;
	}
      if (D_x == D_width - 1)
	D_x += D_AM ? 1 : -1;
      c = D_mbcs;
      c &= 0x7f;
      t &= 0x7f;
      if (D_kanji == EUC)
	{
	  c |= 0x80;
	  t |= 0x80;
	}
      else if (D_kanji == SJIS)
	{
	  t += (c & 1) ? ((t <= 0x5f) ? 0x1f : 0x20) : 0x7e;
	  c = (c - 0x21) / 2 + ((c < 0x5e) ? 0x81 : 0xc1);
	}
      D_mbcs = t;
    }
  else if (D_rend.font == KANA)
    {
      if (D_kanji == EUC)
	{
	  AddChar(0x8e);	/* SS2 */
	  c |= 0x80;
	}
      else if (D_kanji == SJIS)
	c |= 0x80;
    }
  kanjiloop:
#endif
  if (D_xtable && D_xtable[(int)(unsigned char)D_rend.font] && D_xtable[(int)(unsigned char)D_rend.font][(int)(unsigned char)c])
    AddStr(D_xtable[(int)(unsigned char)D_rend.font][(int)(unsigned char)c]);
  else
    AddChar(D_rend.font != '0' ? c : D_c0_tab[(int)(unsigned char)c]);

  if (++D_x >= D_width)
    {
      if (D_AM == 0)
        D_x = D_width - 1;
      else if (!D_CLP || D_x > D_width)
	{
	  D_x -= D_width;
	  if (D_y < D_height-1 && D_y != D_bot)
	    D_y++;
	}
    }
#ifdef KANJI
  if (D_mbcs)
    {
      c = D_mbcs;
      D_mbcs = 0;
      goto kanjiloop;
    }
#endif
}

static void
PutChar(c)
int c;
{
  /* this PutChar for ESC-sequences only (AddChar is a macro) */
  AddChar(c);
}

void
PutStr(s)
char *s;
{
  if (display && s)
    {
      ospeed = D_dospeed;
      tputs(s, 1, PutChar);
    }
}

void
CPutStr(s, c)
char *s;
int c;
{
  if (display && s)
    {
      ospeed = D_dospeed;
      tputs(tgoto(s, 0, c), 1, PutChar);
    }
}


/* Insert mode is a toggle on some terminals, so we need this hack:
 */
void
InsertMode(on)
int on;
{
  if (display && on != D_insert && D_IM)
    {
      D_insert = on;
      if (D_insert)
	PutStr(D_IM);
      else
	PutStr(D_EI);
    }
}

/* ...and maybe keypad application mode is a toggle, too:
 */
void
KeypadMode(on)
int on;
{
#ifdef MAPKEYS
  if (display)
    D_keypad = on;
#else
  if (display && D_keypad != on && D_KS)
    {
      D_keypad = on;
      if (D_keypad)
	PutStr(D_KS);
      else
	PutStr(D_KE);
    }
#endif
}

void
CursorkeysMode(on)
int on;
{
#ifdef MAPKEYS
  if (display)
    D_cursorkeys = on;
#else
  if (display && D_cursorkeys != on && D_CCS)
    {
      D_cursorkeys = on;
      if (D_cursorkeys)
	PutStr(D_CCS);
      else
	PutStr(D_CCE);
    }
#endif
}

void
ReverseVideo(on)
int on;
{
  if (display && D_revvid != on && D_CVR)
    {
      D_revvid = on;
      if (D_revvid)
	PutStr(D_CVR);
      else
	PutStr(D_CVN);
    }
}

void
CursorVisibility(v)
int v;
{
  if (display && D_curvis != v)
    {
      if (D_curvis)
	PutStr(D_VE);		/* do this always, just to be safe */
      D_curvis = 0;
      if (v == -1 && D_VI)
	PutStr(D_VI);
      else if (v == 1 && D_VS)
	PutStr(D_VS);
      else
	return;
      D_curvis = v;
    }
}

static int StrCost;

/* ARGSUSED */
static void
CountChars(c)
int c;
{
  StrCost++;
}

int
CalcCost(s)
register char *s;
{
  ASSERT(display);
  if (!s)
    return EXPENSIVE;
  StrCost = 0;
  ospeed = D_dospeed;
  tputs(s, 1, CountChars);
  return StrCost;
}

void
GotoPos(x2, y2)
int x2, y2;
{
  register int dy, dx, x1, y1;
  register int costx, costy;
  register int m;
  register char *s;
  int CMcost;
  enum move_t xm = M_NONE, ym = M_NONE;

  if (!display)
    return;

  x1 = D_x;
  y1 = D_y;

  if (x1 == D_width)
    if (D_CLP && D_AM)
      x1 = -1;		/* don't know how the terminal treats this */
    else
      x1--;
  if (x2 == D_width)
    x2--;
  dx = x2 - x1;
  dy = y2 - y1;
  if (dy == 0 && dx == 0)
    return;
  if (!D_MS)		/* Safe to move ? */
    SetRendition(&mchar_null);
  if (y1 < 0			/* don't know the y position */
      || (y2 > D_bot && y1 <= D_bot)	/* have to cross border */
      || (y2 < D_top && y1 >= D_top))	/* of scrollregion ?    */
    {
    DoCM:
      if (D_HO && !x2 && !y2)
        PutStr(D_HO);
      else
        PutStr(tgoto(D_CM, x2, y2));
      D_x = x2;
      D_y = y2;
      return;
    }
  /* Calculate CMcost */
  if (D_HO && !x2 && !y2)
    s = D_HO;
  else
    s = tgoto(D_CM, x2, y2);
  CMcost = CalcCost(s);

  /* Calculate the cost to move the cursor to the right x position */
  costx = EXPENSIVE;
  if (x1 >= 0)	/* relativ x positioning only if we know where we are */
    {
      if (dx > 0)
	{
	  if (D_CRI && (dx > 1 || !D_ND))
	    {
	      costx = CalcCost(tgoto(D_CRI, 0, dx));
	      xm = M_CRI;
	    }
	  if ((m = D_NDcost * dx) < costx)
	    {
	      costx = m;
	      xm = M_RI;
	    }
	  /* Speedup: dx <= Rewrite() */
	  if (dx < costx && (m = Rewrite(y1, x1, x2, 0)) < costx)
	    {
	      costx = m;
	      xm = M_RW;
	    }
	}
      else if (dx < 0)
	{
	  if (D_CLE && (dx < -1 || !D_BC))
	    {
	      costx = CalcCost(tgoto(D_CLE, 0, -dx));
	      xm = M_CLE;
	    }
	  if ((m = -dx * D_LEcost) < costx)
	    {
	      costx = m;
	      xm = M_LE;
	    }
	}
      else
	costx = 0;
    }
  /* Speedup: Rewrite() >= x2 */
  if (x2 + D_CRcost < costx && (m = (x2 ? Rewrite(y1, 0, x2, 0) : 0) + D_CRcost) < costx)
    {
      costx = m;
      xm = M_CR;
    }

  /* Check if it is already cheaper to do CM */
  if (costx >= CMcost)
    goto DoCM;

  /* Calculate the cost to move the cursor to the right y position */
  costy = EXPENSIVE;
  if (dy > 0)
    {
      if (D_CDO && dy > 1)	/* DO & NL are always != 0 */
	{
	  costy = CalcCost(tgoto(D_CDO, 0, dy));
	  ym = M_CDO;
	}
      if ((m = dy * ((x2 == 0) ? D_NLcost : D_DOcost)) < costy)
	{
	  costy = m;
	  ym = M_DO;
	}
    }
  else if (dy < 0)
    {
      if (D_CUP && (dy < -1 || !D_UP))
	{
	  costy = CalcCost(tgoto(D_CUP, 0, -dy));
	  ym = M_CUP;
	}
      if ((m = -dy * D_UPcost) < costy)
	{
	  costy = m;
	  ym = M_UP;
	}
    }
  else
    costy = 0;

  /* Finally check if it is cheaper to do CM */
  if (costx + costy >= CMcost)
    goto DoCM;

  switch (xm)
    {
    case M_LE:
      while (dx++ < 0)
	PutStr(D_BC);
      break;
    case M_CLE:
      CPutStr(D_CLE, -dx);
      break;
    case M_RI:
      while (dx-- > 0)
	PutStr(D_ND);
      break;
    case M_CRI:
      CPutStr(D_CRI, dx);
      break;
    case M_CR:
      PutStr(D_CR);
      D_x = 0;
      x1 = 0;
      /* FALLTHROUGH */
    case M_RW:
      if (x1 < x2)
	(void) Rewrite(y1, x1, x2, 1);
      break;
    default:
      break;
    }

  switch (ym)
    {
    case M_UP:
      while (dy++ < 0)
	PutStr(D_UP);
      break;
    case M_CUP:
      CPutStr(D_CUP, -dy);
      break;
    case M_DO:
      s =  (x2 == 0) ? D_NL : D_DO;
      while (dy-- > 0)
	PutStr(s);
      break;
    case M_CDO:
      CPutStr(D_CDO, dy);
      break;
    default:
      break;
    }
  D_x = x2;
  D_y = y2;
}

void
ClearDisplay()
{
  ASSERT(display);
  Clear(0, 0, 0, D_width - 1, D_width - 1, D_height - 1, 0);
}

void
Clear(x1, y1, xs, xe, x2, y2, uselayfn)
int x1, y1, xs, xe, x2, y2, uselayfn;
{
  int y, xxe;

  ASSERT(display);
  if (x1 == D_width)
    x1--;
  if (x2 == D_width)
    x2--;
  if (D_UT)	/* Safe to erase ? */
    SetRendition(&mchar_null);
  if (D_lp_missing && y1 <= D_bot && xe >= D_width - 1)
    {
      if (y2 > D_bot || (y2 == D_bot && x2 >= D_width - 1))
	D_lp_missing = 0;
    }
  if (x2 == D_width - 1 && (xs == 0 || y1 == y2) && xe == D_width - 1 && y2 == D_height - 1)
    {
#ifdef AUTO_NUKE
      if (x1 == 0 && y1 == 0 && D_auto_nuke)
	NukePending();
#endif
      if (x1 == 0 && y1 == 0 && D_CL)
	{
	  PutStr(D_CL);
	  D_y = D_x = 0;
	  return;
	}
      /* 
       * Workaround a hp700/22 terminal bug. Do not use CD where CE
       * is also appropriate.
       */
      if (D_CD && (y1 < y2 || !D_CE))
	{
	  GotoPos(x1, y1);
	  PutStr(D_CD);
	  return;
	}
    }
  if (x1 == 0 && xs == 0 && (xe == D_width - 1 || y1 == y2) && y1 == 0 && D_CCD)
    {
      GotoPos(x1, y1);
      PutStr(D_CCD);
      return;
    }
  xxe = xe;
  for (y = y1; y <= y2; y++, x1 = xs)
    {
      if (y == y2)
	xxe = x2;
      if (x1 == 0 && D_CB && (xxe != D_width - 1 || (D_x == xxe && D_y == y)))
	{
	  GotoPos(xxe, y);
	  PutStr(D_CB);
	  continue;
	}
      if (xxe == D_width - 1 && D_CE)
	{
	  GotoPos(x1, y);
	  PutStr(D_CE);
	  continue;
	}
      if (uselayfn)
        ClearLine(y, x1, xxe);
      else
	DisplayLine(&mline_null, &mline_blank, y, x1, xxe);
    }
}


/*
 * if cur_only > 0, we only redisplay current line, as a full refresh is
 * too expensive over a low baud line.
 */
void
Redisplay(cur_only)
int cur_only;
{
  register int i, stop;

  ASSERT(display);
  DefRestore();
  ClearDisplay();
  stop = D_height;
  i = 0;
  if (cur_only > 0 && D_fore)
    {
      i = stop = D_fore->w_y;
      stop++;
    }
  else
    RedisplayLine(-1, 0, D_width - 1, 1);
  for (; i < stop; i++)
    RedisplayLine(i, 0, D_width - 1, 1);
  RefreshStatus();
  Restore();
  SetCursor();
}


void
ScrollH(y, xs, xe, n, oml)
int y, xs, xe, n;
struct mline *oml;
{
  int i;

  if (n == 0)
    return;
  if (xe != D_width - 1)
    {
      RefreshLine(y, xs, xe, 0);
      /* UpdateLine(oml, y, xs, xe); */
      return;
    }
  GotoPos(xs, y);
  if (D_UT)
    SetRendition(&mchar_null);
  if (n > 0)
    {
      if (D_CDC && !(n == 1 && D_DC))
	CPutStr(D_CDC, n);
      else if (D_DC)
	{
	  for (i = n; i--; )
	    PutStr(D_DC);
	}
      else
	{
	  RefreshLine(y, xs, xe, 0);
	  /* UpdateLine(oml, y, xs, xe); */
	  return;
	}
    }
  else
    {
      if (!D_insert)
	{
	  if (D_CIC && !(n == -1 && D_IC))
	    CPutStr(D_CIC, -n);
	  else if (D_IC)
	    {
	      for (i = -n; i--; )
		PutStr(D_IC);
	    }
	  else if (D_IM)
	    {
	      InsertMode(1);
	      for (i = -n; i--; )
		INSERTCHAR(' ');
	    }
	  else
	    {
	      /* UpdateLine(oml, y, xs, xe); */
	      RefreshLine(y, xs, xe, 0);
	      return;
	    }
	}
      else
	{
	  for (i = -n; i--; )
	    INSERTCHAR(' ');
	}
    }
  if (D_lp_missing && y == D_bot)
    {
      if (n > 0)
        FixLP(D_width - 1 - n, y);
      D_lp_missing = 0;
    }
}

void
ScrollV(xs, ys, xe, ye, n)
int xs, ys, xe, ye, n;
{
  int i;
  int up;
  int oldtop, oldbot;
  int alok, dlok, aldlfaster;
  int missy = 0;

  ASSERT(display);
  if (n == 0)
    return;
  if (n >= ye - ys + 1 || -n >= ye - ys + 1)
    {
      Clear(xs, ys, xs, xe, xe, ye, 0);
      return;
    }
  if (xs != 0 || xe != D_width - 1)
    {
      Redisplay(0);
      return;
    }

  if (D_lp_missing)
    {
      if (D_bot > ye || D_bot < ys)
	missy = D_bot;
      else
	{
	  missy = D_bot - n;
          if (missy > ye || missy < ys)
	    D_lp_missing = 0;
	}
    }

  up = 1;
  if (n < 0)
    {
      up = 0;
      n = -n;
    }
  if (n >= ye - ys + 1)
    n = ye - ys + 1;

  oldtop = D_top;
  oldbot = D_bot;
  if (D_bot != ye)
    ChangeScrollRegion(ys, ye);
  alok = (D_AL || D_CAL || (ye == D_bot &&  up));
  dlok = (D_DL || D_CDL || (ye == D_bot && !up));
  if (D_top != ys && !(alok && dlok))
    ChangeScrollRegion(ys, ye);

  if (D_lp_missing && 
      (oldbot != D_bot ||
       (oldbot == D_bot && up && D_top == ys && D_bot == ye)))
    {
      FixLP(D_width - 1, oldbot);
      if (oldbot == D_bot)		/* have scrolled */
	{
	  if (--n == 0)
	    {
	      ChangeScrollRegion(oldtop, oldbot);
	      return;
	    }
	}
    }

  aldlfaster = (n > 1 && ye == D_bot && ((up && D_CDL) || (!up && D_CAL)));

  if (D_UT)
    SetRendition(&mchar_null);
  if ((up || D_SR) && D_top == ys && D_bot == ye && !aldlfaster)
    {
      if (up)
	{
	  GotoPos(0, ye);
	  while (n-- > 0)
	    PutStr(D_NL);		/* was SF, I think NL is faster */
	}
      else
	{
	  GotoPos(0, ys);
	  while (n-- > 0)
	    PutStr(D_SR);
	}
    }
  else if (alok && dlok)
    {
      if (up || ye != D_bot)
	{
          GotoPos(0, up ? ys : ye+1-n);
          if (D_CDL && !(n == 1 && D_DL))
	    CPutStr(D_CDL, n);
	  else
	    for(i = n; i--; )
	      PutStr(D_DL);
	}
      if (!up || ye != D_bot)
	{
          GotoPos(0, up ? ye+1-n : ys);
          if (D_CAL && !(n == 1 && D_AL))
	    CPutStr(D_CAL, n);
	  else
	    for(i = n; i--; )
	      PutStr(D_AL);
	}
    }
  else
    {
      Redisplay(0);
      return;
    }
  if (D_lp_missing && missy != D_bot)
    FixLP(D_width - 1, missy);
  ChangeScrollRegion(oldtop, oldbot);
  if (D_lp_missing && missy != D_bot)
    FixLP(D_width - 1, missy);
}

void
SetAttr(new)
register int new;
{
  register int i, j, old, typ;

  if (!display || (old = D_rend.attr) == new)
    return;
#if defined(TERMINFO) && defined(USE_SGR)
  debug1("USE_SGR defined, sa is %s\n", D_SA ? D_SA : "undefined");
  if (D_SA)
    {
      char *tparm();
      SetFont(ASCII);
      tputs(tparm(D_SA, new & A_SO, new & A_US, new & A_RV, new & A_BL,
			new & A_DI, new & A_BD, 0         , 0         ,
			0), 1, PutChar);
      D_rend.attr = new;
      D_atyp = 0;
# ifdef COLOR
      if (D_CAF || D_CAB)
	D_rend.color = 0;
# endif
      return;
    }
#endif
  typ = D_atyp;
  if ((new & old) != old)
    {
      if ((typ & ATYP_U))
        PutStr(D_UE);
      if ((typ & ATYP_S))
        PutStr(D_SE);
      if ((typ & ATYP_M))
	{
          PutStr(D_ME);
#ifdef COLOR
	  /* ansi attrib handling: \E[m resets color, too */
	  if (D_CAF || D_CAB)
	    D_rend.color = 0;
#endif
	}
      old = 0;
      typ = 0;
    }
  old ^= new;
  for (i = 0, j = 1; old && i < NATTR; i++, j <<= 1)
    {
      if ((old & j) == 0)
	continue;
      old ^= j;
      if (D_attrtab[i])
	{
	  PutStr(D_attrtab[i]);
	  typ |= D_attrtyp[i];
	}
    }
  D_rend.attr = new;
  D_atyp = typ;
}

void
SetFont(new)
int new;
{
  if (!display || D_rend.font == new)
    return;
  D_rend.font = new;
#ifdef KANJI
  if ((new == KANJI || new == KANA) && D_kanji)
    return;	/* all done in RAW_PUTCHAR */
#endif
  if (D_xtable && D_xtable[(int)(unsigned char)new] &&
      D_xtable[(int)(unsigned char)new][256])
    {
      PutStr(D_xtable[(int)(unsigned char)new][256]);
      return;
    }

  if (!D_CG0 && new != '0')
    new = ASCII;

  if (new == ASCII)
    PutStr(D_CE0);
#ifdef KANJI
  else if (new < ' ')
    {
      AddStr("\033$");
      AddChar(new + '@');
    }
#endif
  else
    CPutStr(D_CS0, new);
}

#ifdef COLOR
void
SetColor(new)
int new;
{
  int of, ob, f, b;

  if (!display || D_rend.color == new)
    return;
  of = D_rend.color & 0xf;
  ob = (D_rend.color >> 4) & 0xf;
  f = new & 0xf;
  b = (new >> 4) & 0xf;

  if (!D_CAX && ((f == 0 && f != of) || (b == 0 && b != ob)))
    {
      int oattr;

      oattr = D_rend.attr;
      AddStr("\033[m");
      D_rend.attr = 0;
      D_rend.color = 0;
      of = ob = 0;
      SetAttr(oattr);
    }
  if (D_CAF || D_CAB)
    {
      if (f != of)
	CPutStr(D_CAF, 9 - f);
      if (b != ob)
	CPutStr(D_CAB, 9 - b);
    }
  D_rend.color = new;
}
#endif

void
SetRendition(mc)
struct mchar *mc;
{
  if (!display)
    return;
  if (D_rend.attr != mc->attr)
    SetAttr(mc->attr);
#ifdef COLOR
  if (D_rend.color != mc->color)
    SetColor(mc->color);
#endif
  if (D_rend.font != mc->font)
    SetFont(mc->font);
}

void
SetRenditionMline(ml, x)
struct mline *ml;
int x;
{
  if (!display)
    return;
  if (D_rend.attr != ml->attr[x])
    SetAttr(ml->attr[x]);
#ifdef COLOR
  if (D_rend.color != ml->color[x])
    SetColor(ml->color[x]);
#endif
  if (D_rend.font != ml->font[x])
    SetFont(ml->font[x]);
}

void
MakeStatus(msg)
char *msg;
{
  register char *s, *t;
  register int max, ti;

  if (!display)
    return;
  
  if (!D_tcinited)
    {
      debug("tc not inited, just writing msg\n");
      AddStr(msg);
      AddStr("\r\n");
      Flush();
      return;
    }
  if (!use_hardstatus || !D_HS)
    {
      max = D_width;
      if (D_CLP == 0)
	max--;
    }
  else
    max = D_WS;
  if (D_status)
    {
      if (!D_status_bell)
	{
	  ti = time((time_t *) 0) - D_status_time;
	  if (ti < MsgMinWait)
	    sleep(MsgMinWait - ti);
	}
      RemoveStatus();
    }
  for (s = t = msg; *s && t - msg < max; ++s)
    if (*s == BELL)
      PutStr(D_BL);
    else if ((unsigned char)*s >= ' ' && *s != 0177)
      *t++ = *s;
  *t = '\0';
  if (t > msg)
    {
      if (t - msg >= D_status_buflen)
        {
          char *buf;
          if (D_status_lastmsg)
	    buf = realloc(D_status_lastmsg, t - msg + 1);
	  else
	    buf = malloc(t - msg + 1);
	  if (buf)
	    {
              D_status_lastmsg = buf;
              D_status_buflen = t - msg + 1;
            }
        }
      if (t - msg < D_status_buflen)
        strcpy(D_status_lastmsg, msg);
      D_status = 1;
      D_status_len = t - msg;
      D_status_lastx = D_x;
      D_status_lasty = D_y;
      if (!use_hardstatus || !D_HS)
	{
	  debug1("using STATLINE %d\n", STATLINE);
	  GotoPos(0, STATLINE);
          SetRendition(&mchar_so);
	  InsertMode(0);
	  AddStr(msg);
          D_x = -1;
	}
      else
	{
	  debug("using HS\n");
          SetRendition(&mchar_null);
	  InsertMode(0);
	  if (D_hstatus)
	    PutStr(D_DS);
	  CPutStr(D_TS, 0);
	  AddStr(msg);
	  PutStr(D_FS);
	  D_hstatus = 1;
	}
      Flush();
      (void) time(&D_status_time);
    }
}

void
RemoveStatus()
{
  struct win *p;

  if (!display)
    return;
  if (!D_status)
    return;
  
  /*
   * UGLY HACK ALERT - this should NOT be in display.c
   * We need to find the window that caused an activity or bell
   * message, to reenable this function there.
   */
  for (p = windows; p; p = p->w_next)
    { 
      if (p->w_display != display)
	continue;
      if (p->w_monitor == MON_MSG)
	{
	  debug1("RemoveStatus clearing monitor win %d\n", p->w_number);
	  p->w_monitor = MON_DONE;
	}
      if (p->w_bell == BELL_MSG)
	{
	  debug1("RemoveStatus clearing bell win %d\n", p->w_number);
	  p->w_bell = BELL_DONE;
	}
    }
  D_status = 0;
  D_status_bell = 0;
  if (!use_hardstatus || !D_HS)
    {
      GotoPos(0, STATLINE);
      RefreshLine(STATLINE, 0, D_status_len - 1, 0);
      GotoPos(D_status_lastx, D_status_lasty);
    }
  else
    {
      /*
      SetRendition(&mchar_null);
      if (D_hstatus)
        PutStr(D_DS);
      */
      RefreshStatus();
    }
  SetCursor();
}

/*
 *  Refreshes the harstatus of the _window_. Shouldn't be here...
 */

void
RefreshStatus()
{
  char *buf;

  if (D_HS)
    {
      SetRendition(&mchar_null);
      if (D_hstatus)
        PutStr(D_DS);
      if (D_fore && D_fore->w_hstatus)
	{
	  buf = MakeWinMsg(D_fore->w_hstatus, D_fore, '\005');
	  CPutStr(D_TS, 0);
	  if (strlen(buf) > D_WS)
	    AddStrn(buf, D_WS);
	  else
	    AddStr(buf);
	  PutStr(D_FS);
	  D_hstatus = 1;
	}
    }
  else if (D_fore && D_fore->w_hstatus)
    {
      buf = MakeWinMsg(D_fore->w_hstatus, D_fore, '\005');
      Msg(0, "%s", buf);
    }
}

void
RefreshLine(y, from, to, isblank)
int y, from, to, isblank;
{
  ASSERT(display);
  debug2("RefreshLine %d %d", y, from);
  debug2(" %d %d\n", to, isblank);
  if (isblank == 0 && D_CE && to == D_width - 1)
    {
      GotoPos(from, y);
      if (D_UT)
	SetRendition(&mchar_null);
      PutStr(D_CE);
      isblank = 1;
    }
  RedisplayLine(y, from, to, isblank);
}

void
FixLP(x2, y2)
register int x2, y2;
{
  struct mchar oldrend;

  ASSERT(display);
  oldrend = D_rend;
  GotoPos(x2, y2);
  SetRendition(&D_lpchar);
  PUTCHAR(D_lpchar.image);
  D_lp_missing = 0;
  SetRendition(&oldrend);
}

void
DisplayLine(oml, ml, y, from, to)
struct mline *oml, *ml;
int from, to, y;
{
  register int x;
  int last2flag = 0, delete_lp = 0;

  ASSERT(display);
  ASSERT(y >= 0 && y < D_height);
  ASSERT(from >= 0 && from < D_width);
  ASSERT(to >= 0 && to < D_width);
  if (!D_CLP && y == D_bot && to == D_width - 1)
    if (D_lp_missing || !cmp_mline(oml, ml, to))
      {
	if ((D_IC || D_IM) && from < to)
	  {
	    to -= 2;
	    last2flag = 1;
	    D_lp_missing = 0;
	  }
	else
	  {
	    to--;
	    delete_lp = (D_CE || D_DC || D_CDC);
	    D_lp_missing = !cmp_mchar_mline(&mchar_blank, ml, to);
	    copy_mline2mchar(&D_lpchar, ml, to);
	  }
      }
    else
      to--;
  for (x = from; x <= to; x++)
    {
      if (x || D_x != D_width || D_y != y - 1)
        {
	  if (x < to || x != D_width - 1 || ml->image[x + 1] == ' ')
	    if (cmp_mline(oml, ml, x))
	      continue;
	  GotoPos(x, y);
        }
#ifdef KANJI
      if (badkanji(ml->font, x))
	{
	  x--;
	  GotoPos(x, y);
	}
#endif
      SetRenditionMline(ml, x);
      PUTCHAR(ml->image[x]);
#ifdef KANJI
      if (ml->font[x] == KANJI)
	PUTCHAR(ml->image[++x]);
#endif
    }
  if (to == D_width - 1 && y < D_height - 1 && ml->image[to + 1] == ' ')
    GotoPos(0, y + 1);
  if (last2flag)
    {
      GotoPos(x, y);
      SetRenditionMline(ml, x + 1);
      PUTCHAR(ml->image[x + 1]);
      GotoPos(x, y);
      SetRenditionMline(ml, x);
      INSERTCHAR(ml->image[x]);
    }
  else if (delete_lp)
    {
      if (D_UT)
	{
	  SetRendition(&mchar_null);
	}
      if (D_DC)
	PutStr(D_DC);
      else if (D_CDC)
	CPutStr(D_CDC, 1);
      else if (D_CE)
	PutStr(D_CE);
    }
}

void
SetLastPos(x,y)
int x,y;
{
  ASSERT(display);
  D_x = x;
  D_y = y;
}

int
ResizeDisplay(wi, he)
int wi, he;
{
  ASSERT(display);
  debug2("ResizeDisplay: to (%d,%d).\n", wi, he);
  if (D_width == wi && D_height == he)
    {
      debug("ResizeDisplay: No change\n");
      return 0;
    }
  if (D_CWS)
    {
      debug("ResizeDisplay: using WS\n");
      PutStr(tgoto(D_CWS, wi, he));
      ChangeScreenSize(wi, he, 0);
      return 0;
    }
  else if (D_CZ0 && (wi == Z0width || wi == Z1width))
    {
      debug("ResizeDisplay: using Z0/Z1\n");
      PutStr(wi == Z0width ? D_CZ0 : D_CZ1);
      ChangeScreenSize(wi, D_height, 0);
      return (he == D_height) ? 0 : -1;
    }
  return -1;
}

void
ChangeScrollRegion(newtop, newbot)
int newtop, newbot;
{
  if (display == 0)
    return;
  if (D_CS == 0)
    {
      D_top = 0;
      D_bot = D_height - 1;
      return;
    }
  if (D_top == newtop && D_bot == newbot)
    return;
  debug2("ChangeScrollRegion: (%d - %d)\n", newtop, newbot);
  PutStr(tgoto(D_CS, newbot, newtop));
  D_top = newtop;
  D_bot = newbot;
  D_y = D_x = -1;		/* Just in case... */
}


/*
 *  Layer creation / removal
 */

int
InitOverlayPage(datasize, lf, block)
int datasize;
struct LayFuncs *lf;
int block;
{
  char *data;
  struct layer *newlay;

  RemoveStatus();
  debug3("Entering new layer  display %#x  D_fore %#x  oldlay %#x\n", 
  	 (unsigned int)display, (unsigned int)D_fore, (unsigned int)D_lay);
  if ((newlay = (struct layer *)malloc(sizeof(struct layer))) == 0)
    {
      Msg(0, "No memory for layer struct");
      return -1;
    }
  data = 0;
  if (datasize)
    {
      if ((data = malloc(datasize)) == 0)
	{
	  free((char *)newlay);
	  Msg(0, "No memory for layer data");
	  return -1;
	}
      bzero(data, datasize);
    }
  newlay->l_layfn = lf;
  newlay->l_block = block | D_lay->l_block;
  newlay->l_data = data;
  newlay->l_next = D_lay;
  if (D_fore)
    {
      D_fore->w_lay = newlay;	/* XXX: CHECK */
      D_fore->w_active = 0;	/* XXX: CHECK */
    }
  D_lay = newlay;
  D_layfn = newlay->l_layfn;
  Restore();
  return 0;
}

void
ExitOverlayPage()
{
  struct layer *oldlay;

  debug3("Exiting layer  display %#x  fore %#x  D_lay %#x\n", 
         (unsigned int)display, (unsigned int)D_fore, (unsigned int)D_lay);
  oldlay = D_lay;
  if (oldlay->l_data)
    free(oldlay->l_data);
  D_lay = oldlay->l_next;
  D_layfn = D_lay->l_layfn;
  free((char *)oldlay);
  if (D_fore)
    D_fore->w_lay = D_lay;	/* XXX: Is this necessary ? */
  Restore();
  SetCursor();
}


/*
 *  Output buffering routines
 */

void
AddStr(str)
char *str;
{
  register char c;

  ASSERT(display);
  while ((c = *str++))
    AddChar(c);
}

void
AddStrn(str, n)
char *str;
int n;
{
  register char c;

  ASSERT(display);
  while ((c = *str++) && n-- > 0)
    AddChar(c);
  while (n-- > 0)
    AddChar(' ');
}

void
Flush()
{
  register int l;
  register char *p;

  ASSERT(display);
  l = D_obufp - D_obuf;
  debug1("Flush(): %d\n", l);
  ASSERT(l + D_obuffree == D_obuflen);
  if (l == 0)
    return;
  if (D_userfd < 0)
    {
      D_obuffree += l;
      D_obufp = D_obuf;
      return;
    }
  p = D_obuf;
  if (fcntl(D_userfd, F_SETFL, 0))
    debug1("Warning: BLOCK fcntl failed: %d\n", errno);
  while (l)
    {
      register int wr;
      wr = write(D_userfd, p, l);
      if (wr <= 0) 
	{
	  if (errno == EINTR) 
	    continue;
	  debug1("Writing to display: %d\n", errno);
	  wr = l;
	}
      D_obuffree += wr;
      p += wr;
      l -= wr;
    }
  D_obuffree += l;
  D_obufp = D_obuf;
  if (fcntl(D_userfd, F_SETFL, FNBLOCK))
    debug1("Warning: NBLOCK fcntl failed: %d\n", errno);
}

void
freetty()
{
  if (D_userfd >= 0)
    close(D_userfd);
  debug1("did freetty %d\n", D_userfd);
  D_userfd = -1;
  D_obufp = 0;
  D_obuffree = 0;
  if (D_obuf)
    free(D_obuf);
  D_obuf = 0;
  D_obuflen = 0;
}

/*
 *  Asynchronous output routines by
 *  Tim MacKenzie (tym@dibbler.cs.monash.edu.au)
 */

void
Resize_obuf()
{
  register int ind;

  ASSERT(display);
  if (D_obuflen && D_obuf)
    {
      ind  = D_obufp - D_obuf;
      D_obuflen += GRAIN;
      D_obuffree += GRAIN;
      D_obuf = realloc(D_obuf, D_obuflen);
    }
  else
    {
      ind  = 0;
      D_obuflen = GRAIN;
      D_obuffree = GRAIN;
      D_obuf = malloc(D_obuflen);
    }
  if (!D_obuf)
    Panic(0, "Out of memory");
  D_obufp = D_obuf + ind;
  debug1("ResizeObuf: resized to %d\n", D_obuflen);
}

#ifdef AUTO_NUKE
void
NukePending()
{/* Nuke pending output in current display, clear screen */
  register int len;
  int oldtop = D_top, oldbot = D_bot;
  struct mchar oldrend;
  int oldkeypad = D_keypad, oldcursorkeys = D_cursorkeys;
  int oldcurvis = D_curvis;

  oldrend = D_rend;
  len = D_obufp - D_obuf;
  debug1("NukePending: nuking %d chars\n", len);
  
  /* Throw away any output that we can... */
# ifdef POSIX
  tcflush(D_userfd, TCOFLUSH);
# else
#  ifdef TCFLSH
  (void) ioctl(D_userfd, TCFLSH, (char *) 1);
#  endif
# endif

  D_obufp = D_obuf;
  D_obuffree += len;
  D_top = D_bot = -1;
  PutStr(D_TI);
  PutStr(D_IS);
  /* Turn off all attributes. (Tim MacKenzie) */
  if (D_ME)
    PutStr(D_ME);
  else
    {
      PutStr(D_SE);
      PutStr(D_UE);
    }
  /* FIXME: reset color! */
  /* Check for toggle */
  if (D_IM && strcmp(D_IM, D_EI))
    PutStr(D_EI);
  D_insert = 0;
  /* Check for toggle */
#ifndef MAPKEYS
  if (D_KS && strcmp(D_KS, D_KE))
    PutStr(D_KE);
  D_keypad = 0;
  if (D_CCS && strcmp(D_CCS, D_CCE))
    PutStr(D_CCE);
  D_cursorkeys = 0;
#endif
  PutStr(D_CE0);
  D_rend = mchar_null;
  D_atyp = 0;
  PutStr(D_DS);
  D_hstatus = 0;
  PutStr(D_VE);
  D_curvis = 0;
  ChangeScrollRegion(oldtop, oldbot);
  SetRendition(&oldrend);
  KeypadMode(oldkeypad);
  CursorkeysMode(oldcursorkeys);
  CursorVisibility(oldcurvis);
  if (D_CWS)
    {
      debug("ResizeDisplay: using WS\n");
      PutStr(tgoto(D_CWS, D_width, D_height));
    }
  else if (D_CZ0 && (D_width == Z0width || D_width == Z1width))
    {
      debug("ResizeDisplay: using Z0/Z1\n");
      PutStr(D_width == Z0width ? D_CZ0 : D_CZ1);
    }
}
#endif /* AUTO_NUKE */

#ifdef KANJI
int
badkanji(f, x)
char *f;
int x;
{
  int i, j;
  if (f[x] != KANJI)
    return 0;
  for (i = j = 0; i < x; i++)
    if (*f++ == KANJI)
      j ^= 1;
  return j;
}
#endif
