/* Main input processing for terminal
   Copyright (C) 1993, 1994 Free Software Foundation

This file is part of the GNU Hurd.

The GNU Hurd is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

The GNU Hurd is distributed in the hope that it will be useful, 
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with the GNU Hurd; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

/* Written by Michael I. Bushnell.  */

/* VDSUSP is unimplemented. */

#include "term.h"
#include <ctype.h>
#include <string.h>
#include <fcntl.h>
#include <hurd/trivfs.h>
#include "tioctl_S.h"

int inputstate;
#define IS_SLASH	0x00000001 /* last char was \ */
#define IS_LNEXT	0x00000002 /* last char was VLNEXT */
#define IS_HDERASE	0x00000004 /* inside \.../ pair for hardcopy erase */
#define IS_SENTVSTOP	0x00000008 /* We've got VSTOP outstanding */
#define IS_VSTOP	0x00000020 /* VSTOP in effect */
#define IS_FLUSHO	0x00000040 /* Output being flushed */

#define IS_LOCAL (IS_SLASH | IS_LNEXT | IS_HDERASE)

/* Number of characters in the rawq which have been
   echoed continuously without intervening output.  */
int echo_qsize;

/* Where the output_psize was when echo_qsize was last 0. */
int echo_pstart;

/* For ICANON mode, this holds the edited line. */
struct queue *rawq;

/* For each character in this table, if the element is set, then
   the character is EVEN */
char const char_parity[] = 
{
  1, 0, 0, 1, 0, 1, 1, 0,	/* nul - bel */
  0, 1, 1, 0, 1, 0, 0, 1,	/* bs - si */
  0, 1, 1, 0, 1, 0, 0, 1,	/* dle - etb */
  1, 0, 0, 1, 0, 1, 1, 0,	/* can - us */
  0, 1, 1, 0, 1, 0, 0, 1,	/* sp - ' */
  1, 0, 0, 1, 0, 1, 1, 0,	/* ( - / */
  1, 0, 0, 1, 0, 1, 1, 0,	/* 0 - 7 */
  0, 1, 1, 0, 1, 0, 0, 1,	/* 8 - ? */
  0, 1, 1, 0, 1, 0, 0, 1,	/* @ - G */
  1, 0, 0, 1, 0, 1, 1, 0,	/* H - O */
  1, 0, 0, 1, 0, 1, 1, 0,	/* P - W */
  0, 1, 1, 0, 1, 0, 0, 1,	/* X - _ */
  1, 0, 0, 1, 0, 1, 1, 0,	/* ` - g */
  0, 1, 1, 0, 1, 0, 0, 1,	/* h - o */
  0, 1, 1, 0, 1, 0, 0, 1,	/* p - w */
  1, 0, 0, 1, 0, 1, 1, 0,	/* x - del */
};
#define checkevenpar(c) (((c)&0x80) \
			 ? !char_parity[(c)&0x7f] \
			 : char_parity[(c)&0x7f])
#define checkoddpar(c) (((c)&0x80) \
			 ? char_parity[(c)&0x7f] \
			 : !char_parity[(c)&0x7f])



void
initialize_input ()
{
  rawq = create_queue ();
}


/* These functions are used by both echo and erase code */

/* Tell if we should echo a character at all */
static inline int
echo_p (char c, int quoted)
{  
  return ((termstate.c_lflag & ECHO)
	  || (c == '\n' && (termstate.c_lflag & ECHONL) && !quoted));
}

/* Tell if this character deserves double-width cntrl processing */
static inline int
echo_double (char c, int quoted)
{
  return (iscntrl (c) && (termstate.c_lflag & ECHOCTL)
	  && !((c == '\n' || c == '\t') && !quoted));
}

/* Do a single C-h SPC C-h sequence */
static inline void
write_erase_sequence ()
{
  poutput ('\b');
  poutput (' ');
  poutput ('\b');
}

/* Echo a single character to the output */
/* If this is an echo of a character which is being hard-erased,
   set hderase.  If this is a newline or tab which should not receive
   their normal special processing, set quoted. */
static void  
echo_char (char c, int hderase, int quoted)
{
  echo_qsize++;

  if (echo_p (c, quoted))
    {
      if (!hderase && (inputstate & IS_HDERASE))
	{
	  output_character ('/');
	  inputstate &= ~IS_HDERASE;
	  echo_qsize = 0;
	  echo_pstart = output_psize;
	}

      if (hderase && !(inputstate & IS_HDERASE))
	{
	  output_character ('\\');
	  inputstate |= IS_HDERASE;
	}

      /* Control characters that should use caret-letter */
      if (echo_double (c, quoted))
	{
	  output_character ('^');
	  output_character (c + ('A' - CHAR_CTRL_A));
	}
      else
	output_character (c);
    }
}


