//   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.plugin.html.commands;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import de.ugoe.cs.autoquest.CommandHelpers;
import de.ugoe.cs.autoquest.SequenceInstanceOf;
import de.ugoe.cs.autoquest.eventcore.Event;
import de.ugoe.cs.autoquest.eventcore.IEventTarget;
import de.ugoe.cs.autoquest.eventcore.gui.KeyPressed;
import de.ugoe.cs.autoquest.eventcore.gui.MouseButtonInteraction.Button;
import de.ugoe.cs.autoquest.eventcore.gui.MouseClick;
import de.ugoe.cs.autoquest.eventcore.gui.TextInput;
import de.ugoe.cs.autoquest.keyboardmaps.VirtualKey;
import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLDocument;
import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLGUIElement;
import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLPageElement;
import de.ugoe.cs.util.console.Command;
import de.ugoe.cs.util.console.Console;
import de.ugoe.cs.util.console.GlobalDataContainer;

/**
 * create XHTML files for Selenium from sequences
 * 
 * @author Xiaowei Wang
 * @version 1.0
 */
public class CMDgenerateSeleniumReplay implements Command {
    /*
     * (non-Javadoc)
     * 
     * @see de.ugoe.cs.util.console.Command#run(java.util.List)
     */
    @SuppressWarnings("unchecked")
    @Override
    public void run(List<Object> parameters) {
        String sequencesName = "sequences";
        String folder = null;
        String baseURL = null; // default
        try {
            if (parameters.size() == 2) {
                folder = (String) parameters.get(0);
                baseURL = (String) parameters.get(1);
            }
            else if (parameters.size() == 3) {
                sequencesName = (String) parameters.get(0);
                folder = (String) parameters.get(1);
                baseURL = (String) parameters.get(2);
            }
            else {
                throw new IllegalArgumentException("The command needs two or three parameters.");
            }

            if (!(new File(folder).isDirectory())) {
                throw new IllegalArgumentException("HTML files folder path: " + folder +
                    " is invalid.");
            }
            // validate the basic URL
            // HttpURLConnection.setFollowRedirects(false);
            HttpURLConnection httpURLConnection =
                (HttpURLConnection) new URL(baseURL).openConnection();
            httpURLConnection.setRequestMethod("HEAD");
            if (!(httpURLConnection.getResponseCode() == HttpURLConnection.HTTP_OK)) {
                throw new IllegalArgumentException("Website basic URL: " + baseURL + " is invalid.");
            }
        }
        catch (SecurityException se) {
            throw new IllegalArgumentException("Parameters are invalid");
        }
        catch (MalformedURLException mue) {
            throw new IllegalArgumentException("Website basic URL: " + baseURL + " is invalid.");
        }
        catch (IOException e) {
            throw new IllegalArgumentException(e);
        }

        try {
            List<List<Event>> sequences = null;
            Object dataObject = GlobalDataContainer.getInstance().getData(sequencesName);
            if (dataObject == null) {
                CommandHelpers.objectNotFoundMessage(sequencesName);
                return;
            }
            if (!SequenceInstanceOf.isCollectionOfSequences(dataObject)) {
                CommandHelpers.objectNotType(sequencesName, "Collection<List<Event<?>>>");
                return;
            }
            sequences = (List<List<Event>>) dataObject;

            String command = null;
            String target = null;
            String value = null;
            for (int seqsIndex = 0; seqsIndex < sequences.size(); seqsIndex++) {
                LinkedList<Map<String, String>> attributeList =
                    new LinkedList<Map<String, String>>();
                List<Event> sequence = sequences.get(seqsIndex);
                Event firstEvent = sequence.get(0);
                HTMLGUIElement htmlGUIElement = (HTMLGUIElement) firstEvent.getTarget();
                while (!(htmlGUIElement instanceof HTMLDocument)) { // the first target must be a
                                                                    // page
                    htmlGUIElement = (HTMLGUIElement) (htmlGUIElement.getParent());
                }
                command = "open";
                target = ((HTMLDocument) htmlGUIElement).getPath();
                value = "";
                addToAttrList(attributeList, command, target, value);

                for (int seqIndex = 1; seqIndex < sequence.size(); seqIndex++) {
                    Event event = sequence.get(seqIndex);
                    target = getXpath(event.getTarget());
                    // Login button is global, must await the last page to load
                    // otherwise, will lead to wrong page after logging in
                    if (target.equals("//ul[@id='navlist2']/li[2]/a") &&
                        (attributeList.size() > 1) &&
                        !attributeList.getLast().get("command").endsWith("AndWait"))
                    {
                        attributeList.getLast().put("command",
                                                    attributeList.getLast().get("command") +
                                                        "AndWait");
                    }

                    if (event.getType() instanceof MouseClick &&
                        (((MouseClick) event.getType()).getButton() == Button.LEFT || ((MouseClick) event
                            .getType()).getButton() == Button.MIDDLE))
                    {
                        command = "click";
                        String precedingCommand = "waitForElementPresent";
                        value = "";
                        addToAttrList(attributeList, precedingCommand, target, value);
                    }
                    else if (event.getType() instanceof TextInput) {
                        command = "type";
                        value = ((TextInput) event.getType()).getEnteredText();
                        // if the session contains login process,
                        // the session must log out at beginning
                        if (target
                            .equals("//form[@id='user-login']/div/div/input[@id='edit-name']") &&
                            !attributeList.get(0).get("target").equals("/user/logout"))
                        {
                            addToAttrList(attributeList, "open", "/user/logout", "", 0);
                        }
                    }
                    else if (event.getType() instanceof KeyPressed) {
                        command = "sendKeys";
                        VirtualKey pressedKey = ((KeyPressed) event.getType()).getKey();
                        switch (pressedKey)
                        {
                            case ENTER:
                                value = "${KEY_ENTER}";
                                break;
                            case TAB:
                                value = "${KEY_TAB}";
                        }
                    }
                    else {// Scroll or KeyboardFocusChange or else
                        continue;
                    }
                    addToAttrList(attributeList, command, target, value);
                }

                if (attributeList.size() > 0) {
                    createHTML(folder, "session-" + seqsIndex + ".html", baseURL, attributeList,
                               "UTF-8");
                }
                else { // the sequence has no action
                    continue;
                }
            }
        }
        catch (Exception e) {
            Console.printerrln("Error on running the command.");
            e.printStackTrace();
        }
    }

