package de.tudarmstadt.informatik.hostage.net;

import java.io.FileDescriptor;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.SocketImpl;

import javax.net.ServerSocketFactory;

import de.tudarmstadt.informatik.hostage.system.P;
import de.tudarmstadt.informatik.hostage.ui2.activity.MainActivity;

/**
 * Server Socket Factory using file descriptors.
 * 
 * @author Mihai Plasoianu
 * @author Lars Pandikow
 */
public class MyServerSocketFactory extends ServerSocketFactory {

	/**
	 * This method creates and returns a ServerSocket. A custom SocketImpl is
	 * injected into the ServerSocket.
	 */
	@Override
	public ServerSocket createServerSocket(int port) throws IOException {
		ServerSocket socket = null;
		if (port > 1023) {
			socket = new ServerSocket();
			socket.setReuseAddress(true);
			socket.bind(new InetSocketAddress(port));
		} else if (MainActivity.porthackInstalled) {
			FileDescriptor fd = new P(port).bindAndGetFD();
			socket = new ServerSocket();
			try {
				SocketImpl impl = getImpl(socket);
				injectFD(fd, impl);
				injectImpl(impl, socket);
				setBound(socket);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return socket;
	}

	/**
	 * Must override.
	 */
	@Override
	public ServerSocket createServerSocket(int port, int backlog)
			throws IOException {
		return createServerSocket(port);
	}

	/**
	 * Must override.
	 */
	@Override
	public ServerSocket createServerSocket(int port, int backlog,
			InetAddress iAddress) throws IOException {
		return createServerSocket(port);
	}

	/**
	 * Extracts the SocketImpl out of a ServerSocket.
	 */
	private SocketImpl getImpl(ServerSocket socket) throws Exception {
		Field implField = socket.getClass().getDeclaredField("impl");
		implField.setAccessible(true);
		return (SocketImpl) implField.get(socket);
	}

	/**
	 * Injects a FileDescriptor into a SocketImpl.
	 */
	private void injectFD(FileDescriptor fd, SocketImpl impl) throws Exception {
		Class<?> plainServerSocketImplClazz = impl.getClass();
		Class<?> plainSocketImplClazz = plainServerSocketImplClazz
				.getSuperclass();
		Class<?> socketImplClazz = plainSocketImplClazz.getSuperclass();
		Field fdField = socketImplClazz.getDeclaredField("fd");
		fdField.setAccessible(true);
		fdField.set(impl, fd);
	}

	/**
	 * Injects a SocketImpl into a ServerSocket.
	 */
	private void injectImpl(SocketImpl impl, ServerSocket socket)
			throws Exception {
		Field implField = socket.getClass().getDeclaredField("impl");
		implField.setAccessible(true);
		implField.set(socket, impl);
	}

	/**
	 * Sets the isBound Field of a ServerSocket to true.
	 */
	private void setBound(ServerSocket socket) throws Exception {
		Field boundField = socket.getClass().getDeclaredField("isBound");
		boundField.setAccessible(true);
		boundField.set(socket, true);
	}

}