/* Re-echo the current rawq preceded by the VREPRINT char. */
static inline void
reprint_line ()
{
  char *cp;
  
  if (termstate.c_cc[VREPRINT] != _POSIX_VDISABLE)
    echo_char (termstate.c_cc[VREPRINT], 0, 0);
  else
    echo_char (CHAR_RPT, 0, 0);
  echo_char ('\n', 0, 0);

  echo_qsize = 0;
  echo_pstart = output_psize;
  /* XXX quoting */
  for (cp = rawq->chars; cp != rawq->qp; cp++)
    echo_char (*cp, 0, 0);
}

/* Erase a single character off the end of the rawq, and delete
   it from the screen appropriately.  */
static void
erase_1 (char erase_char)
{
  int quoted;
  char c;
  
  if (qsize (rawq) == 0)
    return;
  
  c = *(rawq->qp - 1);
  quoted = remove_char (rawq);
  
  if (!echo_p (c, quoted))
    return;

  /* The code for WERASE knows that we echo erase_char iff
     !ECHOPRT && !ECHO.  */

  if (echo_qsize--)
    {
      if (termstate.c_lflag & ECHOPRT)
	echo_char (c, 1, quoted);
      else if (!(termstate.c_lflag & ECHOE) && erase_char)
	echo_char (erase_char, 0, 0);
      else
	{
	  int nerase;
	  
	  if (echo_double (c, quoted))
	    nerase = 2;
	  else if (c == '\t')
	    {
	      char *cp = rawq->qp - echo_qsize;
	      int loc = echo_pstart;
	  
	      while (cp != rawq->qp)
		loc += (echo_double (*cp, quoted)
			? 2
			: output_width (*cp));
	      nerase = output_psize - loc;
	    }
	  else
	    nerase = output_width (c);
	
	  while (nerase--)
	    write_erase_sequence ();
	}
      if (echo_qsize == 0)
	echo_pstart = output_psize;
    }
  else
    reprint_line ();
}



/* This is called when some non-echo output is done. */
void
nonecho_output ()
{
  echo_qsize = 0;
  echo_pstart = output_psize;
}

/* This is called by the lower half for each character that is
   received.  If all remaining pending input characters should be
   dropped, return 1; else return 0.  */
