package fr.umlv.tcp;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Set;

public class EchoNonBlockingServer {
	private final ServerSocketChannel serverSocket;
  private final Selector selector;
	private final static int BUFFER_SIZE = 1024;
	
	public EchoNonBlockingServer(int port) throws IOException {
		serverSocket = ServerSocketChannel.open();
		serverSocket.configureBlocking(false);
		serverSocket.socket().bind(new InetSocketAddress(port));
		selector = Selector.open();
	}
	
	public EchoNonBlockingServer() throws IOException {
		this(0);
	}
	
	public void launch() throws IOException {
		// registers the server socket for 'accept' operations
		serverSocket.register(selector,SelectionKey.OP_ACCEPT);
		// retrieves the selectedKeys set reference
		Set<SelectionKey> selectedKeys = selector.selectedKeys();
		while(!Thread.interrupted()) {
			// blocking selection operation, until there is something to do
			selector.select();
			try {
				for(SelectionKey key : selectedKeys) {
					if(key.isAcceptable()) {
						doAccept(key);
					}
					if(key.isWritable()) {
						doWrite(key);
					}
					if(key.isReadable()) {
						doRead(key);
					}
				}
			} finally {
				selectedKeys.clear();
			}
		}
	}
	
	private void doAccept(SelectionKey key) throws IOException {
		ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
		SocketChannel sc = ssc.accept();
		System.out.println(sc);
		sc.configureBlocking(false);
		sc.register(selector,SelectionKey.OP_READ,ByteBuffer.allocateDirect(BUFFER_SIZE));
	}

	private void doWrite(SelectionKey key) throws IOException {
		SocketChannel sc = (SocketChannel) key.channel();
		ByteBuffer buffer = (ByteBuffer) key.attachment();
		// writes the content of the buffer
		buffer.flip();
		sc.write(buffer);
		// updates the buffer state
		if(buffer.hasRemaining())
			buffer.compact();
		else {
			buffer.clear();
		 // updates the interested operations of the selection key
			key.interestOps(SelectionKey.OP_READ);
		}
	}

	private void doRead(SelectionKey key) throws IOException {
		SocketChannel sc = (SocketChannel) key.channel();
		ByteBuffer buffer = (ByteBuffer) key.attachment();
		// reads data and put them in the buffer
    // if this server was not simply for Echo service, 
    // another buffer should be used in order to
    // append the response in buffer
		int nb = sc.read(buffer);
    if(nb == -1) { // the stream is closed
			key.cancel();
			sc.close();
		} else if(nb > 0) { // at least one byte is to be written
			key.interestOps(SelectionKey.OP_READ|SelectionKey.OP_WRITE);
		} // if, for a spurious reason, no more byte are readable
      // then the interestOps are not modified
	}

	public static void main(String[] args) throws IOException {
    EchoNonBlockingServer server;
    if(args.length == 0) {
      server =
        new EchoNonBlockingServer();
    } else {
      server =
        new EchoNonBlockingServer(Integer.parseInt(args[0]));
    }
    server.launch();
	}
}
