package fr.umlv.ir2.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.Iterator;
import java.util.Set;

public class UpperCaseNonBlockingWithHandlerServer {
  
  final static int DEFAULT_PORT = 1234; 
  final ServerSocketChannel serverChannel;
  final static int SIZE = 1024;
  
  final Selector selector = Selector.open();
  
  public UpperCaseNonBlockingWithHandlerServer(int port) throws IOException {
    serverChannel = ServerSocketChannel.open();
    serverChannel.socket().bind(new InetSocketAddress(port)); 
  }
  
  public UpperCaseNonBlockingWithHandlerServer() throws IOException {
    this(DEFAULT_PORT);
  }
  
  /**
   * Writes in buffer <code>out</code> the uppercase
   * version of the string found in buffer <code>in</code>.
   */
  void serve(ByteBuffer in, ByteBuffer out) throws IOException {
    // version "brutale" sans tenir compte du codage!!!
    byte[] buf = new byte[in.remaining()];
    in.get(buf);
    out.put(new String(buf).toUpperCase().getBytes());
  }
  
  /**
   * Reads and writes bytes on clientChannel.
   * Returns false if connection is terminated.
   */
  boolean useConnection(SocketChannel clientChannel) throws IOException {  
    ByteBuffer bbIn = ByteBuffer.allocate(SIZE);
    ByteBuffer bbOut = ByteBuffer.allocate(SIZE);    
    int bytes = 0;
    while ((bytes = clientChannel.read(bbIn)) > 0) {
      bbIn.flip();
      serve(bbIn,bbOut);
      bbOut.flip();
      clientChannel.write(bbOut);
      bbIn.clear();
      bbOut.clear();
    }
    if (bytes==-1) {
      System.out.println("End of connection...");
      return false; // end of stream
    }
    return true; // nothing more to read by now
  }
  
  /**
   * Accept connection comming on the server socket
   * channel and register it to read data from clients.
   * Returns false if problem occurs with the server socket.
   */
  boolean acceptConnection(ServerSocketChannel ssc) throws IOException {
    SocketChannel sc = ssc.accept();
    sc.configureBlocking(false);
    Handler readHandler = new Handler() {
      public void perform(SelectionKey sk)
      throws IOException {
        SocketChannel sc = (SocketChannel) sk.channel();
        System.out.println("channel for " +
            sc.socket()
            .getRemoteSocketAddress() +
        " is ready to read");
        if (!useConnection(sc)) { // end of stream
          sk.cancel();
          sc.close();
        }
      }
    };
    sc.register(selector,SelectionKey.OP_READ,readHandler);
    return true;
  }
  
  /**
   * Full non blocking version with handlers.
   */
  public void launchNonBlockingWithHandler() throws IOException {
    serverChannel.configureBlocking(false);
    Handler acceptHandler = new Handler() {
      public void perform(SelectionKey sk) {
        ServerSocketChannel ssc = (ServerSocketChannel) sk.channel();
        System.out.println("server socket channel for " 
            + ssc.socket().getLocalSocketAddress() + " is ready to accept");
        try {
          if (!acceptConnection(ssc)) {
            // problem with the server socket
            sk.cancel();
            ssc.close();
          }
        } catch(IOException ioe) {
          ioe.printStackTrace();
          try {
            ssc.close();
          } catch(IOException e) { }
        }
      }
    };
    serverChannel.register(selector, SelectionKey.OP_ACCEPT, acceptHandler);
    // This method could be invoked in main or in any another thread
    runWithHandlers();
  }
  
  /**
   * Handles the selection of accept connections and
   * ready-to-read channels.
   */
  public void runWithHandlers() {
    Set<SelectionKey> selectedKeys = selector.selectedKeys();
    while (true) {
      try {
        selector.select();
        for(Iterator<SelectionKey> iKeys = selectedKeys.iterator(); iKeys.hasNext(); ) {
          SelectionKey key =  iKeys.next();
          Handler handler = (Handler) key.attachment();
          handler.perform(key);
          iKeys.remove();
        }
      } catch(IOException ioe) {
        ioe.printStackTrace();
      }
    }
  }
  
  /**
   * Main de test.
   */
  public static void main(String[] args) throws IOException {
    UpperCaseNonBlockingWithHandlerServer server;
    if(args.length == 0) {
      server = new UpperCaseNonBlockingWithHandlerServer();
    } else {
      server = new UpperCaseNonBlockingWithHandlerServer(Integer.parseInt(args[0]));
    }
    server.launchNonBlockingWithHandler();
  }
}

interface Handler {
  public void perform(SelectionKey sk) throws IOException;
}