int
input_character (int c)
{
  int lflag = termstate.c_lflag;
  int iflag = termstate.c_iflag;
  int cflag = termstate.c_cflag;
  cc_t *cc = termstate.c_cc;
  struct queue **qp = (lflag & ICANON) ? &rawq : &inputq;
  int flush = 0;
  
  /* Handle parity errors */
  if ((iflag & INPCK)      
      && ((cflag & PARODD) ? checkoddpar (c) : checkevenpar (c)))
    {
      if (iflag & IGNPAR)
	goto alldone;
      else if (iflag & PARMRK)
	{
	  quoted_enqueue_char (CHAR_USER_QUOTE, qp);
	  quoted_enqueue_char ('\0', qp);
	  quoted_enqueue_char (c, qp);
	  goto alldone;
	}
      else
	c = 0;
    }

  /* Check to see if we should send IXOFF */
  if ((iflag & IXOFF)
      && (qsize(inputq) + qsize(rawq) > HIGH_WATER_MARK / 2)
      && qsize (inputq)
      && (cc[VSTOP] != _POSIX_VDISABLE))
    {
      output_character (cc[VSTOP]);
      inputstate |= IS_SENTVSTOP;
    }


  /* Character mutations */
  if (!(iflag & ISTRIP) && (iflag & PARMRK) && (c == CHAR_USER_QUOTE))
    quoted_enqueue_char (CHAR_USER_QUOTE, qp); /* cause doubling */

  if (iflag & ISTRIP)
    c &= 0x7f;

  /* Handle LNEXT right away */
  if (inputstate & IS_LNEXT)
    {
      quoted_enqueue_char (c, qp);
      echo_char (c, 0, 1);
      inputstate &= ~IS_LNEXT;
    }
  
  /* Mutate ILCASE */
  if ((iflag & ILCASE) && isalpha(c))
    {
      if (inputstate & IS_SLASH)
	erase_1 (0);	/* remove the slash from input */
      else
	c = isupper(c) ? tolower (c) : c;
    }
  
  /* IEXTEN control chars */
  if (lflag & IEXTEN)
    {
      if (CCEQ (cc[VLNEXT], c))
	{
	  if (lflag & ECHO)
	    {
	      output_character ('^');
	      output_character ('\b');
	    }
	  inputstate |= IS_LNEXT;
	  goto alldone;
	}
      
      if (CCEQ (cc[VDISCARD], c))
	{
	  if (inputstate & IS_FLUSHO)
	    {
	      inputstate &= ~IS_FLUSHO;
	      stop_discarding_output ();
	    }
	  else
	    {
	      (*lowerhalf->drop_output)();
	      discard_output ();
	      inputstate |= IS_FLUSHO;
	    }
	}
    }
  
  /* Signals */
  if (lflag & ISIG)
    {
      if (CCEQ (cc[VINTR], c) || CCEQ (cc[VQUIT], c))
	{
	  if (!(lflag & NOFLSH))
	    {
	      (*lowerhalf->drop_output)();
	      flush = 1;
	    }
	  echo_char (c, 0, 0);
	  send_signal (CCEQ (cc[VINTR], c) ? SIGINT : SIGQUIT);
	  goto alldone;
	}

      if (CCEQ (cc[VSUSP], c))
	{
	  if (!(lflag & NOFLSH))
	    flush = 1;
	  echo_char (c, 0, 0);
	  send_signal (SIGTSTP);
	  goto alldone;
	}
    }
  
  /* IXON */
  if (iflag & IXON)
    {
      if (CCEQ (cc[VSTOP], c))
	{
	  if (!(inputstate & IS_VSTOP))
	    {
	      inputstate |= IS_VSTOP;
	      user_suspend_writing ();
	    }

	  if (!(CCEQ(cc[VSTART], c))) /* toggle if VSTART == VSTOP */
	    goto alldone;
	  else
	    return flush;
	}
      if (CCEQ (cc[VSTART], c))
	goto alldone;
    }
  
  /* Newline frobbing */
  if (c == '\r')
    {
      if (iflag & ICRNL)
	c = '\n';
      else if (iflag & IGNCR)
	goto alldone;
    }
  else if (c == '\n' && iflag & INLCR)
    c = '\r';
  
  /* Canonical mode processing */
  if (lflag & ICANON)
    {
      if (CCEQ (cc[VERASE], c))
	{
	  if (qsize(rawq))
	    erase_1 (c);
	  if (!(inputstate & IS_SLASH)
	      || !(lflag & IEXTEN))
	    goto alldone;
	}
      
      if (CCEQ (cc[VKILL], c))
	{
	  if (!(inputstate & IS_SLASH)
	      || !(lflag & IEXTEN))
	    {
	      if ((lflag & ECHOKE) && !(lflag & ECHOPRT)
		  && echo_qsize == qsize (rawq))
		{
		  while (output_psize > echo_pstart)
		    write_erase_sequence ();
		}
	      else
		{
		  echo_char (c, 0, 0);
		  if ((lflag & ECHOK) || (lflag & ECHOKE))
		    echo_char ('\n', 0, 0);
		}
	      clear_queue (rawq);
	      echo_qsize = 0;
	      echo_pstart = output_psize;
	      inputstate &= ~IS_LOCAL;
	      goto alldone;
	    }
	  else
	    erase_1 (0);	/* remove \ */
	}
  
      if (CCEQ (cc[VWERASE], c))
	{
	  /* If we are not going to echo the erase, then
	     echo a WERASE character right now.  (If we
	     passed it to erase_1; it would echo it multiple
	     times.) */
	  if (!(lflag & (ECHOPRT|ECHOE)))
	    echo_char (cc[VWERASE], 0, 1);

	  /* Erase whitespace */
	  while (qsize (rawq) && isblank (rawq->qp[-1]))
	    erase_1 (0);
	  
	  /* Erase word. */
	  if (lflag & ALTWERASE)
	    /* For ALTWERASE, we erase back to the first blank */
	    while (qsize (rawq) && !isblank (rawq->qp[-1]))
	      erase_1 (0);
	  else
	    /* For regular WERASE, we erase back to the first nonalpha/_ */
	    while (qsize (rawq) && !isblank (rawq->qp[-1])
		   && (isalnum (rawq->qp[-1]) || rawq->qp[-1] != '_'))
	      erase_1 (0);

	  goto alldone;
	}

      if (CCEQ (cc[VREPRINT], c) && (lflag & IEXTEN))
	{
	  reprint_line ();
	  goto alldone;
	}
      
      if (CCEQ (cc[VSTATUS], c) && (lflag & ISIG) && (lflag & IEXTEN))
	{
	  send_signal (SIGINFO);
	  goto alldone;
	}
    }
  
  /* Now we have a character intended as input.  See if it will fit. */
  if (qsize (rawq) + qsize (inputq) > HIGH_WATER_MARK)
    {
      if (iflag & IMAXBEL)
	output_character ('\a');
      else
	{
	  /* Drop everything */
	  (*lowerhalf->drop_output)();
	  flush = 1;
	}
      goto alldone;
    }
  
  /* Echo and put character on appropriate input queue */
  echo_char (c, 0, 0);
  if (CCEQ (cc[VEOF], c) && (lflag & ECHO))
    {
      int n;
      n = echo_double (c, 0) ? 2 : output_width (c);
      while (n--)
	output_character ('\b');
    }
  enqueue_char (c, qp);

  /* Check for final canonical processing */
  if (lflag & ICANON)
    {
      if (CCEQ (cc[VEOL], c) 
	  || CCEQ (cc[VEOL2], c) 
	  || CCEQ (cc[VEOF], c)
	  || c == '\n')
	{
	  /* Make input available */
	  while (inputq->chars_alloced - qsize (inputq) < qsize (rawq))
	    {
	      inputq->chars_alloced *= 2;
	      inputq = realloc (inputq, (sizeof (struct queue) 
					 + (inputq)->chars_alloced));
	    }
	  bcopy (rawq->qs, inputq->qp, qsize (rawq));
	  inputq->qp += qsize (rawq);
	  rawq->qs = rawq->qp = rawq->chars;
	  wakeup_readers ();
	}
    }
  else
    wakeup_readers ();

alldone:
  /* Restart output */
  if ((iflag & IXANY) || (CCEQ (cc[VSTART], c)))
    {
      inputstate &= ~IS_VSTOP;
      user_resume_writing ();
    }
  return flush;
}

