/*
Copyright (C) 2004-2009 John E. Davis

This file is part of the S-Lang Library.

The S-Lang Library 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 of the
License, or (at your option) any later version.

The S-Lang Library 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 library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
USA.  
*/

#include "slinclud.h"

#include <time.h>
#include <ctype.h>

#if !defined(VMS) || (__VMS_VER >= 70000000)
# include <sys/time.h>
# ifdef __QNX__
#  include <sys/select.h>
# endif
# include <sys/types.h>
#endif

#if defined(__BEOS__) && !defined(__HAIKU__)
/* Prototype for select */
# include <net/socket.h>
#endif

#ifdef HAVE_TERMIOS_H
# include <termios.h>
#endif

#ifdef VMS
# include <unixlib.h>
# include <unixio.h>
# include <dvidef.h>
# include <descrip.h>
# include <lib$routines.h>
# include <starlet.h>
#else
# if !defined(sun)
#  include <sys/ioctl.h>
# endif
#endif

#ifdef SYSV
# include <sys/termio.h>
# include <sys/stream.h>
# include <sys/ptem.h>
# include <sys/tty.h>
#endif

#if defined (_AIX) && !defined (FD_SET)
# include <sys/select.h>	/* for FD_ISSET, FD_SET, FD_ZERO */
#endif

#include <errno.h>

#if defined(__DECC) && defined(VMS)
/* These get prototypes for write an sleep */
# include <unixio.h>
#endif
#include <signal.h>

#include "slang.h"
#include "_slang.h"

/* Colors:  These definitions are used for the display.  However, the
 * application only uses object handles which get mapped to this
 * internal representation.  The mapping is performed by the Color_Map
 * structure below. */

#define CHAR_MASK	0x000000FF
#define FG_MASK		0x0000FF00
#define BG_MASK		0x00FF0000
#define ATTR_MASK	0x1F000000
#define BGALL_MASK	0x0FFF0000

/* The 0x10000000 bit represents the alternate character set.  BGALL_MASK does
 * not include this attribute.
 */

#define GET_FG(fgbg) (((fgbg) & FG_MASK) >> 8)
#define GET_BG(fgbg) (((fgbg) & BG_MASK) >> 16)
#define MAKE_COLOR(fg, bg) (((fg) | ((bg) << 8)) << 8)

int SLtt_Screen_Cols = 80;
int SLtt_Screen_Rows = 24;
int SLtt_Term_Cannot_Insert = 0;
int SLtt_Term_Cannot_Scroll = 0;
int SLtt_Use_Ansi_Colors = 0;
int SLtt_Blink_Mode = 0;
int SLtt_Use_Blink_For_ACS = 0;
int SLtt_Newline_Ok = 0;
int SLtt_Has_Alt_Charset = 0;
int SLtt_Force_Keypad_Init = 0;

/* static int UTF8_Mode = -1; */

void (*_pSLtt_color_changed_hook)(void);

static int Bce_Color_Offset = 0;
static int Can_Background_Color_Erase = 1;

/* -1 means unknown */
int SLtt_Has_Status_Line = -1;	       /* hs */
int SLang_TT_Write_FD = -1;

static int Automatic_Margins;
/* static int No_Move_In_Standout; */
static int Worthless_Highlight;
#define HP_GLITCH_CODE
#ifdef HP_GLITCH_CODE
/* This glitch is exclusive to HP term.  Basically it means that to clear
 * attributes, one has to erase to the end of the line.
 */
static int Has_HP_Glitch;
#endif

static char *Reset_Color_String;
static int Is_Color_Terminal = 0;

static int Linux_Console;
static int Mouse_Mode = -1;

/* The following comment is nolonger valid, but is here in case there are
 * some apps that use SLtt_Use_Blink_For_ACS and still need porting to v2.
 * -------
 * It is crucial that JMAX_COLORS must be less than 128 since the high bit
 * is used to indicate a character from the ACS (alt char set).  The exception
 * to this rule is if SLtt_Use_Blink_For_ACS is true.  This means that of
 * the highbit is set, we interpret that as a blink character.  This is
 * exploited by DOSemu.
 */
#define JMAX_COLORS 512
#define JNORMAL_COLOR 0

typedef struct
{
   SLtt_Char_Type fgbg;
   SLtt_Char_Type mono;
}
Brush_Info_Type;

static Brush_Info_Type Brush_Table[JMAX_COLORS];

/* 0 if least significant bit is blue, not red */
static int Is_Fg_BGR = 0;
static int Is_Bg_BGR = 0;
#define COLOR_ARG(color, is_bgr) ((is_bgr) ? RGB_to_BGR[(color)&0x7] : (color))
static SLCONST int RGB_to_BGR[] =
{
     0, 4, 2, 6, 1, 5, 3, 7
};


static SLCONST char *Color_Fg_Str = "\033[3%dm";
static SLCONST char *Color_Bg_Str = "\033[4%dm";
static SLCONST char *Default_Color_Fg_Str = "\033[39m";
static SLCONST char *Default_Color_Bg_Str = "\033[49m";

static int Max_Terminfo_Colors = 8;	       /* termcap Co */

char *SLtt_Graphics_Char_Pairs = NULL;	       /* ac termcap string -- def is vt100 */

/* 1 if terminal lacks the ability to go into insert mode or into delete
   mode. Currently controlled by S-Lang but later perhaps termcap. */

static SLCONST char *UnderLine_Vid_Str;
static SLCONST char *Blink_Vid_Str;
static SLCONST char *Bold_Vid_Str;
static SLCONST char *Ins_Mode_Str; /* = "\033[4h"; */   /* ins mode (im) */
static SLCONST char *Eins_Mode_Str; /* = "\033[4l"; */  /* end ins mode (ei) */
static SLCONST char *Scroll_R_Str; /* = "\033[%d;%dr"; */ /* scroll region */
static SLCONST char *Cls_Str; /* = "\033[2J\033[H"; */  /* cl termcap STR  for ansi terminals */
static SLCONST char *Rev_Vid_Str; /* = "\033[7m"; */    /* mr,so termcap string */
static SLCONST char *Norm_Vid_Str; /* = "\033[m"; */   /* me,se termcap string */
static SLCONST char *Del_Eol_Str; /* = "\033[K"; */	       /* ce */
static SLCONST char *Del_Bol_Str; /* = "\033[1K"; */	       /* cb */
static SLCONST char *Del_Char_Str; /* = "\033[P"; */   /* dc */
static SLCONST char *Del_N_Lines_Str; /* = "\033[%dM"; */  /* DL */
static SLCONST char *Add_N_Lines_Str; /* = "\033[%dL"; */  /* AL */
static SLCONST char *Rev_Scroll_Str;
static SLCONST char *Curs_Up_Str;
static SLCONST char *Curs_F_Str;    /* RI termcap string */
static SLCONST char *Cursor_Visible_Str;    /* ve termcap string */
static SLCONST char *Cursor_Invisible_Str;    /* vi termcap string */
#if 0
static SLCONST char *Start_Mouse_Rpt_Str;  /* Start mouse reporting mode */
static SLCONST char *End_Mouse_Rpt_Str;  /* End mouse reporting mode */
#endif
static SLCONST char *Start_Alt_Chars_Str;  /* as */
static SLCONST char *End_Alt_Chars_Str;   /* ae */
static SLCONST char *Enable_Alt_Char_Set;  /* eA */

static SLCONST char *Term_Init_Str;
static SLCONST char *Keypad_Init_Str;
static SLCONST char *Term_Reset_Str;
static SLCONST char *Keypad_Reset_Str;

/* status line functions */
static SLCONST char *Disable_Status_line_Str;  /* ds */
static SLCONST char *Return_From_Status_Line_Str;   /* fs */
static SLCONST char *Goto_Status_Line_Str;     /* ts */
static int Num_Status_Line_Columns;    /* ws */
/* static int Status_Line_Esc_Ok;	 */       /* es */

/* static int Len_Curs_F_Str = 5; */

/* cm string has %i%d since termcap numbers columns from 0 */
/* char *CURS_POS_STR = "\033[%d;%df";  ansi-- hor and vert pos */
static SLCONST char *Curs_Pos_Str; /* = "\033[%i%d;%dH";*/   /* cm termcap string */

/* scrolling region */
static int Scroll_r1 = 0, Scroll_r2 = 23;
static int Cursor_r, Cursor_c;	       /* 0 based */

/* current attributes --- initialized to impossible value */
static SLtt_Char_Type Current_Fgbg = 0xFFFFFFFFU;

static int Cursor_Set;		       /* 1 if cursor position known, 0
					* if not.  -1 if only row is known
					*/

#define MAX_OUTPUT_BUFFER_SIZE 4096

static unsigned char Output_Buffer[MAX_OUTPUT_BUFFER_SIZE];
static unsigned char *Output_Bufferp = Output_Buffer;

unsigned long SLtt_Num_Chars_Output = 0;

int _pSLusleep (unsigned long usecs)
{
#if !defined(VMS) || (__VMS_VER >= 70000000)
   struct timeval tv;
   tv.tv_sec = usecs / 1000000;
   tv.tv_usec = usecs % 1000000;
   return select(0, NULL, NULL, NULL, &tv);
#else
   return 0;
#endif
}

int SLtt_flush_output (void)
{
   int nwrite = 0;
   unsigned int total;
   int n = (int) (Output_Bufferp - Output_Buffer);

   SLtt_Num_Chars_Output += n;

   total = 0;
   while (n > 0)
     {
	nwrite = write (SLang_TT_Write_FD, (char *) Output_Buffer + total, n);
	if (nwrite == -1)
	  {
	     nwrite = 0;
#ifdef EAGAIN
	     if (errno == EAGAIN)
	       {
		  _pSLusleep (100000);   /* 1/10 sec */
		  continue;
	       }
#endif
#ifdef EWOULDBLOCK
	     if (errno == EWOULDBLOCK)
	       {
		  _pSLusleep (100000);
		  continue;
	       }
#endif
#ifdef EINTR
	     if (errno == EINTR) continue;
#endif
	     break;
	  }
	n -= nwrite;
	total += nwrite;
     }
   Output_Bufferp = Output_Buffer;
   return n;
}

int SLtt_Baud_Rate = 0;
static void tt_write(SLCONST char *str, unsigned int n)
{
   static unsigned long last_time;
   static int total;
   unsigned long now;
   unsigned int ndiff;

   if ((str == NULL) || (n == 0)) return;
   total += n;

   while (1)
     {
	ndiff = MAX_OUTPUT_BUFFER_SIZE - (int) (Output_Bufferp - Output_Buffer);
	if (ndiff < n)
	  {
	     SLMEMCPY ((char *) Output_Bufferp, str, ndiff);
	     Output_Bufferp += ndiff;
	     SLtt_flush_output ();
	     n -= ndiff;
	     str += ndiff;
	  }
	else
	  {
	     SLMEMCPY ((char *) Output_Bufferp, str, n);
	     Output_Bufferp += n;
	     break;
	  }
     }

   if (((SLtt_Baud_Rate > 150) && (SLtt_Baud_Rate <= 9600))
       && (10 * total > SLtt_Baud_Rate))
     {
	total = 0;
	if ((now = (unsigned long) time(NULL)) - last_time <= 1)
	  {
	     SLtt_flush_output ();
	     sleep((unsigned) 1);
	  }
	last_time = now;
     }
}

