/*
 *  Copyright (c) 1992, 1994 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"
#include "mem.h"
#include "misc.h"
#include "hooks.h"

#ifndef sequent
#include <stdlib.h>
#endif

#ifdef unix
#ifndef sequent
# include <unistd.h>
#endif
#endif

int Term_Cannot_Insert = 0;
int Use_Ansi_Colors = 0;
typedef struct 
{
   int fg, bg;
} Ansi_Color_Type;

Ansi_Color_Type Ansi_Color_Map[JMAX_COLORS] = 
{
   {15, 4}, {15, 4}, {0, 7}, {11, 5}, 
   {15,4},  			       /* operator */
   {15,4},			       /* numbers */
   {15,4},			       /* strings */
   {15,4},			       /* comments */
   {15,4},			       /* keyword */
   {15,4}			       /* delimeters */
};

   
/* 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, Cursor_c;
static int Rev_Vid_Flag = -1;	       /* current color */
static int Cursor_Set;		       /* true if cursor position known */
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;
     }
   if ((Cursor_Set == 1) && (n == 1) && (*str >= ' ') && 
       (Cursor_c < Screen_Width))
     {
	Cursor_c++;
     }
   else Cursor_Set = 0;
}


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();
   if (Cursor_Set == 1)
     {
	if (ch >= ' ') Cursor_c++;
	else if (ch == '\b') Cursor_c--;
	else if (ch == '\r') Cursor_c = 1;
	else Cursor_Set = 0;
     }
   
   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)
{
   char *s = NULL;
   int n;
   char buf[6];
   r = r - 1 + Scroll_r1;
   if (Cursor_Set)
     {
	n = r - Cursor_r;
	if ((n >= 0) && (n <= 4))
	  {
	     if ((n == 0) && (Cursor_Set == 1))
	       {
		  if (Cursor_c == c) return;
		  if (Cursor_c == c + 1) 
		    {
		       s = buf;
		       *s++ = '\b'; *s = 0;
		    }
	       }
	     else if (c == 1)
	       {
		  s = buf;
		  if ((Cursor_Set != 1) || (Cursor_c != 1)) *s++ = '\r';
		  while (n--) *s++ = '\n';
#ifdef VMS
   /* Stupid VMS wants a ^J to start a new record.  Lets give it one. */
		  *s++ = '\r';
#endif
		  *s = 0;
	       }
	  }
     }
   if (s != NULL) send_string_to_term(buf);
   else tt_printf(CURS_POS_STR, r, c - 1);
   Cursor_c = c; Cursor_r = r;
   Cursor_Set = 1;
}

void begin_insert()
{
   int cs = Cursor_Set;
   send_string_to_term(INS_MODE_STR);
   Cursor_Set = cs;
}

void end_insert()
{
   int cs = Cursor_Set;
   send_string_to_term(EINS_MODE_STR);
   Cursor_Set = cs;
}

void tt_delete_char()
{
   int cs = Cursor_Set;
   send_string_to_term(DEL_CHAR_STR);
   Cursor_Set = cs;
}

void tt_erase_line()
{
   char *s;
   Rev_Vid_Flag = -1;
   send_string_to_term("\r");
   Cursor_Set = 1; Cursor_c = 1;
   tt_del_eol();
   if (Use_Ansi_Colors) s = "\033[0m";
   else s = NORM_VID_STR;
   send_string_to_term(s);
}

