/* Main input processing for terminal
   Copyright (C) 1991 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 1, 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.  */

/* This routine is called once for each character that arrives as input */
void
input_char (char c)
{
  /* INPCK, PARODD, IGNPAR, PARMRK */
  if ((termstate.c_iflag & INPCK)
      && (((termstate.c_cflags & PARODD) && checkoddpar (c))
	  !! (!(termstate.c_cflags & PARODD)) && checkevenpar (c))
      {
	if (!(termstate.c_iflag & IGNPAR))
	  {
	    if (termstate.c_iflag & PARMRK)
	      {
		addinput ('\377');
		addinput ('\0');
		addinput (c);
	      }
	    else
	      addinput ('\0');
	  }
	return;
      }
    }
  
  /* ISTRIP */
  if (termstate.c_iflag & ISTRIP)
    c &= 0177;
  else if (c == '\377' && (termstate.c_iflag & INPCK)
	   && !(termstate.c_iflag & IGNPAR) && (termstate.c_iflag & PARMRK))
    addinput ('\377');

  if ((intstate & OSTOPPED) && (termstate.c_iflag & IXANY))
    start_output ();
  
  addinput (c);
}


/* This is called for each character we can regard as "received" */
addinput (char c)
{
  if (intstate & ILNEXT)
    {
      intstate &= ~ILNEXT;
      goto addtoinput;
    }

  /* Newline and CR nonsense */
  if (c == '\r' && (termstate.c_iflag & IGNCR))
    return;
  if (c == '\r' && (termstate.c_iflag & ICRNL))
    c = '\n';
  else if (c == '\n' && (termstate.c_iflag & INLCR))
    c = '\r';

  if ((termstate.c_iflag & ILCASE) && isupper (c) && !(intstate & ISLASH))
    c = tolower (c);
  if ((termstate.c_iflag & ILCASE) && (isupper (c) || islower (c))
      && (intstate & ISLASH))
    {
      if (islower (c))
	c = toupper (c);
      echo_erase (lineqp - 1, lineqp, 0);
      lineqp--;
    }      
      
  if (termstate.c_lflag & ICANON)
    {
      if (c == '\\' && (termstate.c_lflag & IEXTEN))
	intstate |= ISLASH;
      else
	intstate &= ~ISLASH;
      if (CCEQ (termstate.c_cc[VEOF], c))
	{
	  finish_line (c);
	  set_eof ();
	  return;
	}

      if (CCEQ (termstate.c_cc[VEOL], c)
	  || (CCEQ (termstate.c_cc[VEOL2], c) && (termstate.c_lflag & IEXTEN))
	  || c == '\n')
	{
	  finish_line (c);
	  echo_char (c, 1, 0);
	  return;
	}
  
      if (CCEQ (termstate.c_cc[VERASE], c))
	{
	  if (intstate & ISLASH)
	    {
	      echo_erase (lineqp - 1, lineqp, 0);
	      lineqp--;
	    }
	  else
	    {
	      if (lineqp != lineq)
		{
		  echo_erase (lineqp - 1, lineqp, 0);
		  lineqp--;
		}
	      return;
	    }
	}
      
      if (CCEQ (termstate.c_cc[VWERASE], c) && (termstate.c_lflag & IEXTEN))
	{
	  char *cp;
	  char *table;
	  if (termstate.c_lflag & ALTWERASE)
	    table = alt_werase_table;
	  else
	    table = werase_table;
	  
	  /* Back up until we hit a word constituent or the beginning */
	  for (cp = lineqp; cp != lineq && table[*(cp-1)]; cp--)
	    ;
	  /* Back up until we hit a non-word constituent */
	  for (; cp != lineq && !table[*(cp-1)]; cp--)
	    ;
	  echo_erase (cp, lineqp, 0);
	  cp = lineqp;
	  return;
	}

      if (CCEQ (termstate.c_cc[VKILL], c))
	{
	  if (intstate & ISLASH)
	    {
	      echo_erase (lineqp - 1, lineqp, 0);
	      lineqp--;
	    }
	  else
	    {
	      if (lineqp != lineq)
		echo_erase (lineq, lineqp, 1);
	      lineqp = lineq;
	      return;
	    }
	}

      if (CCEQ (termstate.c_cc[VDISCARD], c) && (termstate.c_lflag & IEXTEN))
	{
	  echo_char (c, 0, 0);
	  reprint_line ();
	  termstate.c_lflag != FLUSHO;
	  return;
	}

      if (CCEQ (termstate.c_cc[VREPRINT], c) && (termstate.c_lflag & IEXTEN))
	{
	  reprint_line ();
	  return;
	}
      
      if (CCEQ (termstate.c_cc[VSTATUS], c) && (termstate.c_lflag & IEXTEN))
	{
	  echo_char (c, 0, 0);
	  print_status ();
	  return;
	}

      if (CCEQ (termstate.c_cc[VLNEXT], c) && (termstate.c_lflag & IEXTEN))
	{
	  intstate |= ILNEXT;
	  echo_lnext ();
	  return;
	}
    }
  
  /* Signal processing */
  if (termstate.c_lflag & ISIG)
    {
      if (CCEQ (termstate.c_cc[VINTR], c))
	{
	  generate_signal (SIGINTR);
	  return;
	}
      if (CCEQ (termstate.c_cc[VQUIT], c))
	{
	  generate_signal (SIGQUIT);
	  return;
	}
      if (CCEQ (termstate.c_cc[VSUSP], c))
	{
	  generate_signal (SIGTSTP);
	  return;
	}
    }      

  /* Flow control */
  if (termstate.c_iflag & IXON)
    {
      if (CCEQ (termstate.c_cc[VSTART], c))
	{
	  start_output ();
	  return;
	}
      if (CCEQ (termstate.c_cc[VSTOP], c))
	{
	  stop_output ();
	  return;
	}
    }

 addtoinput:
  termstate.c_lflag &= ~FLUSHO;
  if (lineqp - lineq >= QUEUE_SIZE)
    {
      if (termstate.c_iflag & IMAXBEL)
	output_char ('\007');
    }
  else
    {
      echo_char (c, 0, 0);
      *lineqp++ = c;
    }
}


