/*
 * Copyright (c) 1988, 1990 Regents of the University of California.
 * 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.
 */

/*
 * From: @(#)terminal.c	5.3 (Berkeley) 3/22/91
 */
char terminal_rcsid[] = 
  "$Id: terminal.cc,v 1.25 1999/12/12 19:48:05 dholland Exp $";

#include <arpa/telnet.h>
#include <sys/types.h>
#include <sys/time.h>
#include <termios.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "ring.h"
#include "defines.h"
#include "externs.h"
#include "types.h"
#include "proto.h"
#include "terminal.h"

static int TerminalWrite(const char *buf, int n);
static int TerminalRead(char *buf, int n);

ringbuf ttyoring, ttyiring;

#ifndef VDISCARD
cc_t termFlushChar;
#endif

#ifndef VLNEXT
cc_t termLiteralNextChar;
#endif

#ifndef VSUSP
cc_t termSuspChar;
#endif

#ifndef VWERASE
cc_t termWerasChar;
#endif

#ifndef VREPRINT
cc_t termRprntChar;
#endif

#ifndef VSTART
cc_t termStartChar;
#endif

#ifndef VSTOP
cc_t termStopChar;
#endif

#ifndef VEOL
cc_t termForw1Char;
#endif

#ifndef VEOL2
cc_t termForw2Char;
#endif

#ifndef VSTATUS
cc_t termAytChar;
#endif

/*
 * initialize the terminal data structures.
 */
void init_terminal(void) {
    if (ttyoring.init(2*BUFSIZ, ttysink, NULL) != 1) {
	exit(1);
    }
    if (ttyiring.init(BUFSIZ, NULL, ttysrc) != 1) {
	exit(1);
    }
    autoflush = TerminalAutoFlush();
}


/*
 *		Send as much data as possible to the terminal.
 *              if arg "drop" is nonzero, drop data on the floor instead.
 *
 *		Return value:
 *			-1: No useful work done, data waiting to go out.
 *			 0: No data was waiting, so nothing was done.
 *			 1: All waiting data was written out.
 *			 n: All data - n was written out.
 */
int ttyflush(int drop) {
    datasink *s = NULL;
    if (drop) {
	TerminalFlushOutput();
	s = ttyoring.setsink(nullsink);
    }
    int rv = ttyoring.flush();
    if (s) ttyoring.setsink(s);
    return rv;
}



/*
 * These routines decides on what the mode should be (based on the values
 * of various global variables).
 */
int getconnmode(void) {
    extern int linemode;
    int mode = 0;
#ifdef	KLUDGELINEMODE
    extern int kludgelinemode;
#endif

    if (In3270)
	return(MODE_FLOW);

    if (my_want_state_is_dont(TELOPT_ECHO))
	mode |= MODE_ECHO;

    if (localflow)
	mode |= MODE_FLOW;

    if (my_want_state_is_will(TELOPT_BINARY))
	mode |= MODE_INBIN;

    if (his_want_state_is_will(TELOPT_BINARY))
	mode |= MODE_OUTBIN;

#ifdef	KLUDGELINEMODE
    if (kludgelinemode) {
	if (my_want_state_is_dont(TELOPT_SGA)) {
	    mode |= (MODE_TRAPSIG|MODE_EDIT);
	    if (dontlecho && (clocks.echotoggle > clocks.modenegotiated)) {
		mode &= ~MODE_ECHO;
	    }
	}
	return(mode);
    }
#endif
    if (my_want_state_is_will(TELOPT_LINEMODE))
	mode |= linemode;
    return(mode);
}

void setconnmode(int force) {
    int newmode;

    newmode = getconnmode()|(force?MODE_FORCE:0);

    TerminalNewMode(newmode);

}


void setcommandmode(void) {
    TerminalNewMode(-1);
}


/*********************/

static int tout;		/* Output file descriptor */
static int tin;			/* Input file descriptor */


