/*
 * vt100 emulator for p3nfs
 * Rudolf Koenig, Michael Schroeder
 * 5.1.1996
 * Copyright GNU Public License
 */

#include <p_serial.h>		/* set.P_SRCHAR */
#include <plib.h>		/* P_FTEXT & co */
#include <wlib.h>		/* WMSG_KEY */
#include "nfsc.h"		/* set */

#ifdef HAVE_TERMEMU

P_RECT ttyrect;			/* Scroll region */
W_CURSOR wc;

#define MAXARGS 10
#define MAXLINE 80		/* Longest line possible */

static WORD c_x, c_y, l_x, l_y;
static char sbuf[MAXLINE], bufoff, isbold;	/* Speedup buffer */
static UINT clearmode;

#define E_NORMAL	255
#define E_ESCAPE	254

#define E_WRAP_MODE	0x01
#define E_ORIGIN_MODE	0x02
#define E_INSERT_MODE 	0x04
#define E_ISINVERTED	0x08

static unsigned char
	e_args[MAXARGS],	/* Escape seq. arguments */
	e_state = E_NORMAL,	/* escape state machine */
	e_interm,		/* Intermediate character (?*..) */
	e_flags,		/* WRAP_MODE & co */
	e_gstyle,		/* Bold * co */
	e_gflags,		/* gray plane */
	tabs[MAXLINE + 1];

static unsigned char altchar[0x20] = {
  0x20,
  0x04,0xb1,0x12,0x12,0x12,0x12,0xf8,0xf1,
  0x12,0x12,0xd9,0xbf,0xda,0xc0,0xc5,0xc4,
  0xc4,0xc4,0xc4,0xc4,0xc3,0xb4,0xc1,0xc2,
  0xb3,0x11,0x10,0xf4,0xce,0x9c,0xfa
};
static unsigned char gl, font, charsets[4];

GLREF_C VOID Reset(void)
{
  int i;
  G_GC gc;

  for(i = 0; i < MAXLINE; i++) /* Reset the tabs */
    tabs[i] = i && (i & 7) == 0;
  c_x = 0;
  c_y = ascent;
  bufoff  = 0;
  e_state = E_NORMAL;
  e_flags = E_WRAP_MODE;
  clearmode = G_TRMODE_CLR;
  cur_appl = 0;
  gl = 0;
  font = charsets[0] = charsets[1] = charsets[2] = charsets[3] = 'B';

  isbold  = 0;
  e_gstyle = G_STY_MONO;	/* monospaced fonts, please */
  e_gflags = G_GC_FLAG_BOTH_PLANES;
  gc.style = e_gstyle;
  gc.flags = e_gflags;
  gSetGC(hgc, G_GC_MASK_STYLE|G_GC_MASK_GREY, &gc);

  ttyrect.tl.x = 0;
  ttyrect.tl.y = 0;
  ttyrect.br.x = t_w;
  ttyrect.br.y = t_h;
  wc.width  = charwidth;	/* Cursor */
  wc.height = charheight;
  wc.ascent = ascent;
  wc.pos.x  = c_x;
  wc.pos.y  = c_y;
  wTextCursor(main_win, &wc);
}

#define C_EOD	0
#define C_BOD	1
#define C_D	2
#define C_EOL	3
#define C_BOL	4
#define C_L	5

