/*-
 * Copyright (c) 1993, 1994
 *	The Regents of the University of California.  All rights reserved.
 * Copyright (c) 1994, 1995
 *	Keith Bostic.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifndef lint
static char sccsid[] = "@(#)sex_term.c	9.6 (Berkeley) 1/30/95";
#endif /* not lint */

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/queue.h>
#include <sys/time.h>

#include <bitstring.h>
#include <errno.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>

#include "compat.h"
#include <db.h>
#include <regex.h>

#include "vi.h"
#include "excmd.h"
#include "../ex/script.h"

/*
 * sex_key_read --
 *	Read characters from the input.
 */
input_t
sex_key_read(sp, nrp, flags, timeout)
	SCR *sp;
	int *nrp;
	u_int flags;
	struct timeval *timeout;
{
	struct timeval t, *tp;
	GS *gp;
	int maxfd, nr;
	CHAR_T *p;

	*nrp = 0;

	/*
	 * We're about to block; check for signals.  The signal handlers set
	 * global flags.  None of this has anything to do with input keys,
	 * but it's something that can't be done asynchronously without adding
	 * locking to handle the race conditions, and which needs to be done
	 * periodically.
	 *
	 * We call the resizing function here, because SunOS sends SIGWINCH
	 * when windows are uncovered, and we don't want to have to restart
	 * the screen if that happens.  Check the window signal first, so
	 * that an interrupt doesn't mask it.  Clear the window flag before
	 * processing, so that we don't lose the next one.
	 */
	gp = sp->gp;
sigchk:	if (F_ISSET(gp, G_SIGINT | G_SIGWINCH)) {
		if (F_ISSET(gp, G_SIGWINCH)) {
			F_CLR(gp, G_SIGWINCH);
			if (!sp->e_ssize(sp, 1)) {
				F_SET(sp, S_SCR_RESIZE);
				return (INP_INTR);
			}
		}
		if (F_ISSET(gp, G_SIGINT))
			return (INP_INTR);
	}

	/*
	 * There are three cases here:
	 *
	 * 1: A read from a file or a pipe.  In this case, the reads
	 *    never timeout regardless.  This means that we can hang
	 *    when trying to complete a map, but we're going to hang
	 *    on the next read anyway.
	 */
	if (!F_ISSET(gp, G_STDIN_TTY)) {
		if ((nr = read(STDIN_FILENO,
		    gp->i_ch + gp->i_next + gp->i_cnt,
		    gp->i_nelem - (gp->i_next + gp->i_cnt))) > 0)
			goto success;
		return (INP_EOF);
	}

	/*
	 * 2: A read with an associated timeout.  In this case, we are trying
	 *    to complete a map sequence.  Ignore script windows and timeout
	 *    as specified.  If input arrives, we fall into #3, but because
	 *    timeout isn't NULL, don't read anything but command input.
	 *
	 * If interrupted, go back and check to see what it was.
	 */
	if (timeout != NULL) {
		if (F_ISSET(sp, S_SCRIPT))
			FD_CLR(sp->script->sh_master, &sp->rdfd);
		FD_SET(STDIN_FILENO, &sp->rdfd);
		switch (select(STDIN_FILENO + 1,
		    &sp->rdfd, NULL, NULL, timeout)) {
		case -1:			/* Error or interrupt. */
			if (errno == EINTR)
				goto sigchk;
			goto err;
		case  1:			/* Characters ready. */
			break;
		case  0:			/* Timeout. */
			return (INP_OK);
		}
	}

	/*
	 * 3: At this point, we'll take anything that comes.  Select on the
	 *    command file descriptor and the file descriptor for the script
	 *    window if there is one.  Poll the fd's, increasing the timeout
	 *    each time each time we don't get anything until we're blocked
	 *    on I/O.
	 *
	 * If interrupted, go back and check to see what it was.
	 */
	for (t.tv_sec = t.tv_usec = 0;;) {
		/*
		 * Reset each time -- sscr_input() may call other
		 * routines which could reset bits.
		 */
		if (timeout == NULL && F_ISSET(sp, S_SCRIPT)) {
			tp = &t;

			FD_SET(STDIN_FILENO, &sp->rdfd);
			if (F_ISSET(sp, S_SCRIPT)) {
				FD_SET(sp->script->sh_master, &sp->rdfd);
				maxfd =
				    MAX(STDIN_FILENO, sp->script->sh_master);
			} else
				maxfd = STDIN_FILENO;
		} else {
			tp = NULL;

			FD_SET(STDIN_FILENO, &sp->rdfd);
			if (F_ISSET(sp, S_SCRIPT))
				FD_CLR(sp->script->sh_master, &sp->rdfd);
			maxfd = STDIN_FILENO;
		}

		switch (select(maxfd + 1, &sp->rdfd, NULL, NULL, tp)) {
		case -1:		/* Error or interrupt. */
			if (errno == EINTR)
				goto sigchk;
err:			msgq(sp, M_SYSERR, "select");
			return (INP_ERR);
		case 0:			/* Timeout. */
			if (t.tv_usec) {
				++t.tv_sec;
				t.tv_usec = 0;
			} else
				t.tv_usec += 500000;
			continue;
		}

		if (timeout == NULL && F_ISSET(sp, S_SCRIPT) &&
		    FD_ISSET(sp->script->sh_master, &sp->rdfd)) {
			sscr_input(sp);
			continue;
		}

		/*
		 * !!!
		 * What's going on here is fairly scary stuff.  Ex runs the
		 * terminal in canonical mode.  This means that the <newline>
		 * character terminating a line of input is returned in the
		 * buffer, but a trailing <EOF> character is not similarly
		 * included.  Ex uses 0<EOF> and ^<EOF> as autoindent commands,
		 * so it has to see the trailing <EOF> characters to determine
		 * the difference between the user entering "0ab" or "0<EOF>ab".
		 * We leave an extra slot in the buffer, so that if the buffer
		 * isn't terminated by a <newline>, we can add a trailing <EOF>
		 * character.  What happens if the buffer is too small for the
		 * line, and exactly N characters are entered followed by an
		 * <EOF> character?  We screw up, that's what.
		 */
#define	ONE_FOR_EOF	1
		switch (nr = read(STDIN_FILENO,
		    gp->i_ch + gp->i_next + gp->i_cnt,
		    gp->i_nelem - (gp->i_next + gp->i_cnt) - ONE_FOR_EOF)) {
		case  0:			/* EOF. */
			return (INP_EOF);
		case -1:			/* Error or interrupt. */
			if (errno == EINTR)
				goto sigchk;
			msgq(sp, M_SYSERR, "read");
			return (INP_ERR);
		default:
			if (LF_ISSET(TXT_EOFCHAR)) {
				p = gp->i_ch + gp->i_next + gp->i_cnt;
				if (p[nr - 1] != '\n') {
					p[nr] = gp->original_termios.c_cc[VEOF];
					++nr;
				}
			}
			goto success;
		}
		/* NOTREACHED */
	}

success:
	MEMSET(gp->i_chf + gp->i_next + gp->i_cnt, 0, nr);
	gp->i_cnt += *nrp = nr;
	return (INP_OK);
}
