/*****************************************************************************
 *
 * File ..................: mbcico/ttyio.c
 * Purpose ...............: Fidonet mailer 
 * Last modification date : 23-Dec-2000
 *
 *****************************************************************************
 * Copyright (C) 1997-2000
 *   
 * Michiel Broek		FIDO:		2:280/2802
 * Beekmansbos 10
 * 1971 BV IJmuiden
 * the Netherlands
 *
 * This file is part of MBSE BBS.
 *
 * This BBS 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.
 *
 * MBSE BBS 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 MBSE BBS; see the file COPYING.  If not, write to the Free
 * Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *****************************************************************************/

/* ### Modified by P.Saratxaga on 25 Oct 1995 ###
 * - Added if (inetaddr) code from T. Tanaka
 */
#include "../lib/libs.h"
#include "../lib/structs.h"
#include "../lib/records.h"
#include "../lib/common.h"
#include "../lib/clcomm.h"
#include "ttyio.h"
#include "lutil.h"

extern	int	hanged_up;
extern	char	*inetaddr;

#define TT_BUFSIZ 1024
#define NUMTIMERS 3


int		tty_status = 0;
int		f_flags;
static	char	buffer[TT_BUFSIZ];
static	char	*next;
static	int	left = 0;


static	time_t	timer[NUMTIMERS];

char *ttystat[]= {(char *)"Ok", 
		  (char *)"Error", 
		  (char *)"TimeOut", 
		  (char *)"EOF", 
		  (char *)"Hangup", 
		  (char *)"Empty"};




/*
 * timer functions
 */

int tty_resettimer(int tno)
{
	if (tno >= NUMTIMERS) {
		errno = EINVAL;
		WriteError("$ttyio: invalid timer No for resettimer()");
		return -1;
	}

	Syslog('T', "ttyio: resettimer(%d)", tno);
	timer[tno] = (time_t) 0;
	return 0;
}



void tty_resettimers(void)
{
	int i;

	Syslog('T', "ttyio: resettimers");
	for (i = 0; i < NUMTIMERS; i++) 
		timer[i] = (time_t)0;
}



int tty_settimer(int tno, int interval)
{
	if (tno >= NUMTIMERS) {
		errno = EINVAL;
		WriteError("$ttyio: invalid timer No for settimer()");
		return -1;
	}

	Syslog('T', "ttyio: settimer(%d,%d)",tno,interval);
	timer[tno]=time((time_t*)NULL)+interval;
	return 0;
}



int tty_expired(int tno)
{
	time_t	now;

	if (tno >= NUMTIMERS) {
		errno = EINVAL;
		WriteError("$ttyio: invalid timer No for expired(%d)", tno);
		return -1;
	}

	/*
	 * Check if timer is running
	 */
	if (timer[tno] == (time_t) 0)
		return 0;

	(void)time(&now);
	Syslog('T', "ttyio: expired(%d) now=%lu,timer=%lu,return %s",
		tno,now,timer[tno],(now >= timer[tno])?"yes":"no");
	return (now >= timer[tno]);
}



int tty_running(int tno)
{
	if (tno > NUMTIMERS) {
		errno = EINVAL;
		WriteError("$ttyio: invalid timer for tty_running(%d)", tno);
		return -1;
	}

	/*
	 * check if timer is running
	 */
	if (timer[tno] == (time_t) 0)
		return 0;
	else
		return 1;
}



/*
 * private r/w functions
 */

