import java.io.*;
import java.net.*;
import java.util.*;

/**
 * IACReader contains code the send to the server the propper responce to an 
 * IAC (Interpret As Command) request made by the server.
 * <p>
 * From RFC # 1091 a typical exchange should look like:
 * <p>
 * Host1: IAC DO TERMINAL-TYPE
 * <p>
 * Host2: IAC WILL TERMINAL-TYPE
 * <p>
 * (Host1 is now free to request status information at any time.)
 * <p>
 * Host1: IAC SB TERMINAL-TYPE SEND IAC SE
 * <p>
 * Host2: IAC SB TERMINAL-TYPE IS IBM-3278-2 IAC SE
 * <p>
 * <p>
 * The NEW ENVIROMENT option is requested by almost every machine I have 
 * tested this with, but the accompanying subnegotiation request contains an
 * empty options list from the server.
 *
 * @author David Gardner
 * @see <a href="http://www.rfc-editor.org/rfc/rfc854.txt">RFC 854</a>
 * @see <a href="http://www.rfc-editor.org/rfc/rfc1091.txt">RFC 1091</a>
 * @see <a href="http://www.rfc-editor.org/rfc/rfc1572.txt">RFC 1572</a>
 */
public class IACReader {
  private PrintWriter out = null;
  private InputStream in = null;

  /* Telnet Commands */
  private final char IAC = 255;   // Interpret As Command
  private final char SB = 250;    // Sub Negotiation command
  private final char SE = 240;    // end of Sub Negotiation command
  private final char IS = 0;
  
  /* Telnet verbs */
  private final char WILL = 251;  // Will support option
  private final char WONT = 252;  // Won't support
  private final char _DO  = 253;  // do is a researved  word, Do support
  private final char DONT = 254;  // don't support option

  /* Telnet Options */
  private final char TERMINAL = 24;         // terminal
  private final char ECHO = 1;              // echo
  private final char SUPPRESS_GA = 3;       // Suppress Go Ahead
  private final char NEW_ENVIRONMENT = 39;  // rfc 1572
  
  /* Telnet Enviroment Command Codes */
  private final char SEND = 1;
  private final char INFO = 2;
  private final char VAR = 0;
  private final char VALUE = 1;
  private final char ESC = 2;
  
 /**
  * @param inStream   The InputStream of the telnet socket.
  * @param outWriter  A PrintWriter object pointing to the output stream of a 
  *                   socket
  */
  IACReader (InputStream inStream, PrintWriter outWriter) {
    in = inStream;
    out = outWriter;
  }

 /**
  * Handles IAC messages sent from the server and constructs the proper
  * responce. IAC messages may be simple IAC COMMAND messages,
  * IAC VERB COMMAND messages which are handled by iacVerb(), 
  * or IAC SB OPTION [option list] IAC SE messages which are handled by
  * subNegotiate().
  *
  * @return       <code>true</code> if an error has occured.
  */
  public boolean exec () {
    int i;  
    try {
      if ((i = in.read ()) >= 0) {
        if (i == SB)
          return subNegotiate ();
        else if (i >= WILL && i <= DONT) // is verb
          return iacVerb (i);
        else
          return false; //IAC COMMAND not implemented
      } else
        return true;
    } catch (IOException e) {
      return true;
    }
  } 
 
 /**
  * Handles IAC Verb messages.
  *
  * @param verb   The first byte sent by server following the IAC char.
  *               This is a verb specifying the server's desire to either
  *               WILL, WONT, DO, or DONT an option
  * @return       <code>true</code> if an error has occured.
  */
  private boolean iacVerb (int verb) {
    char rtrn [] = new char [3];
    int option;
    boolean bSupported;
    
    try {
      if ((option = in.read ()) >= 0) {
        rtrn [0] = IAC;
        rtrn [2] = (char)option;

        bSupported = supported (option);

        if (verb == _DO) {
          if(bSupported) 
            rtrn [1] = WILL;
          else
            rtrn [1] = WONT;
        } else if (verb == WILL) {
          if (bSupported) 
            rtrn [1] = _DO;
          else 
            rtrn [1] = DONT;
        } else if (verb == DONT) {
          rtrn [1] = WONT;
        } else {//assume won't
          rtrn [1] = DONT;
        }

        out.write (rtrn);
        return out.checkError ();
      } else
        return true;
    } catch (IOException e) {
      return true;
    }
  } 
  
 /**
  * Reads in Subnegotiation requests sent by server and constructs the proper
  * responce. Unlike other IAC requests Subnegotiation requests may
  * be of unspecified length.
  * 
  * @return       <code>true</code> if an error has occured.
  */
  private boolean subNegotiate () {
    LinkedList<Integer> list = new LinkedList<Integer> () ;
    int i, option;
    String sRtrn;
    
    try {
      if ((option = in.read()) >= 0) {
        while ((i = in.read()) >= 0 && i != SE) {
          list.add (i);
        }
      } else
        return true;
    } catch (IOException e) {
      return true;
    }

    sRtrn = new String(new char [] {IAC, SB, (char)option ,IS});
    if (option == TERMINAL) //rfc 1091
      sRtrn = sRtrn + "ANSI";
    else if (option == NEW_ENVIRONMENT) { 
      //haven't found a system that sends something other than a blank list
      if ((!list.isEmpty()) && (i = list.poll()) == SEND) {
        while ((!list.isEmpty()) && (i = list.poll()) != IAC ) {}
      }
    }

    sRtrn = sRtrn + new String(new char[] {IAC, SE});
      
    
    out.write (sRtrn);
    return out.checkError();
  }
  
  private boolean supported (int i) {
    if (i == TERMINAL || i == ECHO || i == SUPPRESS_GA ||
        i == NEW_ENVIRONMENT)
      return true;
    else
      return false;
  } 
}
