package fr.umlv.tcp;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

/**
 * A Server that answers, for each line of text it receives from a client, 
 * with the uppercase line of the same text. This server is concurrent, 
 * i.e., it uses one thread for each newly connected client.
 * 
 * @author duris
 *
 */
public class UpperCaseConcurrentServer {
  final static int DEFAULT_PORT = 1234; 
  final static int SIZE = 1024;
  private final ServerSocketChannel serverChannel;
  
  /**
   * Constructs a server bound to the given port. 
   * @param port the port to bind the server socket.
   * @throws IOException If an I/O error occurs,
   * if the bind operation fails, or if the socket is already bound.
   */
  public UpperCaseConcurrentServer(int port) throws IOException {
    serverChannel = ServerSocketChannel.open();
    serverChannel.socket().bind(new InetSocketAddress(port)); 
  }
  
  /**
   * Constructs a server bound to the default port. 
   * @throws IOException If an I/O error occurs,
   * if the bind operation fails, or if the socket is already bound.
   */
  public UpperCaseConcurrentServer() throws IOException {
    this(DEFAULT_PORT);
  }
  
  /**
   * Full blocking concurrent version.
   */
  public void launchBlockingConcurrent() throws IOException {
    SocketChannel clientChannel = null;
    while (true) {
      // accept a new client connection 
      clientChannel = serverChannel.accept();
      // and serve it in a new thread
      Thread t = new Thread(new Service(clientChannel));
      t.start();
    }
  }
  
  /**
   * Main of test.
   */
  public static void main(String[] args) throws IOException {
    UpperCaseConcurrentServer server;
    if(args.length == 0) {
      server =
        new UpperCaseConcurrentServer();
    } else {
      server =
        new UpperCaseConcurrentServer(Integer.parseInt(args[0]));
    }
    server.launchBlockingConcurrent();
  }
}

/**
 * This runnable represents the service corresponding to a connected client.
 */
class Service implements Runnable {
  private SocketChannel clientChannel;
  private ByteBuffer bbIn = ByteBuffer.allocate(UpperCaseConcurrentServer.SIZE);
  private ByteBuffer bbOut = ByteBuffer.allocate(UpperCaseConcurrentServer.SIZE);
  
  /**
   * Constructs a service object representing the client.
   */
  Service(SocketChannel clientChannel) {
    this.clientChannel = clientChannel;
  }
  
  /**
   * Reads input and writes corresponding output on the client connection. 
   */
  public void run() {
    try {
      System.err.println("New connection accepted from " + clientChannel.socket().getRemoteSocketAddress());
      System.err.println("Channel is " + (clientChannel.isOpen()?"open":"closed"));
      // while not end of stream...
      while(clientChannel.read(bbIn) != -1) {
        bbIn.flip();
        serve(bbIn,bbOut);
        bbOut.flip();
        clientChannel.write(bbOut);
        bbIn.clear();
        bbOut.clear();
      }
    } catch(IOException ioe) {
      ioe.printStackTrace(System.err);
    } finally {
      System.err.println("Ending connection with " + clientChannel.socket().getRemoteSocketAddress());
      bbIn.clear();
      bbOut.clear();
      try {
        clientChannel.close();
      } catch(IOException e) {
        e.printStackTrace(System.err);
      }
      System.err.println("Channel is " + (clientChannel.isOpen()?"open":"closed"));  
    }
  }
  
  /**
   * 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());
  }
}
