//   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.util.HashMap;
import java.util.Map;
import java.util.logging.Level;

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.util.thread.QueuedThreadPool;

import de.ugoe.cs.autoquest.httpmonitor.HttpMonitorComponent;
import de.ugoe.cs.autoquest.httpmonitor.HttpMonitorException;
import de.ugoe.cs.util.console.Console;

/**
 * <p>
 * This is a base class that combines common implementations required for connecting to a central
 * HTTP monitoring server. The class allows for sending HTTP requests to the central server and
 * waiting for the asynchronous response.
 * </p>
 * 
 * @author Patrick Harms
 */
public abstract class HttpMonitorRemoteConnection implements CompleteListener, 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 map of requests send to the central server and the corresponding result.
     * Initially, the result mapped to a request is null until the result is received.
     * </p>
     */
    private Map<Request, Result> openRequests = new HashMap<>();
    
    /**
     * <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 HttpMonitorRemoteConnection(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;
    }

    /**
     * <p>
     * creates a new request that can be used by subclasses to send something to the central server
     * </p>
     *
     * @return as described
     */
    protected Request newRequest() {
        return httpClient.newRequest(httpMonitorServer, httpMonitorPort);
    }

    /**
     * <p>
     * used to send a request to the central server. The request is stored internally to be able
     * to wait for the result.
     * </p>
     *
     * @param httpMonitorRequest the request to be sent to the central server
     */
    protected synchronized void sendRequest(Request httpMonitorRequest) {
        openRequests.put(httpMonitorRequest, null);
        httpMonitorRequest.send(this);
    }

    /**
     * <p>
     * used to synchronously wait for the result of a given request.
     * </p>
     *
     * @param httpMonitorRequest the request for which the result shall be waited for
     * 
     * @return the result of the request or null, if no result was received after 30 seconds
     */
    protected synchronized Result getResult(Request httpMonitorRequest) {

        try {
            // wait for the result of the request to be added to the map asynchronously
            int count = 30;
            while (openRequests.containsKey(httpMonitorRequest) &&
                   (openRequests.get(httpMonitorRequest) == null))
            {
                this.wait(1000);
                
                if (--count == 0) {
                    // after 30 seconds, cancel the sending of the request
                    openRequests.remove(httpMonitorRequest);
                    break;
                }
            }
        }
        catch (InterruptedException e) {
            // ignore, as this may only happen on system shutdown
        }

        return openRequests.get(httpMonitorRequest);
    }

    /* (non-Javadoc)
     * @see org.eclipse.jetty.client.api.Response.CompleteListener#onComplete(Result)
     */
    @Override
    public synchronized void onComplete(Result result) {
        openRequests.put(result.getRequest(), result);
        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;
    }
}
