/*
 *  Copyright (c) 1992, 1993 John E. Davis  (davis@amy.tch.harvard.edu)
 *  All Rights Reserved.
 */
#include <stdio.h>
#include <string.h>
#include "config.h"
#include "display.h"
#include "sysdep.h"
#include "screen.h"


int Term_Cannot_Insert = 0;
/* 1 if terminal lacks the ability to do into insert mode or into delete
   mode. Currently controlled by S-Lang but later perhaps termcap. */


/* char *CLR_BOS_STR = "\033[1J"; */  /* erase to beg of screen */
/* char *DEL_BOL_STR = "\033[1K\r"; */  /* cb termcap entry */
/* char *HOME_CURS_STR = "\033[H"; */  /* ho termcap string */
/* char *BOLD_VID_STR = "\033[1m"; */    /**/
/* char *UNDL_VID_STR = "\033[4m"; */   /**/

char *INS_MODE_STR; /* = "\033[4h"; */   /* ins mode (im) */
char *EINS_MODE_STR; /* = "\033[4l"; */  /* end ins mode (ei) */
char *SCROLL_R_STR; /* = "\033[%d;%dr"; */ /* scroll region */
char *CLS_STR; /* = "\033[2J\033[H"; */  /* cl termcap STR  for ansi terminals */
char *REV_VID_STR; /* = "\033[7m"; */    /* mr,so termcap string */
char *NORM_VID_STR; /* = "\033[m"; */   /* me,se termcap string */
char *DEL_EOL_STR; /* = "\033[K"; */	       /* ce */
char *DEL_CHAR_STR; /* = "\033[P"; */   /* dc */
char *DEL_N_LINES_STR; /* = "\033[%dM"; */  /* DL */
char *ADD_N_LINES_STR; /* = "\033[%dL"; */  /* AL */
char *REV_SCROLL_STR;

char *CURS_F_STR = "\033[%dC";    /* RI termcap string */
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 */
char *CURS_POS_STR; /* = "\033[%i%d;%dH";*/   /* cm termcap string */

int  Output_Rate = 0;	       /* number of chars to output in 1 second */

/* scrolling region */
static int Scroll_r1 = 0, Scroll_r2 = 23;
static int Cursor_r;
static int Rev_Vid_Flag = 0;	       /* true if reverse video */

static void tt_write(char *str, int n)
{
   static unsigned long last_time;
   static int total;
   unsigned long now;
   
   if (str == NULL) return;
   total += n;
   write(fileno(stdout), str, n);
   if ((Output_Rate > 20) && (total > Output_Rate))
     {
	total = 0;
	if ((now = sys_time()) - last_time <= 1)
	  {
	     sleep((unsigned) 1);
	  }
	last_time = now;
     }
}


void send_string_to_term(char *str)
{
   if (str == NULL) return;
   tt_write(str, strlen(str));
}


/* This procedure is called only during 'fast'/no screen update routines
   from cmds.c */
void tt_putchar(char ch)
{
   if (Rev_Vid_Flag) tt_normal_video();
   write(fileno(stdout), &ch, 1);
}

/* this is supposed to be fast--- also handles 
   termcap: %d, &i, %., %+, %r strings as well as terminfo stuff */
