package nl.hsac.fitnesse.fixture.util;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import org.apache.http.entity.ContentType;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Receiver for a callback from application being tested.
 */
public class HttpServer <T extends HttpResponse> {
    private static final ContentType XML_UTF8_TYPE = ContentType.parse(XmlHttpResponse.CONTENT_TYPE_XML_TEXT_UTF8);

    private final T response;
    private final com.sun.net.httpserver.HttpServer server;
    private final AtomicBoolean requestReceived = new AtomicBoolean(false);

    /**
     * Creates new.
     * @param aPath context the server will serve.
     * @param aResponse response to send when request is received, request will
     *                  be added to it when this server receives one.
     */
    public HttpServer(String aPath, T aResponse) {
        response = aResponse;
        try {
            server = com.sun.net.httpserver.HttpServer.create();
            bind(server);
            server.createContext(aPath, getHandler(aResponse));
            server.start();
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * @return address the server listens on.
     */
    public InetSocketAddress getAddress() {
        return server.getAddress();
    }

    /**
     * @return response, with request filled if isRequestReceived() is true.
     */
    public T getResponse() {
        return response;
    }

    /**
     * @return true if this server received a request.
     */
    public boolean isRequestReceived() {
        return requestReceived.get();
    }

    /**
     * @param maxWait ms to wait at most.
     * @return response with request filled, if one was received.
     */
    public T waitForRequest(long maxWait) {
        long start = System.currentTimeMillis();
        try {
            while (!isRequestReceived()
                    && (System.currentTimeMillis() - start) < maxWait) {
                try {
                    Thread.sleep(50);
                } catch (InterruptedException ex) {
                    throw new RuntimeException(ex);
                }
            }
        } finally {
            stopServer();
        }

        return getResponse();
    }

    /**
     * Stops server from listening.
     */
    public void stopServer() {
        server.stop(0);
    }

    private void bind(com.sun.net.httpserver.HttpServer server) {
        try {
            InetAddress address = InetAddress.getLocalHost();
            int port = getFreePort();
            InetSocketAddress isa = new InetSocketAddress(address, port);
            server.bind(isa, 1);
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * Finds free port number.
     * @return first available port number above 8000.
     */
    protected int getFreePort() {
        int port = 0;
        for (int possiblePort = 8000; port == 0; possiblePort++) {
            ServerSocket s = null;
            try {
                s = new ServerSocket(possiblePort);
                port = s.getLocalPort();
            } catch (IOException e) {
                // try next number
                continue;
            } finally {
                if (s != null) {
                    try {
                        s.close();
                    } catch (IOException e) {
                        // why would this happen?
                        throw new IllegalStateException("Unable to close port: " + possiblePort, e);
                    }
                }
            }
        }
        return port;
    }

    private HttpHandler getHandler(final T aResponse) {
        HttpHandler result = new HttpHandler() {
            @Override
            public void handle(HttpExchange he) throws IOException {
                OutputStream os = null;
                try {
                    InputStream is = he.getRequestBody();
                    String request = FileUtil.streamToString(is, "http request");
                    aResponse.setRequest(request);
                    requestReceived.set(true);

                    ContentType contentType = XML_UTF8_TYPE;
                    byte[] responseBytes = aResponse.getResponse()
                                            .getBytes(contentType.getCharset());
                    he.sendResponseHeaders(aResponse.getStatusCode(),
                                            responseBytes.length);
                    he.getResponseHeaders()
                            .add("Content-Type", contentType.toString());
                    os = he.getResponseBody();
                    os.write(responseBytes);
                    os.flush();
                } finally {
                    if (os != null) {
                        os.close();
                    }
                }

            }
        };
        return result;
    }
}

@@ -384,23 +384,23 @@
 c.Atomic
-Boolean
+Integer
 ;%0A%0A/**%0A 
@@ -746,23 +746,23 @@
 omic
-Boolean
+Integer
  request
 Rece
@@ -749,32 +749,33 @@
 cInteger request
+s
 Received = new A
@@ -783,21 +783,63 @@
 omic
-Boolean(false
+Integer(0);%0A    private final Object lock = new Object(
 );%0A%0A
@@ -915,16 +915,38 @@
 ll serve
+ (must start with '/')
 .%0A     *
@@ -1677,20 +1677,51 @@
 led 
-if is
+(with last received request) if get
 Request
+s
 Rece
@@ -1730,20 +1730,29 @@
 ed() is 
-true
+larger than 0
 .%0A     *
@@ -1841,15 +1841,26 @@
 urn 
-true if
+number of requests
  thi
@@ -1880,18 +1880,8 @@
 ived
- a request
 .%0A  
@@ -1901,25 +1901,23 @@
 lic 
-boolean is
+int get
 Request
+s
 Rece
@@ -1939,32 +1939,33 @@
   return request
+s
 Received.get();%0A
@@ -2046,24 +2046,29 @@
 sponse with 
+last 
 request fill
@@ -2074,16 +2074,25 @@
 led, if 
+at least 
 one was 
@@ -2239,26 +2239,24 @@
 le (
-!isR
+r
 equest
+s
 Received
 ()%0A 
@@ -2251,18 +2251,26 @@
 Received
-()
+.get() %3C 1
 %0A       
@@ -4003,21 +4003,23 @@
 %0A%0A    pr
-ivate
+otected
  HttpHan
@@ -4195,16 +4195,123 @@
 ption %7B%0A
+            // ensure we never handle multiple requests at the same time%0A            synchronized (lock) %7B%0A
         
@@ -4579,23 +4579,33 @@
     
-r
+incrementR
 equest
+s
 Received
 .set
@@ -4604,17 +4604,9 @@
 ived
-.set(true
+(
 );%0A%0A
@@ -5327,32 +5327,45 @@
               %7D%0A
+            %7D
 %0A            %7D%0A 
@@ -5402,11 +5402,117 @@
 t;%0A    %7D
+%0A%0A    protected int incrementRequestsReceived() %7B%0A        return requestsReceived.incrementAndGet();%0A    %7D
 %0A%7D%0A

Add support for receiving multiple requests