/*
 * term.c: all the routines we need to do our stuff with the screen.
 *
 * Copyright(c) 1997-2000 - All Rights Reserved
 *
 * See the COPYRIGHT file.
 */

#ifndef lint
static char rcsid[] = "@(#)$Id: term.c,v 1.24 2000/07/31 22:33:56 kalt Exp $";
#endif

#include "os.h"

#include "term.h"

static	struct termios oldt, newt;
static	int mytty;

int	LI,			/* number of lines */
	CO;			/* number of columns */
char    *CM,			/* screen-relative cursor motion */
	*ND,			/* non destructive space (cursor right) */
	*LE,			/* move cursor left one position */

	*CE,			/* clear to end of line */
	*CL,			/* clear screen and home cursor */
	*DC,			/* delete character */ /* UNUSED */
	*IC,			/* insert character */ /* UNUSED */

	*SO, *SE,		/* begin and end standout mode */
	*US, *UE,		/* start and end underscore mode */
	*MD,			/* turn on extra bright (bold) */
	*ME,			/* turn off all attributes */

	*CS,			/* change scrolling region (vt100) */
	*SF,			/* scroll text up */
	*NL,			/* newline character */
	*SR,			/* scroll text down */

	*BL;			/* bell */

/*
 * terminal initialization
 *	read termcap, setup the terminal the way we want it to be..
 */
void
term_init()
{
  static char termcap[2048];
  char	area[1024], *term, *ptr;

  int i = 0, missing = 0;
  char *cap_name[] = { "cm", "nd", "le", "ce", "cl", "dc", "ic", "so", "se", "us", "ue", "md", "me", "cs", "sf", "nl", "sr", "bl", 0 };
  char **cap_var[] = { &CM, &ND, &LE, &CE, &CL, &DC, &IC, &SO, &SE, &US, &UE, &MD, &ME, &CS, &SF, &NL, &SR, &BL };

  /* Open the tty.  Using it might not be necessary, but won't hurt */
  mytty = open("/dev/tty", O_RDWR, 0);
  if (mytty < 0)
    {
      perror("Unable to open /dev/tty");
      exit(1);
    }
  /* Get all the info we need from the termcap entry */
  if ((term = getenv("TERM")) == NULL)
    {
      fprintf(stderr, "TERM variable is not set!\n");
      exit(1);
    }
  if (tgetent(termcap, term) < 1)
    {
      fprintf(stderr, "No TERMCAP entry for ``%s''.\n", term);
      exit(1);
    }

  ptr = area;

  if ((CO = tgetnum("co")) == -1) CO = 80;
  if ((LI = tgetnum("li")) == -1) LI = 24;

  while (cap_name[i])
    {
      *(cap_var[i]) = (char *) tgetstr(cap_name[i], &ptr);
      if (strcmp(cap_name[i], "nl") && strcmp(cap_name[i], "bl")
	  && *(cap_var[i]) == NULL)
	{
	  missing += 1;
	  fprintf(stderr, "%s is undefined for this terminal.\n", cap_name[i]);
	}
      i += 1;
    }
  if (NL == NULL) NL = "\n";
  if (BL == NULL) BL = "\007";
  if (IC == NULL) missing -= 1; /* unused */
  if (DC == NULL) missing -= 1; /* unused */
  if (missing)
    {
      fprintf(stderr, "Things will probably not work well, but might be easy to fix.\n");
      sleep(2);
    }

  if (SO == NULL || SE == NULL) { SO = ""; SE = ""; }
  if (US == NULL || UE == NULL) { US = ""; UE = ""; }
  if (MD == NULL || ME == NULL) { MD = ""; ME = ""; }

  /* let's try to avoid silly crashes */
  i = -1;
  while (cap_name[++i])
      if (*(cap_var[i]) == NULL)
	  *(cap_var[i]) = "";
  
  tcgetattr(mytty, &oldt);		/* save the tty settings */

  newt = oldt;
  newt.c_lflag &= ~(ICANON | ECHO);	/* cbreak() noecho() */
  newt.c_cc[VMIN] = 1;			/* read() returns if >= 1 char */
  newt.c_cc[VTIME] = 0;			/* no timer on read() */

  tcsetattr(mytty, TCSADRAIN, &newt);	/* setup tty the way we like it */
  term_size();				/* try to get the size, really */

  term_clear();				/* clear the screen */
}

/* terminal reinitialization */
void
term_reinit()
{
  tcsetattr(mytty, TCSADRAIN, &newt);
}

/* term_size: try to get the terminal size
 *	returns 0 if same as before
 *	returns 1 if changed
 *	returns -1 if ioctl() failed.
 *	returns -2 if no TIOCGWINSZ to get size
 */
int
term_size()
{
#if defined(TIOCGWINSZ)
  struct winsize ws;
  int nLI = LI, nCO = CO, ret = 0;

  if (ioctl(mytty, TIOCGWINSZ, &ws) < 0)
      return -1;
  if (ws.ws_row)
      nLI = ws.ws_row;
  if (ws.ws_col)
      nCO = ws.ws_col;
  if (LI != nLI || CO != nCO)
      ret = 1;
  LI = nLI; CO = nCO;
  return ret;
#else
  return -2;
#endif
}