int tt_sprintf(char *buf, char *fmt, int x, int y)
{
   register unsigned char *f = (unsigned char *) fmt, *b, ch;
   int offset = 0, tinfo = 0;
   int stack[10];
   int i = 0, z;
   stack[0] = y; stack[1] = x; i = 2;
   
   b = (unsigned char *) buf;
   if (fmt != NULL) while ((ch = *f++) != 0)
     {
	if (ch != '%') *b++ = ch;
	else 
	  {
	     ch = *f++;
	     if (ch == 'p')
	       {
		  tinfo = 1;
		  ch = *f++;
		  if (ch == '1') stack[i++] = x; else stack[i++] = y;
	       }
	     else if (ch == '\'')   /* 'x' */
	       {
		  stack[i++] = *f++;
		  f++;
	       }
	     else if ((ch == 'd') || (ch == 2) || (ch == 3))
	       {
		  z = stack[--i];
		  z += offset;
		  if (z >= 100)
		    {
		       *b++ = z / 100 + '0';
		       z = z % 100;
		       goto ten;
		    }
		  else if (ch == 3) 
		    {
		       *b++ = '0';
		       ch = '2';
		    }
		  
		  if (z >= 10)
		    {
		       ten:
		       *b++ = z / 10 + '0';
		       z = z % 10;
		    }
		  else if (ch == 2) *b++ = '0';
		  
		  *b++ = z + '0';
	       }
	     else if (ch == 'i') 
	       {
		  offset = 1;
	       }
	     else if (ch == '+')
	       {
		  if (tinfo) 
		    {
		       z = stack[--i];
		       stack[i-1] += z;
		    }
		  else
		    {
		       ch = *f++;
		       if ((unsigned char) ch == 128) ch = 0;
		       ch = ch + (unsigned char) stack[--i];
		       if (ch == '\n') ch++;
		       *b++ = ch;
		    }
	       }
	     else if (ch == 'r')
	       {
		  stack[0] = x;
		  stack[1] = y;
	       }
	     else if ((ch == '.') || (ch == 'c'))
	       {
		  ch = (unsigned char) stack[--i];
		  if (ch == '\n') ch++;
		  *b++ = ch;
	       }
	     else *b++ = ch;
	  }
     }
   *b = 0;
   return((int) (b - (unsigned char *) buf));
}

void tt_printf(char *fmt, int x, int y)
{
   char buf[256];
   int n;
   n = tt_sprintf(buf, fmt, x, y);
   tt_write(buf, n);
}


void set_scroll_region(int r1, int r2)
{
   Scroll_r1 = r1 - 1;
   Scroll_r2 = r2 - 1;
   tt_printf(SCROLL_R_STR,Scroll_r1, Scroll_r2);
}

void reset_scroll_region()
{
    set_scroll_region(1, Screen_Height);
}

/* the goto_rc function moves to row relative to scrolling region */
void goto_rc(int r, int c)
{
   Cursor_r = r - 1 + Scroll_r1;
   tt_printf(CURS_POS_STR, Cursor_r,c - 1);
}

void begin_insert()
{
    send_string_to_term(INS_MODE_STR);
}

void end_insert()
{
    send_string_to_term(EINS_MODE_STR);
}

void tt_delete_char()
{
    send_string_to_term(DEL_CHAR_STR);
}

void tt_erase_line()
{
   send_string_to_term("\r");
   send_string_to_term(DEL_EOL_STR);
}

void tt_delete_nlines(int n)
{
   int r1, curs;
   if (!n) return;
   if (DEL_N_LINES_STR != NULL) tt_printf(DEL_N_LINES_STR,n, 0);
   else
   /* get a new terminal */
     {
	r1 = Scroll_r1 + 1;
	curs = Cursor_r + 1;
	set_scroll_region(curs, Scroll_r2 + 1);
	goto_rc(Scroll_r2 - Scroll_r1 + 1, 1);
	while (n--) tt_putchar('\n');
	set_scroll_region(r1, Scroll_r2 + 1);
	goto_rc(curs, 1);
     }
}

void cls()
{
    send_string_to_term(CLS_STR);
}

void reverse_index(int n)
{
   if (!n) return;
   
   if (ADD_N_LINES_STR != NULL) tt_printf(ADD_N_LINES_STR,n, 0);
   else
     {
	while(n--) send_string_to_term(REV_SCROLL_STR);
     }
}

int beep()
{
   if (!Ignore_Beep) 
     {
	tt_putchar('\007');
     }
   flush_input();
   return(0);
}

void tt_del_eol()
{
    send_string_to_term(DEL_EOL_STR);
}

void tt_reverse_video()
{
    send_string_to_term(REV_VID_STR);
   Rev_Vid_Flag = 1;
}

void tt_normal_video()
{
    send_string_to_term(NORM_VID_STR);
   Rev_Vid_Flag = 0;
}

void narrow_width()
{
    send_string_to_term("\033[?3l");
}

void wide_width()
{
    send_string_to_term("\033[?3h");
}