static void tt_write_string (SLCONST char *str)
{
   if (str != NULL) tt_write(str, strlen(str));
}

void SLtt_write_string (SLFUTURE_CONST char *str)
{
   tt_write_string (str);
   Cursor_Set = 0;
}

void SLtt_putchar (char ch)
{
   SLtt_normal_video ();
   if (Cursor_Set == 1)
     {
	if (ch >= ' ') Cursor_c++;
	else if (ch == '\b') Cursor_c--;
	else if (ch == '\r') Cursor_c = 0;
	else Cursor_Set = 0;

	if ((Cursor_c + 1 == SLtt_Screen_Cols)
	    && Automatic_Margins) Cursor_Set = 0;
     }

   if (Output_Bufferp < Output_Buffer + MAX_OUTPUT_BUFFER_SIZE)
     {
	*Output_Bufferp++ = (unsigned char) ch;
     }
   else tt_write (&ch, 1);
}

static unsigned int tt_sprintf(char *buf, unsigned int buflen, SLCONST char *fmt, int x, int y)
{
   SLCONST char *fmt_max;
   unsigned char *b, *bmax, ch;
   int offset;
   int z, z1, parse_level;
   int zero_pad;
   int field_width;
   int variables [26];
   int stack [64];
   unsigned int stack_len;
   int parms [10];
#define STACK_POP (stack_len ? stack[--stack_len] : 0)

   if (fmt == NULL)
     {
	*buf = 0;
	return 0;
     }

   stack [0] = y;	       /* pushed for termcap */
   stack [1] = x;
   stack_len = 2;

   parms [1] = x;	       /* p1 */
   parms [2] = y;	       /* p2 */

   offset = 0;
   zero_pad = 0;
   field_width = 0;

   b = (unsigned char *) buf;
   bmax = b + buflen;

   fmt_max = fmt + strlen (fmt);

   while ((fmt < fmt_max) && (b < bmax))
     {
	ch = *fmt++;

	if (ch != '%')
	  {
	     *b++ = ch;
	     continue;
	  }

	if (fmt == fmt_max) break;
	ch = *fmt++;

	switch (ch)
	  {
	   default:
	     *b++ = ch;
	     break;

	   case 'p':

	     if (fmt == fmt_max) break;
	     ch = *fmt++;
	     if ((ch >= '0') && (ch <= '9'))
	       stack [stack_len++] = parms [ch - '0'];
	     break;

	   case '\'':   /* 'x' */
	     if (fmt == fmt_max) break;
	     stack [stack_len++] = *fmt++;
	     if (fmt < fmt_max) fmt++;     /* skip ' */
	     break;

	   case '{':	       /* literal constant, e.g. {30} */
	     z = 0;
	     while ((fmt < fmt_max) && ((ch = *fmt) <= '9') && (ch >= '0'))
	       {
		  z = z * 10 + (ch - '0');
		  fmt++;
	       }
	     stack [stack_len++] = z;
	     if ((ch == '}') && (fmt < fmt_max)) fmt++;
	     break;

	   case '0':
	     if (fmt == fmt_max) break;
	     ch = *fmt;
	     if ((ch != '2') && (ch != '3'))
	       break;
	     zero_pad = 1;
	     fmt++;
	     /* drop */

	   case '2':
	   case '3':
	     if (fmt == fmt_max) break;
	     if (*fmt == 'x')
	       {
		  char x_fmt_buf [4];
		  char *x_fmt_buf_ptr;

		  x_fmt_buf_ptr = x_fmt_buf;
		  if (zero_pad) *x_fmt_buf_ptr++ = '0';
		  *x_fmt_buf_ptr++ = ch;
		  *x_fmt_buf_ptr++ = 'X';
		  *x_fmt_buf_ptr = 0;

		  z = STACK_POP;
		  z += offset;

		  sprintf ((char *)b, x_fmt_buf, z);
		  b += strlen ((char *)b);
		  zero_pad = 0;
		  break;
	       }

	     field_width = (ch - '0');
		  /* drop */

	   case 'd':
	     z = STACK_POP;
	     z += offset;
	     if (z >= 100)
	       {
		  *b++ = z / 100 + '0';
		  z = z % 100;
		  zero_pad = 1;
		  field_width = 2;
	       }
	     else if (zero_pad && (field_width == 3))
	       *b++ = '0';

	     if (b == bmax) break;
	     if (z >= 10)
	       {
		  *b++ = z / 10 + '0';
		  z = z % 10;
	       }
	     else if (zero_pad && (field_width >= 2))
	       *b++ = '0';

	     if (b == bmax) break;
	     *b++ = z + '0';
	     field_width = zero_pad = 0;
	     break;

	   case 'x':
	     z = STACK_POP;
	     z += offset;
	     if (b + 16 >= bmax)
	       break;
	     sprintf ((char *) b, "%X", z);
	     b += strlen ((char *)b);
	     break;

	   case 'i':
	     offset = 1;
	     break;

	   case '+':
	     /* Handling this depends upon whether or not we are parsing
	      * terminfo.  Terminfo requires the stack so use it as an
	      * indicator.
	      */
	     if (stack_len > 2)
	       {
		  z = STACK_POP;
		  stack [stack_len - 1] += z;
	       }
	     else if (fmt < fmt_max)
	       {
		  ch = *fmt++;
		  if ((unsigned char) ch == 128) ch = 0;
		  ch = ch + (unsigned char) STACK_POP;
		  if (ch == '\n') ch++;
		  *b++ = ch;
	       }
	     break;

	     /* Binary operators */
	   case '-':
	   case '*':
	   case '/':
	   case 'm':
	   case '&':
	   case '|':
	   case '^':
	   case '=':
	   case '>':
	   case '<':
	   case 'A':
	   case 'O':
	     z1 = STACK_POP;
	     z = STACK_POP;
	     switch (ch)
	       {
		case '-': z = (z - z1); break;
		case '*': z = (z * z1); break;
		case '/': z = (z / z1); break;
		case 'm': z = (z % z1); break;
		case '&': z = (z & z1); break;
		case '|': z = (z | z1); break;
		case '^': z = (z ^ z1); break;
		case '=': z = (z == z1); break;
		case '>': z = (z > z1); break;
		case '<': z = (z < z1); break;
		case 'A': z = (z && z1); break;
		case 'O': z = (z || z1); break;
	       }
	     stack [stack_len++] = z;
	     break;

	     /* unary */
	   case '!':
	     z = STACK_POP;
	     stack [stack_len++] = !z;
	     break;

	   case '~':
	     z = STACK_POP;
	     stack [stack_len++] = ~z;
	     break;

	   case 'r':		       /* termcap -- swap parameters */
	     z = stack [0];
	     stack [0] = stack [1];
	     stack [1] = z;
	     break;

	   case '.':		       /* termcap */
	   case 'c':
	     ch = (unsigned char) STACK_POP;
	     if (ch == '\n') ch++;
	     *b++ = ch;
	     break;

	   case 'g':
	     if (fmt == fmt_max) break;
	     ch = *fmt++;
	     if ((ch >= 'a') && (ch <= 'z'))
	       stack [stack_len++] = variables [ch - 'a'];
	     break;

	   case 'P':
	     if (fmt == fmt_max) break;
	     ch = *fmt++;
	     if ((ch >= 'a') && (ch <= 'z'))
	       variables [ch - 'a'] = STACK_POP;
	     break;

	     /* If then else parsing.  Actually, this is rather easy.  The
	      * key is to notice that 'then' does all the work.  'if' simply
	      * there to indicate the start of a test and endif indicates
	      * the end of tests.  If 'else' is seen, then skip to
	      * endif.
	      */
	   case '?':		       /* if */
	   case ';':		       /* endif */
	     break;

	   case 't':		       /* then */
	     z = STACK_POP;
	     if (z != 0)
	       break;		       /* good.  Continue parsing. */

	     /* z == 0 and test has failed.  So, skip past this entire if
	      * expression to the matching else or matching endif.
	      */
	     /* drop */
	   case 'e':		       /* else */

	     parse_level = 0;
	     while (fmt < fmt_max)
	       {
		  unsigned char ch1;

		  ch1 = *fmt++;
		  if ((ch1 != '%') || (fmt == fmt_max))
		    continue;

		  ch1 = *fmt++;

		  if (ch1 == '?') parse_level++;   /* new if */
		  else if (ch1 == 'e')
		    {
		       if ((ch != 'e') && (parse_level == 0))
			 break;
		    }
		  else if (ch1 == ';')
		    {
		       if (parse_level == 0)
			 break;
		       parse_level--;
		    }
	       }
	     break;
	  }
     }
   if (b >= bmax)
     b = bmax - 1;
   *b = 0;

   return (unsigned int) (b - (unsigned char *) buf);
}

static void tt_printf(SLCONST char *fmt, int x, int y)
{
   char buf[1024];
   unsigned int n;
   if (fmt == NULL) return;
   n = tt_sprintf(buf, sizeof (buf), fmt, x, y);
   tt_write(buf, n);
}

void SLtt_set_scroll_region (int r1, int r2)
{
   Scroll_r1 = r1;
   Scroll_r2 = r2;
   tt_printf (Scroll_R_Str, Scroll_r1, Scroll_r2);
   Cursor_Set = 0;
}

void SLtt_reset_scroll_region (void)
{
   SLtt_set_scroll_region(0, SLtt_Screen_Rows - 1);
}

int SLtt_set_cursor_visibility (int show)
{
   if ((Cursor_Visible_Str == NULL) || (Cursor_Invisible_Str == NULL))
     return -1;

   tt_write_string (show ? Cursor_Visible_Str : Cursor_Invisible_Str);
   return 0;
}

