package org.simpleframework.transport.connect;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.URL;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.CountDownLatch;

import junit.framework.TestCase;

import org.simpleframework.http.Request;
import org.simpleframework.http.Response;
import org.simpleframework.http.core.Container;
import org.simpleframework.http.core.ContainerProcessor;
import org.simpleframework.http.core.ThreadDumper;
import org.simpleframework.transport.Processor;
import org.simpleframework.transport.ProcessorServer;
import org.simpleframework.transport.Server;
import org.simpleframework.transport.Socket;
import org.simpleframework.util.buffer.Allocator;
import org.simpleframework.util.buffer.FileAllocator;
import org.simpleframework.util.thread.PoolExecutor;

public class ConnectionTest extends TestCase {
   
   public void testSocketPing() throws Exception {
     // for(int i = 0; i < 10; i++) {
     //    System.err.printf("Ping [%s]%n", i);
     //    testPing(8080, "Hello World!", true, 2);
     // }
   }
   
   public void testURLPing() throws Exception {
      for(int i = 0; i < 20; i++) {
         System.err.printf("Ping [%s]%n", i);
         testPing(8080, "Hello World!", false, 10);
      }
   }
   
   public void testMixPing() throws Exception {
      //for(int i = 0; i < 50; i+=2) {
      //   System.err.printf("Ping [%s]%n", i);
      //   testPing(8080, "Hello World!", true, 2);
      //   System.err.printf("Ping [%s]%n", i+1);
      //   testPing(8080, "Hello World!", false, 10);
      //}
   }
   
   private void testPing(int port, String message, boolean socket, int count) throws Exception {
      PingServer server = new PingServer(8080, message);
      Pinger pinger = new Pinger(8080, socket, count);
      
      server.start();
      List<String> list = pinger.execute();
      
      for(int i = 0; i < count; i++) { // at least 20
         String result = list.get(i);
         
         assertNotNull(result);
         assertEquals(result, message);
      }
      server.stop();
      pinger.validate();
      pinger.stop(); // wait for it all to finish
   }
   
   private static class DebugServer implements Server {
      
      private Server server;
      
      public DebugServer(Server server) {
         this.server = server;
      }
      
      public void process(Socket socket) throws IOException {
         System.err.println("Connect...");
         server.process(socket);
      }
      
      public void stop() throws IOException {
         System.err.println("Stop...");
         server.stop();
      }
   }
   
   private static class PingServer implements Container {
      
      private final Connection connection;
      private final SocketAddress address;
      private final String message;
      
      public PingServer(int port, String message) throws Exception {
         Allocator allocator = new FileAllocator();
         Processor processor  = new ContainerProcessor(this, allocator, 5);
         Server server = new ProcessorServer(processor);
         DebugServer debug = new DebugServer(server);
         
         this.connection = new SocketConnection(debug);
         this.address = new InetSocketAddress(port);
         this.message = message;
      }
      
      public void start() throws Exception {
         try {
            System.err.println("Starting...");
            connection.connect(address);
         }finally {
            System.err.println("Started...");
         }
      }
      
      public void stop() throws Exception {
         connection.close();
      }
       
      public void handle(Request req, Response resp) {
         try {
            System.err.println(req);
            PrintStream out = resp.getPrintStream(1024);
            
            resp.set("Content-Type", "text/plain");
            out.print(message);
            out.close();
         }catch(Exception e) {
            e.printStackTrace();
         }
      }
   }
   
   private static class Pinger implements Runnable {
      
      private final int count;
      private final int port;
      private final boolean socket;
      private final CountDownLatch latch;
      private final CountDownLatch stop;
      private final PoolExecutor executor;
      private final ThreadDumper dumper;
      private final List<String> list;
      private final List<java.net.Socket> sockets;
      
      public Pinger(int port, boolean socket, int count) throws Exception {
         this.executor = new PoolExecutor(Pinger.class, count);
         this.list = new Vector<String>(count);
         this.sockets = new Vector<java.net.Socket>(count);
         this.latch = new CountDownLatch(count);
         this.stop = new CountDownLatch(count + count);
         this.dumper = new ThreadDumper();
         this.port = port;
         this.socket = socket;
         this.count = count;
      }

      public List<String> execute() throws Exception {
         dumper.start();
         
         for(int i = 0; i < count; i++) {
            executor.execute(this);
         }
         latch.await();
         
         // Overrun with pings to ensure they close
         if(socket) {
            for(int i = 0; i < count; i++) {
               executor.execute(this);
            }
         }
         return list;
      }
         
      public void validate() throws Exception {
         if(socket) {
            for(java.net.Socket socket : sockets) {
               if(socket.getInputStream().read() != -1) {
                  throw new IOException("Connection not closed");
               } else {
                  System.err.println("Socket is closed");
               }
            }
         }
      }
      
      public void stop() throws Exception {
         executor.stop();
         
         if(socket) {
            stop.await(); // wait for all excess pings to finish
         }
         dumper.kill();
      }
      
      private String ping() throws Exception {
         if(socket) {
            return pingWithSocket();
         }
         return pingWithURL();
      }
      
      public void run() {
         try {
            String result = ping();
            
            list.add(result);
            latch.countDown();
         }catch(Throwable e){
            System.err.println(e);
         } finally {
            stop.countDown(); // account for excess pings
         }
      }
      
      /**
       * This works as it opens a socket and sends the request.
       * This will split using the CRLF and CRLF ending.
       * 
       * @return the response body
       * 
       * @throws Exception if the socket can not connect
       */
      private String pingWithSocket() throws Exception {
         java.net.Socket socket = new java.net.Socket("localhost", port);
         OutputStream out = socket.getOutputStream();
         out.write(
               ("GET / HTTP/1.1\r\n" +
               "Host: localhost\r\n"+
               "\r\n").getBytes());
         out.flush();
         InputStream in = socket.getInputStream();
         byte[] block = new byte[1024];
         int count = in.read(block);       
         String result = new String(block, 0, count);
         String parts[] = result.split("\r\n\r\n");
         
         if(!result.startsWith("HTTP")) {
            throw new IOException("Header is not valid");
         }
         sockets.add(socket);
         return parts[1];
      }
      
      /**
       * Use the standard URL tool to get the content.
       * 
       * @return the response body
       * 
       * @throws Exception if a connection can not be made.
       */
      private String pingWithURL() throws Exception {
         URL target = new URL("http://localhost:"+ port+"/");
         InputStream in = target.openStream();
         byte[] block = new byte[1024];
         int count = in.read(block);       
         String result = new String(block, 0, count);
         
         return result;
      }
   }
}
