//   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 java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;

import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response.CompleteListener;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.util.OutputStreamContentProvider;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.util.thread.QueuedThreadPool;

import de.ugoe.cs.autoquest.httpmonitor.HttpMonitor;
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.plugin.http.logdata.HttpExchange;
import de.ugoe.cs.autoquest.plugin.http.logdata.ObjectFactory;
import de.ugoe.cs.util.console.Console;

/**
 * <p>
 * If the exchanges recorded by the proxy are to be transmitted to a central {@link HttpMonitor},
 * this exchanges handler is used. It is called by the exchange listener on completion of a proxied
 * request/response. It then creates an HTTP request to the central monitor and sends it there.
 * It is initialized with the name of the server and the port on which the central monitor runs.
 * If the exchanges can not be forwarded to the central server, they are discarded.
 * </p>
 * 
 * @author Patrick Harms
 */
public class HttpMonitorRemoteExchangeHandler
    implements CompleteListener, HttpMonitorExchangeHandler, HttpMonitorComponent
{

    /**
     * <p>
     * the HTTP client used internally to send data to the central server
     * </p>
     */
    private HttpClient httpClient;
    
    /**
     * <p>
     * the host name of the central server
     * </p>
     */
    private String httpMonitorServer;
    
    /**
     * <p>
     * the port of the central server
     * </p>
     */
    private int httpMonitorPort;

    /**
     * <p>
     * a set of requests send to the central server for which the response was not received yet
     * </p>
     */
    private Set<Request> openRequests = new HashSet<Request>();
    
    /**
     * <p>
     * initializes the exchange handler with the host and port of the central server
     * </p>
     *
     * @param httpMonitorServer the host name of the central server
     * @param httpMonitorPort   the port of the central server
     */
    public HttpMonitorRemoteExchangeHandler(String httpMonitorServer, int httpMonitorPort) {
        this.httpMonitorServer = httpMonitorServer;
        this.httpMonitorPort = httpMonitorPort;
    }

    /* (non-Javadoc)
     * @see de.ugoe.cs.autoquest.httpmonitor.HttpMonitorComponent#init()
     */
    @Override
    public void init() throws IllegalStateException, HttpMonitorException {
        httpClient = createHttpClient();
    }

    /* (non-Javadoc)
     * @see de.ugoe.cs.autoquest.httpmonitor.HttpMonitorComponent#start()
     */
    @Override
    public void start() throws IllegalStateException, HttpMonitorException {
        try {
            httpClient.start();
            httpClient.getContentDecoderFactories().clear();
        }
        catch (Exception e) {
            throw new HttpMonitorException("could not start client for HTTP-Monitor", e);
        }
    }

    /* (non-Javadoc)
     * @see de.ugoe.cs.autoquest.httpmonitor.HttpMonitorComponent#stop()
     */
    @Override
    public void stop() {
        if (httpClient != null) {
            try {
                httpClient.stop();
            }
            catch (Exception e) {
                Console.traceln(Level.WARNING, "could not stop client for HTTP-Monitor");
                Console.logException(e);
            }
        }
        
        httpClient = null;
    }

    /* (non-Javadoc)
     * @see de.ugoe.cs.autoquest.httpmonitor.HttpMonitorExchangeHandler#handleHttpExchange()
     */
    @Override
    public synchronized void handleHttpExchange(HttpExchange httpExchange) {
        // send the exchange to the server and wait for completion
        Request httpMonitorRequest = httpClient.newRequest(httpMonitorServer, httpMonitorPort);
        httpMonitorRequest.method(HttpMethod.POST);
        httpMonitorRequest.version(HttpVersion.HTTP_1_1);

        OutputStreamContentProvider out = new OutputStreamContentProvider();
        httpMonitorRequest.content(out);
        
        openRequests.add(httpMonitorRequest);
        httpMonitorRequest.send(this);

        try {
            JAXBContext jaxbContext =
                JAXBContext.newInstance(HttpExchange.class.getPackage().getName());
            Marshaller marshaller = jaxbContext.createMarshaller();

            OutputStreamWriter writer = new OutputStreamWriter(out.getOutputStream(), "UTF-8");
            marshaller.marshal(new ObjectFactory().createHttpExchange(httpExchange), writer);

            out.getOutputStream().close();
        }
        catch (JAXBException e) {
            Console.printerrln("could not convert request and response to XML string: " + e);
            Console.logException(e);
        }
        catch (IOException e) {
            Console.printerrln
                ("could not close the stream for sending data to the HTML monitor: " + e);
            Console.logException(e);
        }

        try {
            // wait for the request to be removed from the list asynchronously
            int count = 30;
            while (openRequests.contains(httpMonitorRequest)) {
                this.wait(1000);
                
                if (--count == 0) {
                    // after 30 seconds, cancel the sending of the loggin request
                    openRequests.remove(httpMonitorRequest);
                    break;
                }
            }
        }
        catch (InterruptedException e) {
            // ignore, as this may only happen on system shutdown
        }
    }

    /* (non-Javadoc)
     * @see org.eclipse.jetty.client.api.Response.CompleteListener#onComplete(Result)
     */
    @Override
    public synchronized void onComplete(Result result) {
        if (result.isFailed()) {
            Console.traceln
                (Level.WARNING, "could not log exchange correctly: " + result.getFailure());
            Console.logException((Exception) result.getFailure());
        }
        
        openRequests.remove(result.getRequest());
        this.notify();
    }

    /**
     * <p>
     * convenience method to create an initialize the utilized HTTP client
     * </p>
     */
    private HttpClient createHttpClient() {
        HttpClient client = new HttpClient();
        
        QueuedThreadPool executor = new QueuedThreadPool(10);
        executor.setName("HttpMonitorClients");
        client.setExecutor(executor);

        client.setMaxConnectionsPerDestination(10000);
        client.setIdleTimeout(30000);
        client.setConnectTimeout(30000);
        client.setStopTimeout(30000);
        client.setRequestBufferSize(1024*1024);
        client.setResponseBufferSize(1024*1024);
        
        return client;
    }
}
