//   Copyright 2012 Georg-August-Universität Göttingen, Germany
//
//   Licensed 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
//
//       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 de.ugoe.cs.autoquest.httpmonitor.proxy;

import javax.servlet.Servlet;

import de.ugoe.cs.autoquest.httpmonitor.HttpMonitorComponent;
import de.ugoe.cs.autoquest.httpmonitor.HttpMonitorException;
import de.ugoe.cs.autoquest.httpmonitor.HttpMonitorExchangeHandler;
import de.ugoe.cs.autoquest.httpmonitor.HttpMonitorLogManager;
import de.ugoe.cs.autoquest.httpmonitor.HttpMonitorServer;
import de.ugoe.cs.autoquest.httpmonitor.IdGenerator;
import de.ugoe.cs.autoquest.httpmonitor.Runner;
import de.ugoe.cs.autoquest.httpmonitor.ShutdownHook;
import de.ugoe.cs.autoquest.httpmonitor.SimpleIdGenerator;
import de.ugoe.cs.util.console.Console;

/**
 * <p>
 * The HTTP monitory proxy monitor starts a web server ({@link HttpMonitorServer}) that receives
 * proxied HTTP messages and response. Each exchange of a request and a response is forwarded to
 * an exchange handler. The exchange handler is either a local log manager
 * ({@link HttpMonitorLogManager}) or a connection to an external and central HTTP monitor via
 * an {@link HttpMonitorRemoteExchangeHandler}. It also holds a reference to an id generator. If
 * the exchanges are handled locally, then also a local {@link SimpleIdGenerator} is used. If
 * the exchanges are send to a central HTTP monitor, then this monitor is used as id generator via
 * an {@link HttpMonitorRemoteIdGenerator}. The class ensures that on shutdown e.g. caused
 * by CTRL-C the server and all other requied components are stopped correctly.
 * </p>
 * 
 * @author Patrick Harms
 */
public class HttpMonitoringProxy implements HttpMonitorComponent {

    /**
     * the port on which the webserver shall listen.
     */
    private int port;
    
    /**
     * the web server receiving the HTTP requests to be proxied
     */
    private HttpMonitorServer server;
    
    /**
     * the manager for all currently recorded exchanges
     */
    private ExchangeListenerManager exchangeListenerManager;
    
    /**
     *  the exchange handler to handle are request/response combinations, i.e., exchanges
     */
    private HttpMonitorExchangeHandler exchangeHandler;
    
    /**
     *  the id generator used to generate order ids for the requests and responses
     */
    private IdGenerator idGenerator;
    
    /**
     * the directory into which the log files shall be written
     */
    private String logFileBaseDir;

    /**
     * the thread needed to handle CTRL-C events
     */
    private Thread shutdownHook;

    /**
     * the name of the proxied server
     */
    private String proxiedServer;
    
    /**
     * the port of the proxied server
     */
    private int proxiedPort;
    
    /**
     * the name of the server where the HTTP monitor runs if the exchanges are not logged locally
     */
    private String httpMonitorServer;
    
    /**
     * the port of the server where the HTTP monitor runs if the exchanges are not logged locally
     */
    private int httpMonitorPort;
    
