/*
 *	Universal Terminal Interface -- Termcap Functions
 *
 *	If you link this with a termcap library, you need to
 *	use the GNU version.
 *
 *	(c) 1997--1999 Martin Mares, <mj@ericsson.cz>
 */

#include <stdio.h>
#include <stdlib.h>
#include <termcap.h>

#define CAPS_LIST_HERE

#include "termint.h"

char *term_type;

/* Capabilities */

int t_bools[TB_SIZEOF], t_ints[TN_SIZEOF];
char *t_strs[TS_SIZEOF], *t_keys[TK_SIZEOF];

/* Sending of strings to the terminal */

ulg pad_param = 1;

int (*send_char)(int);

inline void
do_padding(ulg pad)						/* in 100usec units */
{
  if (pad && padding_char >= 0)
    {
      pad *= 100000;					/* pad is now in nanoseconds */
      pad = (pad + nsec_per_char - 1) / nsec_per_char; /* pad is now in characters */
      if (!pad)
	pad = 1;
      while (pad--)
	send_char(padding_char);
    }
}

void
send_it(char *c, ulg len)
{
  char *e = c + len;
  int pad = 0;

  if (*c >= '0' && *c <= '9')			/* Padding specified */
    {
      while (*c >= '0' && *c <= '9')
	pad = pad*10 + *c++ - '0';
      pad *= 10;
      if (*c == '.' && c[1])
	{
	  pad += c[1] - '0';
	  c += 2;
	}
      if (*c == '*')
	{
	  pad *= pad_param;				/* pad is now in 100 usec units */
	  c++;
	}
    }
  TR("send_it<%.*s>\n", (int)len, c);
  while (c < e)
    send_char(*c++);
  do_padding(pad);
  pad_param = 1;
}

void
sends(char *str)
{
  if (str)
    send_it(str, strlen(str));
}

void
sendpad(byte *str, int pad)
{
  send_it(str, strlen(str));
  if (pad > 0)
    do_padding(pad*10);
}

#ifdef USE_CURSES

void
senda(byte *str, int *p)
{
  byte out_buf[256];
  byte *z = out_buf;
  byte *e = out_buf + sizeof(out_buf) - 16;
  int k, l;
  int stack[32], vars[26];
  int sp = 2;
  int inc = 0;

  if (!str)
    return;
  TR("senda<%s>\n", str);
  while (*str)
    {
      if (z > e)
	fatal_error("Terminal control sequence too long");
      if (*str == '%')
	{
	  str++;
	  switch (*str++)
	    {
	    case '%':
	      *z++ = '%';
	      break;
	    case 'c':
	    case 's':
	    case 'x':
	    case 'd':
	    case '0':
	    case '2':
	    case '3':
	      {
		char zb[6], *t = zb;
		str--;
		*t++ = '%';
		while (*str >= '0' && *str <= '9')
		  if (t < zb + 5)
		    *t++ = *str++;
		  else
		    goto bad;
		if (*str != 'd' && *str != 'c' && *str != 'x' && *str != 's')
		  goto bad;
		*t++ = *str++;
		*t = 0;
		z += sprintf(z, zb, stack[--sp]);
		break;
	      }
	    case 'p':
	      if (*str < '1' || *str > '9')
		goto bad;
	      stack[sp++] = p[*str++ - '1'] + (*str <= '1' ? inc : 0);
	      break;
	    case 'P':
	      if (*str < 'a' || *str > 'z')
		goto bad;
	      vars[*str++ - 'a'] = stack[--sp];
	      break;
	    case 'g':
	      if (*str < 'a' || *str > 'z')
		goto bad;
	      stack[sp++] = vars[*str++ - 'a'];
	      break;
	    case '\'':
	      if (!str[0] || str[1] != '\'')
		goto bad;
	      stack[sp++] = str[0];
	      str += 2;
	      break;
	    case '{':
	      k = 0;
	      while (*str >= '0' && *str <= '9')
		k = 10*k + *str++ - '0';
	      if (*str != '}')
		goto bad;
	      str++;
	      stack[sp++] = k;
	      break;
#define OP(op, o) case op: sp--; stack[sp-1] = stack[sp-1] o stack[sp]; break;
	    OP('+', +)
	    OP('-', -)
	    OP('*', *)
	    case '/':
	      sp--;
	      stack[sp-1] = stack[sp] ? stack[sp-1] / stack[sp] : 10000;
	      break;
	    case 'm':
	      sp--;
	      stack[sp-1] = stack[sp] ? stack[sp-1] % stack[sp] : 0;
	      break;
	    OP('&', &)
	    OP('|', |)
	    OP('^', ^)
	    OP('=', =)
	    OP('<', <)
	    OP('>', >)
	    OP('A', &&)
	    OP('O', ||)
#undef OP
	    case '!':
	      stack[sp] = !stack[sp];
	      break;
	    case '~':
	      stack[sp] = ~stack[sp];
	      break;
	    case 'i':
	      /* Oh horror! People are defining capabilities with one argument and %i! */
	      inc++;
	      break;
	    case '?':
	      break;
	    case 't':
	      if (stack[--sp])
		break;
	      l = 0;
	    doelse:
	      k = 0;
	      for(;;)
		{
		  while (*str && *str != '%')
		    str++;
		  if (!*str++)
		    goto bad;
		  if (*str == '?')
		    k++;
		  else if (*str == 'e' && !k && !l)
		    break;
		  else if (*str == ';')
		    {
		      if (k)
			k--;
		      else
			break;
		    }
		  str++;
		}
	      str++;
	      break;
	    case ';':
	      break;
	    case 'e':
	      l = 1;
	      goto doelse;
	      break;
	    default:
	    bad:
	      DBG(("Unknown control character\n"));
	    }
	  if (sp < 2 || sp > 30)
	    fatal_error("Terminal control sequence stack %sflow", (sp < 2) ? "under" : "over");
	}
      else
	*z++ = *str++;
    }
  send_it(out_buf, z - out_buf);
}