/* the goto_rc function moves to row relative to scrolling region */
void SLtt_goto_rc(int r, int c)
{
   char *s = NULL;
   int n;
   char buf[6];

   if ((c < 0) || (r < 0))
     {
	Cursor_Set = 0;
	return;
     }

   /* if (No_Move_In_Standout && Current_Fgbg) SLtt_normal_video (); */
   r += Scroll_r1;

   if ((Cursor_Set > 0) || ((Cursor_Set < 0) && !Automatic_Margins))
     {
	n = r - Cursor_r;
	if ((n == -1) && (Cursor_Set > 0) && (Cursor_c == c)
	    && (Curs_Up_Str != NULL))
	  {
	     s = (char *)Curs_Up_Str;
	  }
	else if ((n >= 0) && (n <= 4))
	  {
	     if ((n == 0) && (Cursor_Set == 1)
		 && ((c > 1) || (c == Cursor_c)))
	       {
		  if (Cursor_c == c) return;
		  if (Cursor_c == c + 1)
		    {
		       s = buf;
		       *s++ = '\b'; *s = 0;
		       s = buf;
		    }
	       }
	     else if (c == 0)
	       {
		  s = buf;
		  if ((Cursor_Set != 1) || (Cursor_c != 0)) *s++ = '\r';
		  while (n--) *s++ = '\n';
#ifdef VMS
		  /* Need to add this after \n to start a new record.  Sheesh. */
		  *s++ = '\r';
#endif
		  *s = 0;
		  s = buf;
	       }
	     /* Will fail on VMS */
#ifndef VMS
	     else if (SLtt_Newline_Ok && (Cursor_Set == 1) &&
		      (Cursor_c >= c) && (c + 3 > Cursor_c))
	       {
		  s = buf;
		  while (n--) *s++ = '\n';
		  n = Cursor_c - c;
		  while (n--) *s++ = '\b';
		  *s = 0;
		  s = buf;
	       }
#endif
	  }
     }
   if (s != NULL) tt_write_string(s);
   else tt_printf(Curs_Pos_Str, r, c);
   Cursor_c = c; Cursor_r = r;
   Cursor_Set = 1;
}

void SLtt_begin_insert (void)
{
   tt_write_string(Ins_Mode_Str);
}

void SLtt_end_insert (void)
{
   tt_write_string(Eins_Mode_Str);
}

void SLtt_delete_char (void)
{
   SLtt_normal_video ();
   tt_write_string(Del_Char_Str);
}

void SLtt_erase_line (void)
{
   tt_write ("\r", 1);
   Cursor_Set = 1; Cursor_c = 0;
   SLtt_del_eol();
   /* Put the cursor back at the beginning of the line */
   tt_write_string("\r");
   Cursor_Set = 1; Cursor_c = 0;
}

/* It appears that the Linux console, and most likely others do not
 * like scrolling regions that consist of one line.  So I have to
 * resort to this stupidity to make up for that stupidity.
 */
static void delete_line_in_scroll_region (void)
{
   SLtt_goto_rc (Cursor_r - Scroll_r1, 0);
   SLtt_del_eol ();
}

void SLtt_delete_nlines (int nn)
{
   int r1, curs;
   unsigned int n;

   if (nn <= 0) return;
   n = (unsigned int) nn;

   SLtt_normal_video ();

   if (Scroll_r1 == Scroll_r2)
     {
	delete_line_in_scroll_region ();
	return;
     }

   if (Del_N_Lines_Str != NULL) tt_printf(Del_N_Lines_Str, n, 0);
   else
   /* get a new terminal */
     {
	char buf[80];
	unsigned int dn = n;

	if (dn > sizeof (buf))
	  dn = sizeof (buf);

	SLMEMSET (buf, '\n', dn);
	while (n > dn)
	  {
	     tt_write (buf, dn);
	     n -= dn;
	  }
	tt_write (buf, n);

	r1 = Scroll_r1;
	curs = Cursor_r;
	SLtt_set_scroll_region(curs, Scroll_r2);
	SLtt_goto_rc(Scroll_r2 - Scroll_r1, 0);
	SLMEMSET(buf, '\n', (unsigned int) n);
	tt_write(buf, (unsigned int) n);
	/* while (n--) tt_putchar('\n'); */
	SLtt_set_scroll_region(r1, Scroll_r2);
	SLtt_goto_rc(curs, 0);
     }
}

void SLtt_cls (void)
{
   /* If the terminal is a color terminal but the user wants black and 
    * white, then make sure that the colors are reset.  This appears to be
    * necessary.
    */
   if ((SLtt_Use_Ansi_Colors == 0) && Is_Color_Terminal)
     {
	if (Reset_Color_String != NULL)
	  tt_write_string (Reset_Color_String);
	else
	  tt_write_string ("\033[0m\033[m");
     }

   SLtt_normal_video();
   SLtt_reset_scroll_region ();
   tt_write_string(Cls_Str);
}

void SLtt_reverse_index (int n)
{
   if (!n) return;

   SLtt_normal_video();

   if (Scroll_r1 == Scroll_r2)
     {
	delete_line_in_scroll_region ();
	return;
     }

   if (Add_N_Lines_Str != NULL) tt_printf(Add_N_Lines_Str,n, 0);
   else
     {
	while(n--) tt_write_string(Rev_Scroll_Str);
     }
}

int SLtt_Ignore_Beep = 1;
static SLCONST char *Visible_Bell_Str;

void SLtt_beep (void)
{
   if (SLtt_Ignore_Beep & 0x1) SLtt_putchar('\007');

   if (SLtt_Ignore_Beep & 0x2)
     {
	if (Visible_Bell_Str != NULL) tt_write_string (Visible_Bell_Str);
#ifdef __linux__
	else if (Linux_Console)
	  {
	     tt_write ("\033[?5h", 5);
	     SLtt_flush_output ();
	     _pSLusleep (50000);
	     tt_write ("\033[?5l", 5);
	  }
#endif
     }
   SLtt_flush_output ();
}

static void write_string_with_care (SLCONST char *);

static void del_eol (void)
{
#if 0
   int c;
#endif
   if ((Del_Eol_Str != NULL)
       && (Can_Background_Color_Erase || ((Current_Fgbg & ~0xFF) == 0)))
     {
	tt_write_string(Del_Eol_Str);
	return;
     }

#if 0
   c = Cursor_c;
   
   /* Avoid writing to the lower right corner.  If the terminal does not
    * have Del_Eol_Str, then it probably does not have what it takes to play
    * games with insert-mode to "push" the desired character into that corner.
    */
   if (Cursor_r + 1 < SLtt_Screen_Rows)
     c++;

   while (c < SLtt_Screen_Cols)
     {
	tt_write (" ", 1);
	c++;
     }
   Cursor_c = (SLtt_Screen_Cols-1);
#else
   while (Cursor_c < SLtt_Screen_Cols)
     {
	write_string_with_care (" ");
	Cursor_c++;
     }
   Cursor_c = SLtt_Screen_Cols - 1;
   Cursor_Set = 0;
#endif
}

void SLtt_del_eol (void)
{
   if (Current_Fgbg != 0xFFFFFFFFU) SLtt_normal_video ();
   del_eol ();
}

typedef SLCONST struct
{
   SLCONST char *name;
   SLtt_Char_Type color;
}
Color_Def_Type;

#define MAX_COLOR_NAMES 17
static Color_Def_Type Color_Defs [MAX_COLOR_NAMES] =
{
     {"black",		SLSMG_COLOR_BLACK},
     {"red",		SLSMG_COLOR_RED},
     {"green",		SLSMG_COLOR_GREEN},
     {"brown",		SLSMG_COLOR_BROWN},
     {"blue",		SLSMG_COLOR_BLUE},
     {"magenta",	SLSMG_COLOR_MAGENTA},
     {"cyan",		SLSMG_COLOR_CYAN},
     {"lightgray",	SLSMG_COLOR_LGRAY},
     {"gray",		SLSMG_COLOR_GRAY},
     {"brightred",	SLSMG_COLOR_BRIGHT_RED},
     {"brightgreen",	SLSMG_COLOR_BRIGHT_GREEN},
     {"yellow",		SLSMG_COLOR_BRIGHT_BROWN},
     {"brightblue",	SLSMG_COLOR_BRIGHT_BLUE},
     {"brightmagenta",	SLSMG_COLOR_BRIGHT_CYAN},
     {"brightcyan",	SLSMG_COLOR_BRIGHT_MAGENTA},
     {"white",		SLSMG_COLOR_BRIGHT_WHITE},
#define SLSMG_COLOR_DEFAULT 0xFF
     {"default",		SLSMG_COLOR_DEFAULT}
};

static int Brushes_Initialized = 0;

static int initialize_brushes (void)
{
   int fg, bg;
   Brush_Info_Type *b, *bmax;

   b = Brush_Table;
   bmax = b + JMAX_COLORS;

   bg = 0;
   while (b < bmax)
     {
	fg = 7;
	while (b < bmax)
	  {
	     if (fg != bg)
	       {
		  b->fgbg = MAKE_COLOR(fg,bg);
		  b->mono = SLTT_REV_MASK;
		  b++;
	       }
	     if (fg == 0)
	       break;
	     fg--;
	  }
	bg++;
	if (bg == 8)
	  bg = 0;
     }

   Brush_Table[0].mono = 0;
   Brushes_Initialized = 1;
   return 0;
}
   
   
static Brush_Info_Type *get_brush_info (SLsmg_Color_Type color)
{
   if (Brushes_Initialized == 0)
     initialize_brushes ();

   color &= SLSMG_COLOR_MASK;

   if (color >= JMAX_COLORS)
     color = 0;

   return Brush_Table + color;
}

static SLtt_Char_Type get_brush_attr (SLsmg_Color_Type color)
{
   Brush_Info_Type *b;
   
   if (NULL == (b = get_brush_info (color)))
     return (SLtt_Char_Type)-1;
   
   if (SLtt_Use_Ansi_Colors)
     return b->fgbg;
   
   return b->mono;
}

static SLtt_Char_Type get_brush_fgbg (SLsmg_Color_Type color)
{
   return get_brush_info(color)->fgbg;
}

int SLtt_set_mono (int obj, SLFUTURE_CONST char *what, SLtt_Char_Type mask)
{
   Brush_Info_Type *b;

   (void) what;
   if (NULL == (b = get_brush_info (obj)))
     return -1;
   
   b->mono = mask & ATTR_MASK;
   return 0;
}

static SLCONST char *check_color_for_digit_form (SLCONST char *color)
{
   unsigned int i, ich;
   unsigned char *s = (unsigned char *) color;

   i = 0;
   while ((ich = (unsigned int) *s) != 0)
     {
	if ((ich < '0') || (ich > '9'))
	  return color;

	i = i * 10 + (ich - '0');
	s++;
     }

   if (i < MAX_COLOR_NAMES)
     color = Color_Defs[i].name;

   return color;
}

static int get_default_colors (SLCONST char **fgp, SLCONST char **bgp)
{
   static char fg_buf[16], bg_buf[16];
   static SLCONST char *bg, *fg;
   static int already_parsed;
   char *p, *pmax;

   if (already_parsed == -1)
     return -1;

   if (already_parsed)
     {
	*fgp = fg;
	*bgp = bg;
	return 0;
     }

   already_parsed = -1;

   bg = getenv ("COLORFGBG");

   if (bg == NULL)
     {
	bg = getenv ("DEFAULT_COLORS");
	if (bg == NULL)
	  return -1;
     }

   p = fg_buf;
   pmax = p + (sizeof (fg_buf) - 1);

   while ((*bg != 0) && (*bg != ';'))
     {
	if (p < pmax) *p++ = *bg;
	bg++;
     }
   *p = 0;

   if (*bg) bg++;

   p = bg_buf;
   pmax = p + (sizeof (bg_buf) - 1);

   /* Mark suggested allowing for extra application specific stuff following
    * the background color.  That is what the check for the semi-colon is for.
    */
   while ((*bg != 0) && (*bg != ';'))
     {
	if (p < pmax) *p++ = *bg;
	bg++;
     }
   *p = 0;

   if (!strcmp (fg_buf, "default") || !strcmp(bg_buf, "default"))
     {
	*fgp = *bgp = fg = bg = "default";
     }
   else
     {
	*fgp = fg = check_color_for_digit_form (fg_buf);
	*bgp = bg = check_color_for_digit_form (bg_buf);
     }
   already_parsed = 1;
   return 0;
}