static void
clear(int n)
{
  P_RECT pr;

  switch(n)
    {
      /*----------------------------------------------- clear to end of page */
      case C_EOD:
        if(c_y + charheight < t_h) /* clear but the line itself */
	  {
	    pr.tl.x = 0;
	    pr.tl.y = c_y - ascent + charheight;
	    pr.br.x = t_w;
	    pr.br.y = t_h;
	    gClrRect(&pr, clearmode);
	  }
      /*------------------------------------------------------- clear to eol */
      case C_EOL:
        pr.tl.x = c_x;
        pr.tl.y = c_y - ascent;
	pr.br.x = t_w;
	pr.br.y = pr.tl.y + charheight;
	gClrRect(&pr, clearmode);
	break;
      /*------------------------------------- clear to beginning of the page */
      case C_BOD:
        if(c_y > ascent) /* clear but the line itself */
	  {
	    pr.tl.x = 0;
	    pr.tl.y = 0;
	    pr.br.x = t_w;
	    pr.br.y = c_y - ascent;
	    gClrRect(&pr, clearmode);
	  }
      /*------------------------------------- clear to beginning of the line */
      case C_BOL:
	if(c_x > 0)
	  {
	    pr.tl.x = 0;
	    pr.tl.y = c_y - ascent;
	    pr.br.x = c_x + charwidth;
	    pr.br.y = c_y - ascent + charheight;
	    gClrRect(&pr, clearmode);
	  }
	break;
      /*---------------------------------------------- clear the whole screen */
      case C_D:
	pr.tl.x = pr.tl.y = 0;
	pr.br.x = t_w;
	pr.br.y = t_h;
	gClrRect(&pr, clearmode);
	break;
      /*---------------------------------------------- clear the whole line */
      case C_L:
	pr.tl.x = 0;
	pr.tl.y = c_y - ascent;
	pr.br.x = t_w;
	pr.br.y = pr.tl.y + charheight;
	gClrRect(&pr, clearmode);
	break;
    }
}

/* Scroll horizontally, using the ttyrect */

static void
h_scroll(int right, int pixel, int doclear)
{
  P_RECT  r;
  P_POINT o;

  r.tl.x = c_x;
  r.tl.y = c_y - ascent;
  r.br.y = r.tl.y + charheight;
  r.br.x = t_w;
  o.y    = 0;

  if(right)
    {
      o.x = pixel;
      r.br.x -= pixel;
      wScrollRect(main_win, &r, &o);
      r.br.x = r.tl.x + pixel;
    }
  else
    {
      o.x = -pixel;
      r.tl.x += pixel;
      wScrollRect(main_win, &r, &o);
      r.tl.x = r.br.x - pixel;
    }
  if(doclear)
    gClrRect(&r, clearmode);
}

/* It works only within the scroll region (ttyrect) */
static void
v_scroll(int down, int pixel)
{
  P_POINT o;
  int i;

  if(down)
    {
      i = ttyrect.br.y;
      o.y = pixel;
      o.x = 0;
      ttyrect.br.y -= pixel;
      wScrollRect(main_win, &ttyrect, &o);
      ttyrect.br.y = ttyrect.tl.y + pixel;
      gClrRect(&ttyrect, clearmode);
      ttyrect.br.y = i;
    }
  else
    {
      i = ttyrect.tl.y;
      o.y = -pixel;
      o.x = 0;
      ttyrect.tl.y += pixel;
      wScrollRect(main_win, &ttyrect, &o);
      ttyrect.tl.y = ttyrect.br.y - pixel;
      gClrRect(&ttyrect, clearmode);
      ttyrect.tl.y = i;
    }
}

static void
nl(void)
{
  if(c_y + charheight > ttyrect.br.y)
    {
      if(c_y < ttyrect.br.y)
	v_scroll(0, charheight);
    }
  else
    c_y += charheight;
}

static void
flsbuf(void)
{
  int dx;

  if(bufoff == 0)
    return;
  dx = bufoff * charwidth;
  c_x -= dx;
  if(e_flags & E_INSERT_MODE)
    h_scroll(1, dx, 0);
  gPrintText(c_x, c_y, sbuf, bufoff);
  c_x += dx;
  bufoff = 0;
}