class ttysynk : public datasink {
  public:
    virtual int write(const char *buf, int len) {
	return TerminalWrite(buf, len);
    }
    virtual int writeurg(const char *buf, int len) {
	return TerminalWrite(buf, len);
    }
};

class ttysorc : public ringbuf::source {
    virtual int read(char *buf, int maxlen) {
	int l = TerminalRead(buf, maxlen);
	if (l<0 && errno==EWOULDBLOCK) l = 0;
	else if (l==0 && MODE_LOCAL_CHARS(globalmode) && isatty(tin)) {
	    /* EOF detection for line mode!!!! */
	    /* must be an EOF... */
	    *buf = termEofChar;
	    l = 1;
	}
	return l;
    }
};

static ttysynk chan1;
static ttysorc chan2;
datasink *ttysink = &chan1;
ringbuf::source *ttysrc = &chan2;


struct termios old_tc;
struct termios new_tc;

#ifndef	TCSANOW

#if defined(TCSETS)
#define	TCSANOW		TCSETS
#define	TCSADRAIN	TCSETSW
#define	tcgetattr(f, t) ioctl(f, TCGETS, (char *)t)

#elif defined(TCSETA)
#define	TCSANOW		TCSETA
#define	TCSADRAIN	TCSETAW
#define	tcgetattr(f, t) ioctl(f, TCGETA, (char *)t)

#else
#define	TCSANOW		TIOCSETA
#define	TCSADRAIN	TIOCSETAW
#define	tcgetattr(f, t) ioctl(f, TIOCGETA, (char *)t)

#endif

#define	tcsetattr(f, a, t) ioctl(f, a, (char *)t)
#define	cfgetospeed(ptr)	((ptr)->c_cflag&CBAUD)
#ifdef CIBAUD
#define	cfgetispeed(ptr)	(((ptr)->c_cflag&CIBAUD) >> IBSHIFT)
#else
#define	cfgetispeed(ptr)	cfgetospeed(ptr)
#endif

#endif /* no TCSANOW */


static void susp(int sig);

void tlink_init(void) {
#ifdef	SIGTSTP
    signal(SIGTSTP, susp);
#endif
    tout = fileno(stdout);
    tin = fileno(stdin);
}

int tlink_getifd(void) {
    return tin;
}

int tlink_getofd(void) {
    return tout;
}

static int TerminalWrite(const char *buf, int n) {
    int r;
    do {
	r = write(tout, buf, n);
    } while (r<0 && errno==EINTR);
    if (r<0 && (errno==ENOBUFS || errno==EWOULDBLOCK)) r = 0;
    return r;
}

static int TerminalRead(char *buf, int n) {
    int r;
    do {
	r = read(tin, buf, n);
    } while (r<0 && errno==EINTR);
    return r;
}

#ifdef	SIGTSTP
static void susp(int /*sig*/) {
    if ((rlogin != _POSIX_VDISABLE) && rlogin_susp())
	return;
    if (localchars)
	sendsusp();
}
#endif

/*
 * TerminalNewMode - set up terminal to a specific mode.
 *	MODE_ECHO: do local terminal echo
 *	MODE_FLOW: do local flow control
 *	MODE_TRAPSIG: do local mapping to TELNET IAC sequences
 *	MODE_EDIT: do local line editing
 *
 *	Command mode:
 *		MODE_ECHO|MODE_EDIT|MODE_FLOW|MODE_TRAPSIG
 *		local echo
 *		local editing
 *		local xon/xoff
 *		local signal mapping
 *
 *	Linemode:
 *		local/no editing
 *	Both Linemode and Single Character mode:
 *		local/remote echo
 *		local/no xon/xoff
 *		local/no signal mapping
 */