    /**
     * create event files in HTML with Selenium IDE template
     * 
     * @param HTMLFolder
     * @param fileName
     * @param attributeList
     */
    private void createHTML(String HTMLFolder,
                            String fileName,
                            String baseURL,
                            List<Map<String, String>> attributeList,
                            String encoding)
    {

        String htmlStr =
            "<?xml version=\"1.0\" encoding=\"${encoding}\"?>\n"
                + "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" "
                + "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"
                + "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n"
                + "<head profile=\"http://selenium-ide.openqa.org/profiles/test-case\">\n"
                + "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=${encoding}\" />\n"
                + "<link rel=\"selenium.base\" href=\"${baseURL}\" />\n"
                + "<title>${name}</title>\n" + "</head>\n" + "<body>\n"
                + "<table cellpadding=\"1\" cellspacing=\"1\" border=\"1\">\n" + "<thead>\n"
                + "<tr><td rowspan=\"1\" colspan=\"3\">${name}</td></tr>\n" + "</thead><tbody>\n";
        htmlStr = htmlStr.replace("${encoding}", encoding);
        htmlStr = htmlStr.replace("${baseURL}", baseURL);
        htmlStr = htmlStr.replace("${name}", fileName.replace(".html", ""));
        for (Map<String, String> commandMap : attributeList) {
            htmlStr =
                htmlStr + "<tr>\n" + "\t<td>" + commandMap.get("command") + "</td>\n" + "\t<td>" +
                    commandMap.get("target") + "</td>\n" + "\t<td>" + commandMap.get("value") +
                    "</td>\n" + "</tr>\n";
        }

        String fileEnding = "</tbody></table>\n</body>\n</html>";
        htmlStr = htmlStr + fileEnding;
        try {
            File newHtmlFile =
                new File(HTMLFolder + System.getProperty("file.separator") + fileName);
            BufferedWriter bw = new BufferedWriter(new FileWriter(newHtmlFile));
            bw.write(htmlStr);
            bw.flush();
            bw.close();
        }
        catch (Exception e) {
            Console.printerrln("Error on creating xhtml files.");
            e.printStackTrace();
        }
    }