    /**
     * <p>
     * initializes the proxy with the command line arguments. Those are the log directory
     * as first argument, the port to listen to as second argument, the proxied server and port
     * as third argument, and either the server and port of the HTTP monitor or the term local as
     * fourth argument. If the term local is provided as fourth argument, logging of exchanges
     * takes place locally.
     * </p>
     *
     * @param commandLineArguments the command line arguments when starting the monitor using
     *                             the {@link Runner}
     */
    public HttpMonitoringProxy(String[] commandLineArguments) {
        if (commandLineArguments.length != 4) {
            Console.printerrln("invalid number of command line parameters. Three are required:");
            Console.printerrln("  1. the port on which to listen");
            Console.printerrln("  2. the server and port to be proxied (format \"host:port\")");
            Console.printerrln
                ("  3. variant a: the value \"local\" for logging recorded exchanges locally");
            Console.printerrln
                ("     variant b: the server and port of the HTTP monitor to send the recorded\n" +
                 "                exchanges to (format \"host:port\")");
            throw new IllegalArgumentException();
        }
        
        this.logFileBaseDir = commandLineArguments[0];

        try {
            this.port = Integer.parseInt(commandLineArguments[1]);
        }
        catch (NumberFormatException e) {
            Console.printerrln("invalid port specification " + commandLineArguments[1]);
        }
        Console.println("listening on port " + this.port);

        proxiedServer = commandLineArguments[2];
        
        int index = proxiedServer.indexOf(':');
        if (index > -1) {
            try {
                proxiedPort = Integer.parseInt(proxiedServer.substring(index + 1));
            }
            catch (NumberFormatException e) {
                Console.printerrln
                    ("invalid port specification " + proxiedServer.substring(index + 1));
            }
            
            proxiedServer = proxiedServer.substring(0, index);
        }
        else {
            proxiedPort = 80;
        }
        
        Console.println("proxing " + proxiedServer + ":" + proxiedPort);
        
        httpMonitorServer = commandLineArguments[3];
        
        index = httpMonitorServer.indexOf(':');
        if (index > -1) {
            try {
                httpMonitorPort = Integer.parseInt(httpMonitorServer.substring(index + 1));
            }
            catch (NumberFormatException e) {
                Console.printerrln
                    ("invalid port specification " + httpMonitorServer.substring(index + 1));
            }
            
            httpMonitorServer = httpMonitorServer.substring(0, index);
        }
        else if ("local".equals(httpMonitorServer)) {
            httpMonitorServer = null;
        }
        else {
            httpMonitorPort = 80;
        }
        
        if (httpMonitorServer != null) {
            Console.println("sending log data to " + httpMonitorServer + ":" + httpMonitorPort);
            exchangeHandler =
                new HttpMonitorRemoteExchangeHandler(httpMonitorServer, httpMonitorPort);
            
            idGenerator = new HttpMonitorRemoteIdGenerator(httpMonitorServer, httpMonitorPort);
        }
        else {
            Console.println("storing logs locally into directory " + logFileBaseDir);
            exchangeHandler = new HttpMonitorLogManager(logFileBaseDir);
            idGenerator = new SimpleIdGenerator();
        }
    }

    /* (non-Javadoc)
     * @see de.ugoe.cs.autoquest.htmlmonitor.HttpMonitorComponent#init()
     */
    @Override
    public synchronized void init() throws HttpMonitorException {
        if (server != null) {
            throw new IllegalStateException("already initialized.");
        }
        
        exchangeHandler.init();
        idGenerator.init();
        
        exchangeListenerManager = new ExchangeListenerManager(exchangeHandler, idGenerator);
        exchangeListenerManager.init();
        
        Servlet servlet =
            new HttpMonitoringProxyServlet(proxiedServer, proxiedPort, exchangeListenerManager);
        server = new HttpMonitorServer(port, servlet);
        server.init();
      
        shutdownHook = new Thread
            (new ShutdownHook(server, exchangeListenerManager, exchangeHandler));
    }

    /* (non-Javadoc)
     * @see de.ugoe.cs.autoquest.htmlmonitor.HttpMonitorComponent#start()
     */
    @Override
    public synchronized void start() {
        if ((exchangeHandler == null) || (idGenerator == null) ||
            (exchangeListenerManager == null) || (server == null))
        {
            throw new IllegalStateException("not initialized.");
        }
        
        try {
            Runtime.getRuntime().addShutdownHook(shutdownHook);
            exchangeHandler.start();
            idGenerator.start();
            exchangeListenerManager.start();
            server.start();
        }
        catch (HttpMonitorException e) {
            Console.printerrln("could not start HTTP monitoring proxy: " + e);
            Console.logException(e);
        }
    }

    /* (non-Javadoc)
     * @see de.ugoe.cs.autoquest.htmlmonitor.HttpMonitorComponent#stop()
     */
    @Override
    public synchronized void stop() {
        if ((exchangeHandler == null) || (idGenerator == null) ||
            (exchangeListenerManager == null) || (server == null))
        {
            throw new IllegalStateException("not initialized.");
        }
        
        Runtime.getRuntime().removeShutdownHook(shutdownHook);
        server.stop();
        exchangeListenerManager.stop();
        idGenerator.stop();
        exchangeHandler.stop();
        
        server = null;
        exchangeListenerManager = null;
        idGenerator = null;
        exchangeHandler = null;
    }

}