#else

void
senda(byte *str, int *p)
{
  byte out_buf[256];
  byte *z = out_buf;
  byte *e = out_buf + sizeof(out_buf) - 16;
  int k;

  if (!str)
    return;
  TR("senda<%s>\n", str);
  while (*str)
    {
      if (z > e)
	fatal_error("Terminal control sequence too long");
      if (*str == '%')
	{
	  str++;
	  switch (*str++)
	    {
	    case 'd':
	      z += sprintf(z, "%d", *p++);
	      break;
	    case 'b':
	      p--;
	      break;
	    case '2':
	      z += sprintf(z, "%02d", *p++);
	      break;
	    case '3':
	      z += sprintf(z, "%03d", *p++);
	      break;
	    case '.':
	      *z++ = *p++;
	      break;
	    case '+':
	      *z++ = *p++ + *str++;
	      break;
	    case 'i':
	      p[0]++;
	      p[1]++;
	      break;
	    case 'r':
	      k = p[0];
	      p[0] = p[1];
	      p[1] = k;
	      break;
	    case 'f':
	      p++;
	      break;
	    case '>':
	      if (*p > str[0])
		*p += str[1];
	      str += 2;
	      break;
	    case 'a':
	      {
		int op = *str++;
		int type = *str++;
		int pos = *str++;
		int x;

		switch (type)
		  {
		  case 'p':
		    x = p[pos - 64];
		    break;
		  case 'c':
		    x = pos & ~0200;
		    break;
		  default:
		    goto bad;
		  }
		switch (op)
		  {
		  case '=':
		    *p = x;
		    break;
		  case '+':
		    *p += x;
		    break;
		  case '-':
		    *p -= x;
		    break;
		  case '*':
		    *p *= x;
		    break;
		  case '/':
		    if (!x)
		      fatal_error("Divison by zero in terminal control sequence");
		    *p /= x;
		    break;
		  default:
		    goto bad;
		  }
		break;
	      }
	    case 'n':
	      *p ^= 0140;
	      break;
	    case 'm':
	      *p = ~*p;
	      p[2] = ~p[2];
	      break;
	    case 'B':
	      *p = ((*p / 10) << 4) | (*p % 10);
	      break;
	    case 'D':
	      *p -= 2 * (*p % 16);
	      break;
	    default:
	    bad:
	      DBG(("Unknown control character\n"));
	    }
	}
      else
	*z++ = *str++;
    }
  send_it(out_buf, z - out_buf);
}

#endif

void
sendp(byte *str, int p, ...)
{
  senda(str, &p);
}

/* Termcap analysis */

void
init_termcap(void)
{
  int success;
  char buf[3];
  char *c;
  int i;

  term_type = getenv("TERM");
  if (!term_type || !*term_type)
    fatal_error("TERM variable not set");
  DBG(("Using terminal %s\n", term_type));
  success = tgetent(NULL, term_type);
  if (success < 0)
    fatal_error("Cannot access the termcap database");
  if (!success)
    fatal_error("Terminal type `%s' unknown", term_type);

  c = tc_numerics;
  buf[2] = 0;
  i = 0;
  while (*c)
    {
      buf[0] = *c++;
      buf[1] = *c++;
      t_ints[i++] = tgetnum(buf);
    }

  c = tc_bools;
  i = 0;
  while (*c)
    {
      buf[0] = *c++;
      buf[1] = *c++;
      t_bools[i++] = tgetflag(buf);
    }

  c = tc_strings;
  i = 0;
  while (*c)
    {
      buf[0] = *c++;
      buf[1] = *c++;
      t_strs[i++] = tgetstr(buf, NULL);
    }

  c = tc_keys;
  i = 0;
  while (*c)
    {
      buf[0] = *c++;
      buf[1] = *c++;
      t_keys[i] = tgetstr(buf, NULL);
      if (t_keys[i])
	map_key(i + KEY_BASE, t_keys[i]);
      i++;
    }
}
