//   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.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
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.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\"" +
            "    }," +
            "    \"events\":" +
            "    [ {" +
            "        \"time\":\"12345\"," +
            "        \"path\":\"/html[0]/body(id=gsr)\"," +
            "        \"eventType\":\"onunload\"" +
            "      }" +
            "    ]" +
            "  }" +
            "}";

        sendMessageAndAssertResponse(message);
        
        File logFile = new File(LOG_FILE_DIR + File.separator + clientId + File.separator +
                                "htmlmonitor_" + clientId + ".log");
        
        assertTrue(logFile.exists());
        
        String[] logEntries = readLogEntries(logFile);
        assertEquals(1, logEntries.length);
        
        assertLogMessage(logEntries[0], clientId, "12345", "Title", "http://host/path", "Agent",
                         "onunload", "/html[0]/body(id=gsr)");
    }

    /**
     *
     */
    @Test
    public void testSeveralMessagesInOneSession() throws Exception {
        String clientId = "123";
       
        String message =
            "{" +
            "  \"message\": {" +
            "    \"clientInfos\": {" +
            "      \"clientId\":\"" + clientId + "\"," +
            "      \"userAgent\":\"Agent\"," +
            "      \"title\":\"Title\"," +
            "      \"url\":\"http://host/path\"" +
            "    }," +
            "    \"events\":" +
            "    [" +
            "      {" +
            "        \"time\":\"1\"," +
            "        \"path\":\"/html[0]/body(id=gsr)/input(id=input1)\"," +
            "        \"eventType\":\"onclick\"," +
            "        \"coordinates\": [\"194\", \"7\"]" +
            "      }," +
            "      {" +
            "        \"time\":\"2\"," +
            "        \"path\":\"/html[0]/body(id=gsr)/input(id=input2)\"," +
            "        \"eventType\":\"ondblclick\"," +
            "        \"coordinates\": [\"194\", \"7\"]" +
            "      }," +
            "      {" +
            "        \"time\":\"3\"," +
            "        \"path\":\"/html[0]/body(id=gsr)/input(id=input3)\"," +
            "        \"eventType\":\"onfocus\"" +
            "      }," +
            "      {" +
            "        \"time\":\"4\"," +
            "        \"path\":\"/html[0]/body(id=gsr)/input(id=input4)\"," +
            "        \"eventType\":\"onclick\"," +
            "        \"coordinates\": [\"125\", \"14\"]" +
            "      }," +
            "      {" +
            "        \"time\":\"5\"," +
            "        \"path\":\"/html[0]/body(id=gsr)/input(id=input5)\"," +
            "        \"eventType\":\"onfocus\"" +
            "      }," +
            "      {" +
            "        \"time\":\"6\"," +
            "        \"path\":\"/html[0]/body(id=gsr)/input(id=input6)\"," +
            "        \"eventType\":\"onfocus\"" +
            "      }," +
            "      {" +
            "        \"time\":\"7\"," +
            "        \"path\":\"/html[0]/body(id=gsr)/input(id=input7)\"," +
            "        \"eventType\":\"onfocus\"" +
            "      }," +
            "      {" +
            "        \"time\":\"8\"," +
            "        \"path\":\"/html[0]/body(id=gsr)/input(id=input8)\"," +
            "        \"eventType\":\"onclick\"," +
            "        \"coordinates\": [\"255\", \"4\"]" +
            "      }," +
            "      {" +
            "        \"time\":\"9\"," +
            "        \"path\":\"/html[0]/body(id=gsr)\"," +
            "        \"eventType\":\"onscroll\"," +
            "        \"scrollPosition\":\"165\"" +
            "      }," +
            "      {" +
            "        \"time\":\"10\"," +
            "        \"path\":\"/html[0]/body(id=gsr)/input(id=input10)\"," +
            "        \"eventType\":\"onclick\"," +
            "        \"coordinates\": [\"516\", \"154\"]" +
            "      }" +
            "    ]" +
            "  }" +
            "}";
 
        sendMessageAndAssertResponse(message);
        
        File logFile = new File(LOG_FILE_DIR + File.separator + clientId + File.separator +
                                "htmlmonitor_" + clientId + ".log");
        
        assertTrue(logFile.exists());
        
        String[] logEntries = readLogEntries(logFile);
        assertEquals(10, logEntries.length);
        
        assertLogMessage(logEntries[0], clientId, "1", "Title", "http://host/path", "Agent",
                         "onclick", "/html[0]/body(id=gsr)/input(id=input1)", "194,7");
        assertLogMessage(logEntries[1], clientId, "2", "Title", "http://host/path", "Agent",
                         "ondblclick", "/html[0]/body(id=gsr)/input(id=input2)", "194,7");
        assertLogMessage(logEntries[2], clientId, "3", "Title", "http://host/path", "Agent",
                         "onfocus", "/html[0]/body(id=gsr)/input(id=input3)");
        assertLogMessage(logEntries[3], clientId, "4", "Title", "http://host/path", "Agent",
                         "onclick", "/html[0]/body(id=gsr)/input(id=input4)", "125,14");
        assertLogMessage(logEntries[4], clientId, "5", "Title", "http://host/path", "Agent",
                         "onfocus", "/html[0]/body(id=gsr)/input(id=input5)");
        assertLogMessage(logEntries[5], clientId, "6", "Title", "http://host/path", "Agent",
                         "onfocus", "/html[0]/body(id=gsr)/input(id=input6)");
        assertLogMessage(logEntries[6], clientId, "7", "Title", "http://host/path", "Agent",
                         "onfocus", "/html[0]/body(id=gsr)/input(id=input7)");
        assertLogMessage(logEntries[7], clientId, "8", "Title", "http://host/path", "Agent",
                         "onclick", "/html[0]/body(id=gsr)/input(id=input8)", "255,4");
        assertLogMessage(logEntries[8], clientId, "9", "Title", "http://host/path", "Agent",
                         "onscroll", "/html[0]/body(id=gsr)", "165");
        assertLogMessage(logEntries[9], clientId, "10", "Title", "http://host/path", "Agent",
                         "onclick", "/html[0]/body(id=gsr)/input(id=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\"" +
            "    }," +
            "    \"events\":" +
            "    [ {" +
            "        \"time\":\"12345\"," +
            "        \"path\":\"/html[0]/body(id=gsr)\"," +
            "        \"eventType\":\"onunload\"" +
            "      }" +
            "    ]" +
            "  }" +
            "}";

        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);
        }
        
        // assert 9 already rotated log files
        for (int i = 0; i < (numberOfSessions - 1); i++) {
            File logFile = new File(LOG_FILE_DIR + File.separator + clientId + File.separator +
                                    "htmlmonitor_" + clientId + "_00" + i + ".log");
       
            assertTrue(logFile.exists());
       
            String[] logEntries = readLogEntries(logFile);
            assertEquals(1, logEntries.length);
       
            assertLogMessage(logEntries[0], clientId, "12345", "Title", "http://host/path", "Agent",
                             "onunload", "/html[0]/body(id=gsr)");
        }

        // and now the last but current one
        File logFile = new File(LOG_FILE_DIR + File.separator + clientId + File.separator +
                                "htmlmonitor_" + clientId + ".log");
   
        assertTrue(logFile.exists());
   
        String[] logEntries = readLogEntries(logFile);
        assertEquals(1, logEntries.length);
   
        assertLogMessage(logEntries[0], clientId, "12345", "Title", "http://host/path", "Agent",
                         "onunload", "/html[0]/body(id=gsr)");
    }

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

    /**
     * <p>
     * TODO: comment
     * </p>
     *
     * @param logFile
     * @return
     */
    private String[] readLogEntries(File logFile) throws Exception {
        List<String> logEntries = new ArrayList<String>();
        BufferedReader reader = null;
        
        try {
            reader = new BufferedReader
                (new InputStreamReader(new FileInputStream(logFile)));
        
            String line = null;
            while ((line = reader.readLine()) != null) {
                logEntries.add(line);
            }
        }
        finally {
            if (reader != null) {
                reader.close();
            }
        }
        
        return logEntries.toArray(new String[logEntries.size()]);
    }

    /**
     *
     */
    private void assertLogMessage(String    logMessage,
                                  String... assertedMessageContent)
    {
        StringBuffer assertedMessage = new StringBuffer();
        
        for (int i = 0; i < assertedMessageContent.length; i++) {
            if (i > 0) {
                assertedMessage.append(' ');
            }
            
            assertedMessage.append('"');
            assertedMessage.append(assertedMessageContent[i]);
            assertedMessage.append('"');
        }
        
        assertEquals(assertedMessage.toString(), logMessage);
    }

    /**
     * <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
            }
        }
    }

}