static int tty_read(char *buf, int size, int tot)
{
	time_t	timeout, now;
	int	i, rc;
	fd_set	readfds, writefds, exceptfds;
	struct	timeval seltimer;

	Syslog('T', "tty_read: (%08lx,%d,%d)",buf,size,tot);
	if (size == 0) 
		return 0;
	tty_status = 0;

	(void)time(&now);
	timeout = (time_t)300; /* maximum of 5 minutes */

	for (i = 0; i < TIMERNO_TX; i++) {
		if (timer[i]) {
			if (now >= timer[i]) {
				tty_status=STAT_TIMEOUT;
				Syslog('t', "tty_read: timer %d already expired, return",i);
				return -tty_status;
			} else {
				if (timeout > (timer[i]-now))
					timeout=timer[i]-now;
			}
		}
	}
	if ((tot != -1) && (timeout > tot))
		timeout=tot;

	Syslog('T', "tty_read: timeout = %d", timeout);

	FD_ZERO(&readfds);
	FD_ZERO(&writefds);
	FD_ZERO(&exceptfds);
	FD_SET(0,&readfds);
	FD_SET(0,&exceptfds);
	seltimer.tv_sec=timeout;
	seltimer.tv_usec=0;

	rc = select(1,&readfds,&writefds,&exceptfds,&seltimer);

	if (rc < 0) {
		if (hanged_up) {
			tty_status=STAT_HANGUP;
			WriteError("$tty_read: hanged_up flag");
		} else {
			WriteError("$tty_read: select for read failed");
			tty_status = STAT_ERROR;
		}
	} else if (rc == 0) {
		tty_status = STAT_TIMEOUT;
	} else { /* rc > 0 */
		if (FD_ISSET(0,&exceptfds)) {
			Syslog('+', "$tty_read: exeption error");
			tty_status = STAT_ERROR;
		}
	}

	if (tty_status) {
		Syslog('T', "tty_read: return after select: %s",ttystat[tty_status]);
		return -tty_status;
	}

	if (!FD_ISSET(0,&readfds)) {
		WriteError("tty_read: Cannot be: select returned but read fd not set");
		tty_status = STAT_ERROR;
		return -tty_status;
	}

	rc = read(0,buf,size);
	if (rc <= 0) {
		Syslog('t', "tty_read: return %d",rc);
		if (hanged_up || (errno == EPIPE) || (errno == ECONNRESET)) {
			tty_status = STAT_HANGUP;
			WriteError("$tty_read: hanged_up flag");
		} else 
			tty_status = STAT_ERROR;
		rc=-tty_status;
	} else 
		Syslog('T', "tty_read: %s %d characters", printable(buf, rc), rc);
	return rc;
}



int tty_write(char *buf, int size)
{
	int result;

	Syslog('T', "tty_write(%08lx,%d)",buf,size);

	tty_status=0;
	result = write(1,buf,size);

	if (result != size) {
		if (hanged_up || (errno == EPIPE) || (errno == ECONNRESET)) {
			tty_status = STAT_HANGUP;
			WriteError("$tty_write: hanged_up flag");
		} else 
			tty_status=STAT_ERROR;
	}
	if (tty_status)
		Syslog('t', "tty_write: error %s", ttystat[tty_status]);

	return -tty_status;
}



/* public r/w functions */

/**
 * Check if there is data available on stdin.
 */
int tty_check(void)
{
	int rc;

	// try to read available (timeout = 0) data if we have no data in
	// our buffer

	if (!left) {
		rc = tty_read(buffer, TT_BUFSIZ, 0);
		if (rc > 0) {
			left = rc;
		}
	}

	return (left > 0);
}



int tty_putcheck(int size)
{
	fd_set set;
	struct timeval timeout;
     
	/*
	 * Initialize the file descriptor set. 
	 */
	FD_ZERO(&set);
	FD_SET(1, &set);
     
	/*
	 * Initialize the timeout data structure. 
	 */
	timeout.tv_sec = 0;
	timeout.tv_usec = 0;
     
	/*
	 * `select' returns 0 if timeout, 1 if input available, -1 if error. 
	 */
	return select(FD_SETSIZE, NULL, &set, NULL, &timeout);
}



int tty_waitputget(int tot)
{
	int	i, rc;
	time_t	timeout, now;
	fd_set	readfds, writefds, exceptfds;
	struct	timeval seltimer;

	tty_status=0;
	(void)time(&now);
	timeout=(time_t)300; /* maximum of 5 minutes */

	for (i = 0; i < NUMTIMERS; i++) {
		if (timer[i]) {
			if (now >= timer[i]) {
				tty_status = STAT_TIMEOUT;
				WriteError("tty_waitputget: timer %d already expired, return",i);
				return -tty_status;
			} else {
				if (timeout > (timer[i]-now))
					timeout = timer[i]-now;
			}
		}
	}
	if ((tot != -1) && (timeout > tot))
		timeout=tot;
	Syslog('t', "tty_waitputget: timeout=%d",timeout);

	/*
	 * Initialize the file descriptor set. 
	 */
	FD_ZERO(&readfds);
	FD_ZERO(&writefds);
	FD_ZERO(&exceptfds);

	FD_SET(0, &readfds);
	FD_SET(1, &writefds);
	FD_SET(0, &exceptfds);
	FD_SET(1, &exceptfds);
     
	/*
	 * Initialize the timeout data structure. 
	 */
	seltimer.tv_sec = timeout;
	seltimer.tv_usec = 0;
     
	/*
	 * `select' returns 0 if timeout, 1 if input available, -1 if error. 
	 */
	rc = select(FD_SETSIZE, &readfds, &writefds, &exceptfds, &seltimer);

	if (rc < 0) {
		if (hanged_up) {
			tty_status=STAT_HANGUP;
			WriteError("tty_waitputget: hanged_up flag");
		} else {
			WriteError("$tty_waitputget: select failed");
			tty_status=STAT_ERROR;
		}
	} else if (rc == 0) {
		tty_status=STAT_TIMEOUT;
	} else { 
		/* rc > 0 */
		if ((FD_ISSET(0,&exceptfds)) || (FD_ISSET(1,&exceptfds))) {
			WriteError("$tty_waitputget: exeption error");
			tty_status=STAT_ERROR;
		}
	}

	if (tty_status) {
		Syslog('t', "tty_waitputget: return after select status %s",ttystat[tty_status]);
		return -tty_status;
	}

	rc = 0;

	if (FD_ISSET(0,&readfds))
		rc |= 1;

	if (FD_ISSET(1,&writefds))
		rc |= 2;

	return rc;
}