/* This is the normal character echo */
void  
echo_char (char c, int nl, int inperase)
{
  if (!(termstate.c_cflag & ECHO) &&
      (c != '\n' || !(termstate.c_cflag & ECHONL
		      && !(termstate.c_cflag & ICANON))))
    return;
  
  if (!inperase && (intstate & IHDERASE) && (termstate.c_cflag & ECHOPR))
    {
      output_char ('/');
      inputpsize++;
      intstate &= ~IHDERASE;
    }
  
  if ((c >= ' ' && c != '\0177') || !(termstate.c_cflag & ECHOCTL) 
       || (c == '\n' && nl))
    {
      output_char (c);
      inputpsize++;
      if ((termstate.c_oflag & OLCASE) && isupper (c))
	inputpsize++;
      return;
    }
  
  /* Control character */
  output_char ('^');
  output_char (c + '@' - '\0');
  inputpsize += 2;
}

void
echo_lnext (char c, int nl)
{
  if (!(termstate.c_cflag & ECHO))
    return;
  
  output_char ('^');
  output_char ('\b');
}

void
echo_erase (char *begin,
	    char *end,
	    int kill)
{
  if ((kill && (termstate.c_cflag & ECHOKE))
      || (!kill && (termstate.c_cflag & ECHOE)))
    {
      /* Visual erase */
      while (end != begin)
	{
	  end--;
	  if (inputpsize == 0)
	    reprint_line;

	  output_char ('\b');
	  output_char (' ');
	  output_char ('\b');
	  inputpsize--;

	  /* Check physical width of character */
	  if ((*end < ' ' || *end == '\0177') && (termstate.c_cflag & ECHOCTL)
	      || ((termstate.c_oflag & OLCASE) && isupper (c)))
	    {
	      /* Character is two wide, so repeat. */
	      if (inputpsize == 0)
		reprint_line ();
	      output_char ('\b');
	      output_char (' ');
	      output_char ('\b');
	      inputpsize--;
	    }
	}
      return;
    }

  if (kill && (termstate.c_cflag & ECHOK))
    {
      echo_char (termstate.c_cc[VKILL], 0, 0);
      echo_char ('\n', 1, 0);
      inputpsize = 0;
      return;
    }
  
  if (termstate.c_cflag & ECHOPR)
    {
      if (!(intstate & IHDERASE))
	{
	  output_char ('\\');
	  intstate |= IHDERASE;
	  inputpsize++;
	}
      while (begin != end)
	{
	  end--;
	  echo_char (*end, 0, 1);
	}
      return;
    }

  while (begin != end)
    {
      end--;
      if (termstate.c_cc[VERASE] != POSIX_VDISABLE)
	echo_char (termstate.c_cc[VERASE], 0, 0);
      else
	echo_char (0177, 0, 0);	/* DEL */
    }
}

/* A canonical line of input is done, with character C ending it.  
   Make it available to reading processes. */
void
finish_line (char c)
{
  int amt;
  
  amt = lineqp - lineq + 1;	/* one extra for our arg */

  if (amt > QUEUE_SIZE - (readqp - readq))
    {
      if (termstate.c_iflag & IMAXBEL)
	output_char ('\007');
      amt = QUEUE_SIZE - (readqp - readq);
    }
  
  if (amt >= 0)
    {
      bcopy (lineq, readqp, amt - 1);
      readqp += amt;
    }
  *readqp++ = c;
  lineqp = lineq;
  inputpsize = 0;
}
  

/* Reprint the input line */
void
reprint_line ()
{
  char *cp;
  
  if (termstate.c_cc[VREPRINT] != POSIX_VDISABLE)
    echo_char (termstate.c_cc[VREPRINT], 0, 0);
  else
    echo_char (R + 'A' - '001', 0, 0); /* ^R */

  echo_char ('\n', 1, 0);
  inputpsize = 0;
  
  for (cp = lineq; cp != lineqp; cp++)
    echo_char (cp, 0, 0);
}

