//   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 java.io.File;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

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.autoquest.eventcore.Event;
import de.ugoe.cs.autoquest.eventcore.IEventType;
import de.ugoe.cs.autoquest.eventcore.gui.KeyboardFocusChange;
import de.ugoe.cs.autoquest.eventcore.gui.MouseButtonInteraction;
import de.ugoe.cs.autoquest.eventcore.gui.MouseClick;
import de.ugoe.cs.autoquest.eventcore.gui.MouseDoubleClick;
import de.ugoe.cs.autoquest.eventcore.gui.Scroll;
import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModel;
import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement;
import de.ugoe.cs.autoquest.plugin.html.HTMLLogParser;
import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLDocument;
import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLPageElement;
import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLServer;
import de.ugoe.cs.util.console.TextConsole;

/**
 * <p>
 * TODO comment
 * </p>
 * 
 * @author Patrick Harms
 */
public class HtmlMonitorTest {

    /**
     * 
     */
    public static final TextConsole CONSOLE = new TextConsole();
    
    /**
     * 
     */
    private final static String LOG_FILE_DIR = "target/tmp/logfiles/";
    
    /**
     * 
     */
    private static final int PORT = 19098;

    /**
     * 
     */
    private HtmlMonitor htmlMonitor;

    /**
     *
     */
    @Before
    public void setUp() throws Exception {
        htmlMonitor = new HtmlMonitor(new String[] { LOG_FILE_DIR, Integer.toString(PORT) });
        htmlMonitor.init();
        htmlMonitor.start();
    }

    /**
     *
     */
    @After
    public void tearDown() throws Exception {
        if (htmlMonitor != null) {
            try {
                htmlMonitor.stop();
            }
            finally {
                htmlMonitor = null;
            }
        }
        
        deleteFiles(new File(LOG_FILE_DIR));
    }

