//   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;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import java.io.File;
import java.io.StringWriter;
import java.net.URL;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.namespace.QName;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.junit.Test;

import de.ugoe.cs.autoquest.eventcore.Event;
import de.ugoe.cs.autoquest.http.HTTPTestUtils;
import de.ugoe.cs.autoquest.httpmonitor.proxy.HttpMonitoringProxy;
import de.ugoe.cs.autoquest.plugin.http.HTTPLogParser;
import de.ugoe.cs.autoquest.plugin.http.HTTPUtils;
import de.ugoe.cs.autoquest.plugin.http.eventcore.HTTPEventType;
import de.ugoe.cs.autoquest.plugin.http.eventcore.HTTPTarget;
import de.ugoe.cs.autoquest.plugin.http.logdata.HttpExchange;
import de.ugoe.cs.autoquest.plugin.http.logdata.ObjectFactory;
import de.ugoe.cs.autoquest.plugin.http.logdata.Session;
import dummyservice.DummyService;
import dummyservice.DummyServicePortType;
import dummyservice.VerifyUserInMsgType;
import dummyservice.VerifyUserOutMsgType;

/**
 * @author Patrick Harms
 */
public class HttpMonitorTest extends AbstractTC {
    
    /**
     * 
     */
    private HttpMonitoringProxy proxy;

    /**
     * 
     */
    private HttpMonitor monitor;

    /* (non-Javadoc)
     * @see de.ugoe.cs.autoquest.httpmonitor.AbstractTC#setUpHook()
     */
    @Override
    protected void setUpHook() throws Exception {
        // nothing to do
    }

    /**
     *
     */
    public void tearDownHook() throws Exception {
        if (proxy != null) {
            try {
                proxy.stop();
            }
            finally {
                proxy = null;
            }
        }
        if (monitor != null) {
            try {
                monitor.stop();
            }
            finally {
                monitor = null;
            }
        }
    }
    
    /**
     *
     */
    @Test
    public void test_RetrievalOfId_MonitorOnly() throws Exception {
        monitor = new HttpMonitor(new String[] { LOG_FILE_DIR, MONITOR_PORT + "" });

        monitor.init();
        monitor.start();
        
        long id = getId();
        assertTrue(id > 0);
        
        for (int i = 0; i < 300; i++) {
            long prevId = id;
            id = getId();
            assertTrue(id > prevId);
        }
        
        monitor.stop();
        monitor = null;
    }
    
    /**
     *
     */
    @Test
    public void test_SimulatedSession_MonitorOnly() throws Exception {
        monitor = new HttpMonitor(new String[] { LOG_FILE_DIR, MONITOR_PORT + "" });

        monitor.init();
        monitor.start();
        
        Session simulatedSession = HTTPTestUtils.createRandomSession(50);
        sendExchanges(simulatedSession.getHttpExchange());
        
        monitor.stop();
        monitor = null;

        File logFile = new File(LOG_FILE_DIR + File.separator + "httpmonitor_000.log");
        
        assertTrue(logFile.exists());
        
        HTTPLogParser parser = new HTTPLogParser();

        parser.parseFile(logFile);

        // check the sequences
        Collection<List<Event>> sequences = parser.getSequences();

        assertNotNull(sequences);

        Iterator<List<Event>> iterator = sequences.iterator();
        assertTrue(iterator.hasNext());

        List<Event> sequence = iterator.next();
        assertFalse(iterator.hasNext());

        assertNotNull(sequence);
        assertEquals(simulatedSession.getHttpExchange().size(), sequence.size());

        System.out.println("{");
        System.out.println("  {");
        long prevId = 0;
        
        for (int j = 0; j < sequence.size(); j++) {
            System.out.print("    ");
            System.out.print(sequence.get(j));
            System.out.println(",");

            assertNotNull(sequence.get(j));
            assertNotNull(sequence.get(j).getType());
            assertTrue(sequence.get(j).getType() instanceof HTTPEventType);

            assertNotNull(sequence.get(j).getTarget());
            assertTrue(sequence.get(j).getTarget() instanceof HTTPTarget);

            HTTPEventType eventType = (HTTPEventType) sequence.get(j).getType();
            
            HTTPTestUtils.assertExchangeEquals
                (simulatedSession.getHttpExchange().get(j), eventType.getExchange());

            assertEquals(HTTPUtils.toString(simulatedSession.getHttpExchange().get(j).getReceiver()),
                         ((HTTPTarget) sequence.get(j).getTarget()).getStringIdentifier());
            
            assertNotNull(eventType.getExchange().getRequest().getOrderingId());
            assertTrue(prevId < eventType.getExchange().getRequest().getOrderingId());
            prevId = eventType.getExchange().getRequest().getOrderingId();
            
            assertNotNull(eventType.getExchange().getResponse().getOrderingId());
            assertTrue(prevId < eventType.getExchange().getResponse().getOrderingId());
            prevId = eventType.getExchange().getResponse().getOrderingId();
        }
        System.out.println("  }");
        System.out.println("}");
        System.out.println("\n\n");
    }
    
