/*
 *	Universal Terminal Interface -- Keyboard Input
 *
 *	(c) 1997 Martin Mares, <mj@ericsson.cz>
 */

#include <stdio.h>
#include <string.h>
#include <sys/signal.h>
#include <errno.h>
#include <unistd.h>

#include "termint.h"

#define TIMEOUT 1

struct key_tree {
  struct key_tree *right, *down;
  ulg meaning;
  byte ch;
};

static struct key_tree kt_root;

/* Signal handlers */

static void
interrupt_handler(int sig)
{
  char *msg;

  switch (sig)
    {
    case SIGINT:
      msg = "Interrupt";
      break;
    case SIGQUIT:
      msg = "Quit";
      break;
    case SIGHUP:
      msg = "Hangup";
      break;
    case SIGILL:
      msg = "Illegal instruction";
      break;
    case SIGTRAP:
      msg = "Trap";
      break;
    case SIGBUS:
      msg = "Bus error";
      break;
    case SIGFPE:
      msg = "Floating point exception";
      break;
    case SIGSEGV:
      msg = "Segmentation fault";
      break;
    case SIGXCPU:
      msg = "CPU quota exceeded";
      break;
    case SIGXFSZ:
      msg = "File quota exceeded";
      break;
    case SIGPWR:
      msg = "Power is going down";
      break;
    default:
      msg = "Unknown signal";
    }
  //Tclean();
  cleanup_terminal();
  FLUSH;
  signal(sig, SIG_DFL);
  raise(sig);
#if 0
  fprintf(stderr, "%s.\n\n", msg);
  exit(10);
#endif
}

static int susp_cm;

static void
cont_handler(int sig)
{
  refresh_tios();
  current_cm = susp_cm;
  Tredraw();
  signal(sig, cont_handler);
}

static void
ignore_handler(int sig)
{
  signal(sig, ignore_handler);
}

/* Key mapping */

void
map_key(ulg key, byte *string)
{
  struct key_tree *this, *new;

  if (!*string)
    {
      DBG(("Mapping %04x to empty sequence.\n", key));
      return;
    }

  this = &kt_root;
  while (*string)
    {
      for(new = this->down; new; new = new->right)
	if (new->ch == *string)
	  break;
      if (!new)
	{
	  new = xmalloc(sizeof(struct key_tree));
	  new->right = this->down;
	  this->down = new;
	  new->down = NULL;
	  new->ch = *string;
	  new->meaning = 0;
	}
      this = new;
      string++;
    }
  if (!this->meaning)
    this->meaning = key;
}

void
map_single_key(ulg key, ulg meaning)
{
  char z[3];

  if (key)
    {
      z[0] = 0x1b;
      z[1] = key;
      z[2] = 0;
      map_key(meaning, z + 1);
      map_key(META + meaning, z);
    }
}

/* Dump the tree */

#ifdef DEBUG

static void
show_key_map0(struct key_tree *t)
{
  DBG(("(%d", t->ch));
  if (t->meaning)
    DBG(("=%04x", t->meaning));
  t = t->down;
  while (t)
    {
      DBG((" "));
      show_key_map0(t);
      t = t->right;
    }
  DBG((")"));
}

void
show_key_mappings(void)
{
  DBG(("Keyboard tree: "));
  show_key_map0(&kt_root);
  DBG(("\n"));
}

#endif

/* Map standard keys and set up signal handlers */

void
keys_init(void)
{
  map_key(KEY_ESCAPE, "\033");
  map_single_key(13, KEY_ENTER);
  map_single_key(9, KEY_TAB);
  map_key(KEY_REFRESH, "\014");
  signal(SIGINT, interrupt_handler);
  signal(SIGQUIT, interrupt_handler);
  signal(SIGCONT, cont_handler);
  signal(SIGHUP, interrupt_handler);
  signal(SIGILL, interrupt_handler);
  signal(SIGTRAP, interrupt_handler);
  signal(SIGBUS, interrupt_handler);
  signal(SIGFPE, interrupt_handler);
  signal(SIGSEGV, interrupt_handler);
  signal(SIGPIPE, ignore_handler);
  signal(SIGXCPU, interrupt_handler);
  signal(SIGXFSZ, interrupt_handler);
  signal(SIGPWR, interrupt_handler);
}

/* Buffered standard input */

#define INBUFSIZE 64

static byte inbuf[INBUFSIZE];
static byte *ibptr = inbuf;
static byte *ibend = inbuf;
static int eof_flag;

static ulg
get_byte(ulg timeout)
{
  if (eof_flag)
    return ~1;
  if (ibptr == ibend)
    {
      int r;

      signal(SIGALRM, ignore_handler);
      if (timeout)
	alarm(timeout);
      r = read(0, inbuf, INBUFSIZE);
      if (timeout)
	alarm(0);
      if (r < 0 && errno == EINTR)
	return ~0;
      if (r <= 0)
	{
	  eof_flag = 1;
	  return ~1;
	}
      ibptr = inbuf;
      ibend = inbuf + r;
    }
  return *ibptr++;
}

/* Get next key */

static ulg
Tgetkey0(void)
{
  struct key_tree *kt = &kt_root;
  struct key_tree *d;
  ulg z;
  int depth = 0;

  Trefresh();
  for(;;)
    {
      if (kt->meaning && !kt->down)
	return kt->meaning;
      z = get_byte(kt->meaning ? TIMEOUT : 0);
      if (z == ~1)
	return KEY_EOF;
      if (z == ~0)
	return kt->meaning;
      if (!z)				/* NULs are ignored */
	continue;
      for(d = kt->down; d; d = d->right)
	if (d->ch == z)
	  break;
      if (d)
	kt = d;
      else if (!depth)			/* Direct meanings */
	{
	  if (z >= 0x80 && BOOL(HAS_META)) /* META setting 7th bit */
	    return z - 0x80 + META;
	  else
	    return z;
	}
      else if (depth == 1 && kt->ch == 27) /* After single escape */
	{
	  if (z == 27)			/* Dirty double-ESC hack */
	    return KEY_ESCAPE;
	  if (z >= '0' && z <= '9')	/* ESC + digit acting as Fn */
	    return KEY_F0 + z - '0';
	  return META + z;		/* ESC acting as META */
	}
      else
	return KEY_UNKNOWN;
      depth++;
    }
}

ulg
Tgetkey(void)
{
  ulg z;

  for(;;)
    {
      z = Tgetkey0();
      switch (z)
	{
	case KEY_REFRESH:
	  Tredraw();
	  break;
	case KEY_EOF:
	  interrupt_handler(SIGHUP);
	  break;
	case KEY_INTERRUPT:
	  interrupt_handler(SIGINT);
	  break;
	case KEY_QUIT:
	  interrupt_handler(SIGQUIT);
	  break;
	case KEY_SUSPEND:
	  send_attrs(0, default_color);
	  susp_cm = current_cm;
	  current_cm = CM_FOLLOW | CM_NORMAL;
	  cgoto(rows-1, 0);
	  restore_tios();
	  puts("\n\n");
	  kill(getpid(), SIGSTOP);
	  break;
	default:
	  return z;
	}
    }
}