static int Color_0_Modified = 0;

int SLtt_set_color_object (int obj, SLtt_Char_Type attr)
{
   Brush_Info_Type *b;
   
   if (NULL == (b = get_brush_info (obj)))
     return -1;

   b->fgbg = attr;
   if (obj == 0) Color_0_Modified = 1;

   if (_pSLtt_color_changed_hook != NULL)
     (*_pSLtt_color_changed_hook)();
   
   return 0;
}

SLtt_Char_Type SLtt_get_color_object (int obj)
{
   return get_brush_fgbg (obj);
}

int SLtt_add_color_attribute (int obj, SLtt_Char_Type attr)
{
   Brush_Info_Type *b;
   
   if (NULL == (b = get_brush_info (obj)))
     return -1;

   b->fgbg |= (attr & ATTR_MASK);

   if (obj == 0) Color_0_Modified = 1;
   if (_pSLtt_color_changed_hook != NULL)
     (*_pSLtt_color_changed_hook)();
   
   return 0;
}

static SLtt_Char_Type fb_to_fgbg (SLtt_Char_Type f, SLtt_Char_Type b)
{
   SLtt_Char_Type attr;

   if (Max_Terminfo_Colors != 8)
     {
	if (f != SLSMG_COLOR_DEFAULT) f %= Max_Terminfo_Colors;
	if (b != SLSMG_COLOR_DEFAULT) b %= Max_Terminfo_Colors;
	return ((f << 8) | (b << 16));
     }

   /* Otherwise we have 8 ansi colors.  Try to get bright versions
    * by using the BOLD and BLINK attributes.
    */

   attr = 0;

   /* Note:  If f represents default, it will have the value 0xFF */
   if (f != SLSMG_COLOR_DEFAULT)
     {
	if (f & 0x8) attr = SLTT_BOLD_MASK;
	f &= 0x7;
     }

   if (b != SLSMG_COLOR_DEFAULT)
     {
	if (b & 0x8) attr |= SLTT_BLINK_MASK;
	b &= 0x7;
     }

   return ((f << 8) | (b << 16) | attr);
}

/* This looks for colors with name form 'colorN'.  If color is of this
 * form, N is passed back via parameter list.
 */
static int parse_color_digit_name (SLCONST char *color, SLtt_Char_Type *f)
{
   unsigned int i;
   unsigned char ch;

   if (strncmp (color, "color", 5))
     return -1;

   color += 5;
   if (*color == 0)
     return -1;

   i = 0;
   while (1)
     {
	unsigned int j;

	ch = (unsigned char) *color++;
	if (ch == 0)
	  break;
	if ((ch > '9') || (ch < '0'))
	  return -1;
	
	if (i > 0xFFFFFFFFU / 10)
	  return -1;
	j = (i *= 10);
	i += (ch - '0');
	if (i < j)
	  return -1;
     }

   *f = (SLtt_Char_Type) i;
   return 0;
}

static int make_color_fgbg (SLCONST char *fg, SLCONST char *bg, SLtt_Char_Type *fgbg)
{
   SLtt_Char_Type f = 0xFFFFFFFFU, b = 0xFFFFFFFFU;
   SLCONST char *dfg, *dbg;
   unsigned int i;

   if ((fg != NULL) && (*fg == 0)) fg = NULL;
   if ((bg != NULL) && (*bg == 0)) bg = NULL;

   if ((fg == NULL) || (bg == NULL))
     {
	if (-1 == get_default_colors (&dfg, &dbg))
	  return -1;

	if (fg == NULL) fg = dfg;
	if (bg == NULL) bg = dbg;
     }

   if (-1 == parse_color_digit_name (fg, &f))
     {
	for (i = 0; i < MAX_COLOR_NAMES; i++)
	  {
	     if (strcmp(fg, Color_Defs[i].name)) continue;
	     f = Color_Defs[i].color;
	     break;
	  }
     }

   if (-1 == parse_color_digit_name (bg, &b))
     {
	for (i = 0; i < MAX_COLOR_NAMES; i++)
	  {
	     if (strcmp(bg, Color_Defs[i].name)) continue;
	     b = Color_Defs[i].color;
	     break;
	  }
     }

   if ((f == 0xFFFFFFFFU) || (b == 0xFFFFFFFFU))
     return -1;

   *fgbg = fb_to_fgbg (f, b);
   return 0;
}

static int tt_set_color (int obj, SLCONST char *what, SLCONST char *fg, SLCONST char *bg)
{
   SLtt_Char_Type fgbg;

   (void) what;
   
   if (-1 == make_color_fgbg (fg, bg, &fgbg))
     return -1;

   return SLtt_set_color_object (obj, fgbg);
}

int SLtt_set_color (int obj, SLFUTURE_CONST char *what, SLFUTURE_CONST char *fg, SLFUTURE_CONST char *bg)
{
   return tt_set_color (obj, what, fg, bg);
}


int SLtt_set_color_fgbg (int obj, SLtt_Char_Type f, SLtt_Char_Type b)
{
   return SLtt_set_color_object (obj, fb_to_fgbg (f, b));
}

void SLtt_set_alt_char_set (int i)
{
   static int last_i;
   if (SLtt_Has_Alt_Charset == 0) return;

   i = (i != 0);

   if (i == last_i) return;
   tt_write_string (i ? Start_Alt_Chars_Str : End_Alt_Chars_Str );
   last_i = i;
}

static void write_attributes (SLtt_Char_Type fgbg)
{
   int bg0, fg0;
   int unknown_attributes;

   if (Worthless_Highlight) return;
   if (fgbg == Current_Fgbg) return;

   unknown_attributes = 0;

   /* Before spitting out colors, fix attributes */
   if ((fgbg & ATTR_MASK) != (Current_Fgbg & ATTR_MASK))
     {
	if (Current_Fgbg & ATTR_MASK)
	  {
	     tt_write_string(Norm_Vid_Str);
	     /* In case normal video turns off ALL attributes: */
	     if (fgbg & SLTT_ALTC_MASK)
	       Current_Fgbg &= ~SLTT_ALTC_MASK;
	     SLtt_set_alt_char_set (0);
	  }

	if ((fgbg & SLTT_ALTC_MASK)
	    != (Current_Fgbg & SLTT_ALTC_MASK))
	  {
	     SLtt_set_alt_char_set ((int) (fgbg & SLTT_ALTC_MASK));
	  }

	if (fgbg & SLTT_ULINE_MASK) tt_write_string (UnderLine_Vid_Str);
	if (fgbg & SLTT_BOLD_MASK) SLtt_bold_video ();
	if (fgbg & SLTT_REV_MASK) tt_write_string (Rev_Vid_Str);
	if (fgbg & SLTT_BLINK_MASK)
	  {
	     /* Someday Linux will have a blink mode that set high intensity
	      * background.  Lets be prepared.
	      */
	     if (SLtt_Blink_Mode) tt_write_string (Blink_Vid_Str);
	  }
	unknown_attributes = 1;
     }

   if (SLtt_Use_Ansi_Colors)
     {
	fg0 = (int) GET_FG(fgbg);
	bg0 = (int) GET_BG(fgbg);

	if (unknown_attributes 
	    || (fg0 != (int)GET_FG(Current_Fgbg)))
	  {
	     if (fg0 == SLSMG_COLOR_DEFAULT)
	       tt_write_string (Default_Color_Fg_Str);
	     else
	       tt_printf (Color_Fg_Str, COLOR_ARG(fg0, Is_Fg_BGR), 0);
	  }

	if (unknown_attributes
	    || (bg0 != (int)GET_BG(Current_Fgbg)))
	  {
	     if (bg0 == SLSMG_COLOR_DEFAULT)
	       tt_write_string (Default_Color_Bg_Str);
	     else
	       tt_printf (Color_Bg_Str, COLOR_ARG(bg0, Is_Bg_BGR), 0);
	  }
     }

   Current_Fgbg = fgbg;
}

static int Video_Initialized;

void SLtt_reverse_video (int color)
{
   SLtt_Char_Type fgbg;

   if (Worthless_Highlight) return;

   if (Video_Initialized == 0)
     {
	if (color == JNORMAL_COLOR)
	  {
	     tt_write_string (Norm_Vid_Str);
	  }
	else tt_write_string (Rev_Vid_Str);
	Current_Fgbg = 0xFFFFFFFFU;
	return;
     }

  fgbg = get_brush_attr (color);

   if (fgbg == Current_Fgbg) return;
   write_attributes (fgbg);
}

void SLtt_normal_video (void)
{
   SLtt_reverse_video(JNORMAL_COLOR);
}

void SLtt_narrow_width (void)
{
   tt_write ("\033[?3l", 5);
}

void SLtt_wide_width (void)
{
   tt_write ("\033[?3h", 5);
}

/* Highest bit represents the character set. */
#define COLOR_OF(a) ((a)->color & SLSMG_COLOR_MASK)

static int bce_colors_eq (SLsmg_Color_Type ca, SLsmg_Color_Type cb, int just_bg)
{
   Brush_Info_Type *ba, *bb;

   if (ca == cb)
     return 1;

   ba = get_brush_info (ca);
   bb = get_brush_info (cb);
   
   if (SLtt_Use_Ansi_Colors == 0)
     return ba->mono == bb->mono;

   if (Bce_Color_Offset)
     {
	/* If either are color 0, then we do not know what that means since the
	 * terminal does not support BCE 
	 */
	if ((ca == 0) || (cb == 0))
	  return 0;
	ba = get_brush_info (ca-1);
	bb = get_brush_info (cb-1);
     }

   if (ba->fgbg == bb->fgbg)
     return 1;
   if (just_bg)
     return GET_BG(ba->fgbg) == GET_BG(bb->fgbg);
   return 0;
}

/* The whole point of this routine is to prevent writing to the last column
 * and last row on terminals with automatic margins.
 */