    /**
     *
     */
    @Test
    public void test_SimpleText_ProxyAndMonitor() throws Exception {
        monitor = new HttpMonitor(new String[] { LOG_FILE_DIR, MONITOR_PORT + "" });

        monitor.init();
        monitor.start();

        proxy = new HttpMonitoringProxy
            (new String[] { LOG_FILE_DIR, PROXY_PORT + "",
                            "localhost:" + DUMMY_SERVER_PORT, "localhost:" + MONITOR_PORT });

        proxy.init();
        proxy.start();
        
        String message = "dummy message";
        String expectedResponse = "response content";
        String response = sendDummyMessage("POST", null, message, expectedResponse);
        
        assertEquals(expectedResponse, response);
        
        // the monitor needs some time to receive the exchange --> give it
        Thread.sleep(1000);
        
        monitor.stop();
        monitor = null;

        File logFile = new File(LOG_FILE_DIR + File.separator + "httpmonitor_000.log");
        
        assertTrue(logFile.exists());
        
        HTTPLogParser parser = new HTTPLogParser();

        parser.parseFile(logFile);

        // check the sequences
        Collection<List<Event>> sequences = parser.getSequences();

        assertNotNull(sequences);

        Iterator<List<Event>> iterator = sequences.iterator();
        assertTrue(iterator.hasNext());

        List<Event> sequence = iterator.next();
        assertFalse(iterator.hasNext());

        assertNotNull(sequence);
        assertEquals(1, sequence.size());

        assertEvent(sequence.get(0), "POST", null, message, response);
    }
    
    /**
     *
     */
    @Test
    public void test_XMLMessage_ProxyAndMonitor() throws Exception {
        monitor = new HttpMonitor(new String[] { LOG_FILE_DIR, MONITOR_PORT + "" });

        monitor.init();
        monitor.start();

        proxy = new HttpMonitoringProxy
            (new String[] { LOG_FILE_DIR, PROXY_PORT + "",
                            "localhost:" + DUMMY_SERVER_PORT, "localhost:" + MONITOR_PORT });

        proxy.init();
        proxy.start();
        
        String message =
            "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
            "<xsd:schema targetNamespace=\"http://autoquest.informatik.uni-goettingen.de\">" +
            "  <xsd:element name=\"httpEvent\" type=\"tns:HttpEvent\" />" +
            "</xsd:schema>";
    
        String expectedResponse =
            "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
            "<httpEvent status=\"success\" xmlns=\"http://autoquest.informatik.uni-goettingen.de\">" +
            "  <sender><ip>127.0.0.1</ip><host>127.0.0.1</host><port>42688</port></sender>" +
            "  <receiver><ip>127.0.0.1</ip><host>127.0.0.1</host><port>19098</port></receiver>" +
            "  <request method=\"POST\" protocol=\"HTTP/1.1\" url=\"http://localhost:19098/\">" +
            "    <headers>" +
            "      <header key=\"User-Agent\" value=\"Apache-HttpClient/4.2.1 (java 1.5)\"/>" +
            "      <header key=\"Connection\" value=\"keep-alive\"/>" +
            "      <header key=\"Host\" value=\"localhost:19098\"/>" +
            "      <header key=\"Content-Type\" value=\"text/plain; charset=ISO-8859-1\"/>" +
            "      <header key=\"Content-Length\" value=\"13\"/>" +
            "    </headers>" +
            "    <content encoding=\"ISO-8859-1\" type=\"text/plain; charset=ISO-8859-1\"" +
            "             length=\"13\">" +
            "      <data>dummy message</data>" +
            "    </content>" +
            "  </request>" +
            "  <response status=\"200\">" +
            "    <headers>" +
            "      <header key=\"Server\" value=\"Jetty(9.1.0.M0)\"/>" +
            "      <header key=\"Content-Length\" value=\"16\"/>" +
            "    </headers>" +
            "    <content encoding=\"ISO-8859-1\" length=\"16\">" +
            "      <data>response content</data>" +
            "    </content>" +
            "  </response>" +
            "</httpEvent>";

        String response = sendDummyMessage("POST", null, message, expectedResponse);
        
        assertEquals(expectedResponse, response);
        
        // the monitor needs some time to receive the exchange --> give it
        Thread.sleep(1000);
        
        monitor.stop();
        monitor = null;

        File logFile = new File(LOG_FILE_DIR + File.separator + "httpmonitor_000.log");
        
        assertTrue(logFile.exists());
        
        HTTPLogParser parser = new HTTPLogParser();

        parser.parseFile(logFile);

        // check the sequences
        Collection<List<Event>> sequences = parser.getSequences();

        assertNotNull(sequences);

        Iterator<List<Event>> iterator = sequences.iterator();
        assertTrue(iterator.hasNext());

        List<Event> sequence = iterator.next();
        assertFalse(iterator.hasNext());

        assertNotNull(sequence);
        assertEquals(1, sequence.size());

        assertEvent(sequence.get(0), "POST", null, message, response);
    }
    
