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

import static org.junit.Assert.*;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
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.After;
import org.junit.Before;
import org.junit.Test;

import de.ugoe.cs.util.console.TextConsole;

/**
 *
 * @author Patrick Harms
 */
public class HtmlMonitorServerTest implements HtmlMonitorMessageListener {
    
    /**
     * 
     */
    public static final TextConsole CONSOLE = new TextConsole();
    
    /**
     * 
     */
    private static final int PORT = 19098;

    /**
     * 
     */
    private HtmlEvent[] events;

    /**
     * 
     */
    private HtmlGUIElement guiStructure;

    /**
     * 
     */
    private String eventHandlingError;

    /**
     * 
     */
    private HtmlMonitorServer server;

    /**
     * 
     */
    @Before
    public void setUp() throws Exception {
        server = new HtmlMonitorServer(PORT, this);
        server.init();
        server.start();
    }
    
    /**
     * 
     */
    @After
    public void tearDown() throws Exception {
        events = null;
        eventHandlingError = null;

        if (server != null) {
            try {
                server.stop();
            }
            finally {
                server = null;
            }
        }
    }
    

    /**
     * 
     */
    @Test
    public void testSimpleMessage() throws Exception {
        String clientId = "123";
        
        String message =
            "{" +
            "  \"message\": {" +
            "    \"clientInfos\": {" +
            "      \"clientId\":\"" + clientId + "\"," +
            "      \"userAgent\":\"Agent\"," +
            "      \"title\":\"Title\"," +
            "      \"url\":\"http://host/path\"" +
            "    }," +
            "    \"guiModel\": {" +
            "      \"tagName\":\"html\"," +
            "      \"index\":\"0\"," +
            "      \"children\":" +
            "      [ {" +
            "          \"tagName\":\"head\"," +
            "          \"index\":\"0\"," +
            "        }," +
            "        {" +
            "          \"tagName\":\"body\"," +
            "          \"htmlId\":\"gsr\"," +
            "        }" +
            "      ]" +
            "    }," +
            "    \"events\":" +
            "    [ {" +
            "        \"time\":\"12345\"," +
            "        \"path\":\"/html[0]/body(htmlId=gsr)\"," +
            "        \"eventType\":\"onclick\"" +
            "        \"coordinates\": [\"194\", \"7\"]" +
            "      }" +
            "    ]" +
            "  }" +
            "}";
        
        sendMessageAndAssertResponse(message);
        
        if (eventHandlingError != null) {
            fail(eventHandlingError);
        }
        
        // check the GUI structure
        HtmlGUIElement parent = null;
        HtmlGUIElement element = guiStructure;
        
        assertGuiElement(element, HtmlServer.class, parent, 1);
        
        parent = element;
        element = parent.getChildren().get(0);
        assertGuiElement(element, HtmlDocument.class, parent, 1);
        
        HtmlDocument document = (HtmlDocument) element;
        
        parent = element;
        element = parent.getChildren().get(0);
        assertGuiElement(element, HtmlPageElement.class, parent, 2);

        parent = element;
        element = parent.getChildren().get(0);
        assertGuiElement(element, HtmlPageElement.class, parent, 0);
        element = parent.getChildren().get(1);
        assertGuiElement(element, HtmlPageElement.class, parent, 0);

        // check the event
        assertNotNull(events);
        assertEquals(1, events.length);
        
        assertEvent(0, 12345, "/html[0]/body(htmlId=gsr)", element, document, "onclick",
                    null, null, new Integer[] {194, 7},
                    "Title", "Agent", "http://host/path", "123");
        
    }