static void write_string_with_care (SLCONST char *str)
{
   unsigned int len;

   if (str == NULL) return;

   len = strlen (str);
   if (Automatic_Margins && (Cursor_r + 1 == SLtt_Screen_Rows))
     {
	if (_pSLtt_UTF8_Mode == 0) 
	 {
	   if (len + (unsigned int) Cursor_c >= (unsigned int) SLtt_Screen_Cols)
	     {
	        /* For now, just do not write there.  Later, something more
	        * sophisticated will be implemented.
	        */
	        if (SLtt_Screen_Cols > Cursor_c)
	          len = SLtt_Screen_Cols - Cursor_c - 1;
	        else 
	          len = 0;
	     }
	 }
       else
	 {
	    unsigned int nchars = SLutf8_strlen((SLuchar_Type *)str, 1);
	    if (nchars + (unsigned int) Cursor_c >= (unsigned int) SLtt_Screen_Cols)
	     {
	       if (SLtt_Screen_Cols > Cursor_c)
	         {
		    char *p;
		    nchars = SLtt_Screen_Cols - Cursor_c - 1;
		    p = (char *)SLutf8_skip_chars((SLuchar_Type *) str, (SLuchar_Type *)(str + len), nchars, NULL, 1);
		    len = p - str;
		 }
	       else
		  len = 0; 
	     }
	 }
     }
   tt_write (str, len);
}

static void send_attr_str (SLsmg_Char_Type *s, SLsmg_Char_Type *smax)
{
   unsigned char out[1+SLUTF8_MAX_MBLEN*SLSMG_MAX_CHARS_PER_CELL*SLTT_MAX_SCREEN_COLS];
   unsigned char *p, *pmax;
   register SLtt_Char_Type attr;
   SLsmg_Color_Type color, last_color = (SLsmg_Color_Type)-1;
   int dcursor_c;

   p = out;
   pmax = p + (sizeof (out)-1);

   dcursor_c = 0;
   while (s < smax)
     {
	SLwchar_Type wch;
	unsigned int nchars;

	if (0 == (nchars = s->nchars))
	  {
	     /* 2nd element of a char that occupies two columns */
	     s++;
	     if (_pSLtt_UTF8_Mode == 0)
	       *p++ = ' ';
	     dcursor_c++;
	     continue;
	  }

	color = s->color;

#if SLTT_HAS_NON_BCE_SUPPORT
	if (Bce_Color_Offset
	    && (color >= Bce_Color_Offset))
	  color -= Bce_Color_Offset;
#endif

	wch = s->wchars[0];

	if (color != last_color)
	  {
	     attr = get_brush_attr (color);

	     if (color & SLSMG_ACS_MASK) /* alternate char set */
	       {
		  if (SLtt_Use_Blink_For_ACS)
		    {
		       if (SLtt_Blink_Mode) attr |= SLTT_BLINK_MASK;
		    }
		  else attr |= SLTT_ALTC_MASK;
	       }

	     if (attr != Current_Fgbg)
	       {
		  if ((wch != ' ')
		      || (nchars > 1)
		      /* it is a space so only consider it different if it
		       * has different attributes.
		       */
		      || (attr != Current_Fgbg)
		      )
		    {
		       if (p != out)
			 {
			    *p = 0;
			    write_string_with_care ((char *) out);
			    p = out;
			    Cursor_c += dcursor_c;
			    dcursor_c = 0;
			 }
		       write_attributes (attr);
		       last_color = color;
		    }
	       }
	  }
	
	if ((wch < 0x80) && (nchars == 1))
	  *p++ = (unsigned char) wch;
	else if (_pSLtt_UTF8_Mode == 0)
	  {
	     if (wch > 255)
	       wch = '?';
	     else if (wch < (SLwchar_Type)SLsmg_Display_Eight_Bit)
	       wch = '?';
	     *p++ = (unsigned char) wch;
	  }
	else
	  {
	     unsigned int i;
	     for (i = 0; i < nchars; i++)
	       {
		  if (NULL == (p = SLutf8_encode (s->wchars[i], p, pmax-p)))
		    {
		       fprintf (stderr, "*** send_attr_str: buffer too small\n");
		       return;
		    }
	       }
	  }
	dcursor_c++;
	s++;
     }
   *p = 0;
   if (p != out) write_string_with_care ((char *) out);
   Cursor_c += dcursor_c;
}

static void forward_cursor (unsigned int n, int row)
{
   char buf [1024];


   /* if (Current_Fgbg & ~0xFF) */
   /*   { */
   /* 	unsigned int num = 0; */
   /* 	while (num < n) */
   /* 	  { */
   /* 	     write_string_with_care (" "); */
   /* 	     num++; */
   /* 	  } */
   /* 	Cursor_c += n; */
   /* 	return; */
   /*   } */
   
	
   if (n <= 4)
     {
	SLtt_normal_video ();
#if 0
	if (n >= sizeof (buf))
	  n = sizeof (buf) - 1;
#endif
	SLMEMSET (buf, ' ', n);
	buf[n] = 0;
	write_string_with_care (buf);
	Cursor_c += n;
     }
   else if (Curs_F_Str != NULL)
     {
	Cursor_c += n;
	n = tt_sprintf(buf, sizeof (buf), Curs_F_Str, (int) n, 0);
	tt_write(buf, n);
     }
   else SLtt_goto_rc (row, (int) (Cursor_c + n));
}


/* FIXME!!  If the terminal does not support color, then this route has 
 * problems of color object 0 has been assigned some monochrome attribute
 * such as reverse video.  In such a case, space_char=' ' is not a simple
 * space character as is assumed below.
 */

#define COLORS_EQ(ca,cb) (((ca) == (cb)) || bce_colors_eq((ca), (cb), 0))
#define BG_COLORS_EQ(ca,cb) (((ca) == (cb)) || (bce_colors_eq((ca),(cb),1)))

#define COLORS_OF_EQ(a,b) COLORS_EQ(COLOR_OF(a),COLOR_OF(b))

#define CHARSET(a) ((a)->color&SLSMG_ACS_MASK)
#define CHAR_EQS(a, b) (((a)->nchars==(b)->nchars) \
			   && (((a)->nchars == 0) \
				  || ((((a)->wchars[0]==(b)->wchars[0]) \
					 && (0 == memcmp((a)->wchars, (b)->wchars, (a)->nchars*sizeof(SLwchar_Type)))) \
					 && (COLORS_OF_EQ(a,b)) \
					 && (CHARSET(a)==CHARSET(b)))))

#if 0
# define CHAR_EQS_SPACE(a) \
   (((a)->wchars[0]==' ') && ((a)->color==0) && ((a)->nchars==1))
#else
# define CHAR_EQS_SPACE(a) \
   (((a)->wchars[0]==' ') && ((a)->nchars==1) \
     && BG_COLORS_EQ(COLOR_OF(a),Bce_Color_Offset))
#endif

