package beacon;

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

public class Chatter implements Runnable {
  boolean listener, lineBuffered;
  int port, rport;
  String rhost;
  ChatterListener chat;
  Socket connection;
  PrintWriter to;

  /** Create a new chatter, possibly a listener
    @param isListener True if this is to listen for connections
    @param cl This is the ChatListener to whom appropriate reports will be made
    */
  Chatter(boolean isListener, ChatterListener cl){
    listener = isListener;
    chat = cl;
  }

  /** Create a new chatter with an already open socket
    @param s The socket to use
    @param cl This is the ChatListener to whom appropriate reports will be made
   */
  Chatter(Socket s, ChatterListener cl){
    connection = s;
    chat = cl;
  }

  /** Specify who we are talking to
    @param host The host to connnect to
    @param p The port on the host
    */
  public void talkTo(String host, int p) 
       throws UnknownHostException, IOException {
    rhost = host;
    rport = p;
    connection = new Socket(rhost, rport);
  }

  /** Specify whether reads from the remote host should be line buffered
    @param yesorno true if it is line buffered
   */
  public void lineBuffered(boolean yesorno){
    lineBuffered = yesorno;
  }

  /** Start the Chat
   */
  public void go() 
       throws NotConnectedException, IOException {
	 if((!listener) && (connection == null)){
	   throw new NotConnectedException("Not connected.  Use talkTo() to specify endpoint");
	 }
	 else
	   (new Thread(this)).start();
  }


  /** The body of the chatter.
    Listens for connections if it is a listener, otherwise reads from the
    connection and makes callbacks.
   */
  public void run() {
    ServerSocket ls=null;
    Socket s;
    int ct = 0;

    // Listeners do this
    if(listener){
      System.out.println("Creating server");
      // Find an unused port
      ct = 2020;
      while(ls == null){
	try {
	  ls = new ServerSocket(ct);
	} catch (java.io.IOException e){
	  if(e instanceof java.net.BindException){
	    ct++;
	    ls = null;
	  }
	  else{
	    chat.exception(e, true);
	    return;
	  }
	}
      }
      port = ct;

      // Sit and listen
      while(true){
	try {
	  s = ls.accept();
	  Chatter nchat = new Chatter(s, chat);
	  nchat.lineBuffered(lineBuffered);
	  nchat.go();
	  chat.newConnection(nchat);
	} catch (IOException e){
	  chat.exception(e, false);
	}
      }
    }
    // Initiators do this
    else{
      // If it is line buffered, do one thing..
      if(lineBuffered){
	// Connect the reader/writer
	BufferedReader from;
	try {
	  from = new BufferedReader(new InputStreamReader(connection.getInputStream()));
	  to = new PrintWriter(connection.getOutputStream());
	} catch (IOException e){
	  chat.exception(e, true);
	  return;
	}

	// Read one line at a time
	while(true){
	  try {
	    if(from.ready()){
	      chat.received(from.readLine(), this);
	    }
	    else
	      Thread.currentThread().yield();
	  } catch (IOException e){
	    chat.exception(e, true);
	  }
	}
      }
      // If it isn't line buffered
      else {
	InputStream from;
	// Hook up the readier/writer
	byte buf[] = new byte[512];
	try {
	  from = connection.getInputStream();
	  to = new PrintWriter(connection.getOutputStream());
	} catch (IOException e){
	  chat.exception(e, true);
	  return;
	}

	// Read the raw data in and send it off
	while(true){
	  int len;
	  try {
	    if((len=from.available()) > 0){
	      if(len > 512)
		len=512;
	      from.read(buf, 0, len);
	      chat.received(new String(buf,0,len), this);
	    }
	    else
	      Thread.currentThread().yield();
	  } catch (IOException e){
	    chat.exception(e, true);
	  }
	}
      }

    }
  }

  /** Send a message
   */
  public void send(String s) 
       throws NotConnectedException {
	 if(to == null)
	   throw new NotConnectedException("Not connected.  Use talkTo() to specify endpoint");
	 else {
	   to.print(s);
	   to.flush();
	 }
  }

}