    /**
     *
     */
    @Test
    public void testOneSimpleMessage() 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);
        
        htmlMonitor.stop();
        htmlMonitor = null;

        File logFile = new File(LOG_FILE_DIR + File.separator + clientId + File.separator +
                                "htmlmonitor_" + clientId + "_000.log");
        
        assertTrue(logFile.exists());
        
        HTMLLogParser parser = new HTMLLogParser();
        
        parser.parseFile(logFile);
        
        // check the GUI model
        GUIModel guiModel = parser.getGuiModel();
        assertNotNull(guiModel);
        
        List<IGUIElement> nodes = guiModel.getRootElements();
        assertNotNull(nodes);
        assertEquals(1, nodes.size());
        
        // get server node
        IGUIElement node = nodes.get(0);
        assertNotNull(node);
        assertTrue(node instanceof HTMLServer);
        assertEquals("HTML", node.getPlatform());
        assertFalse(node.isUsed());
        
        nodes = guiModel.getChildren(node);
        assertNotNull(nodes);
        assertEquals(1, nodes.size());
        
        // get document node
        node = nodes.get(0);
        assertNotNull(node);
        assertTrue(node instanceof HTMLDocument);
        assertEquals("HTML", node.getPlatform());
        assertFalse(node.isUsed());
        
        nodes = guiModel.getChildren(node);
        assertNotNull(nodes);
        assertEquals(1, nodes.size());
        
        // get html node
        node = nodes.get(0);
        assertNotNull(node);
        assertTrue(node instanceof HTMLPageElement);
        assertEquals("HTML", node.getPlatform());
        assertFalse(node.isUsed());
        
        nodes = guiModel.getChildren(node);
        assertNotNull(nodes);
        assertEquals(1, nodes.size()); // only one child as the head tag should have been ignored
        
        // get body node
        node = nodes.get(0);
        assertNotNull(node);
        assertTrue(node instanceof HTMLPageElement);
        assertEquals("HTML", node.getPlatform());
        assertTrue(node.isUsed());

        nodes = guiModel.getChildren(node);
        assertNotNull(nodes);
        assertEquals(0, nodes.size());
        
        // 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), 12345, MouseClick.class, node, 194, 7);
    }

    /**
     *
     */
    @Test
    public void testSeveralMessagesInOneSession() 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);
        
        htmlMonitor.stop();
        htmlMonitor = null;

        File logFile = new File(LOG_FILE_DIR + File.separator + clientId + File.separator +
                                "htmlmonitor_" + clientId + "_000.log");
        
        assertTrue(logFile.exists());
        
        HTMLLogParser parser = new HTMLLogParser();
        
        parser.parseFile(logFile);
        
        // check the GUI model
        GUIModel guiModel = parser.getGuiModel();
        assertNotNull(guiModel);
        
        List<IGUIElement> nodes = guiModel.getRootElements();
        assertNotNull(nodes);
        assertEquals(1, nodes.size());
        
        // get server node
        IGUIElement node = nodes.get(0);
        assertNotNull(node);
        assertTrue(node instanceof HTMLServer);
        assertEquals("HTML", node.getPlatform());
        assertFalse(node.isUsed());
        
        nodes = guiModel.getChildren(node);
        assertNotNull(nodes);
        assertEquals(1, nodes.size());
        
        // get document node
        node = nodes.get(0);
        assertNotNull(node);
        assertTrue(node instanceof HTMLDocument);
        assertEquals("HTML", node.getPlatform());
        assertFalse(node.isUsed());
        
        nodes = guiModel.getChildren(node);
        assertNotNull(nodes);
        assertEquals(1, nodes.size());
        
        // get html node
        node = nodes.get(0);
        assertNotNull(node);
        assertTrue(node instanceof HTMLPageElement);
        assertEquals("HTML", node.getPlatform());
        assertFalse(node.isUsed());
        
        nodes = guiModel.getChildren(node);
        assertNotNull(nodes);
        assertEquals(1, nodes.size()); // only one child as the head tag should have been ignored
        
        // get body node
        IGUIElement body = nodes.get(0);
        assertNotNull(body);
        assertTrue(body instanceof HTMLPageElement);
        assertEquals("HTML", body.getPlatform());
        assertTrue(body.isUsed());

        nodes = guiModel.getChildren(body);
        assertNotNull(nodes);
        assertEquals(10, nodes.size());
        
        // get input nodes
        for (int i = 0; i < nodes.size(); i++) {
            node = nodes.get(i);
            assertNotNull(node);
            assertTrue(node instanceof HTMLPageElement);
            assertEquals("HTML", node.getPlatform());
            
            if (i != 8) {
                assertTrue(node.isUsed());
            }
            else {
                assertFalse(node.isUsed());
            }

            assertNotNull(guiModel.getChildren(node));
            assertEquals(0, guiModel.getChildren(node).size());
        }
        
        // 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(10, sequence.size());
        
        assertEvent(sequence.get(0), 1, MouseClick.class, nodes.get(0), 194, 7);
        assertEvent(sequence.get(1), 2, MouseDoubleClick.class, nodes.get(1), 194, 7);
        assertEvent(sequence.get(2), 3, KeyboardFocusChange.class, nodes.get(2), 0, 0);
        assertEvent(sequence.get(3), 4, MouseClick.class, nodes.get(3), 125, 14);
        assertEvent(sequence.get(4), 5, KeyboardFocusChange.class, nodes.get(4), 0, 0);
        assertEvent(sequence.get(5), 6, KeyboardFocusChange.class, nodes.get(5), 0, 0);
        assertEvent(sequence.get(6), 7, KeyboardFocusChange.class, nodes.get(6), 0, 0);
        assertEvent(sequence.get(7), 8, MouseClick.class, nodes.get(7), 255, 4);
        assertEvent(sequence.get(8), 9, Scroll.class, body, 0, 0);
        assertEvent(sequence.get(9), 10, MouseClick.class, nodes.get(9), 516, 154);

    }

    /**
     *
     */
    @Test
    public void testSeveralSessions() 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);
        
        int numberOfSessions = 10;
        for (int i = 0; i < numberOfSessions; i++) {
            htmlMonitor.stop();
            htmlMonitor = new HtmlMonitor(new String[] { LOG_FILE_DIR, Integer.toString(PORT) });
            htmlMonitor.init();
            htmlMonitor.start();
            sendMessageAndAssertResponse(message);
        }
        
        htmlMonitor.stop();
        htmlMonitor = null;
        
        HTMLLogParser parser = new HTMLLogParser();
        
        // assert 9 already rotated log files
        for (int i = 0; i < numberOfSessions; i++) {
            File logFile = new File(LOG_FILE_DIR + File.separator + clientId + File.separator +
                                    "htmlmonitor_" + clientId + "_00" + i + ".log");
       
            assertTrue(logFile.exists());
       
            parser.parseFile(logFile);
        }

        // check the GUI model
        GUIModel guiModel = parser.getGuiModel();
        assertNotNull(guiModel);
        
        List<IGUIElement> nodes = guiModel.getRootElements();
        assertNotNull(nodes);
        assertEquals(1, nodes.size());
        
        // get server node
        IGUIElement node = nodes.get(0);
        assertNotNull(node);
        assertTrue(node instanceof HTMLServer);
        assertEquals("HTML", node.getPlatform());
        assertFalse(node.isUsed());
        
        nodes = guiModel.getChildren(node);
        assertNotNull(nodes);
        assertEquals(1, nodes.size());
        
        // get document node
        node = nodes.get(0);
        assertNotNull(node);
        assertTrue(node instanceof HTMLDocument);
        assertEquals("HTML", node.getPlatform());
        assertFalse(node.isUsed());
        
        nodes = guiModel.getChildren(node);
        assertNotNull(nodes);
        assertEquals(1, nodes.size());
        
        // get html node
        node = nodes.get(0);
        assertNotNull(node);
        assertTrue(node instanceof HTMLPageElement);
        assertEquals("HTML", node.getPlatform());
        assertFalse(node.isUsed());
        
        nodes = guiModel.getChildren(node);
        assertNotNull(nodes);
        assertEquals(1, nodes.size()); // only one child as the head tag should have been ignored
        
        // get body node
        node = nodes.get(0);
        assertNotNull(node);
        assertTrue(node instanceof HTMLPageElement);
        assertEquals("HTML", node.getPlatform());
        assertTrue(node.isUsed());

        nodes = guiModel.getChildren(node);
        assertNotNull(nodes);
        assertEquals(0, nodes.size());
        
        // check the sequences
        Collection<List<Event>> sequences = parser.getSequences();
        
        assertNotNull(sequences);
        assertEquals(numberOfSessions, sequences.size());
        
        Iterator<List<Event>> iterator = sequences.iterator();
        
        while (iterator.hasNext()) {
            List<Event> sequence = iterator.next();
        
            assertNotNull(sequence);
            assertEquals(1, sequence.size());
       
            assertEvent(sequence.get(0), 12345, MouseClick.class, node, 194, 7);
        }
    }

    /**
     * <p>
     * TODO: comment
     * </p>
     *
     * @param message
     */
    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));
        }
        finally {
            httpPost.releaseConnection();
        }
    }

    /**
     *
     */
    private void assertEvent(Event                       event,
                             int                         timestamp,
                             Class<? extends IEventType> eventType,
                             IGUIElement                 eventTarget,
                             int                         xCoordinate,
                             int                         yCoordinate)
    {
        assertEquals(timestamp, event.getTimestamp());
        assertTrue(eventType.isInstance(event.getType()));
        assertEquals(eventTarget, event.getTarget());
        
        if (event.getType() instanceof MouseButtonInteraction) {
            assertEquals(xCoordinate, ((MouseButtonInteraction) event.getType()).getX());
            assertEquals(yCoordinate, ((MouseButtonInteraction) event.getType()).getY());
        }
    }

    /**
     * <p>
     * TODO: comment
     * </p>
     *
     * @param file
     */
    private void deleteFiles(File file) {
        if (file.exists()) {
            if (file.isDirectory()) {
                for (File child : file.listFiles()) {
                    deleteFiles(child);
                }
            }
            
            try {
                file.delete();
            }
            catch (Exception e) {
                // ignore and delete as much as possible
            }
        }
    }

}