void SLtt_smart_puts(SLsmg_Char_Type *neww, SLsmg_Char_Type *oldd, int len, int row)
{
   register SLsmg_Char_Type *p, *q, *qmax, *pmax, *buf;
   SLsmg_Char_Type buffer[SLTT_MAX_SCREEN_COLS+1];
   unsigned int n_spaces;
   SLsmg_Char_Type *space_match, *last_buffered_match;
#ifdef HP_GLITCH_CODE
   int handle_hp_glitch = 0;
#endif
   SLsmg_Char_Type *space_char;
   SLsmg_Char_Type space_char_buf;

#define SLTT_USE_INSERT_HACK 1
#if SLTT_USE_INSERT_HACK
   SLsmg_Char_Type *insert_hack_prev = NULL;
   SLsmg_Char_Type *insert_hack_char = NULL;

   if ((row + 1 == SLtt_Screen_Rows)
       && (len == SLtt_Screen_Cols)
       && (len > 1)
       && (SLtt_Term_Cannot_Insert == 0)
       && Automatic_Margins)
     {
	SLsmg_Char_Type *a, *b;
	insert_hack_char = &neww[len-1];

	a = oldd+(len-1);
	b = neww+(len-1);
	
	if (CHAR_EQS(a,b))
	  insert_hack_char = NULL;
	else 
	  insert_hack_prev = &neww[len-2];
     }
#endif
     
   memset ((char *) &space_char_buf, 0, sizeof (SLsmg_Char_Type));
   space_char = &space_char_buf;
   space_char->nchars = 1;
   space_char->wchars[0] = ' ';

   if (len > SLTT_MAX_SCREEN_COLS)
     len = SLTT_MAX_SCREEN_COLS;

   q = oldd; p = neww;
   qmax = oldd + len;
   pmax = p + len;

   /* Find out where to begin --- while they match, we are ok */
   while (1)
     {
	if (q == qmax) return;
#if SLANG_HAS_KANJI_SUPPORT
# undef SLANG_HAS_KANJI_SUPPORT
# define SLANG_HAS_KANJI_SUPPORT 0
#endif
#if SLANG_HAS_KANJI_SUPPORT
	if (*p & 0x80)
	  { /* new is kanji */
	     if ((*q & 0x80) && ((q + 1) < qmax))
	       { /* old is also kanji */
		  if (((0xFF & *q) != (0xFF & *p))
		      || ((0xFF & q[1]) != (0xFF & p[1])))
		    break; /* both kanji, but not match */

		  else
		    { /* kanji match ! */
		       if (!COLORS_OF_EQ(*q, *p)) break;
		       q++; p++;
		       if (!COLORS_OF_EQ(*q, *p)) break;
		       /* really match! */
		       q++; p++;
		       continue;
		    }
	       }
	     else break; /* old is not kanji */
	  }
	else
	  { /* new is not kanji */
	     if (*q & 0x80) break; /* old is kanji */
	  }
#endif
	if (!CHAR_EQS(q, p)) break;
	q++; p++;
     }

#ifdef HP_GLITCH_CODE
   if (Has_HP_Glitch)
     {
	SLsmg_Char_Type *qq = q;

	SLtt_goto_rc (row, (int) (p - neww));

	while (qq < qmax)
	  {
	     if (qq->color)
	       {
		  SLtt_normal_video ();
		  SLtt_del_eol ();
		  qmax = q;
		  handle_hp_glitch = 1;
		  break;
	       }
	     qq++;
	  }
     }
#endif
   /* Find where the last non-blank character on old/new screen is */

   /* if (CHAR_EQS_SPACE(pmax-1)) */
   if (((pmax-1)->wchars[0]==' ') && ((pmax-1)->nchars==1))
     {
	/* If we get here, then we can erase to the end of the line to create
	 * the final space.  However, this will only work _if_ erasing will 
	 * get us the correct color.  If the terminal supports BCE, then this
	 * is easy.  If it does not, then we can only perform this operation
	 * if the color is known via something like COLORFGBG.  For now, 
	 * I just will not perform the optimization for such terminals.
	 */
	if ((Can_Background_Color_Erase)
	    && SLtt_Use_Ansi_Colors)
	  space_char = pmax - 1;

	while (pmax > p)
	  {
	     pmax--;
	     if (!CHAR_EQS(pmax, space_char))
	       {
		  pmax++;
		  break;
	       }
	  }
     }

   while (qmax > q)
     {
	qmax--;
	if (!CHAR_EQS(qmax, space_char))
	  {
	     qmax++;
	     break;
	  }
     }

   last_buffered_match = buf = buffer;		       /* buffer is empty */

#ifdef HP_GLITCH_CODE
   if (handle_hp_glitch)
     {
	while (p < pmax)
	  {
	     *buf++ = *p++;
	  }
     }
#endif

#ifdef HP_GLITCH_CODE
   if (Has_HP_Glitch == 0)
     {
#endif
	/* Try use use erase to bol if possible */
	if ((Del_Bol_Str != NULL) && (CHAR_EQS_SPACE(neww)))
	  {
	     SLsmg_Char_Type *p1;
	     SLsmg_Color_Type blank_color = 0;

	     p1 = neww;
	     if ((Can_Background_Color_Erase)
		 && SLtt_Use_Ansi_Colors)
	       {
		  SLsmg_Char_Type *blank = p1;
		  blank_color = COLOR_OF(blank);
		  while ((p1 < pmax) && (CHAR_EQS (p1, blank)))
		    p1++;
	       }
	     else
	       {
		  /* black+white attributes do not support bce */
		  while ((p1 < pmax) && (CHAR_EQS_SPACE (p1)))
		    p1++;
	       }
	     
	     /* Is this optimization worth it?  Assume Del_Bol_Str is ESC [ 1 K
	      * It costs 4 chars + the space needed to properly position the 
	      * cursor, e.g., ESC [ 10;10H. So, it costs at least 13 characters.
	      */
	     if ((p1 > neww + 13) 
		 && (p1 >= p)
		 /* Avoid erasing from the end of the line */
		 && ((p1 != pmax) || (pmax < neww + len)))
	       {
		  int ofs = (int) (p1 - neww);
		  q = oldd + ofs;
		  p = p1;
		  SLtt_goto_rc (row, ofs - 1);
		  SLtt_reverse_video (blank_color);
		  tt_write_string (Del_Bol_Str);
		  tt_write (" ", 1);
		  Cursor_c += 1;
	       }
	     else
	       SLtt_goto_rc (row, (int) (p - neww));
	  }
	else
	  SLtt_goto_rc (row, (int) (p - neww));
#ifdef HP_GLITCH_CODE
     }
#endif
   
   
   /* loop using overwrite then skip algorithm until done */
   while (1)
     {
	/* while they do not match and we do not hit a space, buffer them up */
	n_spaces = 0;
	while (p < pmax)
	  {
	     if (CHAR_EQS_SPACE(q) && CHAR_EQS_SPACE(p))
	       {
		  /* If *q is not a space, we would have to overwrite it.
		   * However, if *q is a space, then while *p is also one,
		   * we only need to skip over the blank field.
		   */
		  space_match = p;
		  p++; q++;
		  while ((p < pmax)
			 && CHAR_EQS_SPACE(q)
			 && CHAR_EQS_SPACE(p))
		    {
		       p++;
		       q++;
		    }
		  n_spaces = (unsigned int) (p - space_match);
		  break;
	       }
#if SLANG_HAS_KANJI_SUPPORT
	     if ((*p & 0x80) && ((p + 1) < pmax))
	       { /* new is kanji */
		  if (*q & 0x80)
		    { /* old is also kanji */
		       if (((0xFF & *q) != (0xFF & *p))
			   || ((0xFF & q[1]) != (0xFF & p[1])))
			 {
			    /* both kanji, but not match */
			    *buf++ = *p++;
			    *buf++ = *p++;
			    q += 2;
			    continue;
			 }
		       else
			 { /* kanji match ? */
			    if (!COLORS_OF_EQ(*q, *p) || !COLORS_OF_EQ(*(q+1), *(p+1)))
			      {
				 /* code is match, but color is diff */
				 *buf++ = *p++;
				 *buf++ = *p++;
				 q += 2;
				 continue;
			      }
			    /* really match ! */
			    break;
			 }
		    }
 		  else
		    { /* old is not kanji */
		       *buf++ = *p++;
		       *buf++ = *p++;
		       q += 2;
		       continue;
		    }
	       }
	     else
	       { /* new is not kanji */
 		  if (*q & 0x80)
		    { /* old is kanji */
		       *buf++ = *p++;
		       q++;
		       continue;
		    }
	       }
#endif

	     if (CHAR_EQS(q, p)) 
	       {
		  /* Could be the second half of a double width character */
		  if (p->nchars || q->nchars)
		    break;
	       }
	     *buf++ = *p++;
	     q++;
	  }

	/* At this point, the buffer contains characters that do not match */
	if (buf != buffer) send_attr_str (buffer, buf);
	buf = buffer;

	if (n_spaces 
	    && ((p < pmax) 	       /* erase to eol will achieve this effect*/
		|| (!CHAR_EQS_SPACE(space_char))))/* unless space_char is not a simple space */
	  {
	     forward_cursor (n_spaces, row);
	  }
	/* Now we overwrote what we could and cursor is placed at position
	 * of a possible match of new and old.  If this is the case, skip
	 * some more.
	 */
	
	/* Note that from here on, the buffer will contain matched characters */
#if !SLANG_HAS_KANJI_SUPPORT
	while ((p < pmax) && CHAR_EQS(p, q))
	  {
	     *buf++ = *p++;
	     q++;
	  }
#else
	/* Kanji */
	while (p < pmax)
	  {
	     if ((*p & 0x80) && ((p + 1) < pmax))
	       { /* new is kanji */
		  if (*q & 0x80)
		    { /* old is also kanji */
		       if (((0xFF & *q) == (0xFF & *p))
			   && ((0xFF & q[1]) == (0xFF & p[1])))
			 {
			    /* kanji match ? */
			    if (!COLORS_OF_EQ(*q, *p)
				|| !COLORS_OF_EQ(q[1], p[1]))
			      break;

			    *buf++ = *p++;
			    q++;
			    if (p >= pmax)
			      {
				 *buf++ = 32;
				 p++;
				 break;
			      }
			    else
			      {
				 *buf++ = *p++;
				 q++;
				 continue;
			      }
			 }
		       else break; /* both kanji, but not match */
		    }
		  else break; /* old is not kanji */
	       }
	     else
	       {  /* new is not kanji */
		  if (*q & 0x80) break; /* old is kanji */
		  if (!CHAR_EQS(*q, *p)) break;
		  *buf++ = *p++;
		  q++;
	       }
	  }
#endif
	last_buffered_match = buf;
	if (p >= pmax) break;

	/* jump to new position is it is greater than 5 otherwise
	 * let it sit in the buffer and output it later.
	 */
	if ((int) (buf - buffer) >= 5)
	  {
	     forward_cursor ((unsigned int) (buf - buffer), row);
	     last_buffered_match = buf = buffer;
	  }
     }

   /* At this point we have reached the end of the new string with the 
    * exception of space_chars hanging off the end of it, but we may not have
    * reached the end of the old string if they did not match.
    */
   
   /* Here the buffer will consist only of characters that have matched */
   if (buf != buffer)
     {
	if (q < qmax)
	  {
	     if ((buf == last_buffered_match)
		 && ((int) (buf - buffer) >= 5))
	       {
		  forward_cursor ((unsigned int) (buf - buffer), row);
	       }
	     else
	       {
		  send_attr_str (buffer, buf);
	       }
	  }
     }

   if (q < qmax) 
     {
	SLtt_reverse_video (COLOR_OF(space_char));
	del_eol ();
     }
   
#if SLTT_USE_INSERT_HACK
   else if (insert_hack_char != NULL)
     {
	SLtt_goto_rc (SLtt_Screen_Rows-1, SLtt_Screen_Cols-2);
	send_attr_str (insert_hack_char, insert_hack_char+1);
	SLtt_goto_rc (SLtt_Screen_Rows-1, SLtt_Screen_Cols-2);
	SLtt_begin_insert ();
	send_attr_str (insert_hack_prev, insert_hack_prev+1);
	SLtt_end_insert ();
     }
#endif

   if (Automatic_Margins && (Cursor_c + 1 >= SLtt_Screen_Cols)) Cursor_Set = 0;
}

static void get_color_info (void)
{
   SLCONST char *fg, *bg;

   /* Allow easy mechanism to override inadequate termcap/terminfo files. */
   if (SLtt_Use_Ansi_Colors == 0)
     SLtt_Use_Ansi_Colors = (NULL != getenv ("COLORTERM"));

   if (SLtt_Use_Ansi_Colors)
     Is_Color_Terminal = 1;

#if SLTT_HAS_NON_BCE_SUPPORT
   if (Can_Background_Color_Erase == 0)
     Can_Background_Color_Erase = (NULL != getenv ("COLORTERM_BCE"));
#endif

   if (-1 == get_default_colors (&fg, &bg))
     return;

   /* Check to see if application has already set them. */
   if (Color_0_Modified)
     return;

   tt_set_color (0, NULL, fg, bg);
   tt_set_color (1, NULL, bg, fg);
}

/* termcap stuff */

#ifdef __unix__

static int Termcap_Initalized = 0;

/* #define USE_TERMCAP 1 */
#ifdef USE_TERMCAP
/* Termcap based system */
static char Termcap_Buf[4096];
/* static char Termcap_String_Buf[4096]; */
/* static char *Termcap_String_Ptr; */
extern char *tgetstr(char *, char **);
extern int tgetent(char *, char *);
extern int tgetnum(char *);
extern int tgetflag(char *);
#else
/* Terminfo */
static SLterminfo_Type *Terminfo;
#endif

#define TGETFLAG(x) (tt_tgetflag(x) > 0)

static char *fixup_tgetstr (char *what)
{
   register char *w, *w1;
   char *wsave;
   
   if (what == NULL)
     return NULL;

   /* Check for AIX brain-damage */
   if (*what == '@')
     return NULL;

   /* lose pad info --- with today's technology, term is a loser if
    it is really needed */
   while ((*what == '.') ||
	  ((*what >= '0') && (*what <= '9'))) what++;
   if (*what == '*') what++;
   
   /* lose terminfo padding--- looks like $<...> */
   w = what;
   while (*w) if ((*w++ == '$') && (*w == '<'))
     {
	w1 = w - 1;
	while (*w && (*w != '>')) w++;
	if (*w == 0) break;
	w++;
	wsave = w1;
	while ((*w1++ = *w++) != 0)
	  ;
	w = wsave;
     }

   if (*what == 0) what = NULL;
   return what;
}

static char *tt_tgetstr (SLCONST char *cap)
{
   char *s;
#ifdef USE_TERMCAP
   char area_buf[4096];
   char *area;
#endif
   if (Termcap_Initalized == 0)
     return NULL;
   
#ifdef USE_TERMCAP
   /* tmp_area = &Termcap_String_Buf; */
   area = area_buf;
   s = tgetstr (cap, &area);
   if (area > area_buf + sizeof(area_buf))
     {
	SLang_exit_error ("\
The termcap tgetstr appears to have overflowed a buffer.\n\
The integrity of this program has been violated.\n");
     }
#else
   s = _pSLtt_tigetstr (Terminfo, cap);
#endif

   /* Do not strip pad info for alternate character set.  I need to make
    * this more general.
    */
   /* FIXME: Priority=low; */
   if (0 != strcmp (cap, "ac"))
     s = fixup_tgetstr (s);

#ifdef USE_TERMCAP
   if ((s >= area_buf) && (s < area_buf + sizeof(area_buf)))
     {
	/* It looks like tgetstr placed the object in the buffer and
	 * returned a pointer to that buffer.  So, we have to make a
	 * copy of it.
	 * 
	 * Yes, this introduces a leak...
	 */
	s = SLmake_string (s);
     }
#endif
   return s;
}