    /**
     * 
     */
    @Test
    public void testComplexMessage() throws Exception {
        String clientId = "123";
        
        String message =
            "{" +
            "  \"message\": {" +
            "    \"clientInfos\": {" +
            "      \"clientId\":\"" + clientId + "\"," +
            "      \"userAgent\":\"Agent\"," +
            "      \"title\":\"Title\"," +
            "      \"url\":\"http://host/path\"" +
            "    }," +
            "    \"guiModel\": {" +
            "      \"tagName\":\"html\"," +
            "      \"index\":\"0\"," +
            "      \"children\":" +
            "      [ {" +
            "          \"tagName\":\"head\"," +
            "          \"index\":\"0\"," +
            "        }," +
            "        {" +
            "          \"tagName\":\"body\"," +
            "          \"htmlId\":\"gsr\"," +
            "          \"children\":" +
            "          [ {" +
            "              \"tagName\":\"input_button\"," +
            "              \"htmlId\":\"input1\"," +
            "            }," +
            "            {" +
            "              \"tagName\":\"input_button\"," +
            "              \"htmlId\":\"input2\"," +
            "            }," +
            "            {" +
            "              \"tagName\":\"input_button\"," +
            "              \"htmlId\":\"input3\"," +
            "            }," +
            "            {" +
            "              \"tagName\":\"input_button\"," +
            "              \"htmlId\":\"input4\"," +
            "            }," +
            "            {" +
            "              \"tagName\":\"input_button\"," +
            "              \"htmlId\":\"input5\"," +
            "            }," +
            "            {" +
            "              \"tagName\":\"input_button\"," +
            "              \"htmlId\":\"input6\"," +
            "            }," +
            "            {" +
            "              \"tagName\":\"input_button\"," +
            "              \"htmlId\":\"input7\"," +
            "            }," +
            "            {" +
            "              \"tagName\":\"input_button\"," +
            "              \"htmlId\":\"input8\"," +
            "            }," +
            "            {" +
            "              \"tagName\":\"input_button\"," +
            "              \"htmlId\":\"input9\"," +
            "            }," +
            "            {" +
            "              \"tagName\":\"input_button\"," +
            "              \"htmlId\":\"input10\"," +
            "            }," +
            "          ]" +
            "        }" +
            "      ]" +
            "    }," +
            "    \"events\":" +
            "    [ {" +
            "        \"time\":\"1\"," +
            "        \"path\":\"/html[0]/body(htmlId=gsr)/input_button(htmlId=input1)\"," +
            "        \"eventType\":\"onclick\"," +
            "        \"coordinates\": [\"194\", \"7\"]" +
            "      }," +
            "      {" +
            "        \"time\":\"2\"," +
            "        \"path\":\"/html[0]/body(htmlId=gsr)/input_button(htmlId=input2)\"," +
            "        \"eventType\":\"ondblclick\"," +
            "        \"coordinates\": [\"194\", \"7\"]" +
            "      }," +
            "      {" +
            "        \"time\":\"3\"," +
            "        \"path\":\"/html[0]/body(htmlId=gsr)/input_button(htmlId=input3)\"," +
            "        \"eventType\":\"onfocus\"" +
            "      }," +
            "      {" +
            "        \"time\":\"4\"," +
            "        \"path\":\"/html[0]/body(htmlId=gsr)/input_button(htmlId=input4)\"," +
            "        \"eventType\":\"onclick\"," +
            "        \"coordinates\": [\"125\", \"14\"]" +
            "      }," +
            "      {" +
            "        \"time\":\"5\"," +
            "        \"path\":\"/html[0]/body(htmlId=gsr)/input_button(htmlId=input5)\"," +
            "        \"eventType\":\"onfocus\"" +
            "      }," +
            "      {" +
            "        \"time\":\"6\"," +
            "        \"path\":\"/html[0]/body(htmlId=gsr)/input_button(htmlId=input6)\"," +
            "        \"eventType\":\"onfocus\"" +
            "      }," +
            "      {" +
            "        \"time\":\"7\"," +
            "        \"path\":\"/html[0]/body(htmlId=gsr)/input_button(htmlId=input7)\"," +
            "        \"eventType\":\"onfocus\"" +
            "      }," +
            "      {" +
            "        \"time\":\"8\"," +
            "        \"path\":\"/html[0]/body(htmlId=gsr)/input_button(htmlId=input8)\"," +
            "        \"eventType\":\"onclick\"," +
            "        \"coordinates\": [\"255\", \"4\"]" +
            "      }," +
            "      {" +
            "        \"time\":\"9\"," +
            "        \"path\":\"/html[0]/body(htmlId=gsr)\"," +
            "        \"eventType\":\"onscroll\"," +
            "        \"scrollPosition\": [\"23\", \"567\"]" +
            "      }," +
            "      {" +
            "        \"time\":\"10\"," +
            "        \"path\":\"/html[0]/body(htmlId=gsr)/input_button(htmlId=input10)\"," +
            "        \"eventType\":\"onclick\"," +
            "        \"coordinates\": [\"516\", \"154\"]" +
            "      }" +
            "    ]" +
            "  }" +
            "}";
 
        sendMessageAndAssertResponse(message);
        
        if (eventHandlingError != null) {
            fail(eventHandlingError);
        }
        
        // check the GUI structure
        HtmlGUIElement parent = null;
        HtmlGUIElement element = guiStructure;
        
        assertGuiElement(element, HtmlServer.class, parent, 1);
        
        parent = element;
        element = parent.getChildren().get(0);
        assertGuiElement(element, HtmlDocument.class, parent, 1);
        
        HtmlDocument document = (HtmlDocument) element;
        
        parent = element;
        element = parent.getChildren().get(0);
        assertGuiElement(element, HtmlPageElement.class, parent, 2);

        parent = element;
        element = parent.getChildren().get(0);
        assertGuiElement(element, HtmlPageElement.class, parent, 0);
        element = parent.getChildren().get(1);
        assertGuiElement(element, HtmlPageElement.class, parent, 10);
        
        parent = element;
        for (HtmlGUIElement child : parent.getChildren()) {
            assertGuiElement(child, HtmlPageElement.class, parent, 0);
        }

        // check the events
        assertNotNull(events);
        assertEquals(10, events.length);

        for (int i = 0; i < events.length; i++) {
            String targetDOMPath;
            HtmlGUIElement target;
            Integer[] scrollPosition = null;
            
            if (i == 8) {
                targetDOMPath = "/html[0]/body(htmlId=gsr)";
                target = parent;
                scrollPosition = new Integer[] { 23, 567 };
            }
            else {
                targetDOMPath =
                    "/html[0]/body(htmlId=gsr)/input_button(htmlId=input" + (i + 1) + ")";
                target = parent.getChildren().get(i);
            }
            
            String eventType;
            if ((i == 1)) {
                eventType = "ondblclick";
            }
            else if ((i == 2) || ((4 <= i) && (i <= 6))) {
                eventType = "onfocus";
            }
            else if ((i == 8)) {
                eventType = "onscroll";
            }
            else {
                eventType = "onclick";
            }
            
            assertNull("event " + i, events[i].getKey());
            
            Integer[] coordinates = null;
            if (i <= 1) {
                coordinates = new Integer[] { 194, 7 };
            }
            else if (i == 3) {
                coordinates = new Integer[] { 125, 14 };
            }
            else if (i == 7) {
                coordinates = new Integer[] { 255, 4 };
            }
            else if (i == 9) {
                coordinates = new Integer[] { 516, 154 };
            }
            
            assertEvent(i, i + 1, targetDOMPath, target, document, eventType,
                        null, scrollPosition, coordinates,
                        "Title", "Agent", "http://host/path", "123");
            
        }
    }
    