    /**
     * add an event to the event list
     * 
     * @param attributeList
     * @param command
     * @param target
     * @param value
     * 
     */
    private void addToAttrList(LinkedList<Map<String, String>> attributeList,
                               String command,
                               String target,
                               String value)
    {
        Map<String, String> ctvMap = new HashMap<String, String>();
        ctvMap.put("command", command);
        ctvMap.put("target", target);
        ctvMap.put("value", value);
        attributeList.add(ctvMap);
    }

    private void addToAttrList(LinkedList<Map<String, String>> attributeList,
                               String command,
                               String target,
                               String value,
                               int position)
    {
        Map<String, String> ctvMap = new HashMap<String, String>();
        ctvMap.put("command", command);
        ctvMap.put("target", target);
        ctvMap.put("value", value);
        attributeList.add(position, ctvMap);
    }

    /**
     * 
     * @param iEventTarget
     * @return the relative xpath
     */
    private String getXpath(IEventTarget iEventTarget) {
        HTMLGUIElement htmlGUIElement = (HTMLGUIElement) iEventTarget;
        List<String> xpathElements = new LinkedList<String>();
        String htmlId = null;
        int htmlIndex = 0;

        // obtain the relative xpath of the element
        while (!(htmlGUIElement instanceof HTMLDocument)) {
            HTMLPageElement htmlPageElement = (HTMLPageElement) htmlGUIElement;
            StringBuffer xpathElement = new StringBuffer();
            // the real tag is "input" without _*
            xpathElement.append(htmlPageElement.getTagName().replace("input_text", "input")
                .replace("input_password", "input").replace("input_submit", "input"));
            // better not use absolute xpath,
            // Selenium IDE specifies only the
            // 1st tag's id, if it's not the only tag
            if ((htmlId = htmlPageElement.getHtmlId()) != null) {
                xpathElement.append("[@id='");
                xpathElement.append(htmlId);
                xpathElement.append("']");
                xpathElements.add(0, xpathElement.toString());
                // the xpath must have more then one tag with id
                // but the Selenium IDE uses only the 1st one
                // which is enough for finding out the element
                if (xpathElements.size() > 1) {
                    break;
                }
            }
            else if ((htmlIndex = htmlPageElement.getIndex()) != 0) {
                xpathElement.append("[");
                // find element by xpath, index starts from 1, not 0
                xpathElement.append(htmlIndex + 1);
                xpathElement.append("]");
                xpathElements.add(0, xpathElement.toString());
            }
            else {
                // if the index is 0, only add tag
                xpathElements.add(0, xpathElement.toString());
            }
            htmlGUIElement = (HTMLGUIElement) (htmlGUIElement.getParent());
        }

        StringBuffer xPathStatement = new StringBuffer();
        xPathStatement.append("/");
        for (String xpathElem : xpathElements) {
            xPathStatement.append("/");
            xPathStatement.append(xpathElem);
        }

        return xPathStatement.toString();
    }

    /*
     * (non-Javadoc)
     * 
     * @see de.ugoe.cs.util.console.Command#help()
     */
    @Override
    public String help() {

        return "generateSeleniumReplay [sequenceName] <HTMLFilesFolder> <websiteBaseURL>";
    }

}
