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

/** A simple server that lets clients chat with one another. */
public class ChatServer {

  public static final int PORT = 5678;

  /** Program that runs the chat server until killed. */
  public static void main(String[] args) throws IOException {
    ServerSocket serverSocket = new ServerSocket(PORT);  // crash if this fails

    // List of all connections that are listening for messages.
    List<Socket> listeners = new ArrayList<>();

    // Wait for new connections until the program is killed.
    while (true) {
      Socket clientSocket = serverSocket.accept();  // crash if this fails

      try {
        // Process this client as a listener or speaker:
        //  * Listeners are recorded in the list above.
        //  * Messages from speakers are echoed to each listener.
        String line = readLine(clientSocket);
        if (line.equals("listen")) {
          listeners.add(clientSocket);
        } else if (line.startsWith("speak ")) {
          removeClosed(listeners);  // drop those that disconnected
          for (Socket listenSocket : listeners)
            writeLine(listenSocket, line.substring(6));
          closeSocket(clientSocket);
        } else {
          writeLine(clientSocket, "Error: unknown command '" + line + "'");
          closeSocket(clientSocket);
        }
      } catch (IOException ex) {
        // This is an error reading from the new connection. Just ignore it.
        ex.printStackTrace(System.err);
        closeSocket(clientSocket);
      }
    }
  }

  /** Reads the first line from the given socket. */
  private static String readLine(Socket socket) throws IOException {
    BufferedReader reader = new BufferedReader(
        new InputStreamReader(socket.getInputStream()), 1);
    return reader.readLine();
  }

  /** Writes the given line to the given socket, ignoring any exception. */
  private static void writeLine(Socket socket, String line) {
    try {
      Writer writer = new OutputStreamWriter(socket.getOutputStream());
      writer.write(line + "\n");
      writer.flush();
    } catch (IOException ex) {
      ex.printStackTrace(System.err);
      closeSocket(socket);  // don't try to write anymore
    }
  }

  /** Closes the given socket, ignoring (but printing) any exception. */
  private static void closeSocket(Socket socket) {
    try {
      socket.close();
    } catch (IOException ex) {
      ex.printStackTrace(System.err);
    }
  }

  /** Removes all closed sockets from the given list. */
  private static void removeClosed(List<Socket> sockets) {
    Iterator<Socket> iter = sockets.iterator();
    while (iter.hasNext()) {
      Socket socket = iter.next();
      if (socket.isClosed())
        iter.remove();
    }
  }
}