import java.io.*;
import java.util.*;
import java.net.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;
import java.util.regex.*;

public class LargerHttpd {
	Selector clientSelector;
	ClientQueue readyClients = new ClientQueue();

	public void run( int port, int threads ) throws IOException {
		clientSelector = Selector.open();
		ServerSocketChannel ssc = ServerSocketChannel.open();
		ssc.configureBlocking(false);
		InetSocketAddress sa = 
			new InetSocketAddress( InetAddress.getLocalHost(), port );
		ssc.socket().bind( sa );
		ssc.register( clientSelector, SelectionKey.OP_ACCEPT );
	
		for (int i=0; i<threads; i++)
			new Thread() { public void run() { 
				while (true) try { handleClient(); } catch (IOException e ) { } 
			} }.start();

		while ( true ) try {
			while ( clientSelector.select(50) == 0 );
			Set readySet = clientSelector.selectedKeys();
			for( Iterator it = readySet.iterator(); it.hasNext(); ) {
				SelectionKey key = (SelectionKey)it.next();
				it.remove();
				if ( key.isAcceptable() ) 
					acceptClient( ssc );
				else {
					key.interestOps( 0 );
					readyClients.add( key );
				}
			}
		} catch ( IOException e ) { System.out.println(e); }
	}

	void acceptClient( ServerSocketChannel ssc ) throws IOException {
		SocketChannel clientSocket = ssc.accept();
		clientSocket.configureBlocking(false);
		SelectionKey key = 
			clientSocket.register( clientSelector, SelectionKey.OP_READ );
		HttpdConnection client = new HttpdConnection( clientSocket );
		key.attach( client );
	}

	void handleClient() throws IOException {
		SelectionKey key = (SelectionKey)readyClients.next();
		HttpdConnection client = (HttpdConnection)key.attachment();
		if ( key.isReadable() )
			client.read( key );
		else 
			client.write( key );
	}

	public static void main( String argv[] ) throws IOException {
		new LargerHttpd().run( Integer.parseInt(argv[0]), 3 );
	}
}

class HttpdConnection {
	static Charset charset = Charset.forName("8859_1");
	static Pattern httpGetPattern = Pattern.compile("(?s)GET /?(\\S*).*");
	SocketChannel clientSocket;
	ByteBuffer buff = ByteBuffer.allocateDirect( 64*1024 );
	String request;
	String response;
	FileChannel file;
	int filePosition;

	HttpdConnection ( SocketChannel clientSocket ) {
		this.clientSocket = clientSocket;
	}

	void read( SelectionKey key ) throws IOException {
		if ( request == null && (clientSocket.read( buff ) == -1 
				|| buff.get( buff.position()-1 ) == '\n' ) )
			processRequest( key );
		else
			key.interestOps( SelectionKey.OP_READ );
	}

	void processRequest( SelectionKey key ) {
		buff.flip();
		request = charset.decode( buff ).toString();
		Matcher get = httpGetPattern.matcher( request );
		if ( get.matches() ) {
			request = get.group(1);
			if ( request.endsWith("/") || request.equals("") )
				request = request + "index.html";
			//System.out.println( "Request: "+request);
			try {
				file = new FileInputStream ( request ).getChannel();
			} catch ( FileNotFoundException e ) {
				response = "404 Object Not Found";
			}
		} else
			response = "400 Bad Request" ;

		if ( response != null ) {
			buff.clear();
			charset.newEncoder().encode( 
				CharBuffer.wrap( response ), buff, true );
			buff.flip();
		}
		key.interestOps( SelectionKey.OP_WRITE );
	}

	void write( SelectionKey key ) throws IOException {
		if ( response != null ) {
			clientSocket.write( buff );
			if ( buff.remaining() == 0 ) 
				response = null;
		} else if ( file != null ) {
			int remaining = (int)file.size()-filePosition;
			long got = file.transferTo( filePosition, remaining, clientSocket );
			if ( got == -1 || remaining <= 0 ) {
				file.close();
				file = null;
			} else
				filePosition += got;
		} 
		if ( response == null && file == null ) {
			clientSocket.close();
			key.cancel();		
		} else 
			key.interestOps( SelectionKey.OP_WRITE );
	}
}

class ClientQueue extends ArrayList {
	synchronized void add( SelectionKey key ) { 
		super.add(key); 
		notify();
	}
	synchronized SelectionKey next() {
		while ( isEmpty() )
			try { wait(); } catch ( InterruptedException e ) { }
		return (SelectionKey)remove(0);
	}
}

