#if !defined(lint) && !defined(__INSIGHT__)
static char sos__rcsid[] = "io.c,v 1.1.1.1 1995/06/16 17:42:41 seth Exp";
static char sos__copyright[] = "Copyright (c) 1994, 1995 SOS Corporation";
static char sos__contact[] = "SOS Corporation <sos-info@soscorp.com> +1 800 SOS UNIX";
#endif /* not lint */

/*
 * ++Copyright Released Product++
 *
 * Copyright (c) 1994, 1995 Sources of Supply Corporation ("SOS").
 * All rights reserved.
 *
 * The SOS Released Product License Agreement specifies the terms and
 * conditions for redistribution.  You may find the License Agreement
 * in the file LICENSE.
 *
 * SOS Corporation
 * 461 5th Ave.; 16th floor
 * New York, NY 10017
 *
 * +1 800 SOS UNIX
 * <sos-info@soscorp.com>
 *
 * --Copyright Released Product--
 */

/*
 * Read a line from a file descriptor.
 *
 * (Performing primitive editing, multiple
 * EOL sequences, various echoing options, etc)
 */

#include "sos.h"


/* #define DEBUG3 */

/*
 * Gather a line's worth of data
 *
 * This performs character-by-character processing, but it uses getc
 * You need to specify echo mode,
 * You need to handle line editing,
 * You need to handle escape characters (telnet options)
 *
 * If the EOL sequence has special (editing, escape, etc) characters
 * in it, they will be in the sequence *and* be interpreted.  Be warned
 *
 * String will be null terminated
 * String will have eolseq in it
 * Will exit -1 on error
 * Will exit 0 on EOF
 * Will return # of characters read if all is well
 * Will return early if line fills up and return code will be negative
 * (-charread-1) to leave -1 free for error indication
 */
int 
sos_readline(FILE * in, FILE * out, sos_string * line, struct sos_readline_t *opts)
{
  int eol_state[MAX_EOLSEQ];
  int eol_termstate[MAX_EOLSEQ];
  int numchar = 0;
  sos_string delseq;		/* Deletion sequence */
  int c;
  int exitstate = 0;

#ifdef DEBUG3
  fprintf(stderr, "readline(): Entering\n");
  fflush(stderr);
#endif

  /* Initialize exit conditions */
  for (c = 0; c < opts->numeolseq; c++)
    {
      eol_state[c] = 0;
      eol_termstate[c] = opts->eolseq[c].len;
    }

  delseq.str = "\010 \010";
  delseq.len = strlen(delseq.str);

  /* Loop until we have a full line */
  while ((!exitstate) && (numchar < line->len))
    {
      char input;
      int ret;
      int dooutput;

      dooutput = 1;

#ifdef DEBUG3
      fprintf(stderr, "readline(): About to read\n");
      fflush(stderr);
#endif
      /* Read one character--in better not be NBIO */
      ret = getc(in);

#ifdef DEBUG3
      fprintf(stderr, "XXread char :%c:%d:%d:\r\n", (unsigned char)ret, (unsigned char)ret, opts->escape_char);
#endif
      if (ret == EOF || ferror(in))
	{
	  line->str[numchar] = '\0';
	  return (0);
	}
      input = (unsigned char)ret;

      /* If this might be part of the EOL sequence */
      for (c = 0; c < opts->numeolseq; c++)
	{
	tryagain:
	  if (input == opts->eolseq[c].str[eol_state[c]])
	    {
	      eol_state[c]++;
	      if (eol_state[c] >= eol_termstate[c])
		exitstate = 1;	/* We have SEEN the EOL */
	    }
	  else
	    {
	      if (eol_state[c] != 0)
		{
		  eol_state[c] = 0;
		  goto tryagain;
		}
	      eol_state[c] = 0;
	    }
	}

      if (!opts->wantnull && !input)
	continue;		/* Null does not make sense */

      /* Command line editing, SOS style */
      if (opts->wantedit)
	switch (input)
	  {
	  case 0:		/* Null does not make sense in a string */
	    continue;
	  case 3:		/* INTR */
	    numchar = 0;
	    line->str[numchar] = '\0';
	    return (0);
	  case 4:		/* EOF */
	    write(fileno(out), "EOF\r\n", 5);
	    errno = EIO;
	    return (-1);
	  case 8:		/* Delete */
	  case 127:
	    if (numchar > 0)
	      {
		write(fileno(out), delseq.str, delseq.len);
		numchar--;
	      }
	    dooutput = 0;
	    continue;
	  case 21:		/* ^U kill line */
	    for (; numchar > 0; numchar--)
	      write(fileno(out), delseq.str, delseq.len);
	    numchar = 0;
	    continue;
	  }

      /*
       * Check for protocol escape sequences
       *
       * Currently, nothing uses this, I believe
       */
      if ((int)(unsigned char)input == opts->escape_char)
	{
#ifdef DEBUG3
	  fprintf(stderr, "  Is a escape mode\r\n");
#endif
	  if ((ret = (*opts->escape_mode) (in, out, (unsigned char)input)) < 0)
	    return (-1);
	  if (ret == 0)		/* Don't do anything */
	    continue;
	  if (ret > 0)
	    input = ret;	/* Insert this character */
	}

      /* Add to input line */
      line->str[numchar++] = input;

      /* What kind of output options we have */
      if (dooutput)
	{
	  unsigned char echochar = (unsigned char)opts->echo;

	  ret = 1;
	  if (opts->echo == 0)	/* echo the input */
	    ret = write(fileno(out), &input, 1);
	  if (opts->echo > 0)	/* echo the specified character */
	    ret = write(fileno(out), &echochar, 1);

	  if (ret != 1)		/* echo naught */
	    return (ret);
	}
    }

  line->str[numchar] = '\0';

  /*
   * Under certain circumstances (i.e. it makes sense)
   * print the first EOL sequence to make sure we have
   * performed a CRLF (or whatever)
   */
  if (opts->echo > 0 || (opts->echo == 0 && eol_termstate[0] != eol_state[0]))
    if (write(fileno(out), opts->eolseq[0].str, eol_termstate[0]) != eol_termstate[0])
      return (-1);

  /* We are exiting with errors (usually timeouts or line too long) */
  if (!exitstate)
    {
      /* Alert (ring bell) that we don't like what is happening */
      if (opts->wantalert)
	if (write(fileno(out), "\a", 3) != 3)
	  return (-1);

      /* XXX - return number of characters encoded to avoid -1 */
      return (-numchar - 1);
    }

  /* return number of characters read */
  return (numchar);
}


/*
 * Standard readline, but setup a signal handler
 * to handle timeouts
 */
int
sos_readline_timeout(FILE * in, FILE * out, sos_string * line, struct sos_readline_t *opts, struct sigaction *readtimeout, int timeout)
{
  struct sigaction old_sigaction;
  int ret_val;
  int old_alarm;

  if (timeout <= 0)
    {
      errno = EINVAL;
      return -1;
    }

  sigaction(SIGALRM, readtimeout, (struct sigaction *)&old_sigaction);

  old_alarm = alarm(timeout);


  ret_val = sos_readline(in, out, line, opts);

  alarm(old_alarm);
  sigaction(SIGALRM, (struct sigaction *)&old_sigaction, NULL);

  return ret_val;
}