/* TIOCSTART -- start output as if VSTART were typed */
kern_return_t
S_tioctl_tiocstart (io_t port)
{
  struct trivfs_protid *cred = ports_check_port_type (port, PT_TTY);
  error_t err;
  if (!cred)
    return EOPNOTSUPP;

  if (!(cred->po->openmodes & (O_READ|O_WRITE)))
    err = EBADF;
  else
    {
      inputstate &= ~IS_VSTOP;
      user_resume_writing ();
      err = 0;
    }
  
  ports_done_with_port (cred);
  return err;
}

/* TIOCSTOP -- stop output as if VSTOP were typed */
kern_return_t
S_tioctl_tiocstop (io_t port)
{
  struct trivfs_protid *cred = ports_check_port_type (port, PT_TTY);
  error_t err;
  if (!cred)
    return EOPNOTSUPP;

  if (!(cred->po->openmodes & (O_READ|O_WRITE)))
    err = EBADF;
  else
    {
      if (!(inputstate & IS_VSTOP))
	{
	  inputstate |= IS_VSTOP;
	  user_suspend_writing ();
	}
      err = 0;
    }
  
  ports_done_with_port (cred);
  return err;
}
  

/* This is called by the lower half when a break is received. */
void
input_break ()
{
  struct queue **qp = termstate.c_lflag & ICANON ? &rawq : &inputq;
  
  /* Don't do anything if IGNBRK is set. */
  if (termstate.c_iflag & IGNBRK)
    return;

  /* If BRKINT is set, then flush queues and send SIGINT. */
  if (termstate.c_iflag & BRKINT)
    {
      (*lowerhalf->drop_output)();
      /* XXX drop pending input How?? */
      send_signal (SIGINT);
      return;
    }

  /* A break is then read as a null byte; marked specially if PARMRK is set. */
  if (termstate.c_iflag & PARMRK)
    {
      quoted_enqueue_char (CHAR_USER_QUOTE, qp);
      quoted_enqueue_char ('\0', qp);
    }
  quoted_enqueue_char ('\0', qp);
  wakeup_readers ();
}

/* Called when a character is recived with a framing error. */
void
input_framing_error (int c)
{
  /* Ignore it if IGNPAR is set. */
  if (termstate.c_iflag & IGNPAR)
    return;
  
  /* If PARMRK is set, pass it specially marked. */
  if (termstate.c_iflag & PARMRK)
    {
      quoted_enqueue_char (CHAR_USER_QUOTE, &rawq);
      quoted_enqueue_char ('\0', &rawq);
      quoted_enqueue_char (c, &rawq);
    }
  else
    /* Otherwise, it looks like a null. */
    quoted_enqueue_char ('\0', &rawq);
  wakeup_readers ();
}

/* Copy the characters in RAWQ to the end of INPUTQ and clear RAWQ. */
void
copy_rawq ()
{
  int size = qsize (rawq);
  
  /* Grow INPUTQ to hold SIZE chars. */
  while (inputq->qp + size > inputq->chars + inputq->chars_alloced)
    inputq = reallocate (inputq);
  
  /* Copy the chars.  XXX should we preserve the quotes? */
  bcopy (rawq->qs, inputq->qp, size);
  inputq->qp += size;

  clear_queue (rawq);
}

/* Process all the characters in INPUTQ as if they had just been read. */
void
rescan_inputq ()
{
  char *buf;
  int i, n;
  
  n = qsize (inputq);
  buf = alloca (n);
  bcopy (inputq->qs, buf, n);
  clear_queue (inputq);
  
  for (i = 0; i < n; i++)
    input_character (buf[i]);
}
