package talk;

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

/**
   An implementation of the ITalkSession interface
   @see ITalkSession
*/
public class TalkSession implements ITalkSession {
  private ITalkApp myApp;
  private ITalkView myView = null;
  private boolean iAmConnected = false;

  private InputStream fromRemote = null;
  private OutputStream toRemote = null;
  private Socket clientSock = null;

  private String myRuser;
  private String myRhost;
  private String myRtty;
  private String myLuser;
  private String myLhost;

  private java.util.logging.Logger logger = Logger.getLogger();

  public TalkSession(ITalkApp app) {
    myApp = app;
  }
  
  public void connect(String ruser, String rhost, String rtty,
		      String luser, String lhost) {
    if (!iAmConnected) {
      iAmConnected = true;
      myRuser = ruser;
      myRhost = rhost;
      myRtty = rtty;
      myLuser = luser;
      myLhost = lhost;
      if (myView == null)
	myView = myApp.createView(this, ruser+"@"+rhost);
      
      Thread th = new Thread() {
	public void run() {
	  establishConnection(myRuser, myRhost, myRtty, myLuser, myLhost);
	}
      };
      th.start();
    } else {
      myApp.displayErrorMessage("connect called when already connected");
    }
  }

  public void disconnect() {
    if (iAmConnected) {
      iAmConnected = false;
      myView.disconnected();
      try {
	if (fromRemote != null) 
	  fromRemote.close();
	if (toRemote != null)
	  toRemote.close();
	if (clientSock != null)
	  clientSock.close();
      } catch (IOException ex) {}
    }
  }

  public boolean isConnected() { return iAmConnected; }

  public void sendUserInput(String input) {
    if (iAmConnected) {
      try {
	toRemote.write(input.getBytes());
      } catch (IOException e) {
	logger.info("connection closed on write error");
	myView.displayStatusMessage("connection closed");
      }
    } else
      logger.severe("Trying to send input when not connected");
  }


  public void sendBackspace() {
    if (iAmConnected) {
      try {
	toRemote.write('\b');
      } catch (IOException e) {
	logger.info("connection closed on write error");
	myView.displayStatusMessage("connection closed");
      }
    } else
      logger.severe("Trying to send input when not connected");    
  }

  private void establishConnection(String ruser, String rhost, String rtty,
				   String luser, String lhost) {
    String[] args = {ruser, rhost, rtty, luser, lhost};
    logger.entering("TalkSession", "establishConnection", args);
    
    byte[] editChars = new byte[3];
    
    myView.displayStatusMessage("Setting up connection to "
			       + rhost + "...");

    try {
      TalkTransact talkd = new TalkTransact(rhost, ruser, rtty,
					    lhost, luser);
      myView.displayStatusMessage("Looking for invitation...");
      Socket sock = talkd.lookUp();
      logger.finer("Got sock from lookup " + sock);
      if (sock == null) {
	myView.displayStatusMessage("Lookup failed.");
	ServerSocket ssock = null;

	try {
	  ssock = new ServerSocket(0, 1, talkd.getLocalAddress());
	  ssock.setSoTimeout(30000); // 30 seconds
	  int tries = 1;

	  // loop until get a connection, or fail
	  while (sock == null) {
	    myView.displayStatusMessage("Announcing: try " + tries);
	    if (!talkd.announce()) {
	      myView.connectFailed("Announce failed: " +
				   talkd.getLastResponseStr());
	      break;
	    }
	    logger.fine("Leaving invite at "+ssock);
	    talkd.leaveInvite(ssock.getInetAddress(), ssock.getLocalPort());
	    logger.fine("Left invite, waiting 30 seconds..");
	    try {
	      sock = ssock.accept();
	    } catch (InterruptedIOException e) {}
	    logger.fine("Going to try again");
	    tries++;
	  }
	}
	finally {
	  if (ssock != null)
	    ssock.close();
	  talkd.deleteInvite(TalkTransact.MINE);
	}
      
	if (sock == null)	{	// failed
	  iAmConnected = false;
	  return;
	} else
	  myView.displayStatusMessage("Accepted connection from "
				      + sock.getInetAddress());
      } else {
	myView.displayStatusMessage("Found invitation, connected");
      }

      talkd = null;  // so it can get garbage collected
    
    // now connection is made, on sock.  Set up handlers to send local
    // input out over sock, and read remote input and add it to
    // remoteText.   This thread will handle reading input from the
    // remote end.  The GUI thread handles sending to text to the
    // remote end.
    
      clientSock = sock;
      fromRemote = sock.getInputStream();
      toRemote = sock.getOutputStream();

      toRemote.write("\b\0\0".getBytes());
      fromRemote.read(editChars);
    }
    catch (IOException e) {
      myView.connectFailed("Got exception while connecting: "+e);
      logger.throwing("TalkSession", "establishConnection", e);
      iAmConnected = false;
      return;
    }
    myView.connected(ruser+"@"+rhost);

    byte[] b = {0};

    try {
      while (true) {
	int i = fromRemote.read();
	if (i >= 0) {
	  if (i == editChars[0])
	    myView.displayBackspace();
	  else {
	    b[0] = (byte)i;
	    myView.displayRemoteInput(new String(b));
	  }
	} else
	  break;
      } // while
    }
    catch (IOException e) {}
    finally {
      logger.info("connection closed on read error");
      myView.displayStatusMessage("connection closed");
      disconnect();
    }
  }
}