void TerminalNewMode(int f)
{
    static int prevmode = 0;
    struct termios tmp_tc;

    int onoff;
    int old;
    cc_t esc;

    globalmode = f&~MODE_FORCE;
    if (prevmode == f)
	return;

    /*
     * Write any outstanding data before switching modes
     * ttyflush() returns 0 only when there is no more data
     * left to write out, it returns -1 if it couldn't do
     * anything at all, otherwise it returns 1 + the number
     * of characters left to write.
     */
    old = ttyflush(SYNCHing|flushout);
    if (old < 0 || old > 1) {
	tcgetattr(tin, &tmp_tc);
	do {
	    /*
	     * Wait for data to drain, then flush again.
	     */
	    tcsetattr(tin, TCSADRAIN, &tmp_tc);
	    old = ttyflush(SYNCHing|flushout);
	} while (old < 0 || old > 1);
    }

    old = prevmode;
    prevmode = f&~MODE_FORCE;
    tmp_tc = new_tc;

    if (f&MODE_ECHO) {
	tmp_tc.c_lflag |= ECHO;
	tmp_tc.c_oflag |= ONLCR;
	if (crlf)
		tmp_tc.c_iflag |= ICRNL;
    } 
    else {
	tmp_tc.c_lflag &= ~ECHO;
	tmp_tc.c_oflag &= ~ONLCR;
	if (crlf) tmp_tc.c_iflag &= ~ICRNL;
    }

    if ((f&MODE_FLOW) == 0) {
	tmp_tc.c_iflag &= ~(IXANY|IXOFF|IXON);
    } 
    else {
	tmp_tc.c_iflag |= IXANY|IXOFF|IXON;
    }

    if ((f&MODE_TRAPSIG) == 0) {
	tmp_tc.c_lflag &= ~ISIG;
	localchars = 0;
    } 
    else {
	tmp_tc.c_lflag |= ISIG;
	localchars = 1;
    }

    if (f&MODE_EDIT) {
	tmp_tc.c_lflag |= ICANON;
    } 
    else {
	tmp_tc.c_lflag &= ~ICANON;
	tmp_tc.c_iflag &= ~ICRNL;
	tmp_tc.c_cc[VMIN] = 1;
	tmp_tc.c_cc[VTIME] = 0;
    }

    if ((f&(MODE_EDIT|MODE_TRAPSIG)) == 0) {
#ifdef VLNEXT
	tmp_tc.c_cc[VLNEXT] = (cc_t)(_POSIX_VDISABLE);
#endif
    }

    if (f&MODE_SOFT_TAB) {
#ifdef OXTABS
	tmp_tc.c_oflag |= OXTABS;
#endif
#ifdef TABDLY
	tmp_tc.c_oflag &= ~TABDLY;
	tmp_tc.c_oflag |= TAB3;
#endif
    } 
    else {
#ifdef OXTABS
	tmp_tc.c_oflag &= ~OXTABS;
#endif
#ifdef TABDLY
	tmp_tc.c_oflag &= ~TABDLY;
#endif
    }

    if (f&MODE_LIT_ECHO) {
#ifdef ECHOCTL
	tmp_tc.c_lflag &= ~ECHOCTL;
#endif
    } 
    else {
#ifdef ECHOCTL
	tmp_tc.c_lflag |= ECHOCTL;
#endif
    }

    if (f == -1) {
	onoff = 0;
    } 
    else {
	if (f & MODE_INBIN) {
		tmp_tc.c_iflag &= ~ISTRIP;
	}
	else {
		// Commented this out 5/97 so it works with 8-bit characters
		// ...and put it back 12/99 because it violates the RFC and
		// breaks SunOS.
	 	tmp_tc.c_iflag |= ISTRIP;
	}
	if (f & MODE_OUTBIN) {
		tmp_tc.c_cflag &= ~(CSIZE|PARENB);
		tmp_tc.c_cflag |= CS8;
		tmp_tc.c_oflag &= ~OPOST;
	} else {
		tmp_tc.c_cflag &= ~(CSIZE|PARENB);
		tmp_tc.c_cflag |= old_tc.c_cflag & (CSIZE|PARENB);
		tmp_tc.c_oflag |= OPOST;
	}
	onoff = 1;
    }

    if (f != -1) {
#ifdef	SIGTSTP
	signal(SIGTSTP, susp);
#endif	/* SIGTSTP */

#ifdef	SIGINFO
	signal(SIGINFO, ayt);
#endif	SIGINFO

#if defined(NOKERNINFO)
	tmp_tc.c_lflag |= NOKERNINFO;
#endif
	/*
	 * We don't want to process ^Y here.  It's just another
	 * character that we'll pass on to the back end.  It has
	 * to process it because it will be processed when the
	 * user attempts to read it, not when we send it.
	 */
#ifdef VDSUSP
	tmp_tc.c_cc[VDSUSP] = (cc_t)(_POSIX_VDISABLE);
#endif
	/*
	 * If the VEOL character is already set, then use VEOL2,
	 * otherwise use VEOL.
	 */
	esc = (rlogin != _POSIX_VDISABLE) ? rlogin : escapechar;
	if ((tmp_tc.c_cc[VEOL] != esc)
#ifdef VEOL2
	    && (tmp_tc.c_cc[VEOL2] != esc)
#endif
	    ) {
		if (tmp_tc.c_cc[VEOL] == (cc_t)(_POSIX_VDISABLE))
		    tmp_tc.c_cc[VEOL] = esc;
#ifdef VEOL2
		else if (tmp_tc.c_cc[VEOL2] == (cc_t)(_POSIX_VDISABLE))
		    tmp_tc.c_cc[VEOL2] = esc;
#endif
	}
    } 
    else {

#ifdef	SIGINFO
	signal(SIGINFO, ayt_status);
#endif	SIGINFO

#ifdef	SIGTSTP
	signal(SIGTSTP, SIG_DFL);
/*	(void) sigsetmask(sigblock(0) & ~(1<<(SIGTSTP-1))); */
#endif	/* SIGTSTP */

	tmp_tc = old_tc;
    }
    if (tcsetattr(tin, TCSADRAIN, &tmp_tc) < 0)
	tcsetattr(tin, TCSANOW, &tmp_tc);

    ioctl(tin, FIONBIO, (char *)&onoff);
    ioctl(tout, FIONBIO, (char *)&onoff);

#if defined(TN3270)
    if (noasynchtty == 0) {
	ioctl(tin, FIOASYNC, (char *)&onoff);
    }
#endif	/* defined(TN3270) */

}

