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

/**
 * <p>
 * TODO comment
 * </p>
 * 
 * @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 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 message =
            "{" +
            "  \"message\": {" +
            "    \"clientInfos\": {" +
            "      \"clientId\":\"123\"," +
            "      \"userAgent\":\"Agent\"," +
            "      \"title\":\"Title\"," +
            "      \"url\":\"http://host/path\"" +
            "    }," +
            "    \"events\":" +
            "    [ {" +
            "        \"time\":\"12345\"," +
            "        \"path\":\"/html[0]/body(id=gsr)\"," +
            "        \"eventType\":\"onunload\"" +
            "      }" +
            "    ]" +
            "  }" +
            "}";
        
        sendMessageAndAssertResponse(message);
        
        if (eventHandlingError != null) {
            fail(eventHandlingError);
        }
        
        assertNotNull(events);
        assertEquals(1, events.length);
        
        assertEquals(new Long(12345), events[0].getTime());
        assertEquals("/html[0]/body(id=gsr)", events[0].getPath());
        assertEquals("onunload", events[0].getEventType());
        
        assertNull(events[0].getKey());
        assertNull(events[0].getScrollPosition());
        assertNull(events[0].getCoordinates());
        
        assertEquals("Title", events[0].getClientInfos().getTitle());
        assertEquals("Agent", events[0].getClientInfos().getUserAgent());
        assertEquals("http://host/path", events[0].getClientInfos().getUrl().toString());
        assertEquals("123", events[0].getClientInfos().getClientId());
    }

    /**
     * 
     */
    @Test
    public void testComplexMessage() throws Exception {
        String message =
            "{" +
            "  \"message\": {" +
            "    \"clientInfos\": {" +
            "      \"clientId\":\"123\"," +
            "      \"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);
        
        if (eventHandlingError != null) {
            fail(eventHandlingError);
        }
        
        assertNotNull(events);
        assertEquals(10, events.length);

        for (int i = 0; i < events.length; i++) {
            assertEquals("event " + i, new Long(i + 1), events[i].getTime());
            
            if (i == 8) {
                assertEquals("event " + i, "/html[0]/body(id=gsr)", events[i].getPath());
            }
            else {
                assertEquals("event " + i, "/html[0]/body(id=gsr)/input(id=input" + (i + 1) + ")",
                             events[i].getPath());
            }
            
            if ((i == 1)) {
                assertEquals("event " + i, "ondblclick", events[i].getEventType());
            }
            else if ((i == 2) || ((4 <= i) && (i <= 6))) {
                assertEquals("event " + i, "onfocus", events[i].getEventType());
            }
            else if ((i == 8)) {
                assertEquals("event " + i, "onscroll", events[i].getEventType());
            }
            else {
                assertEquals("event " + i, "onclick", events[i].getEventType());
            }
            
            assertNull("event " + i, events[i].getKey());
            
            if ((i == 8)) {
                assertNotNull("event " + i, events[i].getScrollPosition());
                assertEquals("event " + i, new Integer(165), events[i].getScrollPosition());
            }
            else {
                assertNull("event " + i, events[i].getScrollPosition());
            }
            
            if ((i == 2) || ((4 <= i) && (i <= 6)) || (i == 8)) {
                assertNull("event " + i, events[i].getCoordinates());
            }
            else {
                assertNotNull("event " + i, events[i].getCoordinates());
                assertEquals("event " + i, 2, events[i].getCoordinates().length);
            }
            
            assertEquals("event " + i, "Title", events[i].getClientInfos().getTitle());
            assertEquals("event " + i, "Agent", events[i].getClientInfos().getUserAgent());
            assertEquals("event " + i, "http://host/path",
                         events[i].getClientInfos().getUrl().toString());
            assertEquals("event " + i, "123", events[i].getClientInfos().getClientId());
        }
    }
    
    /**
     * 
     */
    @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\"" +
            "    }," +
            "    \"events\":" +
            "    [ {" +
            "        \"time\":\"12345\"," +
            "        \"path\":\"/html[0]/body(id=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\"" +
            "    }," +
            "    \"events\":" +
            "    [ {" +
            "        \"time\":\"12345\"," +
            "        \"path\":\"/html[0]/body(id=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\"" +
            "    }," +
            "    \"events\":" +
            "    [ {" +
            "        \"time\":\"blub\"," +
            "        \"path\":\"/html[0]/body(id=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, HtmlEvent[] events) {
        if (clientInfos == null) {
            eventHandlingError = "client infos were 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;
        }
    }

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

}