static void
put(unsigned char ch)
{
  int i, n;
  char lbuf[32];
  int a0, a1;
  int nargs;

  if(e_state == E_NORMAL)
    {
      if(ch >= ' ')
	{
	  if(c_x >= t_w)			/* last column printable  */
	    {
	      if(e_flags & E_WRAP_MODE)
	        {
		  flsbuf();
		  nl();
		  c_x = 0;
		}
	      else
	        {
		  c_x = t_w - charwidth;
		  if (bufoff > 0)
		    bufoff--;
	        }
	    }
          if(ch > 127)
	    ch = iso2cp(ch);
	  switch (font)
	    {
	      case 'A':
	        if (ch == '#')
		  ch = 0x9c;
		break;
	      case '0':
	        if (ch >= 0x5f && ch <= 0x7e)
		  ch = altchar[ch - 0x5f];
		break;
	      default:
	        break;
	    }
	  sbuf[bufoff++] = ch;
	  /* We can't write bold characters buffered, as they are
	     1 Pixel wider and we NEED a monospaced font */
	  c_x += charwidth;
	  if(isbold)
	    flsbuf();
	  return;
	}
      flsbuf();

      /* hack: normalize c_x for nowrap mode */
      if (!(e_flags & E_WRAP_MODE) && c_x >= t_w)
        c_x = t_w - charwidth;

      switch(ch)
	{
	case 7:		/* Bell*/
	  p_sound(5, 320);
	  break;
	case '\b':	/* BackSpace */
	  if(c_x > 0)
	    c_x -= charwidth;
	  return;
	case '\t':
	  n = c_x / charwidth;      	/* current column */
	  if (n >= cols)		/* curses bug workaround */
	    {
	      nl();
	      n = 0;
	    }
	  n++;
	  while (!tabs[n] && n < cols - 1)
	    n++;
	  c_x = n * charwidth;
	  return;
	case '\n':
	  nl();
	  return;
	case '\r':
	  c_x = 0;
	  return;
	case 0x1b:		/* Escape */
	  e_state = E_ESCAPE;
	  e_interm = 0;
	  return;
	case 0x0e:		/* C-n */
	case 0x0f:		/* C-o */
	  font = charsets[gl = 0x0f - ch];
	  break;
	default:
	  break;
	}
      return;
    }

  if(ch < ' ')		/* that's the way a vt100 does it */
    {
      int oldstate = e_state;
      e_state = E_NORMAL;
      put(ch);
      if (e_state != E_ESCAPE)
        e_state = oldstate;
      return;
    }

  if(e_state == E_ESCAPE) /* first character after escape */
    {
      if(ch <= '/')
        {
	  e_interm = e_interm ? -1 : ch;
	  return;
	}
      switch (e_interm)
	{
	case 0:
	  switch(ch)
	    {
	    case 'E': /* CR/NL */
	      nl();
	      c_x = 0;
	      return;
	    case 'D': /* Scroll down */
	      nl();
	      break;
	    case 'M': /* Scroll up */
	      if(c_y - charheight < ttyrect.tl.y)
		{
		  if(c_y > ttyrect.tl.y)
		    v_scroll(1, charheight);
		}
	      else
		c_y -= charheight;
	      break;
	    case 'H':
	      n = c_x/charwidth;      /* current column */
	      tabs[n] = 1;
	      break;
	    case 'Z':
	      p_atos(lbuf, "\033[?1;2c"); 
	      P_WRITE(serial, lbuf, p_slen(lbuf));
	      break;
	    case '7': /* save cursor position */
	      l_x = c_x; l_y = c_y;
	      break;
	    case '8': /* restore cursor */
	      c_x = l_x; c_y = l_y;
	      break;
	    case 'c': /* Reset */
	      clear(2);
	      Reset();
	      break;;
	    case '[': /* the longer ones */
	      e_state = 0;
	      e_interm = 0;
	      e_args[0] = e_args[1] = 0;
	      return;
	    case 'n':
	    case 'o':
	      font = charsets[gl = ch - ('n' - 2)];
	      return;
	    default:
	      break;
	    }
	  break;
	case '#':
	  if (ch == '8')		/* fill with 'E' */
	    {
	      for (i = 0; i < cols; i++)
	      	sbuf[i] = 'E';
	      for (i = 0; i < rows; i++)
		gPrintText(0, i * charheight + ascent, sbuf, cols);
	    }
	  break;
	case '(':
	case ')':
	case '*':
	case '+':
	  charsets[e_interm - '('] = ch;
	  font = charsets[gl];
	  break;
	default:
	  break;
	}
      e_state = E_NORMAL;
      return;
    }

  /* e_state >= 0 : longer escape sequences */

  if(ch >= '0' && ch <= '9')		/* Arguments */
    {
      if(e_state < MAXARGS)
	e_args[e_state] = e_args[e_state] * 10 + (ch - '0');
      return;
    }

  if(ch == ';' || ch == ':')
    {
      e_state++;
      if(e_state < MAXARGS)
	e_args[e_state] = 0;
      return;
    }

  if(ch <= 0x3f)			/* Intermediate */
    {
      e_interm = e_interm ? -1 : ch;
      return;
    }

  nargs = e_state + 1;
  if (nargs >= MAXARGS)
    nargs = MAXARGS;

  a0 = e_args[0];
  if (a0 == 0)
    a0++;
  a1 = e_args[1];
  if (a1 == 0)
    a1++;

  e_state = E_NORMAL;			/* Last character in sequence */

  switch (e_interm)
    {
    case 0:
      switch(ch)
	{
	case 's':				/* save cursor position */
	  l_x = c_x; l_y = c_y;
	  break;

	case '8':				/* restore cursor */
	  c_x = l_x; c_y = l_y;
	  break;

	case 'H':				/* Cursor movement */
	case 'f':
	  c_y = (a0 - 1) * charheight;
	  c_x = (a1 - 1) * charwidth;

	  if(e_flags & E_ORIGIN_MODE)
	    {
	      if(c_y > ttyrect.br.y - charheight) c_y = ttyrect.br.y - charheight;
	      if(c_x > ttyrect.br.x - charwidth)  c_x = ttyrect.br.x - charwidth;
	      c_y += ttyrect.tl.y;
	      c_x += ttyrect.tl.x;
	    }
	  else
	    {
	      if(c_y > t_h - charheight) c_y = t_h - charheight;
	      if(c_x > t_w - charwidth)  c_x = t_w - charwidth;
	    }
	  c_y += ascent;
	  return;

	case 'J':				/* Clear Display*/
	  if(e_args[0] < 3)
	    clear(e_args[0]);
	  return;

	case 'K':				/* Clear Line */
	  if(e_args[0] < 3)
	    clear(e_args[0] + 3);
	  return;

	case 'A':				/* Up / don't scroll */
	  if(c_y > ttyrect.tl.y) /* Damn scroll region */
	    {
	      c_y -= a0 * charheight;
	      if(c_y < ttyrect.tl.y)
		c_y = ttyrect.tl.y + ascent;
	    }
	  else
	    {
	      c_y -= a0 * charheight;
	      if(c_y < 0)
		c_y = ascent;
	    }
	  return;

	case 'B':				/* Down / don't scroll*/
	  if(c_y < ttyrect.br.y) 
	    {
	      c_y += a0 * charheight;
	      if(c_y >= ttyrect.br.y)
		c_y = ttyrect.br.y - charheight + ascent;
	    }
	  else
	    {
	      c_y += a0 * charheight;
	      if(c_y >= t_h)
		c_y = t_h - charheight + ascent;
	    }
	  return;

	case 'C':				/* Right */
	  c_x += a0 * charwidth;
	  if(c_x >= t_w)
	    c_x = t_w - charwidth;
	  return;

	case 'D':				/* Left */
	  c_x -= a0 * charwidth;
	  if(c_x < 0)
	    c_x = 0;
	  return;

	case 'm':				/* Attributes */
	  {
	    G_GC gc;

	    for(i = 0; i < nargs; i++)
	      switch(e_args[i])
		{
		case  0: e_gstyle  = G_STY_MONO;
			 e_gflags  = G_GC_FLAG_BOTH_PLANES;
			 isbold = 0;
			 break;
		case  1: e_gstyle |= G_STY_BOLD; isbold = 1;	break;
		case  2: e_gflags  = G_GC_FLAG_GREY_PLANE;	break;
		case  3: e_gstyle |= G_STY_ITALIC;		break;
		case  4: e_gstyle |= G_STY_UNDERLINE;		break;
		case  5: /* Blinking */
		case  7: e_gstyle |= G_STY_INVERSE;		break;
		case 22: e_gflags  = G_GC_FLAG_BOTH_PLANES;	break;
		case 23: e_gstyle &= ~G_STY_ITALIC;		break;
		case 24: e_gstyle &= ~G_STY_UNDERLINE;	break;
		case 25: /* Not Blinking */
		case 27: e_gstyle &= ~G_STY_INVERSE;		break;
		default: break;
		}
	    gc.style = e_flags & E_ISINVERTED ? e_gstyle ^ G_STY_INVERSE
					      : e_gstyle;
	    gc.flags = e_gflags;
	    gSetGC(hgc, G_GC_MASK_STYLE|G_GC_MASK_GREY, &gc);
	  }

	case 'g':				/* Tab clear */
	  if(e_args[0] == 3)
	    for(i = 0; i < MAXLINE; i++) /* Clear all the tabs */
	      tabs[i] = 0;
	  if(e_args[0] == 0)
	    tabs[c_x / charwidth] = 0;
	  break;

	case 'r':				/* Scroll region */
	  i = (a0 - 1) * charheight;
	  n = a1 * charheight;

	  for(i = 0; i < nargs; i++) 	/* Reset region ? */
	    if(e_args[i] != 0)
	      break;
	  if(i == nargs)		/* Yes */
	    n = t_w;

	  if(i > t_h) i = t_h;
	  if(n > t_h) n = t_h;

	  if(i >= n)
	    return;

	  ttyrect.tl.y = i;
	  ttyrect.br.y = n;
	  c_x = 0;
	  c_y = ascent;
	  if (e_flags & E_ORIGIN_MODE)
	    {
	      c_y += ttyrect.tl.y;
	      c_x += ttyrect.tl.x;
	    }
	  return;

	case 'I':	/* Forward tabs */
	  while (a0-- > 0)
	    put('\t');
	  return;

	case 'Z':	/* Backward tabs */
	  n = c_x / charwidth;      /* current column */
	  while (a0-- > 0)
	    {
	      if (n > 0)
		n--;
	      while (!tabs[n] && n > 0)
		n--;
	    }
	  c_x = n * charwidth;
	  return;

	case 'M':			/* delete lines (scroll up) */
	case 'L':			/* Insert lines (scroll down) */
	  if(c_y > ttyrect.br.y || c_y < ttyrect.tl.y)
	    return;			/* not in scroll region */
	  n = ttyrect.tl.y;
	  ttyrect.tl.y = c_y - ascent;
	  i = a0 * charheight;
	  if(ttyrect.tl.y + i > ttyrect.br.y)
	    i = ttyrect.br.y - ttyrect.tl.y;
	  v_scroll(ch == 'M' ? 0 : 1, i);
	  ttyrect.tl.y = n;
	  break;

	case 'P':			/* delete characters (scroll left) */
	case '@':			/* Insert characters (scroll right) */
	  if(c_x > t_w)
	    return;
	  i = a0 * charwidth;

	  if(c_x + i > t_w)
	    i = t_w - c_x;
	  h_scroll(ch == 'P' ? 0 : 1, i, 1);
	  break;

	case 'h':			/* Set Mode */
	case 'l':			/* Reset Mode */
	  for(i = 0; i < nargs; i++)
	    {
	      switch(e_args[i])
		{
		case 4:
		  e_flags &= ~E_INSERT_MODE;
		  if (ch == 'h')
		    e_flags |= E_INSERT_MODE;
		  break;
		default:
		  break;
		}
	    }
	  return;

	case 'c':
	  if (e_args[0] == 0)
	    {
	      p_atos(lbuf, "\033[?1;2c"); 
	      P_WRITE(serial, lbuf, p_slen(lbuf));
	    }
	  return;

	case 'n':
	  if(a0 == 5)		/* terminal status */
	    {
	      p_atos(lbuf, "\033[0n");
	      P_WRITE(serial, lbuf, p_slen(lbuf));
	    }
	  if(a0 == 6)		/* get position / resize needs it */
	    {
	      p_atos(lbuf, "\033[%d;%dR", 
		(c_y-ascent) / charheight + 1, c_x / charwidth + 1);
	      P_WRITE(serial, lbuf, p_slen(lbuf));
	    }
	  return;

	case 'x':			/* decreqtparm */
/*
 * Response CSI sol; par; nbits; xspeed; rspeed; clkmul; flags x
 *                        DECREPTPARM Report of terminal parameters
 *        sol
 *        0       terminal can send unsolicited reports, supported as sol = 1
 *        1       terminal reports only on request
 *        2       this is a report (DECREPTPARM)
 *        3       terminal reporting only on request
 *        par = 1 none, 2 space, 3 mark, 4 odd, 5 even
 *        nbits = 1 (8 bits/char), 2 (7 bits/char)
 *        xspeed, rspeed = transmit and receive speed index:
 *  0,8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128 correspond to speeds of
 *  50,75,110,134.5,150,200,300,600,1200,1800,2000,2400,3600,4800,9600,19200,
 *        clkmul = 1 (clock rate multiplier is 16)
 *        flags = 0-15 (Setup Block #5), always 0 here
 */
	  if (a0 == 1)
	    {
	      /* Convert baudrate */
	      a0 = set.tty.tbaud - 1;
	      if(a0 >= P_BAUD_300 && a0 < P_BAUD_7200)
	        a0++;
	      a0 *= 8;
	      p_atos(lbuf, "\033[%d;%d;%d;%d;%d;1;0x",
	        e_args[0] + 2,
		(set.tty.frame & P_PARITY) ? 6 - set.tty.parity : 0,
		4 - (set.tty.frame & P_DATA_FRM),
		a0, a0);
	      P_WRITE(serial, lbuf, p_slen(lbuf));
	    }
	  return;

	default:
	  break;
	}
      break;

    case '?':		/* DEC private */
      switch(ch)
	{
	  case 'h':
	  case 'l':
	    for(i = 0; i < nargs; i++)
	      {
		switch(e_args[i])
		  {
		  case 1: 
		    cur_appl = ch == 'h';
		    break;
		  case 5: 
		    if((ch == 'h' && !(e_flags & E_ISINVERTED)) ||
		       (ch == 'l' && (e_flags & E_ISINVERTED)))
		      {
		      	G_GC gc;
			P_RECT p;

			p.tl.x = 0; p.tl.y = 0;
			p.br.x = t_w; p.br.y = t_h;
			gClrRect(&p, G_TRMODE_INV);
			e_flags ^= E_ISINVERTED;
			gc.style = e_gstyle;
			if (e_flags & E_ISINVERTED)
			  {
			    gc.style ^= G_STY_INVERSE;
			    clearmode = G_TRMODE_SET;
			  }
			else
			  clearmode = G_TRMODE_CLR;
			gSetGC(hgc, G_GC_MASK_STYLE, &gc);
		      }
		    break;
		  case 6: 
		    e_flags &= ~E_ORIGIN_MODE;
		    if (ch == 'h')
		      e_flags |= E_ORIGIN_MODE;
		    c_x = 0;
		    c_y = ascent;
		    if (e_flags & E_ORIGIN_MODE)
		      {
			c_y += ttyrect.tl.y;
			c_x += ttyrect.tl.x;
		      }
		    break;
		  case 7:
		    e_flags &= ~E_WRAP_MODE;
		    if (ch == 'h')
		      e_flags |= E_WRAP_MODE;
		    break;
		  }
	      }
	    break;
	  default:
	    break;
	}
      break;
    case '>':
      if (ch == 'c' && e_args[0] == 0)
	{
	  p_atos(lbuf, "\033[>%d;%d;0c", 'P', VERSION);
	  P_WRITE(serial, lbuf, p_slen(lbuf));
	}
      break;
    default:
      break;
    }
}