/* restore the terminal to what it was before */
void
term_end()
{
  term_move_cursor(0, LI-1);		/* last line */
  term_clreol();			/* clear it */
  sic_tputs(tgoto(CS, LI-1, 0));	/* reset scrolling region */
  term_move_cursor(0, LI-1);		/* go to last line again */
  tcsetattr(mytty, TCSADRAIN, &oldt);	/* reset tty settings */
  fflush(stdout);
}

/* display a character on the screen.
 *	control characters are displayed in reverse video uppercase letters
 */
void
term_putchar(c)
  unsigned int c;
{
  if (c < 32)
    {
      term_standout_on();
      c = (c & 127) | 64;
      fputc(c, stdout);
      term_standout_off();
    }
  else if (c == '\177')
    {
      term_standout_on();
      c = '?';
      fputc(c, stdout);
      term_standout_off();
    }
  else
      fputc(c, stdout);
 }

/* display up to len characters from the string str on the screen */
int
term_puts(str, len)
  char    *str;
  int     len;
{
  int     i;
  
  for (i = 0; *str && (i < len); str++, i++)
      term_putchar(*str);
  return (i);
}

/* display up to len characters from the string str on the screen
 * the attr argument is optional, it's used for video attributes
 * attrmask is a mask to be used on attributes
 */
int
term_putes(str, len, attr, attrmask)
  char    *str;
  u_char  *attr;
  u_int   attrmask;
  int     len;
{
  int	i;
  u_char *ap = attr, av, hidden = 0;
  unsigned int video = 0;
  
  for (i = 0; *str && (i < len); ap++, str++, i++)
    {
      av = 0;
      if (attr)
	{
	  av = *ap & attrmask;
	  av = (av & 0x0F) | ((av & 0xF0) >> 4);
	  if (av & TERM_HIDE)
	    {
	      hidden = 1;
	      continue;
	    }
	  if (av & TERM_BOLD)
	    {
	      if (!(video & TERM_BOLD))
		{
		  term_bold_on();
		  video |= TERM_BOLD;
		}
	    }
	  else
	      if (video & TERM_BOLD)
		{
		  term_bold_off();
		  video ^= TERM_BOLD;
		}
	  
	  if (av & TERM_STANDOUT)
	    {
	      if (!(video & TERM_STANDOUT))
		{
		  term_standout_on();
		  video |= TERM_STANDOUT;
		}
	    }
	  else
	      if (video & TERM_STANDOUT)
		{
		  term_standout_off();
		  video ^= TERM_STANDOUT;
		}
	  if (av & TERM_UNDERLINE)
	    {
	      if (!(video & TERM_UNDERLINE))
		{
		  term_underline_on();
		  video |= TERM_UNDERLINE;
		}
	    }
	  else
	      if (video & TERM_UNDERLINE)
		{
		  term_underline_off();
		  video ^= TERM_UNDERLINE;
		}
	}
      term_putchar(*str);
    }
  if (video & TERM_BOLD)
      term_bold_off();
  if (video & TERM_STANDOUT)
      term_standout_off();
  if (video & TERM_UNDERLINE)
      term_underline_off();
  if (hidden)
    {
      while (i++ < len)
	term_putchar(' ');
      term_standout_on();
      term_putchar('+');
      term_standout_off();
    }
  return (i);
}

/* flush the standard output */
void
term_flush()
{
  fflush(stdout);
}

/* scroll n lines from line1 to line2 */
int
term_scroll(line1, line2, n)
  int     line1, line2, n;
{
  int     i;
  char    *code = NULL;
  
  if (CS == NULL)
    return -1;

  if (n > 0)
      code = SF ? SF : NL;
  else if (n < 0)
      code = SR;

  assert(code); /* a little bit excessive, but makes a point */

  sic_tputs(tgoto(CS, line2, line1));
  if (n < 0)
    {
      term_move_cursor(0, line1);
      n = -n;
    }
  else
    term_move_cursor(0, line2);
  for (i = 0; i < n; i++)
    sic_tputs(code);
  sic_tputs(tgoto(CS, LI - 1, 0));

  return 0;
}

void
term_status(str, bold)
  char *str;
  int bold;
{
  static int i;

  term_move_cursor(0, LI-2);
  if (bold)
      term_bold_on();
  else
    {
      term_standout_on();
      for (i = strlen(str); i < CO-1; i++)
	str[i] = ' ';
      str[CO] = '\0';
    }
  term_puts(str, CO-1);
  if (bold)
      term_bold_off();
  else
      term_standout_off();
}

/* displays the string at the bottom of the screen (input line) */
void
term_input(str, pos)
  char	*str;
  int	pos;
{
  static int c = 0;

  if (str)
    {
      term_move_cursor(0, LI-1);
      term_puts(str, CO-1);
      term_clreol();
      c = pos;
    }
  term_move_cursor(c, LI-1);
}
