/*
 *
 *  This file contains Linux/Unix specific code for setting terminal modes,
 *  very little is specific to ZMODEM per se 
 *  Copyright 2011 Omen Technology INC All Rights Reserved
 */


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <termio.h>
#include <sys/ioctl.h>
#include <string.h>
#include <sys/select.h>
#include <sys/time.h>
#include <setjmp.h>

#ifndef POSIX
#define POSIX
#endif

#define FLUSH

char Pathname[1024];
long Modtime;		/* Unix style mod time for incoming file */


#define RSIZE 120	/* Size (max) of read requests */

fd_set kbdfds, rfdreadfds, rfdwritefds, rfdexceptfds;
struct timeval rfdtimeout;

char Rxbuf[RSIZE+1];
int Rmode = -1;		/* Current mode for FD rdfd */
int Rxnum = RSIZE;	/* Make no larger than TTYHOG/4 ?? */
int Rxleft = 0;		/* number of characters available */
char *Rxcdq = Rxbuf;	/* pointer for removing chars from Rxbuf */
struct itimerval value;

#define TSIZE 32	/* Must be < TSIZE/5 */
char Txlbuf[TSIZE+1];
int Txnum = TSIZE;	/* number of characters WE SHALL USE in Txlbuf */
int Txleft = TSIZE;	/* number of characters in Txlbuf */
char *Txcq = Txlbuf;	/* pointer for storing chars to Txlbuf */
extern Xtimint;


/* VARARGS */
void
vfile(f, a, b, c, d)
/* VARARGS */
char *f;
long a, b, c, d;
{
	if (Verbose > 2) {
		fprintf(stderr, f, a, b, c, d);
		fprintf(stderr, "\n");
		fflush(stderr);
	}
}



#ifdef ICANON
struct termio oldtty, tty;
#else
struct sgttyb oldtty, tty;
struct tchars oldtch, tch;
#endif

int rdfd = 0;		/* Read File descriptor for ioctls & reads */
int wrfd = 1;		/* Write File descriptor for ioctls & reads */

/*
 * mode(n)
 *  3: save old tty stat, set raw mode with flow control
 *  2: set XON/XOFF for sb/sz with ZMODEM 
 *  1: save old tty stat, set raw mode 
 *  0: restore original tty mode
 */
mode(n)
{
	static did0 = FALSE;

	vfile("mode:%d", n);
	switch(n) {
	case 2:		/* Un-raw mode used by sz, sb when -g detected */
		if(!did0)
			(void) ioctl(rdfd, TCGETA, &oldtty);
		tty = oldtty;

		tty.c_iflag = BRKINT|IXON;

		tty.c_oflag = 0;	/* Transparent output */

		tty.c_cflag &= ~PARENB;	/* Disable parity */
		tty.c_cflag |= CS8;	/* Set character size = 8 */


		tty.c_lflag = 0;
		tty.c_cc[VINTR] = -1;	/* Interrupt char */
		tty.c_cc[VQUIT] = -1;			/* Quit char */
#ifdef NFGVMIN
		tty.c_cc[VMIN] = 1;
#else
		tty.c_cc[VMIN] = 3;	 /* This many chars satisfies reads */
#endif
		tty.c_cc[VTIME] = 1;	/* or in this many tenths of seconds */

		(void) ioctl(rdfd, TCSETAW, &tty);
		did0 = TRUE;
		return IZOK;
	case 1:
	case 3:
		if(!did0)
			(void) ioctl(rdfd, TCGETA, &oldtty);
		tty = oldtty;

		tty.c_iflag = n==3 ? (IGNBRK|IXOFF) : IGNBRK;

		 /* No echo, crlf mapping, INTR, QUIT, delays, no erase/kill */
		tty.c_lflag &= ~(ECHO | ICANON | ISIG);

		tty.c_oflag = 0;	/* Transparent output */

		tty.c_cflag &= ~PARENB;	/* Same baud rate, disable parity */
		tty.c_cflag |= CS8;	/* Set character size = 8 */
#ifdef NFGVMIN
		tty.c_cc[VMIN] = 1; /* This many chars satisfies reads */
#else
		tty.c_cc[VMIN] = RSIZE; /* This many chars satisfies reads */
#endif
		tty.c_cc[VTIME] = 1;	/* or in this many tenths of seconds */
		(void) ioctl(rdfd, TCSETAW, &tty);
		did0 = TRUE;
		return IZOK;
	case 0:
		if(!did0)
			return IZERROR;
		(void) ioctl(wrfd, TCSBRK, 1);	/* Wait for output to drain */
		(void) ioctl(rdfd, TCFLSH, 1);	/* Flush input queue */
		(void) ioctl(rdfd, TCSETAW, &oldtty);	/* Restore modes */
		(void) ioctl(wrfd, TCXONC,1);	/* Restart output */

		return IZOK;
	default:
		return IZERROR;
	}
}

void
sendbrk()
{
#define CANBREAK
	ioctl(wrfd, TCSBRK, 0);
}

FILE *fout;


/*
 * Putsec writes the n characters of buf to receive file fout.
 *  This code is specific to Unix like systems!!!
 */