GLDEF_C VOID TtyEmu(unsigned char *buf, INT len)  
{
  extern void *logfd;

  if(logfd)				/* Log into the file */
    p_write(logfd, buf, len);
  while(len-- > 0)
    {
      if(debug)
        {
	  char mfb[6];
	  if(*buf >=' ' && *buf < 0x80)
	    AddToHistory(*buf);
	  else
	    {
	      p_atos(mfb, "%02x", *buf);
	      AddToHistory('\\');
	      AddToHistory(mfb[0]);
	      AddToHistory(mfb[1]);
	    }
        }

      /* Last line in scroll region && Jumpscroll */
      if(*buf == '\n' && (set.flags & FLAGS_JUMPSCROLL) &&
	 c_y + charheight > ttyrect.br.y && c_y < ttyrect.br.y)
        {
	  int i, n;

	  n = 1;
	  for(i = 1; i < len; i++)
	    if(buf[i] < ' ')
	      {
		if(buf[i] == '\n')
		  n++;
		else if(buf[i] != '\r')
		  break;
	      }

	  if(n >= rows)
	    {
              c_y = ascent;
	      clear(C_D);
	    }
	  else if(n > 1)
	    {
	      v_scroll(0, n*charheight);
	      c_y -= n * charheight;
	    }
	}

      put(*buf++);
    }

  flsbuf();

  wc.pos.x = c_x; wc.pos.y = c_y;
  if(c_x >= t_w) 			/* last column printable */
    wc.pos.x = t_w - charwidth;
  wTextCursor(main_win, &wc);

  wFlush();
}