    /**
     * 
     */
    @Test
    public void testInvalidMessages() throws Exception {
        String message = "}";
        
        sendMessageAndAssertResponse(message);
            
        if (eventHandlingError != null) {
            fail(eventHandlingError);
        }

        assertNull(events);

        message = "blaublbidalslkdjflqkerowercalksdjfdlkkjdjfk";
        
        sendMessageAndAssertResponse(message);
            
        if (eventHandlingError != null) {
            fail(eventHandlingError);
        }

        assertNull(events);
        
        // the following message doesn't work because of the missing scroll position in the event
        message =
            "{" +
            "  \"message\": {" +
            "    \"clientInfos\": {" +
            "      \"clientId\":\"123\"," +
            "      \"userAgent\":\"Agent\"," +
            "      \"title\":\"Title\"," +
            "      \"url\":\"http://host/path\"" +
            "    }," +
            "    \"guiModel\": {" +
            "      \"tagName\":\"html\"," +
            "      \"index\":\"0\"," +
            "      \"children\":" +
            "      [ {" +
            "          \"tagName\":\"head\"," +
            "          \"index\":\"0\"," +
            "        }," +
            "        {" +
            "          \"tagName\":\"body\"," +
            "          \"htmlId\":\"gsr\"," +
            "        }" +
            "      ]" +
            "    }," +
            "    \"events\":" +
            "    [ {" +
            "        \"time\":\"9\"," +
            "        \"path\":\"/html[0]/body(htmlId=gsr)\"," +
            "        \"eventType\":\"onscroll\"," +
            "      }" +
            "    ]" +
            "  }" +
            "}";
        
        sendMessageAndAssertResponse(message);
        
        if (eventHandlingError != null) {
            fail(eventHandlingError);
        }

        assertNull(events);
        
        // the following message doesn't work because of the missing client id
        message =
            "{" +
            "  \"message\": {" +
            "    \"clientInfos\": {" +
            "      \"userAgent\":\"Agent\"," +
            "      \"title\":\"Title\"," +
            "      \"url\":\"http://host/path\"" +
            "    }," +
            "    \"guiModel\": {" +
            "      \"tagName\":\"html\"," +
            "      \"index\":\"0\"," +
            "      \"children\":" +
            "      [ {" +
            "          \"tagName\":\"head\"," +
            "          \"index\":\"0\"," +
            "        }," +
            "        {" +
            "          \"tagName\":\"body\"," +
            "          \"htmlId\":\"gsr\"," +
            "        }" +
            "      ]" +
            "    }," +
            "    \"events\":" +
            "    [ {" +
            "        \"time\":\"12345\"," +
            "        \"path\":\"/html[0]/body(htmlId=gsr)\"," +
            "        \"eventType\":\"onunload\"" +
            "      }" +
            "    ]" +
            "  }" +
            "}";
        
        sendMessageAndAssertResponse(message);
        
        if (eventHandlingError != null) {
            fail(eventHandlingError);
        }

        assertNull(events);
        
        // the following message doesn't work because of the invalid time stamp
        message =
            "{" +
            "  \"message\": {" +
            "    \"clientInfos\": {" +
            "      \"clientId\":\"123\"," +
            "      \"userAgent\":\"Agent\"," +
            "      \"title\":\"Title\"," +
            "      \"url\":\"http://host/path\"" +
            "    }," +
            "    \"guiModel\": {" +
            "      \"tagName\":\"html\"," +
            "      \"index\":\"0\"," +
            "      \"children\":" +
            "      [ {" +
            "          \"tagName\":\"head\"," +
            "          \"index\":\"0\"," +
            "        }," +
            "        {" +
            "          \"tagName\":\"body\"," +
            "          \"htmlId\":\"gsr\"," +
            "        }" +
            "      ]" +
            "    }," +
            "    \"events\":" +
            "    [ {" +
            "        \"time\":\"blub\"," +
            "        \"path\":\"/html[0]/body(htmlId=gsr)\"," +
            "        \"eventType\":\"onunload\"" +
            "      }" +
            "    ]" +
            "  }" +
            "}";
        
        sendMessageAndAssertResponse(message);
        
        if (eventHandlingError != null) {
            fail(eventHandlingError);
        }

        assertNull(events);
        
    }
    