char *SLtt_tgetstr (SLFUTURE_CONST char *cap)
{
   return tt_tgetstr (cap);
}

static int tt_tgetnum (SLCONST char *s)
{
   if (Termcap_Initalized == 0)
     return -1;
#ifdef USE_TERMCAP
   return tgetnum (s);
#else
   return _pSLtt_tigetnum (Terminfo, s);
#endif
}

int SLtt_tgetnum (SLFUTURE_CONST char *s)
{
   return tt_tgetnum (s);
}

static int tt_tgetflag (SLCONST char *s)
{
   if (Termcap_Initalized == 0)
     return -1;
#ifdef USE_TERMCAP
   return tgetflag (s);
#else
   return _pSLtt_tigetflag (Terminfo, s);
#endif
}

int SLtt_tgetflag (SLFUTURE_CONST char *s)
{
   return tt_tgetflag (s);
}

#if 0
int SLtt_tgetent(char *term)
{
   return SLtt_initialize(term) == 0;
}

int SLtt_tputs(char *str, int affcnt, int (*p)(int))
{
   while (*str) (*p)(*str++);
   return 0;
}


char *SLtt_tgoto(char *cap, int col, int row)
{
  static char buf[64];

  /* beware of overflows. 2^64 is 20 bytes printed */
  if (strlen(cap) > 23)
	strcpy(buf, "capability too long");
  else
	tt_sprintf(buf, cap, row, col);
  return buf;
}
#endif

static int Vt100_Like = 0;

void SLtt_get_terminfo (void)
{
   char *term;
   int status;

   term = getenv ("TERM");
   if (term == NULL)
     SLang_exit_error("TERM environment variable needs set.");

   if (0 == (status = SLtt_initialize (term)))
     return;

   if (status == -1)
     {
	SLang_exit_error ("Unknown terminal: %s\n\
Check the TERM environment variable.\n\
Also make sure that the terminal is defined in the terminfo database.\n\
Alternatively, set the TERMCAP environment variable to the desired\n\
termcap entry.",
			  term);
     }

   if (status == -2)
     {
	SLang_exit_error ("\
Your terminal lacks the ability to clear the screen or position the cursor.\n");
     }
}

/* Returns 0 if all goes well, -1 if terminal capabilities cannot be deduced,
 * or -2 if terminal cannot position the cursor.
 */
int SLtt_initialize (SLFUTURE_CONST char *term)
{
   SLCONST char *t;
   char ch;
   int is_xterm;
   int almost_vtxxx;

   if (_pSLtt_UTF8_Mode == -1)
     _pSLtt_UTF8_Mode = _pSLutf8_mode;

   if (SLang_TT_Write_FD == -1)
     {
	/* Apparantly, this cannot fail according to the man pages. */
	SLang_TT_Write_FD = fileno (stdout);
     }
   
   if (term == NULL)
     {
	term = getenv ("TERM");
	if (term == NULL)
	  return -1;
     }

   if (_pSLsecure_issetugid ()
       && ((term[0] == '.') || (NULL != strchr(term, '/'))))
     return -1;
   
   Linux_Console = (!strncmp (term, "linux", 5)
# ifdef linux
		    || !strncmp(term, "con", 3)
# endif
		    );

   t = term;

   if (strcmp(t, "vt52") && (*t++ == 'v') && (*t++ == 't')
       && (ch = *t, (ch >= '1') && (ch <= '9'))) Vt100_Like = 1;

   is_xterm = ((0 == strncmp (term, "xterm", 5))
	       || (0 == strncmp (term, "rxvt", 4))
	       || (0 == strncmp (term, "Eterm", 5)));

   almost_vtxxx = (Vt100_Like
		   || Linux_Console
		   || is_xterm
		   || !strcmp (term, "screen"));

# ifndef USE_TERMCAP
   if (Terminfo != NULL)
     _pSLtt_tifreeent (Terminfo);

   if (NULL == (Terminfo = _pSLtt_tigetent (term)))
     {
	if (almost_vtxxx) /* Special cases. */
	  {
	     int vt102 = 1;
	     if (!strcmp (term, "vt100")) vt102 = 0;
	     get_color_info ();
   	     SLtt_set_term_vtxxx (&vt102);
	     (void) SLtt_get_screen_size ();
	     return 0;
	  }
	return -1;
     }
# else				       /* USE_TERMCAP */
   if (1 != tgetent(Termcap_Buf, term))
     return -1;
   /* Termcap_String_Ptr = Termcap_String_Buf; */
# endif				       /* NOT USE_TERMCAP */

   Termcap_Initalized = 1;

   Cls_Str = tt_tgetstr ("cl");
   Curs_Pos_Str = tt_tgetstr ("cm");

   if ((NULL == (Ins_Mode_Str = tt_tgetstr("im")))
       || ( NULL == (Eins_Mode_Str = tt_tgetstr("ei")))
       || ( NULL == (Del_Char_Str = tt_tgetstr("dc"))))
     SLtt_Term_Cannot_Insert = 1;

   Visible_Bell_Str = tt_tgetstr ("vb");
   Curs_Up_Str = tt_tgetstr ("up");
   Rev_Scroll_Str = tt_tgetstr("sr");
   Del_N_Lines_Str = tt_tgetstr("DL");
   Add_N_Lines_Str = tt_tgetstr("AL");

   /* Actually these are used to initialize terminals that use cursor
    * addressing.  Hard to believe.
    */
   Term_Init_Str = tt_tgetstr ("ti");
   Term_Reset_Str = tt_tgetstr ("te");

   /* If I do this for vtxxx terminals, arrow keys start sending ESC O A,
    * which I do not want.  This is mainly for HP terminals.
    */
   if ((almost_vtxxx == 0) || SLtt_Force_Keypad_Init)
     {
	Keypad_Init_Str = tt_tgetstr ("ks");
	Keypad_Reset_Str = tt_tgetstr ("ke");
     }

   /* Make up for defective termcap/terminfo databases */
   if ((Vt100_Like && (term[2] != '1'))
       || Linux_Console
       || is_xterm
       )
     {
	if (Del_N_Lines_Str == NULL) Del_N_Lines_Str = "\033[%dM";
	if (Add_N_Lines_Str == NULL) Add_N_Lines_Str = "\033[%dL";
     }

   Scroll_R_Str = tt_tgetstr("cs");

   SLtt_get_screen_size ();

   if ((Scroll_R_Str == NULL)
       || (((NULL == Del_N_Lines_Str) || (NULL == Add_N_Lines_Str))
	   && (NULL == Rev_Scroll_Str)))
     {
	if (is_xterm
	    || Linux_Console
	    )
	  {
	     /* Defective termcap mode!!!! */
	     SLtt_set_term_vtxxx (NULL);
	  }
	else SLtt_Term_Cannot_Scroll = 1;
     }

   Del_Eol_Str = tt_tgetstr("ce");
   Del_Bol_Str = tt_tgetstr("cb");
   if (is_xterm && (Del_Bol_Str == NULL))
     Del_Bol_Str = "\033[1K";
   if (is_xterm && (Del_Eol_Str == NULL))
     Del_Eol_Str = "\033[K";

   Rev_Vid_Str = tt_tgetstr("mr");
   if (Rev_Vid_Str == NULL) Rev_Vid_Str = tt_tgetstr("so");

   Bold_Vid_Str = tt_tgetstr("md");

   /* Although xterm cannot blink, it does display the blinking characters
    * as bold ones.  Some Rxvt will display the background as high intensity.
    */
   if ((NULL == (Blink_Vid_Str = tt_tgetstr("mb")))
       && is_xterm)
     Blink_Vid_Str = "\033[5m";

   UnderLine_Vid_Str = tt_tgetstr("us");

   Start_Alt_Chars_Str = tt_tgetstr ("as");   /* smacs */
   End_Alt_Chars_Str = tt_tgetstr ("ae");   /* rmacs */
   Enable_Alt_Char_Set = tt_tgetstr ("eA");   /* enacs */
   SLtt_Graphics_Char_Pairs = tt_tgetstr ("ac");

   if (NULL == SLtt_Graphics_Char_Pairs)
     {
	/* make up for defective termcap/terminfo */
	if (Vt100_Like)
	  {
	     Start_Alt_Chars_Str = "\016";
	     End_Alt_Chars_Str = "\017";
	     Enable_Alt_Char_Set = "\033)0";
	  }
     }

    /* aixterm added by willi */
   if (is_xterm || !strncmp (term, "aixterm", 7))
     {
#if 0
	Start_Alt_Chars_Str = "\016";
	End_Alt_Chars_Str = "\017";
	Enable_Alt_Char_Set = "\033(B\033)0";
#else
	Start_Alt_Chars_Str = "\033(0";
	End_Alt_Chars_Str = "\033(B";
	Enable_Alt_Char_Set = "";
#endif
     }

   if ((SLtt_Graphics_Char_Pairs == NULL) &&
       ((Start_Alt_Chars_Str == NULL) || (End_Alt_Chars_Str == NULL)))
     {
	SLtt_Has_Alt_Charset = 0;
	Enable_Alt_Char_Set = NULL;
     }
   else SLtt_Has_Alt_Charset = 1;

#ifdef AMIGA
   Enable_Alt_Char_Set = Start_Alt_Chars_Str = End_Alt_Chars_Str = NULL;
#endif

   /* status line capabilities */
   if ((SLtt_Has_Status_Line == -1)
       && (0 != (SLtt_Has_Status_Line = TGETFLAG ("hs"))))
     {
	Disable_Status_line_Str = tt_tgetstr ("ds");
	Return_From_Status_Line_Str = tt_tgetstr ("fs");
	Goto_Status_Line_Str = tt_tgetstr ("ts");
	/* Status_Line_Esc_Ok = TGETFLAG("es"); */
	Num_Status_Line_Columns = tt_tgetnum ("ws");
	if (Num_Status_Line_Columns < 0) Num_Status_Line_Columns = 0;
     }

   if (NULL == (Norm_Vid_Str = tt_tgetstr("me")))
     {
	Norm_Vid_Str = tt_tgetstr("se");
     }

   Cursor_Invisible_Str = tt_tgetstr("vi");
   Cursor_Visible_Str = tt_tgetstr("ve");

   Curs_F_Str = tt_tgetstr("RI");

# if 0
   if (NULL != Curs_F_Str)
     {
	Len_Curs_F_Str = strlen(Curs_F_Str);
     }
   else Len_Curs_F_Str = strlen(Curs_Pos_Str);
# endif

   Automatic_Margins = TGETFLAG ("am");
   /* No_Move_In_Standout = !TGETFLAG ("ms"); */
# ifdef HP_GLITCH_CODE
   Has_HP_Glitch = TGETFLAG ("xs");
# else
   Worthless_Highlight = TGETFLAG ("xs");
# endif

   if (Worthless_Highlight == 0)
     {				       /* Magic cookie glitch */
	Worthless_Highlight = (tt_tgetnum ("sg") > 0);
     }

   if (Worthless_Highlight)
     SLtt_Has_Alt_Charset = 0;

   Reset_Color_String = tt_tgetstr ("op");
   
   /* Apparantly the difference between "AF" and "Sf" is that AF uses RGB, 
    * but Sf uses BGR.
    */
   Color_Fg_Str = tt_tgetstr ("AF"); /* ANSI setaf */
   if (Color_Fg_Str == NULL)
     {
	Color_Fg_Str = tt_tgetstr ("Sf");   /* setf */
	Is_Fg_BGR = (Color_Fg_Str != NULL);
     }
   Color_Bg_Str = tt_tgetstr ("AB"); /* ANSI setbf */
   if (Color_Bg_Str == NULL)
     {
	Color_Bg_Str = tt_tgetstr ("Sb");   /* setb */
	Is_Bg_BGR = (Color_Bg_Str != NULL);
     }

   if ((Max_Terminfo_Colors = tt_tgetnum ("Co")) < 0)
     Max_Terminfo_Colors = 8;

   if ((Color_Bg_Str != NULL) && (Color_Fg_Str != NULL))
     SLtt_Use_Ansi_Colors = 1;
   else
     {
#if 0
	Color_Fg_Str = "%?%p1%{7}%>%t\033[1;3%p1%{8}%m%dm%e\033[3%p1%dm%;";
	Color_Bg_Str = "%?%p1%{7}%>%t\033[5;4%p1%{8}%m%dm%e\033[4%p1%dm%;";
	Max_Terminfo_Colors = 16;
#else
	Color_Fg_Str = "\033[3%dm";
	Color_Bg_Str = "\033[4%dm";
	Max_Terminfo_Colors = 8;
#endif
     }

#if SLTT_HAS_NON_BCE_SUPPORT
   Can_Background_Color_Erase = TGETFLAG ("ut");   /* bce */
   /* Modern xterms have the BCE capability as well as the linux console */
   if (Can_Background_Color_Erase == 0)
     {
	Can_Background_Color_Erase = (Linux_Console
# if SLTT_XTERM_ALWAYS_BCE
				      || is_xterm
# endif
				      );
     }
#endif
   get_color_info ();

  
   if ((Cls_Str == NULL)
       || (Curs_Pos_Str == NULL))
     return -2;

   return 0;
}