void
AddFnKey(key, modifier, str)
  int key;
  int modifier;
  char *str;
{
  extern struct fkey *fk;
  struct fkey **fp, *f;
  char *s;
  int l;

  for(fp = &fk; *fp; fp = &(*fp)->next)
    if((*fp)->key == key && (*fp)->modifier == modifier)
      break;
  
  l = *str + sizeof(struct fkey) + 1; 
  if((f = (struct fkey *)p_realloc(*fp, l)) == 0)
    {
      extern char c_nomem[];

      wInfoMsgCorner(c_nomem, W_CORNER_BOTTOM_RIGHT);
      return;
    }

  f->key = key;
  f->modifier = modifier;
  if(!*fp)
    f->next = 0;
  s = (char *)f + sizeof(struct fkey);
  p_bcpy(s, str+1, *str);
  s[*str] = 0;
  *fp = f;
}

/* 
 * Parse \<nnn> and \s<nn>
 * NOTE: \<nn> and \<n> are also accepted.
 */
void
SendFnKey(f)
  struct fkey *f;
{
  char *s;
  unsigned char n;

#define isnum(s) s >= '0' && s <= '9'
  for(s = (char *)f + sizeof(struct fkey); *s; s++)
    {
      if(*s == '\\')
        {
	  if(s[1] == 's') /* sleep */
	    {
	      s++; n = 0;
	      if(isnum(s[1])) { s++; n =          s[0] - '0'; }
	      if(isnum(s[1])) { s++; n = n * 10 + s[0] - '0'; }
	      p_sleep((ULONG)n);
	      continue;
	    }
	  else if(isnum(s[1]))
	    {
	      s++; n = s[0] - '0';
	      if(isnum(s[1])) { s++; n = n * 10 + s[0] - '0'; }
	      if(isnum(s[1])) { s++; n = n * 10 + s[0] - '0'; }
	    }
	  else if(s[1] == 0)		/* \somenthing or */
	    break;
	  else
	    n = *s++;
	}
      else
	n = *s;

      P_WRITE(serial, (char *)&n, 1); 
    }
}