void tty_flushin(void)
{
	tcflush(0, TCIFLUSH);
}



void tty_flushout(void)
{
	tcflush(1, TCOFLUSH);
}



int tty_ungetc(int c)
{
	if (next == buffer) {
		if (left >= TT_BUFSIZ) {
			return -1;
		}

		next = buffer + TT_BUFSIZ - left;
		memcpy(next, buffer, left);
	}

	next--;
	*next = c;
	left++;

	return 0;
}



int tty_getc(int tot)
{
	if (!left) {
		left=tty_read(buffer,TT_BUFSIZ,tot);
		next=buffer;
	}

	if (left <= 0) {
		left=0;
		return -tty_status;
	} else {
		left--;
		return (*next++)&0xff;
	}
}



int tty_get(char *buf, int size, int tot)
{
	int result=0;

	if (left >= size) {
		memcpy(buf,next,size);
		next += size;
		left -= size;
		return 0;
	}

	if (left > 0) {
		memcpy(buf,next,left);
		buf += left;
		next += left;
		size -= left;
		left=0;
	}

	while ((result=tty_read(buf,size,tot)) > 0) {
		buf += result;
		size -= result;
	}

	return result;
}



int tty_putc(int c)
{
	char	buf = c;

	return tty_write(&buf,1);
}



int tty_put(char *buf, int size)
{
	return tty_write(buf,size);
}



int tty_putget(char **obuf, int *osize, char **ibuf, int *isize)
{
	time_t	timeout, now;
	int	i, rc;
	fd_set	readfds, writefds, exceptfds;
	struct	timeval seltimer;

	tty_status = 0;
	(void)time(&now);
	timeout = (time_t)300; /* maximum of 5 minutes */

	for (i = 0; i < NUMTIMERS; i++) {
		if (timer[i]) {
			if (now >= timer[i]) {
				tty_status = STAT_TIMEOUT;
				WriteError("tty_putget: timer %d already expired, return",i);
				return -tty_status;
			} else {
				if (timeout > (timer[i]-now))
					timeout=timer[i]-now;
			}
		}
	}

	Syslog('t', "tty_putget: timeout=%d",timeout);

	FD_ZERO(&readfds);
	FD_ZERO(&writefds);
	FD_ZERO(&exceptfds);
	FD_SET(0,&readfds);
	FD_SET(1,&writefds);
	FD_SET(0,&exceptfds);
	FD_SET(1,&exceptfds);
	seltimer.tv_sec=timeout;
	seltimer.tv_usec=0;

	rc=select(2,&readfds,&writefds,&exceptfds,&seltimer);
	if (rc < 0) {
		if (hanged_up) {
			tty_status=STAT_HANGUP;
			WriteError("tty_putget: hanged_up flag");
		} else {
			WriteError("$tty_putget: select failed");
			tty_status=STAT_ERROR;
		}
	} else if (rc == 0) {
		tty_status=STAT_TIMEOUT;
	} else {
		/* rc > 0 */
		if ((FD_ISSET(0,&exceptfds)) || (FD_ISSET(1,&exceptfds))) {
			WriteError("$tty_putget: exeption error");
			tty_status=STAT_ERROR;
		}
	}

	if (tty_status) {
		Syslog('t', "tty_putget: return after select status %s",ttystat[tty_status]);
		return -tty_status;
	}

	if (FD_ISSET(0,&readfds) && *isize) {
		rc=read(0,*ibuf,*isize);
		if (rc < 0) {
			WriteError("$tty_putget: read failed");
			tty_status=STAT_ERROR;
		} else {
			(*ibuf)+=rc;
			(*isize)-=rc;
		}
	}

	if (FD_ISSET(1,&writefds) && *osize) {
		rc=write(1,*obuf,*osize);
		if (rc < 0) {
			WriteError("$tty_putget: write failed");
			tty_status=STAT_ERROR;
		} else {
			(*obuf)+=rc;
			(*osize)-=rc;
		}
	}

	if (tty_status) 
		return -tty_status;
	else 
		return ((*isize == 0) | ((*osize == 0) << 1));
}