/* ### Modified by P.Saratxaga on 25 Oct 1995 ###
 * - Added if (inetaddr) code from T. Tanaka
 */
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>

#if defined(HAS_TCP) || defined(HAS_TERM)
#if defined(HAS_NET_ERRNO_H) || defined(SELECT_NEED_BSDTYPES_H)
#include <sys/bsdtypes.h>
#endif
#ifdef HAS_NET_ERRNO_H
#include <net/errno.h>
#endif
#include <sys/socket.h>
#endif

#ifdef HAS_SELECT
#ifdef HAS_TERMIOS_H
#include <termios.h>
#else
#error "No termios.h - don't know what to do"
#endif
#ifdef SELECT_NEED_SYS_TIMES_H
#include <sys/times.h>
#endif
#ifdef HAS_SYS_SELECT_H
#include <sys/select.h>
#else
#include <sys/time.h>
#endif
#else
#include <signal.h>
#endif

#ifdef HAS_IOCTL_H
#include <sys/ioctl.h>
#endif

#ifdef NOISEDEBUG
#include <stdlib.h>
#include <limits.h>
#include <time.h>
#endif

#include "ttyio.h"
#include "lutil.h"

extern int hanged_up;
#ifdef HAS_TCP
extern char *inetaddr;
#endif

#ifndef BUFSIZ
#define BUFSIZ 1024
#endif

#ifndef NUMTIMERS
#define NUMTIMERS 3
#endif

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

static time_t timer[NUMTIMERS];

	/* timer functions */

int tty_resettimer(int tno)
{
	if (tno >= NUMTIMERS)
	{
		errno=EINVAL;
		logerr("$invalid timer No for resettimer()");
		return -1;
	}
	debug(18,"resettimer(%d)", tno);
	timer[tno] = (time_t) 0;
	return 0;
}

void tty_resettimers(void)
{
	int i;

	debug(18,"resettimers");
	for (i=0;i<NUMTIMERS;i++) timer[i]=(time_t)0;
}

int tty_settimer(tno,interval)
int tno;
int interval;
{
	if (tno >= NUMTIMERS)
	{
		errno=EINVAL;
		logerr("$invalid timer No for settimer()");
		return -1;
	}
	debug(18,"settimer(%d,%d)",tno,interval);
	timer[tno]=time((time_t*)NULL)+interval;
	return 0;
}

int tty_expired(tno)
int tno;
{
	time_t now;


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

	(void)time(&now);
	if (tno >= NUMTIMERS)
	{
		errno=EINVAL;
		logerr("$invalid timer No for expired()");
		return -1;
	}
	debug(19,"expired(%d) now=%lu,timer=%lu,return %s",
		tno,now,timer[tno],(now >= timer[tno])?"yes":"no");
	return (now >= timer[tno]);
}

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

	/* private r/w functions */

static int tty_read(buf,size,tot)
char *buf;
int size,tot;
{
	time_t timeout,now;
	int i,rc;

#ifdef HAS_SELECT
	fd_set readfds,writefds,exceptfds;
	struct timeval seltimer;
#else
#error unimplemented non-select()-based i/o
#endif

	debug(18,"tty_read(%08lx,%d)",buf,size);
	if (size == 0) return 0;
	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;
	      debug(18,"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;
	debug(18,"tty_read timeout=%d",timeout);

#ifdef HAS_SELECT
	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;
		}
		else
		{
			logerr("$select for read failed");
			tty_status=STAT_ERROR;
		}
	}
	else if (rc == 0)
	{
		tty_status=STAT_TIMEOUT;
	}
	else /* rc > 0 */
	{
		if (FD_ISSET(0,&exceptfds)) tty_status=STAT_ERROR;
	}

	if (tty_status)
	{
		debug(18,"returning after select with status %d",tty_status);
		return -tty_status;
	}

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

	rc=read(0,buf,size);
	if (rc <= 0)
	{
		debug(18,"read() return %d",rc);
		if (hanged_up || (errno == EPIPE)
#if defined(HAS_TCP) || defined(HAS_TERM)
				|| (errno == ECONNRESET)
#endif
							)
			tty_status=STAT_HANGUP;
		else tty_status=STAT_ERROR;
		rc=-tty_status;
	}
	else debug(19,"read %d: \"%s\"",rc,printable(buf,rc));
	return rc;
#else
#endif
}

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

	debug(18,"tty_write(%08lx,%d)",buf,size);
	tty_status=0;
	result=write(1,buf,size);
	if (result != size)
	{
		if (hanged_up || (errno == EPIPE)
#if defined(HAS_TCP) || defined(HAS_TERM)
				|| (errno == ECONNRESET)
#endif
							)
			tty_status=STAT_HANGUP;
		else tty_status=STAT_ERROR;
	}
	return -tty_status;
}

	/* public r/w functions */

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


  if (!left)
  {
    // try to read available (timeout = 0) data if we have no data in
    // our buffer
    rc = tty_read(buffer, 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;

#ifdef HAS_SELECT
  fd_set readfds, writefds, exceptfds;
  struct timeval seltimer;
#else
#error unimplemented non-select()-based i/o
#endif

  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;
	debug(18,"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;
  debug(18,"tty_waitputget timeout=%d",timeout);

#ifdef HAS_SELECT
  /* 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;
    }
    else
    {
      logerr("$select for putget 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)))
      tty_status=STAT_ERROR;
  }

  if (tty_status)
  {
    debug(18,"returning after select with status %d",tty_status);
    return -tty_status;
  }

  rc = 0;

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

  if (FD_ISSET(1,&writefds))
  {
    rc |= 2;
  }
#else
#endif

  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 >= BUFSIZ)
    {
      return -1;
    }

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

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

  return 0;
}

int tty_getc(tot)
int tot;
{
	if (!left)
	{
		left=tty_read(buffer,BUFSIZ,tot);
		next=buffer;
	}
	if (left <= 0)
	{
		left=0;
		return -tty_status;
	}
	else
	{
		left--;
		return (*next++)&0xff;
	}
}

int tty_get(buf,size,tot)
char *buf;
int size,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;
	}

#if 0
	tty_resettimers();
	tty_settimer(0,tot);
#endif

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

#if 0
	tty_resettimers();
#endif

	return result;
}

int tty_putc(c)
int c;
{
	char buf=c;
	return tty_write(&buf,1);
}

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

int tty_putget(obuf,osize,ibuf,isize)
char **obuf;
int *osize;
char **ibuf;
int *isize;
{
	time_t timeout,now;
	int i,rc;

#ifdef HAS_SELECT
	fd_set readfds,writefds,exceptfds;
	struct timeval seltimer;
#else
#error unimplemented non-select()-based i/o
#endif

	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;
	      debug(18,"timer %d already expired, return",i);
	      return -tty_status;
	    }
	    else
	    {
	      if (timeout > (timer[i]-now))
		timeout=timer[i]-now;
	    }
	  }
	}
	debug(18,"tty_putget timeout=%d",timeout);

#ifdef HAS_SELECT
	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;
		}
		else
		{
			logerr("$select for putget 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)))
			tty_status=STAT_ERROR;
	}

	if (tty_status)
	{
		debug(18,"returning after select with status %d",tty_status);
		return -tty_status;
	}

	if (FD_ISSET(0,&readfds) && *isize)
	{
		rc=read(0,*ibuf,*isize);
		if (rc < 0)
		{
			logerr("$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)
		{
			logerr("$write failed");
			tty_status=STAT_ERROR;
		}
		else
		{
			(*obuf)+=rc;
			(*osize)-=rc;
		}
	}
#else
#endif

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