    /**
     *
     */
    @Test
    public void test_SOAP_ProxyAndMonitor() throws Exception {
        monitor = new HttpMonitor(new String[] { LOG_FILE_DIR, MONITOR_PORT + "" });

        monitor.init();
        monitor.start();

        proxy = new HttpMonitoringProxy
            (new String[] { LOG_FILE_DIR, PROXY_PORT + "",
                            "localhost:" + DUMMY_SERVER_PORT, "localhost:" + MONITOR_PORT });

        proxy.init();
        proxy.start();

        DummyService service = new DummyService
            (new URL("http://localhost:" + PROXY_PORT + "/dummyWebapp/DummyServiceSOAPPort?wsdl"),
             new QName("DummyService", "DummyService"));

        DummyServicePortType dummyService = service.getDummyServiceSOAPPort();
        
        dummyservice.ObjectFactory factory = new dummyservice.ObjectFactory();
        VerifyUserInMsgType request = factory.createVerifyUserInMsgType();
        VerifyUserOutMsgType response = dummyService.verifyUser(request);

        assertNotNull(response);
        
        // the monitor needs some time to receive the exchange --> give it
        Thread.sleep(1000);
        
        monitor.stop();
        monitor = null;

        File logFile = new File(LOG_FILE_DIR + File.separator + "httpmonitor_000.log");
        
        assertTrue(logFile.exists());
        
        HTTPLogParser parser = new HTTPLogParser();

        parser.parseFile(logFile);

        // check the sequences
        Collection<List<Event>> sequences = parser.getSequences();

        assertNotNull(sequences);

        Iterator<List<Event>> iterator = sequences.iterator();
        assertTrue(iterator.hasNext());

        List<Event> sequence = iterator.next();
        assertFalse(iterator.hasNext());

        assertNotNull(sequence);
        assertEquals(2, sequence.size());

        assertEvent(sequence.get(0), "GET", "wsdl", null, null); // get WSDL
        assertEvent(sequence.get(1), "POST", null, null, null); // send request
    }
    
