
/**
 *  The Network class makes it possible to open a connection over
 *  the network with another computer and then send and receive
 *  lines of text over that connection.
 *     To open a connection, one side of the connection should
 *  call Network.listen(port), where port is an integer between 2049
 *  and 65535.  This makes the computer listen for a connection request
 *  from some other computer.  The other computer should request a
 *  connection by calling Network.connect(host,port) where
 *  host is a string that gives the name of the computer that is 
 *  waiting for a connection and port is an integer that gives the
 *  port on which the other computer is listening.
 *    Once a connection has been opened between two computers,
 *  either computer can call Network.send(line) to send a line of
 *  text to the other side of the connection.  To receive a line of
 *  text, a computer should call Network.receive(), which waits for
 *  a transmission from the other side of the connection, and then
 *  returns the line of text that was received as a String.
 *    The connection can be closed by either side by calling
 *  Network.close().  A computer can test whether a connection is
 *  still open by calling the boolean-valued function Network.isOpen().
 *    Note that communication is "synchronous".  To receive data over
 *  the network, a computer must know that the data is coming.  Once it
 *  calls Network.receive(), it will wait forever until the other side
 *  sends some data or closes the connection.
 */

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

public class Network {

   private static Writer out;  // For sending data to the other side of the connection.
   private static Reader in;   // For receiving data from the other side.


   /**
    *  Listen for a connection request on a specified port.  The port should be an
    *  integer in the range 2049 to 65535.  (Users who have sufficient privleges can
    *  use port numbers between 1 and 2048.)   If the specifed port number is already
    *  in use, an error will occur.  Otherwise, the computer will wait until a
    *  request is received for a connection on the specified port.  The program will
    *  not continue until the request is received.  Once the connection is made, the
    *  two computers can start sending and receiving data over the connection.
    */
   public static void listen(int port) {
      if (out != null)
         throw new RuntimeException("Attempt to open a connection when one is already open.");
      try {
         ServerSocket listener = new ServerSocket(port);
         System.out.println("\nWaiting for connection on port " + port + "...");
         Socket connection = listener.accept();
         listener.close();
         out = new OutputStreamWriter(connection.getOutputStream());
         in = new InputStreamReader(connection.getInputStream());
         System.out.println("Connected.\n");
      }
      catch (IOException e) {
         throw new RuntimeException("Attempt to open connection failed:  " + e);
      }
   }
   
   /**
    *  Make a connection to a computer that is listening for a connection request.
    *  It is necessary to know the Internet host name or the IP number of the listening
    *  computer, and it is necessary to know the port number on which it is listening.
    */
   public static void connect(String host, int port) {
      if (out != null)
         throw new IllegalStateException("Attempt to open a connection when one is already open.");
      if (out != null)
         throw new RuntimeException("Attempt to open a connection when one is already open.");
      try {
         System.out.println("\nAttempting to connect to " + host + " on port " + port + "...");
         Socket connection = new Socket(host,port);
         out = new OutputStreamWriter(connection.getOutputStream());
         in = new InputStreamReader(connection.getInputStream());
         System.out.println("Connected.\n");
      }
      catch (IOException e) {
         throw new RuntimeException("Attempt to open connection failed:  " + e);
      }
   }
   

   /**
    *  Close the network connection.  No error occurs if there is no connection in place.
    */
   public static void close() {
      try {
         if (in != null)
            in.close();
         if (out != null)
            out.close();
      }
      catch (IOException e) {
      }
      in = null;
      out = null;
   }
   

   /**
    *  Returns true if there is a network connection in place; otherwise, returns false.
    *  This can be used after calling Network.receive() to find out if the computer on the
    *  other side of the connection has closed the connection.
    */
   public static boolean isOpen() {
      return out != null;
   }
   

   /**
    *  Send a line of text to the computer on the other side of the connection.  An error occurs
    *  if no connection is open.  An error can also occur during the transmission, for example if
    *  the connection had been closed from the other end.
    */
   public static void send(String line) {
      if (out == null)
         throw new RuntimeException("Attempt to write to the network when no connection is open.");
      try {
         out.write('.');
         for (int i = 0; i < line.length(); i++)
            out.write(line.charAt(i));
         out.write('\n');
         out.flush();
      }
      catch (IOException e) {
         close();
         throw new RuntimeException("An error occured while writing data to the network:  " + e);
      }
   }
   

   /**
    *  Wait for a line of text to be transmitted from the other side of the connection.
    *  An error occurs if no connection is open.  An error can also occur during transmission.
    *  It is possible that the connection has been closed from the other side.  You can call
    *  Network.isOpen() after calling Network.recieve() to test whether the connection has
    *  been closed.  Usually, when the connection has been closed, the string returned by
    *  Network.receive() will be empty.  However, this is not guaranteed to be the case.
    */
   public static String receive() {
      if (in == null)
         throw new RuntimeException("Attempt to read from the network when no connection is open.");
      try {
         int ch = in.read();
         while (ch == '\n' || ch == '\r')
            ch = in.read();
         if (ch == -1) {
            close();
            return "";
         }
         StringBuffer buffer = new StringBuffer();
         ch = in.read();
         while (ch != '\n' && ch != '\r' && ch != -1) {
            buffer.append((char)ch);
            ch = in.read();
         }
         if (ch == -1)
            close();
         return buffer.toString();
      }
      catch (IOException e) {
         close();
         throw new RuntimeException("An error occurred while reading data from network:  " + e);
      }
   }
   

} // end class Network

