/*
 * Licensed to Jasig under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Jasig licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License.  You may obtain a
 * copy of the License at the following location:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.jasig.cas.adaptors.x509.util;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;

import org.springframework.core.io.Resource;


/**
 * Provides a simple HTTP Web server that can serve out a single resource for
 * all requests.  SSL/TLS is not supported.
 *
 * @author Marvin S. Addison
 * @version $Revision$
 * @since 3.4.6
 *
 */
public class MockWebServer {
    /** Request handler. */
    private Worker worker;

    /** Controls the worker thread. */
    private Thread workerThread;


    /**
     * Creates a new server that listens for requests on the given port and
     * serves the given resource for all requests.
     *
     * @param port Server listening port.
     * @param resource Resource to serve.
     * @param contentType MIME content type of resource to serve.
     */
    public MockWebServer(final int port, final Resource resource, final String contentType) {
        try {
            this.worker = new Worker(new ServerSocket(port), resource, contentType);
        } catch (IOException e) {
            throw new RuntimeException("Cannot create Web server", e);
        }
    }

    /** Starts the Web server so it can accept requests on the listening port. */
    public void start() {
        this.workerThread = new Thread(this.worker, "MockWebServer.Worker");
        this.workerThread.start();
    }

    /** Stops the Web server after processing any pending requests. */
    public void stop() {
        if (!isRunning()) {
            return;
        }
        this.worker.stop();
        try {
            this.workerThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * Determines whether the server is running or not.
     *
     * @return True if server is running, false otherwise.
     */
    public boolean isRunning() {
        return this.workerThread.isAlive();
    }

    /**
     * Worker class handles request processing.
     */
    final class Worker implements Runnable {
        /** Server always returns HTTP 200 response. */
        private static final String STATUS_LINE = "HTTP/1.1 200 Success\r\n";

        /** Separates HTTP header from body. */
        private static final String SEPARATOR = "\r\n";

        /** Response buffer size. */
        private static final int BUFFER_SIZE = 2048;

        /** Run flag. */
        private boolean running;

        /** Server socket. */
        private final ServerSocket serverSocket;

        /** Resource to serve. */
        private final Resource resource;

        /** MIME content type of resource to serve. */
        private final String contentType;


        /**
         * Creates a request-handling worker that listens for requests on the
         * given socket and serves the given resource for all requests.
         *
         * @param sock Server socket.
         * @param resource Single resource to serve.
         * @param contentType MIME content type of resource to serve.
         */
        public Worker(final ServerSocket sock, final Resource resource, final String contentType) {
            this.serverSocket = sock;
            this.resource = resource;
            this.contentType = contentType;
            this.running = true;
        }

        public void run() {
            while (this.running) {
                try {
                    writeResponse(this.serverSocket.accept());
                } catch (SocketException se) {
                    System.out.println("Stopping on socket close.");
                    this.running = false;
                } catch (IOException ioe) {
                    ioe.printStackTrace();
                }
            }
        }
        
        public void stop() {
            try {
                this.serverSocket.close();
            } catch (IOException e) {
                // Ignore error on close
            }
        }
        
        private void writeResponse(final Socket socket) throws IOException {
            final OutputStream out = socket.getOutputStream();
            out.write(STATUS_LINE.getBytes());
            out.write(header("Content-Length", this.resource.contentLength()));
            out.write(header("Content-Type", this.contentType));
            out.write(SEPARATOR.getBytes());
           
            final byte[] buffer = new byte[BUFFER_SIZE];
            final InputStream in = this.resource.getInputStream();
            int count = 0;
            while ((count = in.read(buffer)) > -1) {
                out.write(buffer, 0, count);
            }
            in.close();
            socket.shutdownOutput();
        }
        
        private byte[] header(final String name, final Object value) {
            return String.format("%s: %s\r\n", name, value).getBytes();
        }
    }
}
