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

import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.net.URL;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Unmarshaller;
import javax.xml.namespace.QName;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.Test;

import dummyservice.ObjectFactory;
import dummyservice.VerifyUserInMsgType;
import dummyservice.VerifyUserOutMsgType;

import de.ugoe.cs.autoquest.eventcore.Event;
import de.ugoe.cs.autoquest.httpmonitor.exchange.HttpExchange;
import de.ugoe.cs.autoquest.httpmonitor.exchange.Method;
import de.ugoe.cs.autoquest.httpmonitor.proxy.HttpMonitoringProxy;
import de.ugoe.cs.autoquest.plugin.http.HTTPLogParser;
import dummyservice.DummyService;
import dummyservice.DummyServicePortType;

/**
 * @author Patrick Harms
 */
public class HttpMonitoringProxyTest extends AbstractTC {

    /**
     * 
     */
    private HttpMonitoringProxy proxy;

    /**
     * the jetty web server used for receiving messages copied by the proxy
     */
    private Server dummyMonitor;
    
    /**
     * the messages received by the dummy monitor
     */
    private List<String> messages;

    /**
     * 
     */
    protected void setUpHook() throws Exception {
        // setup a simple HTTP server
        messages = new LinkedList<String>();
        
        dummyMonitor = new Server(MONITOR_PORT);
        ServletContextHandler root =
            new ServletContextHandler(dummyMonitor, "/", ServletContextHandler.SESSIONS);

        root.addServlet(new ServletHolder(new DummyMonitorServlet()), "/*");
        
        dummyMonitor.start();
    }
    
    /**
     *
     */
    protected void tearDownHook() throws Exception {
        if (proxy != null) {
            try {
                proxy.stop();
            }
            finally {
                proxy = null;
            }
        }
        if (dummyMonitor != null) {
            try {
                dummyMonitor.stop();
            }
            finally {
                dummyMonitor = null;
            }
        }
        messages = null;
    }
    
    @Test(expected=java.lang.IllegalArgumentException.class)
    public void test_InvalidInitialization_01() throws Exception {
        proxy = new HttpMonitoringProxy(new String[] {  });
    }
    
    @Test
    public void test_SimpleText_Local() throws Exception {
        proxy = new HttpMonitoringProxy
            (new String[] { LOG_FILE_DIR, PROXY_PORT + "", "localhost:" + DUMMY_SERVER_PORT,
                            "local" });

        proxy.init();
        proxy.start();
        
        String message = "dummy message";
        String expectedResponse = "response content";
        String response = sendDummyMessage("POST", message, expectedResponse);
        
        assertEquals(expectedResponse, response);
        
        proxy.stop();
        proxy = 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", message, expectedResponse);
    }

    @Test
    public void test_SimpleText_Remote() throws Exception {
        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", message, expectedResponse);
        
        assertEquals(expectedResponse, response);
        
        // give the proxy some time to send the copy of the message
        while(messages.size() < 1) {
            Thread.sleep(1000);
        }
        
        assertEquals(1, messages.size());
        
        JAXBContext jaxbContext = JAXBContext.newInstance(HttpExchange.class.getPackage().getName());
        Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
        
        @SuppressWarnings("unchecked")
        JAXBElement<HttpExchange> jaxObject =
            (JAXBElement<HttpExchange>) unmarshaller.unmarshal(new StringReader(messages.get(0)));
        
        HttpExchange exchange = jaxObject.getValue();
        
        assertEquals(message, exchange.getRequest().getContent().getData());
        assertEquals(response, exchange.getResponse().getContent().getData());
    }
    
    @Test
    public void test_XMLMessage_Local() throws Exception {
        proxy = new HttpMonitoringProxy
            (new String[] { LOG_FILE_DIR, PROXY_PORT + "", "localhost:" + DUMMY_SERVER_PORT,
                            "local" });

        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", message, expectedResponse);
        
        assertEquals(expectedResponse, response);
        
        proxy.stop();
        proxy = 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", message, expectedResponse);
    }
    
    @Test
    public void test_XMLMessage_Remote() throws Exception {
        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", message, expectedResponse);
        
        assertEquals(expectedResponse, response);
        
        // give the proxy some time to send the copy of the message
        while(messages.size() < 1) {
            Thread.sleep(1000);
        }
        
        assertEquals(1, messages.size());
        
        JAXBContext jaxbContext = JAXBContext.newInstance(HttpExchange.class.getPackage().getName());
        Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
        
        @SuppressWarnings("unchecked")
        JAXBElement<HttpExchange> jaxObject =
            (JAXBElement<HttpExchange>) unmarshaller.unmarshal(new StringReader(messages.get(0)));
        
        HttpExchange exchange = jaxObject.getValue();
        
        assertEquals(message, exchange.getRequest().getContent().getData());
        assertEquals(response, exchange.getResponse().getContent().getData());
    }
    
    @Test
    public void test_SOAP_Local() throws Exception {
        proxy = new HttpMonitoringProxy
            (new String[] { LOG_FILE_DIR, PROXY_PORT + "", "localhost:" + DUMMY_SERVER_PORT,
                            "local" });

        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();
        
        ObjectFactory factory = new ObjectFactory();
        VerifyUserInMsgType request = factory.createVerifyUserInMsgType();
        VerifyUserOutMsgType response = dummyService.verifyUser(request);

        assertNotNull(response);
        
        proxy.stop();
        proxy = 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", null, null); // get WSDL
        assertEvent(sequence.get(1), "POST", null, null); // send request
    }
    
    @SuppressWarnings("unchecked")
    @Test
    public void test_SOAP_Remote() throws Exception {
        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();
        
        ObjectFactory factory = new ObjectFactory();
        VerifyUserInMsgType request = factory.createVerifyUserInMsgType();
        VerifyUserOutMsgType response = dummyService.verifyUser(request);

        assertNotNull(response);
        
        proxy.stop();
        proxy = null;

        // give the proxy some time to send the copy of the message
        while(messages.size() < 1) {
            Thread.sleep(1000);
        }
        
        assertEquals(2, messages.size());
        
        JAXBContext jaxbContext = JAXBContext.newInstance(HttpExchange.class.getPackage().getName());
        Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
        
        JAXBElement<HttpExchange> jaxObject =
            (JAXBElement<HttpExchange>) unmarshaller.unmarshal(new StringReader(messages.get(0)));
        
        HttpExchange exchange = jaxObject.getValue();
        
        assertEquals(Method.GET, exchange.getRequest().getMethod());

        assertNull(exchange.getRequest().getContent());
        assertNotNull(exchange.getResponse().getContent().getData());

        jaxObject =
            (JAXBElement<HttpExchange>) unmarshaller.unmarshal(new StringReader(messages.get(1)));
            
        exchange = jaxObject.getValue();
            
        assertEquals(Method.POST, exchange.getRequest().getMethod());

        assertNotNull(exchange.getRequest().getContent().getData());
        assertNotNull(exchange.getResponse().getContent().getData());
    }

    /**
     * @author Patrick Harms
     */
    private class DummyMonitorServlet extends HttpServlet {

        /**  */
        private static final long serialVersionUID = 1L;
        
        /* (non-Javadoc)
         * @see javax.servlet.http.HttpServlet#service(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
         */
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException
        {
            messages.add(readToString(req.getReader()));
            
            resp.setStatus(HttpServletResponse.SC_OK);
        }
        
        
    }

}
