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

import static org.junit.Assert.*;

import java.io.File;

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.genericeventmonitor.GenericEventMonitor;
import de.ugoe.cs.util.console.TextConsole;

/**
 *  
 * @author Patrick Harms
 */
public class GenericEventMonitorTest {

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

    /**
     * 
     */
    private GenericEventMonitor genericEventMonitor;

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

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

    /**
     *
     */
    @Test
    public void testOneSimpleMessage() throws Exception {
        String clientId = "123";
        String appId = "456";
        
        String message =
            "{" +
            "  \"message\": {" +
            "    \"clientInfos\": {" +
            "      \"clientId\":\"" + clientId + "\"," +
            "      \"appId\":\"" + appId + "\"," +
            "    }," +
            "    \"targetStructure\": [{" +
            "      \"targetId\":\"target1\"," +
            "      \"param1\":\"value1\"," +
            "      \"param3\":\"value3\"," +
            "      \"param2\":\"value2\"," +
            "      \"children\":" +
            "      [ {" +
            "          \"targetId\":\"target3\"," +
            "          \"index\":\"0\"," +
            "        }," +
            "        {" +
            "          \"targetId\":\"target2\"," +
            "          \"htmlId\":\"gsr\"," +
            "        }" +
            "      ]" +
            "    }]," +
            "    \"events\":" +
            "    [ {" +
            "        \"time\":\"12345\"," +
            "        \"targetId\":\"target2\"," +
            "        \"type\":\"gaze\"," +
            "        \"xcoordinate\": \"194\"," +
            "        \"ycoordinate\": \"12\"" +
            "      }" +
            "    ]" +
            "  }" +
            "}";

        sendMessageAndAssertResponse(message);
        
        genericEventMonitor.stop();
        genericEventMonitor = null;

        File logFile = new File(LOG_FILE_DIR + File.separator + appId + File.separator +
                                clientId + File.separator + "genericevents_" + clientId + "_000.log");
        
        assertTrue(logFile.exists());
        
        /*HTMLLogParser parser = new HTMLLogParser(null);
        
        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 appId = "456";
        
        String message =
            "{" +
            "  \"message\": {" +
            "    \"clientInfos\": {" +
            "      \"clientId\":\"" + clientId + "\"," +
            "      \"appId\":\"" + appId + "\"," +
            "    }," +
            "    \"targetStructure\": [{" +
            "      \"targetId\":\"html\"," +
            "      \"index\":\"0\"," +
            "      \"children\":" +
            "      [ {" +
            "          \"targetId\":\"head\"," +
            "          \"index\":\"0\"," +
            "        }," +
            "        {" +
            "          \"tagName\":\"body\"," +
            "          \"targetId\":\"gsr\"," +
            "          \"children\":" +
            "          [ {" +
            "              \"tagName\":\"input_button\"," +
            "              \"targetId\":\"input1\"," +
            "            }," +
            "            {" +
            "              \"tagName\":\"input_button\"," +
            "              \"targetId\":\"input2\"," +
            "            }," +
            "            {" +
            "              \"tagName\":\"input_button\"," +
            "              \"targetId\":\"input3\"," +
            "            }," +
            "            {" +
            "              \"tagName\":\"input_button\"," +
            "              \"targetId\":\"input4\"," +
            "            }," +
            "            {" +
            "              \"tagName\":\"input_button\"," +
            "              \"targetId\":\"input5\"," +
            "            }," +
            "            {" +
            "              \"tagName\":\"input_button\"," +
            "              \"targetId\":\"input6\"," +
            "            }," +
            "            {" +
            "              \"tagName\":\"input_button\"," +
            "              \"targetId\":\"input7\"," +
            "            }," +
            "            {" +
            "              \"tagName\":\"input_button\"," +
            "              \"targetId\":\"input8\"," +
            "            }," +
            "            {" +
            "              \"tagName\":\"input_button\"," +
            "              \"targetId\":\"input9\"," +
            "            }," +
            "            {" +
            "              \"tagName\":\"input_button\"," +
            "              \"targetId\":\"input10\"," +
            "            }," +
            "          ]" +
            "        }" +
            "      ]" +
            "    }]," +
            "    \"events\":" +
            "    [ {" +
            "        \"time\":\"1\"," +
            "        \"targetId\":\"input1\"," +
            "        \"type\":\"onclick\"," +
            "        \"coordinates\": \"194\"" +
            "      }," +
            "      {" +
            "        \"time\":\"2\"," +
            "        \"targetId\":\"input2\"," +
            "        \"type\":\"ondblclick\"," +
            "        \"coordinates\": \"194\"" +
            "      }," +
            "      {" +
            "        \"time\":\"3\"," +
            "        \"targetId\":\"input3\"," +
            "        \"type\":\"onfocus\"" +
            "      }," +
            "      {" +
            "        \"time\":\"4\"," +
            "        \"targetId\":\"input4\"," +
            "        \"type\":\"onclick\"," +
            "        \"coordinates\": \"255\"" +
            "      }," +
            "      {" +
            "        \"time\":\"5\"," +
            "        \"targetId\":\"input5\"," +
            "        \"type\":\"onfocus\"" +
            "      }," +
            "      {" +
            "        \"time\":\"6\"," +
            "        \"targetId\":\"input6\"," +
            "        \"type\":\"onfocus\"" +
            "      }," +
            "      {" +
            "        \"time\":\"7\"," +
            "        \"targetId\":\"input7\"," +
            "        \"type\":\"onfocus\"" +
            "      }," +
            "      {" +
            "        \"time\":\"8\"," +
            "        \"targetId\":\"input8\"," +
            "        \"type\":\"onclick\"," +
            "        \"coordinates\": \"255\"" +
            "      }," +
            "      {" +
            "        \"time\":\"9\"," +
            "        \"targetId\":\"input9\"," +
            "        \"type\":\"onscroll\"," +
            "        \"scrollPosition\": \"194\"" +
            "      }," +
            "      {" +
            "        \"time\":\"10\"," +
            "        \"targetId\":\"input10\"," +
            "        \"type\":\"onclick\"," +
            "        \"coordinates\": \"194\"" +
            "      }" +
            "    ]" +
            "  }" +
            "}";
 
        sendMessageAndAssertResponse(message);
        
        genericEventMonitor.stop();
        genericEventMonitor = null;

        File logFile = new File(LOG_FILE_DIR + File.separator + appId + File.separator +
                                clientId + File.separator + "genericevents_" + clientId + "_000.log");
        
        assertTrue(logFile.exists());
        
        /*HTMLLogParser parser = new HTMLLogParser(null);
        
        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);
        
        // wait for all 10 GUI elements on the same page to be logged although only 9 are used
        assertEquals(10, nodes.size());
        
        Map<String, IGUIElement> inputs = new HashMap<>();
        
        // 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 (!"input9".equals(((HTMLPageElement) node).getHtmlId())) {
                assertTrue(node.isUsed());
            }
            else {
                assertFalse(node.isUsed());
            }
            
            inputs.put(((HTMLPageElement) node).getHtmlId(), node);

            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, inputs.get("input1"), 194, 7);
        assertEvent(sequence.get(1), 2, MouseDoubleClick.class, inputs.get("input2"), 194, 7);
        assertEvent(sequence.get(2), 3, KeyboardFocusChange.class, inputs.get("input3"), 0, 0);
        assertEvent(sequence.get(3), 4, MouseClick.class, inputs.get("input4"), 125, 14);
        assertEvent(sequence.get(4), 5, KeyboardFocusChange.class, inputs.get("input5"), 0, 0);
        assertEvent(sequence.get(5), 6, KeyboardFocusChange.class, inputs.get("input6"), 0, 0);
        assertEvent(sequence.get(6), 7, KeyboardFocusChange.class, inputs.get("input7"), 0, 0);
        assertEvent(sequence.get(7), 8, MouseClick.class, inputs.get("input8"), 255, 4);
        assertEvent(sequence.get(8), 9, Scroll.class, body, 0, 0);
        assertEvent(sequence.get(9), 10, MouseClick.class, inputs.get("input10"), 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)\"," +
            "        \"type\":\"onclick\"" +
            "        \"coordinates\": [\"194\", \"7\"]" +
            "      }" +
            "    ]" +
            "  }" +
            "}";

        sendMessageAndAssertResponse(message);
        
        int numberOfSessions = 10;
        for (int i = 0; i < numberOfSessions; i++) {
            genericEventMonitor.stop();
            genericEventMonitor =
                new GenericEventMonitor(new String[] { LOG_FILE_DIR, Integer.toString(PORT) });
            
            genericEventMonitor.init();
            genericEventMonitor.start();
            sendMessageAndAssertResponse(message);
        }
        
        genericEventMonitor.stop();
        genericEventMonitor = null;
        
        /*HTMLLogParser parser = new HTMLLogParser(null);
        
        // assert 9 already rotated log files
        for (int i = 0; i < numberOfSessions; i++) {
            File logFile = new File(LOG_FILE_DIR + File.separator + "host" + 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);
        }*/
    }

    /**
     *
     */
    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 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());
        }
    }

    /**
     *
     */
    private void deleteFiles(File file) {
        if (file.exists()) {
            if (file.isDirectory()) {
            	File[] files = file.listFiles();
            	if (files != null) {
                    for (File child : files) {
                       deleteFiles(child);
                    }
                }
            }
            
            try {
                file.delete();
            }
            catch (Exception e) {
                // ignore and delete as much as possible
            }
        }
    }

}