void send_attr_str(unsigned short *s)
{
   unsigned char out[250], ch, attr, *p;
   register unsigned short sh;
   
   p = out;
   while (0 != (sh = (unsigned short) *s++))
     {
	ch = sh & 0xFF;
	attr = sh >> 8;
	if ((attr == 0) && (Rev_Vid_Flag != 0))
	  {
	     if (p != out)
	       {
		  *p = 0;
		  send_string_to_term((char *) out);
		  p = out;
	       }
	     tt_normal_video();
	     /* Rev_Vid_Flag = 0; */
	  }
	else if ((attr != 0) && (Rev_Vid_Flag == 0))
	  {
	     if (p != out)
	       {
		  *p = 0;
		  send_string_to_term((char *) out);
		  p = out;
	       }
	     tt_reverse_video();
	     /* Rev_Vid_Flag = 1; */
	  }
	*p++ = ch;
     }
   *p = 0;
   if (p != out) send_string_to_term((char *) out);
   /* if (Rev_Vid_Flag) tt_normal_video(); */
}

   
void smart_puts(unsigned short *neww,unsigned short *oldd, int row, int spc)
{
   unsigned short out[250], *mark;
   register unsigned short *p, ch, ch1;
   register unsigned short *neew = neww, *old = oldd;
   char curs[20];
   int ii,max_len,i, curs_set = 0, curs_len;
   unsigned short *new_save;

   
    i = 0;
    ii = 0;
    *curs = 0;
    max_len = LEN_CURS_F_STR;
   
   
   /* many times we are scrolling and line to compare is blank.  Treat this
    case special */
   if (spc == 0)
     {
	p = neew;
	while(*p == ' ') p++;
	
	if (*p == 0) return;
	goto_rc(row, p - neew + 1);
	old = out;
	ch1 = ' '; 
	while (1)
	  {
	     while (ch = *p++, (ch1 != ch) && ch) *old++ = ch;
	     mark = old;
	     if (!ch) break;
	     *old++ = ch1;
	     while(ch = *p++, (ch == ch1) && ch) *old++ = ch;
	     if (old - mark > max_len)
	       {
		  *mark = 0;
		  send_attr_str(out);
		  if (ch == 0) return;
		  if (mark != out) goto_rc(row, p - neew);
		  old = out;
	       }
	     if (!ch) break;
	     p--;
	  }
	*old = 0;
	send_attr_str(out);
	return;
     }
   
   
    /* while they match, go on */
   /* Note that neew - new_save is then column of character */
   new_save = neew + 1;
   while (((ch = *neew++) == *old++) && ch);
   i += neew - new_save;

   if (!ch)
    /* we are at the end of the new, so delete eond of old line */
      {
	 if ((ch1 = *(old - 1)) == ' ')
	   {
	      while (*old++ == ch1);
	      ch1 = *(old - 1);
	   }

	 if (ch1 == 0) return;

	 goto_rc(row, i + 1);
	 tt_del_eol();
	 return;
      }


    if (i)
      {
	 curs_len = tt_sprintf(curs, CURS_POS_STR, row - 1, i);
	 curs_set = 1;
      }

    while(1)
      {
	 ch1 = 0;
	 p = out;
	 *p++ = ch;
	 while (ch1 = *old++, ch = *neew++, (ch != ch1) && ch) *p++ = ch;
	 mark = p;
         *p++ = ch;
	 if (ch) while (ch = *neew++, ch1 = *old++, (ch == ch1) && ch)
	   {
	      *p++ = ch;
	   }
	 *p = 0;
	 i = p - mark;
	 if (i > max_len)
	   {
	      *mark = 0;
	      if (*curs)
		{
		   tt_write(curs, curs_len);
		   *curs = 0;
		}
	      
	      if (!curs_set)
		{
		   goto_rc(row, 1);
		   curs_set = 1;
		}

	      send_attr_str(out);
	      if (!ch)
		{
		   if (ch1)
		     {
			old--;  ch = ' ';
			while (ch1 = *old++, (ch1 == ch));
		     }

		   if (ch1 == 0) return;
		   if (curs_set && (CURS_F_STR != NULL)) tt_printf(CURS_F_STR, i, 0);
		   else
		     {
			tt_printf(CURS_POS_STR, row - 1, neew - new_save);
			curs_set = 1;
		     }
		   
		   tt_del_eol();
		   return;
		}
	      
	      if (i)
		{
		   if (curs_set && (CURS_F_STR != NULL)) curs_len = tt_sprintf(curs, CURS_F_STR, i, 0);
		   else
		     {
			curs_len = tt_sprintf(curs, CURS_POS_STR, row - 1, neew - new_save);
			curs_set = 1;
		     }
		}
	   }
	 else
	   {
	      if (*curs)
		{
		   tt_write(curs, curs_len);
		   *curs = 0;
		}
	      if (!curs_set)
		{
		   goto_rc(row, 1);
		   curs_set = 1;
		}
	      send_attr_str(out);
	      if (!ch)
		{
		   if (ch1 == ' ')
		     {
			while (*old++ == ch1);
			ch1 = *(old - 1);
		     }
		   
		   if (ch1) tt_del_eol();
		   return;
		}
	   }
      }
}

