package vs;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

/**
 * threaded server for var.sockets.tcp.echo Echo service. waits for the next
 * client to connect, creates thread and handles connection in concurrently:
 * sends greeting message to client, reads line by line from client and sends it
 * back adding "echo: " in front of each line until connection is closed by
 * client.
 *
 * @author Sandro Leuchter
 *
 */
public class EchoServerThreadPool {
	/**
	 * port on which this service is currently listening on localhost
	 */
	private final int port;
	/**
	 * thread pool of this server
	 */
	private final Executor threadPool;

	/**
	 * the only constructor for this class
	 *
	 * @param port port on which this service will be listening on localhost
	 */
	public EchoServerThreadPool(int port) {
		this.port = port;
		// threadPool = Executors.newSingleThreadExecutor();
		// threadPool = Executors.newCachedThreadPool();
		this.threadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
	}

	/**
	 * creates server socket on localhost:port, infinitely handles connections to
	 * clients concurrently
	 */
	public void start() {
		try (ServerSocket serverSocket = new ServerSocket(this.port)) {
			System.out.println("EchoServer (threaded) auf " + serverSocket.getLocalSocketAddress() + " gestartet ...");
			while (true) {
				Socket socket = serverSocket.accept();
				this.threadPool.execute(new EchoThread(socket));
			}
		} catch (IOException e) {
			System.err.println(e);
		}
	}

	/**
	 * Each connection is handled with an instance of this class.
	 */
	private class EchoThread implements Runnable {
		/**
		 * TCP connection to client
		 */
		private final Socket socket;

		/**
		 * the only constructor for this class
		 *
		 * @param socket the individual socket that the server created on accepting a
		 *               client that this EchoThread instance will be communicating with
		 */
		public EchoThread(Socket socket) {
			this.socket = socket;
		}

		/**
		 * defines the behavior of this Thread instance, will be executed concurrently
		 * if start() is called on instance
		 *
		 */
		@Override
		public void run() {
			SocketAddress socketAddress = this.socket.getRemoteSocketAddress();
			System.out.println("Verbindung zu " + socketAddress + " aufgebaut");
			try (BufferedReader in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
					PrintWriter out = new PrintWriter(this.socket.getOutputStream(), true)) {
				out.println("Server ist bereit ...");
				String input;
				while ((input = in.readLine()) != null) {
					System.out.println(socketAddress + ">> [" + input + "]");
					out.println("echo: " + input);
				}
			} catch (Exception e) {
				System.err.println(e);
			} finally {
				System.out.println("Verbindung zu " + socketAddress + " abgebaut");
			}
		}
	}

	/**
	 * main method: entrypoint to run service
	 *
	 * @param args args[0] must be the port number of the server (int); rest of args
	 *             is ignored
	 */
	public static void main(String[] args) {
		int port = Integer.parseInt(args[0]);
		new EchoServerThreadPool(port).start();
	}
}
