package de.tudarmstadt.informatik.hostage;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Iterator;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;

import de.tudarmstadt.informatik.hostage.net.MyServerSocketFactory;
import de.tudarmstadt.informatik.hostage.protocol.Protocol;
import de.tudarmstadt.informatik.hostage.protocol.SSLProtocol;

/**
 * Protocol listener class:<br>
 * Creates a Socket on the port of a given protocol and listens for incoming
 * connections.<br>
 * For each connection creates a Socket and instantiate an {@link HoneyHandler}.
 * 
 * @author Mihai Plasoianu
 * 
 */
public class HoneyListener implements Runnable {

	private ArrayList<HoneyHandler> handlers = new ArrayList<HoneyHandler>();

	private Protocol protocol;

	private ServerSocket server;
	private Thread thread;
	private int port;
	private HoneyService service;

	private ConnectionRegister conReg;
	private boolean running = false;

	/**
	 * Constructor for the class. Instantiate class variables.
	 * 
	 * @param service
	 *            The Background service that started the listener.
	 * @param protocol
	 *            The Protocol on which the listener is running.
	 */
	public HoneyListener(HoneyService service, Protocol protocol) {
		this.service = service;
		this.protocol = protocol;
		port = protocol.getPort();
		conReg = new ConnectionRegister(service);
	}

	public HoneyListener(HoneyService service, Protocol protocol, int port) {
		this.service = service;
		this.protocol = protocol;
		this.port = port;
		conReg = new ConnectionRegister(service);
	}

	/**
	 * Determines the amount of active handlers.
	 * 
	 * @return The number of active handlers.
	 */
	public int getHandlerCount() {
		return handlers.size();
	}

	/**
	 * Return the port number on which the listener listening.
	 * 
	 * @return Used port number.
	 */
	public int getPort() {
		return port;
	}

	/**
	 * Determine the name of the protocol the listener is running on.
	 * 
	 * @return Name of the protocol
	 */
	public String getProtocolName() {
		return protocol.toString();
	}

	/**
	 * Determines if the service is running.
	 * 
	 * @return True if the service is running, else false.
	 */
	public boolean isRunning() {
		return running;
	}

	/**
	 * Remove all terminated handlers from its internal ArrayList.
	 */
	public void refreshHandlers() {
		for (Iterator<HoneyHandler> iterator = handlers.iterator(); iterator
				.hasNext();) {
			HoneyHandler handler = iterator.next();
			if (handler.isTerminated()) {
				conReg.closeConnection();
				iterator.remove();
			}
		}
	}

	@Override
	public void run() {
		while (!thread.isInterrupted()) {
			addHandler();
		}
		for (HoneyHandler handler : handlers) {
			handler.kill();
		}
	}

	/**
	 * Starts the listener. Creates a server socket runs itself in a new Thread
	 * and notifies the background service.
	 */
	public boolean start() {
		try {
			server = new MyServerSocketFactory().createServerSocket(port);
			if (server == null)
				return false;
			(this.thread = new Thread(this)).start();
			running = true;
			service.notifyUI(
					this.getClass().getName(),
					new String[] {
							service.getString(R.string.broadcast_started),
							protocol.toString(), Integer.toString(port) });
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * Stops the listener. Closes the server socket, interrupts the Thread its
	 * running in and notifies the background service.
	 */
	public void stop() {
		try {
			server.close();
			thread.interrupt();
			running = false;
			service.notifyUI(
					this.getClass().getName(),
					new String[] {
							service.getString(R.string.broadcast_stopped),
							protocol.toString(), Integer.toString(port) });
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Waits for an incoming connection, accepts it and starts a
	 * {@link HoneyHandler}
	 */
	private void addHandler() {
		if (conReg.isConnectionFree()) {
			try {
				Socket client = server.accept();
				conReg.newOpenConnection();
				if (protocol.isSecure()) {
					startSecureHandler(client);
				} else {
					startHandler(client);
				}
			} catch (Exception e) {
				if(server.isClosed()) return;
				e.printStackTrace();
			}
		}
	}

	/**
	 * Creates a new instance of an {@link HoneyHandler}.
	 * 
	 * @param service
	 *            The background service
	 * @param listener
	 *            The listener that created the handler
	 * @param protocol
	 *            The Protocol the handler will run on
	 * @param client
	 *            The Socket the handler uses
	 * @return A Instance of a {@link HoneyHandler} with the specified
	 *         parameter.
	 */
	private HoneyHandler newInstance(HoneyService service,
			HoneyListener listener, Protocol protocol, Socket client) {
		return new HoneyHandler(service, listener, protocol, client);
	}

	/**
	 * Starts a {@link HoneyHandler} with the given socket.
	 * 
	 * @param client
	 *            The socket with the accepted connection.
	 * @throws Exception
	 */
	private void startHandler(Socket client) throws Exception {
		handlers.add(newInstance(service, this, protocol.getClass()
				.newInstance(), client));
	}

	/**
	 * Creates a SSLSocket out of the given socket and starts a
	 * {@link HoneyHandler}.
	 * 
	 * @param client
	 *            The socket with the accepted connection.
	 * @throws Exception
	 */
	private void startSecureHandler(Socket client) throws Exception {
		SSLContext sslContext = ((SSLProtocol) protocol).getSSLContext();
		SSLSocketFactory factory = sslContext.getSocketFactory();
		SSLSocket sslClient = (SSLSocket) factory.createSocket(client, null,
				client.getPort(), false);
		sslClient.setUseClientMode(false);
		handlers.add(newInstance(service, this, protocol.getClass()
				.newInstance(), sslClient));
	}
}