#ifndef	B19200
#define B19200 B9600
#endif

#ifndef	B38400
#define B38400 B19200
#endif

#ifndef B57600
#define B57600 B38400
#endif

#ifndef B115200
#define B115200 B57600
#endif

/*
 * This code assumes that the values B0, B50, B75...
 * are in ascending order.  They do not have to be
 * contiguous.
 */
struct termspeeds {
	long speed;
	long value;
} termspeeds[] = {
	{ 0,      B0 },      { 50,     B50 },    { 75,     B75 },
	{ 110,    B110 },    { 134,    B134 },   { 150,    B150 },
	{ 200,    B200 },    { 300,    B300 },   { 600,    B600 },
	{ 1200,   B1200 },   { 1800,   B1800 },  { 2400,   B2400 },
	{ 4800,   B4800 },   { 9600,   B9600 },  { 19200,  B19200 },
	{ 38400,  B38400 },  { 57600,  B57600 }, { 115200, B115200 },
	{ -1,     B115200 }
};

void TerminalSpeeds(long *ispeed, long *ospeed) {
    register struct termspeeds *tp;
    register long in, out;

    out = cfgetospeed(&old_tc);
    in = cfgetispeed(&old_tc);
    if (in == 0)
	in = out;

    tp = termspeeds;
    while ((tp->speed != -1) && (tp->value < in))
	tp++;
    *ispeed = tp->speed;

    tp = termspeeds;
    while ((tp->speed != -1) && (tp->value < out))
	tp++;
    *ospeed = tp->speed;
}