/* termcap stuff */

#ifdef unix
#ifndef __GO32__
extern char *tgetstr(char *, char **);
extern int tgetent(char *, char *);
static char tbuf[1024];
static char *Null_String = "";
   
static char *my_tgetstr(char *what, char **p)
{
   register char *w, *w1;
   char *wsave;
   what = tgetstr(what, p);
   if (what != 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 tstr_buf[512];
static int vt100_like = 0;
void get_terminfo()
{
   char *term, ch;
   char *p = tstr_buf;
   
   if (NULL == (term = getenv("TERM")))
     {
	exit_error("TERM environment variable needs set.");
     }
   if (1 != tgetent(tbuf, term)) exit_error("Unknown terminal.");
   
   if (strcmp(term, "vt52") && (*term++ == 'v') && (*term++ == 't')
       && (ch = *term, (ch >= '1') && (ch <= '9'))) vt100_like = 1; 
   
   if ((NULL == (CLS_STR = my_tgetstr("cl", &p))) 
       || (NULL == (CURS_POS_STR = my_tgetstr("cm", &p))))
     {
	exit_error("Terminal not powerful enough for JED.");
     }
   
   if ((NULL == (INS_MODE_STR = my_tgetstr("im", &p)))
       || ( NULL == (EINS_MODE_STR = my_tgetstr("ei", &p)))
       || ( NULL == (DEL_CHAR_STR = my_tgetstr("dc", &p))))
     Term_Cannot_Insert = 1;
   
   REV_SCROLL_STR = my_tgetstr("sr", &p);
   DEL_N_LINES_STR = my_tgetstr("DL", &p);
   ADD_N_LINES_STR = my_tgetstr("AL", &p);
   SCROLL_R_STR = my_tgetstr("cs", &p);
   if ((Screen_Width = tgetnum("co")) <= 0) Screen_Width = 80;
   if ((Screen_Height = tgetnum("li")) <= 0) Screen_Height = 24;
   
   if ((SCROLL_R_STR == NULL) 
       || (((NULL == DEL_N_LINES_STR) || (NULL == ADD_N_LINES_STR))
	   && (NULL == REV_SCROLL_STR)))
     Term_Cannot_Scroll = 1;
   
   DEL_EOL_STR = my_tgetstr("ce", &p);

   if (NULL == (REV_VID_STR = my_tgetstr("so", &p))) REV_VID_STR = Null_String;
   if (NULL == (NORM_VID_STR = my_tgetstr("se", &p))) NORM_VID_STR = Null_String;
   if (NULL != (CURS_F_STR = my_tgetstr("RI", &p)))
     {
	LEN_CURS_F_STR = strlen(CURS_F_STR);
     }
   else LEN_CURS_F_STR = strlen(CURS_POS_STR);
   
}

/* specific to vtxxx only */
void enable_cursor_keys()
{
   if (vt100_like) send_string_to_term("\033=\033[?1l");
}
#endif
#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). */
							   
int set_term_vtxxx(int *vt100)
{
   SCROLL_R_STR = "\033[%i%d;%dr"; 
   CLS_STR = "\033[2J\033[H";
   REV_VID_STR = "\033[7m";
   NORM_VID_STR = "\033[m"; 
   DEL_EOL_STR = "\033[K";
   REV_SCROLL_STR = "\033M";
   CURS_F_STR = "\033[%dC";
   CURS_POS_STR = "\033[%i%d;%dH";
#ifdef __GO32__
   Term_Cannot_Insert = 1;
   Term_Cannot_Scroll = 1;
#else
   if (*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";
	Term_Cannot_Insert = 0;
     }
   else
     {
	DEL_N_LINES_STR = NULL;
	ADD_N_LINES_STR = NULL;
	Term_Cannot_Insert = 1;
     }
   Term_Cannot_Scroll = 0;
#endif
   return (0);
}