    /**
     *
     */
    @Test
    public void test_LargeRequest_ProxyAndMonitor() throws Exception {
        monitor = new HttpMonitor(new String[] { LOG_FILE_DIR, MONITOR_PORT + "" });

        monitor.init();
        monitor.start();

        proxy = new HttpMonitoringProxy
            (new String[] { LOG_FILE_DIR, PROXY_PORT + "",
                            "localhost:" + DUMMY_SERVER_PORT, "localhost:" + MONITOR_PORT });

        proxy.init();
        proxy.start();
        
        StringBuffer message = new StringBuffer();
        StringBuffer expectedResponse = new StringBuffer();
        
        for (int i = 0; i < 1000; i++) {
            message.append(" # " + i + " test request data");
            expectedResponse.append(" # " + i + " test response data");
        }
        
        String response =
            sendDummyMessage("POST", null, message.toString(), expectedResponse.toString());
        
        assertEquals(expectedResponse.toString(), response);
        
        // the monitor needs some time to receive the exchange --> give it
        Thread.sleep(1000);
        
        monitor.stop();
        monitor = null;

        File logFile = new File(LOG_FILE_DIR + File.separator + "httpmonitor_000.log");
        
        assertTrue(logFile.exists());
        
        HTTPLogParser parser = new HTTPLogParser();

        parser.parseFile(logFile);

        // check the sequences
        Collection<List<Event>> sequences = parser.getSequences();

        assertNotNull(sequences);

        Iterator<List<Event>> iterator = sequences.iterator();
        assertTrue(iterator.hasNext());

        List<Event> sequence = iterator.next();
        assertFalse(iterator.hasNext());

        assertNotNull(sequence);
        assertEquals(1, sequence.size());

        assertEvent(sequence.get(0), "POST", null, message.toString(), response);
    }
    
    /**
     *
     */
    @Test
    public void test_Query_ProxyAndMonitor() throws Exception {
        monitor = new HttpMonitor(new String[] { LOG_FILE_DIR, MONITOR_PORT + "" });

        monitor.init();
        monitor.start();

        proxy = new HttpMonitoringProxy
            (new String[] { LOG_FILE_DIR, PROXY_PORT + "",
                            "localhost:" + DUMMY_SERVER_PORT, "localhost:" + MONITOR_PORT });

        proxy.init();
        proxy.start();
        
        String message = "dummy message";
        String query = "key=value&key2=value2&key3=%3E%3CXXX";
        String expectedResponse = "response content";
        String response = sendDummyMessage("POST", query, message, expectedResponse);
        
        assertEquals(expectedResponse, response);
        
        // the monitor needs some time to receive the exchange --> give it
        Thread.sleep(1000);
        
        monitor.stop();
        monitor = null;

        File logFile = new File(LOG_FILE_DIR + File.separator + "httpmonitor_000.log");
        
        assertTrue(logFile.exists());
        
        HTTPLogParser parser = new HTTPLogParser();

        parser.parseFile(logFile);

        // check the sequences
        Collection<List<Event>> sequences = parser.getSequences();

        assertNotNull(sequences);

        Iterator<List<Event>> iterator = sequences.iterator();
        assertTrue(iterator.hasNext());

        List<Event> sequence = iterator.next();
        assertFalse(iterator.hasNext());

        assertNotNull(sequence);
        assertEquals(1, sequence.size());

        assertEvent(sequence.get(0), "POST", query, message, response);
    }

    /**
     *
     */
    private void sendExchanges(List<HttpExchange> exchanges) throws Exception {
        DefaultHttpClient httpclient = new DefaultHttpClient();
        
        for (HttpExchange exchange : exchanges) {
            HttpPost httpRequest = new HttpPost("http://localhost:" + MONITOR_PORT + "/");
            
            JAXBContext jaxbContext =
                    JAXBContext.newInstance(HttpExchange.class.getPackage().getName());
            Marshaller marshaller = jaxbContext.createMarshaller();

            StringWriter out = new StringWriter();
            marshaller.marshal(new ObjectFactory().createHttpExchange(exchange), out);

            HttpEntity entity = new StringEntity
                (out.toString(), ContentType.create("text/plain", "UTF-8"));
            ((HttpPost) httpRequest).setEntity(entity);
        
            try {
                httpclient.execute(httpRequest);
            }
            finally {
                httpRequest.releaseConnection();
                out.close();
            }
        }
    }

    /**
     *
     */
    private long getId() throws Exception {
        DefaultHttpClient httpclient = new DefaultHttpClient();
        
        HttpGet httpRequest = new HttpGet("http://localhost:" + MONITOR_PORT + "/");
            
        try {
            HttpResponse response = httpclient.execute(httpRequest);
            return Long.parseLong(response.getFirstHeader("X-AutoQUEST-OrderingId").getValue());
        }
        finally {
            httpRequest.releaseConnection();
        }
    }

}