#endif
/* Unix */

/* specific to vtxxx only */
void SLtt_enable_cursor_keys (void)
{
#ifdef __unix__
   if (Vt100_Like)
#endif
     tt_write_string("\033=\033[?1l");
}

#ifdef VMS
int SLtt_initialize (char *term)
{
   SLtt_get_terminfo ();
   return 0;
}

void SLtt_get_terminfo ()
{
   int zero = 0;

   /* Apparantly, this cannot fail according to the man pages. */
   if (SLang_TT_Write_FD == -1)
     SLang_TT_Write_FD = fileno (stdout);
   
   Can_Background_Color_Erase = 0;

   Color_Fg_Str = "\033[3%dm";
   Color_Bg_Str = "\033[4%dm";
   Max_Terminfo_Colors = 8;

   get_color_info ();

   SLtt_set_term_vtxxx(&zero);
   Start_Alt_Chars_Str = "\016";
   End_Alt_Chars_Str = "\017";
   SLtt_Has_Alt_Charset = 1;
   SLtt_Graphics_Char_Pairs = "aaffgghhjjkkllmmnnooqqssttuuvvwwxx";
   Enable_Alt_Char_Set = "\033(B\033)0";
   SLtt_get_screen_size ();
}
#endif

/* This sets term for vt102 terminals it parameter vt100 is 0.  If vt100
 * is non-zero, set terminal appropriate for a only vt100
 * (no add line capability). */

void SLtt_set_term_vtxxx(int *vt100)
{
   Norm_Vid_Str = "\033[m";

   Scroll_R_Str = "\033[%i%d;%dr";
   Cls_Str = "\033[2J\033[H";
   Rev_Vid_Str = "\033[7m";
   Bold_Vid_Str = "\033[1m";
   Blink_Vid_Str = "\033[5m";
   UnderLine_Vid_Str = "\033[4m";
   Del_Eol_Str = "\033[K";
   Del_Bol_Str = "\033[1K";
   Rev_Scroll_Str = "\033M";
   Curs_F_Str = "\033[%dC";
   /* Len_Curs_F_Str = 5; */
   Curs_Pos_Str = "\033[%i%d;%dH";
   if ((vt100 == NULL) || (*vt100 == 0))
     {
	Ins_Mode_Str = "\033[4h";
	Eins_Mode_Str = "\033[4l";
	Del_Char_Str =  "\033[P";
	Del_N_Lines_Str = "\033[%dM";
	Add_N_Lines_Str = "\033[%dL";
	SLtt_Term_Cannot_Insert = 0;
     }
   else
     {
	Del_N_Lines_Str = NULL;
	Add_N_Lines_Str = NULL;
	SLtt_Term_Cannot_Insert = 1;
     }
   SLtt_Term_Cannot_Scroll = 0;
   /* No_Move_In_Standout = 0; */
}

int SLtt_init_video (void)
{
   /*   send_string_to_term("\033[?6h"); */
   /* relative origin mode */
   tt_write_string (Term_Init_Str);
   tt_write_string (Keypad_Init_Str);
   SLtt_reset_scroll_region();
   SLtt_end_insert();
   tt_write_string (Enable_Alt_Char_Set);
   Video_Initialized = 1;
   return 0;
}

int SLtt_reset_video (void)
{
   SLtt_goto_rc (SLtt_Screen_Rows - 1, 0);
   Cursor_Set = 0;
   SLtt_normal_video ();	       /* MSKermit requires this  */
   tt_write_string(Norm_Vid_Str);

   Current_Fgbg = 0xFFFFFFFFU;
   SLtt_set_alt_char_set (0);
   if (SLtt_Use_Ansi_Colors)
     {
	if (Reset_Color_String == NULL)
	  {
	     SLtt_Char_Type attr;
	     if (-1 != make_color_fgbg (NULL, NULL, &attr))
	       write_attributes (attr);
	     else tt_write_string ("\033[0m\033[m");
	  }
	else tt_write_string (Reset_Color_String);
	Current_Fgbg = 0xFFFFFFFFU;
     }
   SLtt_erase_line ();
   tt_write_string (Keypad_Reset_Str);
   tt_write_string (Term_Reset_Str);

   if (Mouse_Mode == 1)
     SLtt_set_mouse_mode (0, 1);

   SLtt_flush_output ();
   Video_Initialized = 0;
   return 0;
}

void SLtt_bold_video (void)
{
   tt_write_string (Bold_Vid_Str);
}

int SLtt_set_mouse_mode (int mode, int force)
{
   char *term;

   if (force == 0)
     {
	if (NULL == (term = (char *) getenv("TERM"))) return -1;
	if (strncmp ("xterm", term, 5))
	  return -1;
     }

   Mouse_Mode = (mode != 0);

   if (mode)
     tt_write_string ("\033[?9h");
   else
     tt_write_string ("\033[?9l");

   return 0;
}

void SLtt_disable_status_line (void)
{
   if (SLtt_Has_Status_Line > 0)
     {
	tt_write_string (Disable_Status_line_Str);
	SLtt_flush_output ();
     }
}

int SLtt_write_to_status_line (SLFUTURE_CONST char *s, int col)
{
   if ((SLtt_Has_Status_Line <= 0)
       || (Goto_Status_Line_Str == NULL)
       || (Return_From_Status_Line_Str == NULL))
     return -1;

   tt_printf (Goto_Status_Line_Str, col, 0);
   tt_write_string (s);
   tt_write_string (Return_From_Status_Line_Str);
   return 0;
}

void SLtt_get_screen_size (void)
{
#ifdef VMS
   int status, code;
   unsigned short chan;
   $DESCRIPTOR(dev_dsc, "SYS$INPUT:");
#endif
   int r = 0, c = 0;

#ifdef TIOCGWINSZ
   struct winsize wind_struct;

   do
     {
	if ((ioctl(1,TIOCGWINSZ,&wind_struct) == 0)
	    || (ioctl(0, TIOCGWINSZ, &wind_struct) == 0)
	    || (ioctl(2, TIOCGWINSZ, &wind_struct) == 0))
	  {
	     c = (int) wind_struct.ws_col;
	     r = (int) wind_struct.ws_row;
	     break;
	  }
     }
   while (errno == EINTR);

#endif

#ifdef VMS
   status = sys$assign(&dev_dsc,&chan,0,0,0);
   if (status & 1)
     {
	code = DVI$_DEVBUFSIZ;
	status = lib$getdvi(&code, &chan,0, &c, 0,0);
	if (!(status & 1))
	  c = 80;
	code = DVI$_TT_PAGE;
	status = lib$getdvi(&code, &chan,0, &r, 0,0);
	if (!(status & 1))
	  r = 24;
	sys$dassgn(chan);
     }
#endif

   if (r <= 0)
     {
	char *s = getenv ("LINES");
	if (s != NULL) r = atoi (s);
     }

   if (c <= 0)
     {
	char *s = getenv ("COLUMNS");
	if (s != NULL) c = atoi (s);
     }

   if ((r <= 0) || (r > SLTT_MAX_SCREEN_ROWS)) r = 24;
   if ((c <= 0) || (c > SLTT_MAX_SCREEN_COLS)) c = 80;
   SLtt_Screen_Rows = r;
   SLtt_Screen_Cols = c;
}

#if SLTT_HAS_NON_BCE_SUPPORT
int _pSLtt_get_bce_color_offset (void)
{
   if ((SLtt_Use_Ansi_Colors == 0)
       || Can_Background_Color_Erase
       || SLtt_Use_Blink_For_ACS)      /* in this case, we cannot lose a color */
     Bce_Color_Offset = 0;
   else
     {
	SLtt_Char_Type fgbg = get_brush_fgbg (0);
	if (GET_BG(fgbg) == SLSMG_COLOR_DEFAULT)
	  Bce_Color_Offset = 0;
	else
	  Bce_Color_Offset = 1;
     }
   
   return Bce_Color_Offset;
}
#endif

int SLtt_utf8_enable (int mode)
{
   if (mode == -1)
     mode = _pSLutf8_mode;

   return _pSLtt_UTF8_Mode = mode;
}

int SLtt_is_utf8_mode (void)
{
   int mode = _pSLtt_UTF8_Mode;
   if (mode == -1)
     mode = _pSLutf8_mode;

   return mode;
}
