/*
 **********************************************************************
 *              File io.c
 **********************************************************************
 *
 * Non blocking input routine
 * Works by puttng the terminal in CBREAK mode and using the FIONREAD
 * ioctl call to determine the number of characters in the input queue
 */

#include "stdio.h"
#include "io.h"
#include <sys/types.h>
#include <sys/time.h>
#include <signal.h>
#include <sgtty.h>
#include "cext.h"

int     IOinputfd;      /* input file descriptor (usually 0) */

int     IOnochar;       /* Value to be returned by IOgetchar()
				   where there is no input to be had */

static  struct sgttyb IOoldmodes, IOcurrentmodes;
				/* Initial and current tty modes */

/*
 * IOsetup(inputfd)
 *   Args:
 *      inputfd - input file descriptor (should be zero for standard input)
 *   Returns:
 *      0 - if all goes well
 *      -1 - if an ioctl fails (also calls perror)
 *   Side Effects:
 *      Puts the terminal in CBREAK mode - before process termination
 *      IOcleanup() should be called to restore old terminal modes
 *      Catch's interrupts (if they are not already being caught) and
 *      calls IOcleanup before exiting
 *
 */

#define ERROR(s)        return (perror(s), -1)

IOsetup(inputfd)
{
	static IOdiegracefully();
	int (*interrupt_handler)();

	IOinputfd = inputfd;
	IOnochar = NOCHAR;
	if(ioctl(IOinputfd, TIOCGETP,  &IOoldmodes) < 0)
		ERROR("IOsetup");

	IOcurrentmodes = IOoldmodes;
	IOcurrentmodes.sg_flags |= CBREAK;
	IOcurrentmodes.sg_flags &= ~ECHO;
	if(ioctl(IOinputfd, TIOCSETP,  &IOcurrentmodes))
		ERROR("IOsetup-2");

	if( (interrupt_handler = signal(SIGINT, IOdiegracefully)) != 0)
		signal(SIGINT, interrupt_handler);
	return 0;
}

static
IOdiegracefully()
{
	write(2, "\nBye\n", 5);
	IOcleanup();
	EXIT(2);
}

/*
 * IOcleanup()
 *   Returns:
 *      0 - if all goes well
 *      -1 - if an ioctl fails (also calls perror)
 *   Side Effects:
 *      Restores initial terminal modes
 */

IOcleanup()
{
	if(ioctl(IOinputfd, TIOCSETP,  &IOoldmodes) < 0)
		ERROR("IOclean");
	return 0;
}


/*
 * IOgetchar()
 *    Returns:
 *      A character off the input queue if there is one,
 *      IOnochar if there is no character waiting to be read,
 *      -1 if an ioctl fails (shouldn't happen if IOsetup went OK)
 */

#ifndef UNIX_MACH

IOgetchar()
{
	int n;
	char c;

	if(ioctl(IOinputfd, FIONREAD, &n) < 0)
		ERROR("IOgetchar");
	if(n <= 0)
		return IOnochar;
	switch(read(IOinputfd, &c, 1)) {
	case 1:
		return c;
	case 0:
		return EOF;
	default:
		ERROR("IOgetchar-read");
	}
}

#ifdef IOGETCHAR2
IOgetchar2()
{
	int nfds, readfds = 1 << IOinputfd;
	char c;
	static struct timeval zero;

	if(IOinputfd < 0 || IOinputfd >= 32) {
		printf("IOgetchar2: bad IOinputfd (%d)%s\n", IOinputfd,
			IOinputfd == -1 ? "Did you call IOsetup(fd)?" : "");
	}
	nfds = select(32, &readfds, 0, 0, &zero);
	if(nfds > 0) {
		switch(read(IOinputfd, &c, 1)) {
		case 0:
			return EOF;
		case 1:
			return c;
		default:
			printf("IOgetchar2: read failed!\n");
			return NOCHAR;
		}
	}
	else if(nfds < 0)
		printf("IOgetchar2: select failed!\n");
	return NOCHAR;
}
#endif

/*
 * IOwaitchar()
 *   Returns:
 *      A character off the input queue.  Waits if necessary.
 */

int IOwaitchar()
{
    char c;
    if (read(IOinputfd, &c, 1) == 1) return c;
    else return EOF;
}

#endif /* not UNIX_MACH */