void
putsec(buf, n)
char *buf;
register n;
{
	register char *p;

	if (n == 0)
		return;
	for (p=buf; --n>=0; )
		putc( *p++, fout);
}
/*
 * Log an error
 */
/*VARARGS1*/
void
zperr(s,p,u)
char *s, *p, *u;
{
	if (Verbose <= 0)
		return;
	fprintf(stderr, s, p, u);
	fprintf(stderr, "\n");
}
void
zbitch(s)
char *s;
{
	if (Verbose <= 0)
		return;
	fprintf(stderr, s);
	fprintf(stderr, "\n");
}


/*
 * Close the receive dataset
 */
void
closeit(status)
{
	time_t timep[2];

	fclose(fout);
	if (Modtime) {
		timep[0] = time(NULL);
		timep[1] = Modtime;
		utime(Pathname, timep);
	}
}
/* ------------------------------------------------------------------ */


void
purgeline()
{
	Rxleft=0;
#ifdef POSIX
	tcflush(rdfd, TCIFLUSH);
#else
	ioctl(rdfd, TCFLSH, "TCFLSH", 0);
#endif
}


/*
 * This version of readline is reasoably well suited for
 * reading many characters.
 *
 * timeout is in tenths of seconds
 * no wait must not be set on this f.d.
 */

jmp_buf tohere;		/* For the interrupt on RX timeout */

void alrm(c)
{
	longjmp(tohere, -1);
}

readline(timeout)
int timeout;
{
	register int c;
	register char *p;

	if (--Rxleft>=0)
		return (*Rxcdq++ & 0377);
	Rxleft = 0;
	Rxcdq=Rxbuf;

	FD_ZERO(&rfdreadfds);
	FD_SET(rdfd, &rfdreadfds);
	FD_ZERO(&rfdexceptfds);
	FD_SET(rdfd, &rfdexceptfds);
	rfdtimeout.tv_sec = timeout/10;
	rfdtimeout.tv_usec = (timeout * 100000L) % 1000000;
	c = select(rdfd+1, &rfdreadfds, (fd_set *)0, &rfdexceptfds, &rfdtimeout);
	if ( c < 0) {
		vfile("readline select");
		return TIMEOUT;
	}
	if ( c == 0 || !FD_ISSET(rdfd, &rfdreadfds)) {
		if (Verbose>6)
			vfile("TIMEOUT", Rxleft);
		return TIMEOUT;
	}
	if (setjmp(tohere)) {
		Rxleft = 0;
		if (Verbose>1) {
			fprintf(stderr, "Readline: alarm TIMEOUT\n");
			fflush(stderr);
		}
		return TIMEOUT;
	}
	signal(SIGALRM, alrm); alarm(2);
	if (Verbose > 6)
		vfile("readline: calling read");
	Rxleft=read(rdfd, Rxbuf, RSIZE);
	alarm(0);
	if (Verbose>6)
		vfile("Read returned %d bytes\n", Rxleft);
	if (Rxleft <= 0) {
		Rxleft = 0;
		return TIMEOUT;
	}
	if (Verbose > 8) {
		vfile("Read returned: %d", Rxleft);
		for (p = Rxbuf, c = Rxleft; --c >= 0; ) {
			vfile("%02x ", (*p++ &0377));
		}
	}
	--Rxleft;
	return (*Rxcdq++ & 0377);
}


void
sendline(c)
{
	if (--Txleft >= 0)
		*Txcq++ = c;
	else {
		Txleft = 0;
		flushmoc();
		--Txleft;
		*Txcq++ = c;
	}
}

void
flushmoc()
{
	register int n;

	n = Txcq - Txlbuf;
	Txcq=Txlbuf;  Txleft = Txnum;
	if (n == 0)
		return;
	write(wrfd, Txlbuf, n);
	return;
}

/*
 *  Wait for the modem line outbuffer to drain
 */
void
flushmo()
{
	fflush(stdout);
	flushmoc();
#ifdef POSIX
	tcdrain(wrfd);
#else
	ioctl(wrfd, TCSBRK, 1);
#endif
}

void
purgemo()
{
	Txcq=Txlbuf;  Txleft = Txnum;
#ifdef POSIX
	tcflush(wrfd, TCOFLUSH);
#else
	ioctl(wrfd, TCFLSH, 1);
#endif
}




/*
 * return non 0 if modem char is available
 */
checkline()
{
	int c;

	if (Rxleft > 0)
		return 1;
	FD_ZERO(&rfdreadfds);
	FD_SET(rdfd, &rfdreadfds);
	FD_ZERO(&rfdexceptfds);
	FD_SET(rdfd, &rfdexceptfds);
	rfdtimeout.tv_sec = 0;
	rfdtimeout.tv_usec = 0;
	c = select(rdfd+1, &rfdreadfds, (fd_set *)0, &rfdexceptfds, &rfdtimeout);
	if (c == 0)
		return 0;
	if ( FD_ISSET(rdfd, &rfdexceptfds)) {
		vfile("checkline: Exception");
		return 0;
	}
	if ( FD_ISSET(rdfd, &rfdreadfds)) {
		Rxleft=read(rdfd, Rxbuf, RSIZE);
		if (Rxleft <= 0)
			return 0;
		return 1;
	}
	return(0);
}