    /* (non-Javadoc)
     * @see de.ugoe.cs.autoquest.htmlmonitor.HtmlMonitoringListener#handleEvents(de.ugoe.cs.autoquest.htmlmonitor.HtmlClientInfos, de.ugoe.cs.autoquest.htmlmonitor.HtmlEvent[])
     */
    @Override
    public void handleMessage(HtmlClientInfos clientInfos,
                              HtmlGUIElement  guiStructure,
                              HtmlEvent[]     events)
    {
        if (clientInfos == null) {
            eventHandlingError = "client infos were null";
        }
        else if (guiStructure == null) {
            eventHandlingError = "gui structure was null";
        }
        else if (events == null) {
            eventHandlingError = "events were null";
        }
        else {
            for (HtmlEvent event : events) {
                if (!clientInfos.equals(event.getClientInfos())) {
                    eventHandlingError = "one of the events did not have a correct client info";
                }
            }
            
            this.events = events;
            this.guiStructure = guiStructure;
        }
    }

    /**
     *
     */
    private void sendMessageAndAssertResponse(String message) throws Exception {
        DefaultHttpClient httpclient = new DefaultHttpClient();
        HttpPost httpPost = new HttpPost("http://localhost:" + PORT + "/");
        HttpEntity entity = new StringEntity(message, ContentType.APPLICATION_JSON);
        httpPost.setEntity(entity);
        
        try {
            HttpResponse response = httpclient.execute(httpPost);
            
            // the monitor always returns 200 without any additional information. The client must
            // never get more or less information. This is especially important for preventing
            // hackers from finding out more
            assertEquals(200, response.getStatusLine().getStatusCode());
            assertTrue
                ((response.getEntity() == null) || (response.getEntity().getContentLength() == 0) ||
                 ((response.getEntity().getContentLength() == 1) &&
                  (response.getEntity().getContent().read() == ' ')));
        }
        finally {
            httpPost.releaseConnection();
        }
    }

