package examples.example2_one_client_OO;

import java.io.BufferedReader;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

import javax.swing.JOptionPane;

/**
 * <pre>
 * NetworkingExamples project: examples.example2_one_client_OO.
 * 
 * A simple example of networking (Sockets) in which:
 *   -- A single Server and Client exchange information, one after the other.
 *   
 * This is the Server code.
 * 
 * This is the same example as {@link examples.example1_one_client}
 * except structured in a more OO way.  In particular, this structure has 3 classes:
 *   -- Main: for starting the program.
 *   -- Server: for initiating and running the Server.
 *   -- Client: for initiating and running the Client.
 * 
 * See {@link examples.example1_one_client#MainForServer} for:
 *   -- Exactly what information the Server and Client exchange in this demo.
 *   -- The 7 Key Statements that are all-you-need-to-know to do networking in Java.
 * 
 * See {@link examples.example3_one_client_OO_library} for this same example
 * but using the simple networking library in package {@link simpleNetworking}.
 * </pre>
 * 
 * @author David Mutchler, based on the Java Tutorials on networking. May, 2009.
 */
public class Server implements Runnable {
	private Socket socketToCommunicateWithClient;
	private BufferedReader in;
	private PrintWriter out;
	
	/**
	 * <pre>
	 * Constructs readers and writers for talking to the Client, as follows:
	 * 
	 * 1. Constructs a ServerSocket.
	 * 
	 * 2. Use the ServerSocket to accept a connection from a Client
	 *    (blocks -- i.e. waits -- until a Client offers to connect)
	 *    (a better program would establish a timeout for connecting).
	 *    
	 * 3. In accepting a connection to the Client, it gets a Socket.
	 *    Use that Socket to:
	 *    a. Get an InputStream.  Then decorate the InputStream
	 *       into a BufferedReader (for more efficient communication).
	 *    b. Get an OutputStream.  Then decorate the OutputStream
	 *       into a PrintWriter (for convenient ways to write messages).
	 *       
	 * Finally, this constructor:
	 * 4. Starts a Thread in which the Server repeatedly communicates with the
	 *    Client (so that you can see a simple example of network communication).
	 * </pre>
	 * 
	 * @param port Port to use for the ServerSocket
	 *             that accepts a connection to the Client.
	 * @throws Throwable if any Exception or Error occurs.
	 */
	public Server(int port) throws Throwable {
		try {
			//-------------------------------------------------------------------------------------
			// KEY #1: Construct a ServerSocket that accepts connections from Clients.
			ServerSocket serverSocket = new ServerSocket(port);
			//-------------------------------------------------------------------------------------

			JOptionPane.showMessageDialog(null,
					"The Server has started and will wait for a connection.\n\n"
					+ "Start the Client AFTER pressing OK in this dialog.\n\n");

			//-------------------------------------------------------------------------------------
			// KEY #2: Block (wait) until a Client connects;
			//         get a Socket for communicating with that Client.
			this.socketToCommunicateWithClient = serverSocket.accept();
			//-------------------------------------------------------------------------------------

			// Done with the ServerSocket, since we wanted to accept only a single Client in this example.
			serverSocket.close();

			//-------------------------------------------------------------------------------------
			// KEY #3: From the Socket, construct a BufferedReader for reading from the Client.
			this.in = new BufferedReader(new InputStreamReader(
					this.socketToCommunicateWithClient.getInputStream()));
			//-------------------------------------------------------------------------------------

			//-------------------------------------------------------------------------------------
			// KEY #4: From the Socket, construct a PrintWriter for writing to the Client.
			this.out = new PrintWriter(
					this.socketToCommunicateWithClient.getOutputStream(), true);
			//-------------------------------------------------------------------------------------
			
			new Thread(this).start();
			
		} catch (Throwable exception) {
			this.close();
			throw exception;
		}
	}

	/**
	 * <pre>
	 * A simple example of communication between this Server and the Client.
	 * 
	 * It behaves as follows:
	 * 
	 * Repeatedly, until the Client sends STOP or the user says to stop:
	 *   -- Get a number from the Client.
	 *   -- Display the number from the Client, and get a number from the user.
	 *   -- Add these numbers and send the total to the Client.
	 *   
	 * The Client can send whatever number it wishes at each iteration;
	 * in fact, the particular Client in this program takes the number
	 * that the Server sends it, chooses a random number between 0 and 10,
	 * and sends the sum back to this Server.
	 * 
	 * If the user says to stop, the Server sends a STOP message to the Client
	 * so that the Client will stop too.
	 * </pre>
	 */
	@Override
	public void run() {
		try {
			//-------------------------------------------------------------------------------------
			// Now start communicating with the Client.
			//-------------------------------------------------------------------------------------

			String dataFromClient, dataFromUser;
			int numberFromUser;

			JOptionPane.showMessageDialog(null,
					"This Server is running on   "
					+ InetAddress.getLocalHost().getCanonicalHostName() + "\n"
					+ "which is IP address   "
					+ InetAddress.getLocalHost().getHostAddress() + "\n\n");

			while (true) {

				//-----------------------------------------------------------------------------
				// KEY #5: Read from the Client.
				dataFromClient = this.in.readLine();
				//-----------------------------------------------------------------------------

				if (dataFromClient.equals("STOP")) {	// Client says to stop.
					JOptionPane.showMessageDialog(null,
							"Server ended because the Client asked it to do so.\n\n");
					break;	
				}

				dataFromUser = JOptionPane.showInputDialog(
						"The number from the Client is " + dataFromClient + ".\n\n"
						+ "What do you want to add to it?\n\n"
						+ "(Or press     Cancel     to quit this program.)\n\n");

				if (dataFromUser == null) {				// User selected   Cancel   in input dialog.
					JOptionPane.showMessageDialog(null,
							"Server ended because the user asked it to do so.\n\n"
							+ "Tell the Client to stop too.\n\n");
					this.out.println("STOP");	// Tell the Client to stop
					break;								// and stop this Server.
				}

				try {
					numberFromUser = Integer.parseInt(dataFromUser);
				} catch (NumberFormatException exception) {
					JOptionPane.showMessageDialog(null,
							"You entered   " + dataFromUser + "\n"
							+ "which is not a number!  I will use   0   instead.\n\n");
					numberFromUser = 0;
				}

				//-----------------------------------------------------------------------------
				// KEY #6: Write to the Client.
				this.out.println(Integer.parseInt(dataFromClient) + numberFromUser);
				//-----------------------------------------------------------------------------
			}

		} catch (IOException exception) {
			System.out.println(exception);
			JOptionPane.showMessageDialog(null,
					"Server ended, probably because the user closed the Client.\n\n");

		} catch (Throwable exception) {
			System.out.println(exception);
			JOptionPane.showMessageDialog(null, "Server ended abnormally.\n\n");
			JOptionPane.showMessageDialog(null, exception.getStackTrace());
		}

		this.close();
	}

	/**
	 * Closes all resources that this Server uses: writer, reader, socket.
	 */
	void close() {
		Closeable[] resourcesToClose = {this.out, this.in};

		JOptionPane.showMessageDialog(null, "Closing the Server's resources.");

		for (Closeable resource : resourcesToClose) {
			try {
				resource.close();

			} catch (Exception exception) {
				// Ignore; we made our best effort at closing the resource.
			}
		}

		try {
			this.socketToCommunicateWithClient.close();

		} catch (Exception exception) {
			// Ignore; we made our best effort at closing the resource.
		}
	}
}