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.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 Client 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 Client implements Runnable {
	private BufferedReader in;
	private PrintWriter out;
	private Socket socketToCommunicateWithServer;

	/**
	 * <pre>
	 * Constructs readers and writers for talking to the Server, as follows:
	 * 
	 * 1. Constructs a Socket to the Server, using the given hostname and port.
	 *    Note: The Server must be accepting clients at this point,
	 *    else the construction of the Socket by this Client will fail.
	 *  
	 * 2. 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:
	 * 3. Starts a Thread in which the Client repeatedly communicates with the
	 *    Server (so that you can see a simple example of network communication).
	 * </pre>
	 * 

	 * @param hostName HostName or IP address of the Server to which to connect.
	 * @param port Port to use for the Socket that connects to the Server
	 *             at the given hostName.
	 * @throws Throwable if any Exception or Error occurs.
	 */
	public Client(String hostName, int port) throws Throwable {
		try {

			//-------------------------------------------------------------------------------------
			// KEY #7: Connect to the given Server, getting a Socket for communicating with it.
			this.socketToCommunicateWithServer = new Socket(hostName, port);
			//-------------------------------------------------------------------------------------

			//-------------------------------------------------------------------------------------
			// From the Socket, construct a BufferedReader for reading from the Client.
			this.in = new BufferedReader(new InputStreamReader(
					this.socketToCommunicateWithServer.getInputStream()));
			//-------------------------------------------------------------------------------------

			//-------------------------------------------------------------------------------------
			// From the Socket, construct a PrintWriter for writing to the Client.
			this.out = new PrintWriter(
					this.socketToCommunicateWithServer.getOutputStream(), true);
			//-------------------------------------------------------------------------------------

			new Thread(this).start();

		} catch (Throwable exception) {
			this.close();
			throw exception;
		}
	}

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

			int number;
			String dataFromServer;
			int dataFromUser;

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

			number = (int) (Math.random() * 100);
			JOptionPane.showMessageDialog(null,
					"The Client starts the ball rolling with the random number " + number);

			//-----------------------------------------------------------------------------
			// Write to the Server.
			this.out.println(number);
			//-----------------------------------------------------------------------------

			while (true) {

				//-----------------------------------------------------------------------------
				// Read from the Server.
				dataFromServer = this.in.readLine();
				//-----------------------------------------------------------------------------

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

				number = (int) (Math.random() * 10);	// What the Client does in this example.

				dataFromUser = JOptionPane.showConfirmDialog(
						null,
						"The Client got " + dataFromServer + " from the Server.\n\n"
						+ "The Client chooses to add " + number + " to the above.\n\n"
						+ "Thus the Client will send "
						+ (number + Integer.parseInt(dataFromServer))
						+ " to the Server after a few seconds pause.\n\n"
						+ "Press   OK   to continue or   Cancel   to quit this program.\n\n",
						"Continue?",
						JOptionPane.OK_CANCEL_OPTION);

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

				Thread.sleep((long) (Math.random() * 5000));

				//-----------------------------------------------------------------------------
				// Write to the Server.
				this.out.println(number + Integer.parseInt(dataFromServer));
				//-----------------------------------------------------------------------------
			}

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

		catch (Throwable exception) {
			System.out.println(exception);
			JOptionPane.showMessageDialog(null, "Client ended abnormally.  See stack trace.");
			exception.printStackTrace();
		}

		this.close();
	}

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

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

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

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

		try {
			this.socketToCommunicateWithServer.close();

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