    /**
     *
     */
    private void assertGuiElement(HtmlGUIElement                  element,
                                  Class<? extends HtmlGUIElement> type,
                                  HtmlGUIElement                  parent,
                                  int                             noOfChildren)
    {
        assertTrue(type.isInstance(element));
        
        if (parent != null) {
            assertNotNull(element.getParent());
            assertEquals(parent.getId(), element.getParent().getId());
        }
        else {
            assertNull(element.getParent());
        }
        
        assertNotNull(element.getId());
        
        if (noOfChildren > 0) {
            assertNotNull(element.getChildren());
            assertEquals(noOfChildren, element.getChildren().size());
        }
        else {
            assertNull(element.getChildren());
        }
    }

    /**
     *
     */
    private void assertEvent(int            index,
                             long           timestamp,
                             String         targetDOMPath,
                             HtmlGUIElement target,
                             HtmlDocument   document,
                             String         eventType,
                             Integer        key,
                             Integer[]      scrollPosition,
                             Integer[]      coordinates,
                             String         clientInfoTitle,
                             String         clientInfoAgent,
                             String         clientInfoURL,
                             String         clientId)
    {
        assertEquals("event " + index, Long.valueOf(timestamp), events[index].getTime());
        assertEquals("event " + index, targetDOMPath, events[index].getTargetDOMPath());
        assertEquals("event " + index, target, events[index].getTarget());
        assertEquals("event " + index, document, events[index].getTargetDocument());
        assertEquals("event " + index, eventType, events[index].getEventType());
        
        assertEquals("event " + index, key, events[index].getKey());
        assertArrayEquals("event " + index, scrollPosition, events[index].getScrollPosition());
        assertArrayEquals("event " + index, coordinates, events[index].getCoordinates());
        
        assertEquals
            ("event " + index, clientInfoTitle, events[index].getClientInfos().getTitle());
        assertEquals
            ("event " + index, clientInfoAgent, events[index].getClientInfos().getUserAgent());
        assertEquals
            ("event " + index, clientInfoURL, events[index].getClientInfos().getUrl().toString());
        assertEquals
            ("event " + index, clientId, events[index].getClientInfos().getClientId());
    }

}