int TerminalWindowSize(long *rows, long *cols) {
#ifdef	TIOCGWINSZ
    struct winsize ws;

    if (ioctl(fileno(stdin), TIOCGWINSZ, (char *)&ws) >= 0) {
	*rows = ws.ws_row;
	*cols = ws.ws_col;
	return 1;
    }
#endif	/* TIOCGWINSZ */
    return 0;
}


/*
 * EmptyTerminal - called to make sure that the terminal buffer is 
 * empty. Note that we consider the buffer to run all the way to the 
 * kernel (thus the select).
 */
void EmptyTerminal(void) {
    fd_set o;
    FD_ZERO(&o);
    
    if (TTYBYTES() == 0) {
	FD_SET(tout, &o);
	select(tout+1, NULL, &o, NULL, NULL);	/* wait for TTLOWAT */
    } 
    else {
	while (TTYBYTES()) {
	    ttyflush(0);
	    FD_SET(tout, &o);
	    select(tout+1, NULL, &o, NULL, NULL); /* wait for TTLOWAT */
	}
    }
}

int
TerminalAutoFlush(void)
{
#if	defined(LNOFLSH)
    int flush;

    ioctl(tin, TIOCLGET, (char *)&flush);
    return !(flush&LNOFLSH);	/* if LNOFLSH, no autoflush */
#else	/* LNOFLSH */
    return 1;
#endif	/* LNOFLSH */
}

/*
 * Flush output to the terminal
 */
    void
TerminalFlushOutput()
{
#ifdef	TIOCFLUSH
    (void) ioctl(fileno(stdout), TIOCFLUSH, (char *) 0);
#else
    (void) ioctl(fileno(stdout), TCFLSH, (char *) 0);
#endif
}

    void
TerminalSaveState()
{
#ifndef	USE_TERMIO
    ioctl(0, TIOCGETP, (char *)&ottyb);
    ioctl(0, TIOCGETC, (char *)&otc);
    ioctl(0, TIOCGLTC, (char *)&oltc);
    ioctl(0, TIOCLGET, (char *)&olmode);

    ntc = otc;
    nltc = oltc;
    nttyb = ottyb;

#else	/* USE_TERMIO */
    tcgetattr(0, &old_tc);

    new_tc = old_tc;

#ifndef	VDISCARD
    termFlushChar = CONTROL('O');
#endif
#ifndef	VWERASE
    termWerasChar = CONTROL('W');
#endif
#ifndef	VREPRINT
    termRprntChar = CONTROL('R');
#endif
#ifndef	VLNEXT
    termLiteralNextChar = CONTROL('V');
#endif
#ifndef	VSTART
    termStartChar = CONTROL('Q');
#endif
#ifndef	VSTOP
    termStopChar = CONTROL('S');
#endif
#ifndef	VSTATUS
    termAytChar = CONTROL('T');
#endif
#endif	/* USE_TERMIO */
}

void TerminalDefaultChars(void) {
#ifndef	USE_TERMIO
    ntc = otc;
    nltc = oltc;
    nttyb.sg_kill = ottyb.sg_kill;
    nttyb.sg_erase = ottyb.sg_erase;
#else	/* USE_TERMIO */
    memcpy(new_tc.c_cc, old_tc.c_cc, sizeof(old_tc.c_cc));
#ifndef	VDISCARD
    termFlushChar = CONTROL('O');
#endif
#ifndef	VWERASE
    termWerasChar = CONTROL('W');
#endif
#ifndef	VREPRINT
    termRprntChar = CONTROL('R');
#endif
#ifndef	VLNEXT
    termLiteralNextChar = CONTROL('V');
#endif
#ifndef	VSTART
    termStartChar = CONTROL('Q');
#endif
#ifndef	VSTOP
    termStopChar = CONTROL('S');
#endif
#ifndef	VSTATUS
    termAytChar = CONTROL('T');
#endif
#endif	/* USE_TERMIO */
}