char cant_open_the_file[] = "Can't open the file";

#define BLEN 128
void
LoadFnKey(fname, warn)
  char *fname;
  int warn;
{
  VOID *fcb;
  char buf[BLEN], *p;
  extern struct fkey *fk;
  struct fkey **fp, *f;
  int len;
  UWORD key, modifier;

  /* First delete all definitions */
  for(fp = &fk, f = fk; *fp; f = *fp)
    {
      *fp = f->next;
      p_free(f);
    }

  fname[*fname+1] = 0;
  if(p_open(&fcb, fname+1, P_FTEXT | P_FSHARE))
    {
      if(warn)
	wInfoMsgCorner(cant_open_the_file, W_CORNER_BOTTOM_RIGHT);
      return;
    }
  while((len = p_read(fcb, buf, BLEN)) > 0)
    {
      if(*buf == '#')	/* Allow comments */
        continue;
      p = buf;   p_stog(&p, &key, 16);
      p = buf+5; p_stog(&p, &modifier, 16);
      p = buf+7; *p = len - 8;
      AddFnKey(key, modifier, p);
    }
  p_close(fcb);
  if(warn)
    wInfoMsgCorner("Definition loaded", W_CORNER_BOTTOM_RIGHT);
  return;
}

void
SaveFnKey(fname)
  char *fname;
{
  VOID *fcb;
  char buf[BLEN];
  extern struct fkey *fk;
  struct fkey *f;

  fname[*fname+1] = 0;
  if(p_open(&fcb, fname+1, P_FTEXT | P_FREPLACE | P_FUPDATE))
    {
      wInfoMsgCorner(cant_open_the_file, W_CORNER_BOTTOM_RIGHT);
      return;
    }
  p_atos(buf, "# Key(Hex) Modifier(Hex) String");
  p_write(fcb, buf, p_slen(buf));
  for(f = fk; f; f = f->next)
    {
      p_atos(buf, "%04x %02x %s", f->key, f->modifier, (char *)f+sizeof(*f));
      p_write(fcb, buf, p_slen(buf));
    }
  p_close(fcb);
  wInfoMsgCorner("Definition saved", W_CORNER_BOTTOM_RIGHT);
  return;
}

#endif /* HAVE_TERMEMU */
