/* Telnet TTY class -- Williams
   This class provides a base for writing objects
   that do the telnet protocol.

   Bare minimum options. Assumes host will accept
   "go ahead suppression"

*/
import java.io.*;
import java.net.*;


public class TelnetTTY extends Thread {
    // Telnet constants
    final static int IAC=255;
    final static int DONT=254;
    final static int DO=253;
    final static int WONT=252;
    final static int WILL=251;
    final static int NOP=241;
    final static int OP_ECHO=1;
    final static int OP_SGA=3;

    // default NVT has echo off, go ahead on in both directions
    // however, we assume Suppress GA will work



    protected Socket sock;
    protected InputStreamReader rdr;
    protected OutputStreamWriter wrt;
    protected boolean errflag=false;
    protected IOException ioerror; // if an error, what was it?
    private boolean crflag=false;
    private boolean echoflag=false;
    protected boolean stopthread=false;
    // Constructor
    public TelnetTTY(String host, int port) throws IOException,
       UnknownHostException {
	TelnetTTYCons(host,port);
    }
    // Constructor
    public TelnetTTY(String host) throws IOException,
       UnknownHostException {
	TelnetTTYCons(host,23);
    }

    // Core code for constructors
    public void TelnetTTYCons(String host, int port) throws IOException,
       UnknownHostException {
	sock=new Socket(host,port);
	rdr=new InputStreamReader(sock.getInputStream());
	wrt=new OutputStreamWriter(sock.getOutputStream());
	start();
	// some hosts won't do anything until you start talking
	sendc(IAC); sendc(DO); sendc(OP_SGA);
	sendc(IAC); sendc(WILL); sendc(OP_SGA);
    }

    // read error flag and information 
    public boolean getErrorFlag() {
	return errflag || (ioerror!=null);
    }

    public IOException getErrorException() {
	return ioerror;
    }

    // send a character to the remote 
    public void send(int c) throws IOException {
	if (c=='\n') wrt.write('\r');  // LF=>CR LF
	wrt.write(c);
	if (c=='\r') wrt.write(0);    // CR=>CR NUL
	if (c==IAC) wrt.write(IAC); // double IACs in stream
	wrt.flush();
    }

    // Send an entire string
    public void send(String s) throws IOException {
	for (int i=0;i<s.length();i++) {
	    int c=(int)s.charAt(i);
	    if (c=='\n') wrt.write('\r');  // LF=>CR LF
	    wrt.write(c);
	    if (c=='\r') wrt.write(0);    // CR=>CR NUL	   
	    if (c==IAC) wrt.write(IAC);
	}
	wrt.flush();
    }

    // This thread listens for incoming characters
    public void run() {
	int c;
	while (!stopthread) {
  	    c=getChar();
	    if (c==-1||ioerror!=null) break;
	    if (c==IAC) {
		processCmd();
		if (ioerror!=null) return;
	    }
	    else
		processChar(c);
	}
	errflag=true;
    }

    // Get a character from the stream
    protected int getChar() {
	try {
	    int c;
	    if (crflag) {
		crflag=false;
		c=rdr.read();
		if (echoflag) wrt.write(c);
		if (c!=0) return c; // ignore CR NUL
	    }
	    c=rdr.read();
	    if (echoflag) wrt.write(c);
	    if (c=='\r') crflag=true;
	    return c;
	}
	catch (IOException e) {
	    ioerror=e;
	    return -1;
	}
    }

    // Send a "bare" character
    private void sendc(int c) {
	try {
	    wrt.write(c);
	    wrt.flush();
	}
	catch (IOException e) {
	    ioerror=e;
	}
    }

    // Process a command
    protected synchronized void processCmd() {
	int c;
	c=getChar();
	if (ioerror!=null) return;
	switch (c) {
	case IAC:
	    processChar(IAC);  // escaped 8-bit character
	case NOP: // nop
	    return;
	case DO:  // DO
	    int state=WONT; // wont
	    c=getChar();
	    if (ioerror!=null) return;
	    if (c==OP_SGA) {
		state=WILL; // will
	    }
	    if (c==OP_ECHO && !echoflag) {
		state=WILL; // will
		echoflag=true;
	    }
	    sendc(IAC);
	    sendc(state);
	    sendc(c);
	    return;

	case WILL:  // WILL
	    c=getChar();
	    return;

	case WONT:  // WONT
	    c=getChar();
	    if (c==OP_SGA) {
		// hmm... we have to have SGA
	    }
	    return;

	case DONT:  
	    c=getChar();
	    if (c==OP_ECHO && echoflag) {
		echoflag=false;
		sendc(IAC);
		sendc(WONT);
		sendc(OP_ECHO);
	    }
	    if (c==OP_SGA) {
		// hmm... we have to have SGA
	    }
	    return;

	// do more IAC commands
	default:       // just for debugging purposes
	    System.out.println("\n[CMD: " + c + "]");
	}
    }


    // process incoming characters - subclass will override
    protected synchronized void processChar(int c) {
	if (c<32 && c != 10 && c !=13 && c !=9 && c !=8)
	    return;
	System.out.print((char)c);
    }

    // Test main
    public static void main(String args[]) throws Exception {
	int c;
	TelnetTTY tty = new TelnetTTY(args[0]);
	while ((c=System.in.read())!=-1) {
	    if (tty.getErrorFlag()) break;
	    tty.send(c);
	}
	System.out.println("Finished");
    }
}