void tt_delete_nlines(int n)
{
   int r1, curs;
   char buf[132];
   if (!n) return;
   tt_normal_video ();
   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);
	MEMSET(buf, '\n', n);
	tt_write(buf, n);
	/* 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;
   
   tt_normal_video();
   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()
{
   int cs = Cursor_Set;
   tt_normal_video ();
   send_string_to_term(DEL_EOL_STR);
   Cursor_Set = cs;
}

static char *term_colors[16] =
{
   "black", "red", "green", "brown", "blue", "magenta", "cyan", "lightgray",
   "gray", "brightred", "brightgreen", "yellow", "brightblue", 
   "brightmagenta", "brightcyan", "white"
};

static void term_set_color (int obj, char *what, char *fg, char *bg)
{
   int i, f = -1, g = -1;
   
   if ((obj == -1) || (what == NULL))
     {
	return;
     }
   
   
   i = 0; while (i < 16)
     {
	if (!strcmp(fg, term_colors[i]))
	  {
	     f = i;
	     break;
	  }
	i++;
     }
   i = 0; while (i < 16)
     {
	if (!strcmp(bg, term_colors[i]))
	  {
	     g = i & 0x7;
	     break;
	  }
	i++;
     }
   if ((f == -1) || (g == -1) || (f == g)) return;
   Ansi_Color_Map[obj].fg = f;
   Ansi_Color_Map[obj].bg = g;
}

   
void tt_reverse_video(int color)
{
   char *fmt;
   int fg, bg;
   int cs = Cursor_Set;
   if (Use_Ansi_Colors)
     {
	fg = Ansi_Color_Map[color].fg; 
	bg = Ansi_Color_Map[color].bg;
	if (fg & 0x8)
	  {
	     fmt = "\033[1m\033[3%d;4%dm";
	  }
	else fmt = "\033[0m\033[3%d;4%dm";
	tt_printf(fmt, fg & 0x7, bg);
     }
   else if (color == JNORMAL_COLOR) send_string_to_term(NORM_VID_STR);
   else send_string_to_term(REV_VID_STR);
   Cursor_Set = cs;
   Rev_Vid_Flag = color;
}

void tt_normal_video()
{
   int cs = Cursor_Set;
   tt_reverse_video(JNORMAL_COLOR);
   Cursor_Set = cs;
   Rev_Vid_Flag = JNORMAL_COLOR;
}

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 != attr))
	  {
	     if (p != out)
	       {
		  *p = 0;
		  send_string_to_term((char *) out);
		  p = out;
	       }
	     tt_reverse_video(attr);
	     /* 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 = 0, cc = 0, row1 = row - 1;
   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);
	if (Cursor_Set == 0) Cursor_Set = -1; 
	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)
      {
	 cc = i + 1;
	 if ((Cursor_Set != 1) || (Cursor_r != row1) || (Cursor_c != cc))
	  curs_len = tt_sprintf(curs, CURS_POS_STR, row1, 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);
		   Cursor_Set = 1;  Cursor_r = row1; Cursor_c = cc;
		   *curs = 0;
		}
	      
	      if (!curs_set)
		{
		   goto_rc(row, 1);
		   curs_set = 1;
		}

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

		   if (ch1 == 0) return;
		   
		   cc = neew - new_save + 1;
		   if ((Cursor_Set != 1) || (Cursor_r != row1) || (Cursor_c != cc))
		     {
			Cursor_r = row1;
			Cursor_c = cc;

			if (curs_set && (CURS_F_STR != NULL)) 
			tt_printf(CURS_F_STR, i, 0);
			else
			  {
			     tt_printf(CURS_POS_STR, row1, Cursor_c - 1);
			     curs_set = 1;
			  }
			Cursor_Set = 1;
		     }
		   
		   tt_del_eol();
		   return;
		}
	      
	      cc = neew - new_save + 1;
	      if (i && ((Cursor_Set != 1) || 
			(Cursor_r != row1) || (Cursor_c != cc)))
		{
		   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, row1, cc - 1);
			curs_set = 1;
		     }
		}
	   }
	 else
	   {
	      if (*curs)
		{
		   tt_write(curs, curs_len);
		   Cursor_Set = 1; Cursor_r = row1; Cursor_c = cc;
		   *curs = 0;
		}
	      if (!curs_set)
		{
		   goto_rc(row, 1);
		   curs_set = 1;
		}
	      send_attr_str(out);
	      if (Cursor_Set == 0) Cursor_Set = -1; 
	      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 *);
EXTERN int tgetnum(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, *t, ch;
   char *p = tstr_buf;
   
   if (NULL == (term = (char *) getenv("TERM")))
     {
	exit_error("TERM environment variable needs set.", 0);
     }
   if (1 != tgetent(tbuf, term)) exit_error("Unknown terminal.", 0);
   
   t = term;
   if (strcmp(t, "vt52") && (*t++ == 'v') && (*t++ == 't')
       && (ch = *t, (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.", 0);
     }
   
   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("me", &p))) 
     {
	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);
   
   X_Set_Color_Hook = term_set_color;
}

/* specific to vtxxx only */
void enable_cursor_keys()
{
   if (vt100_like) send_string_to_term("\033=\033[?1l");
}
#endif
/* __GO32__ */
#endif
/* Unix */

#ifdef VMS
void get_terminfo ()
{
   set_term_vtxxx(&Number_Zero);
   X_Set_Color_Hook = term_set_color;
}
#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 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";
   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
}

