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

import static org.junit.Assert.*;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import de.ugoe.cs.autoquest.eventcore.gui.TextInput;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTask;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IIteration;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ISelection;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ISequence;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskTree;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskTreeNode;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskTreeNodeInfo;

/**
 * TODO comment
 * 
 * @version $Revision: $ $Date: 01.04.2012$
 * @author 2012, last modified by $Author: patrick$
 */
public class TaskTreeChecker {
    
    /** */
    private static Pattern taskPattern = Pattern.compile("([^{}]+)\\{|\\}");
    
    /** */
    private static Pattern taskDetailsPattern =
        Pattern.compile("\\s*(\\w*)\\s*([\\w\\(\\)\"]*)\\s*((\\w*)|(\".*\"))?");
    
    /** */
    private boolean doTrace;

    /**
     * TODO: comment
     * 
     */
    public TaskTreeChecker() {
        this(false);
    }

    /**
     * TODO: comment
     * 
     */
    public TaskTreeChecker(boolean doTrace) {
        this.doTrace = doTrace;
    }

    /**
     *
     */
    public void assertTaskTree(String taskTreeSpec, ITaskTree taskTree) {
        Map<ITaskTreeNode, Integer> taskMapCopy = new HashMap<ITaskTreeNode, Integer>();

        for (Map.Entry<ITaskTreeNode, ITaskTreeNodeInfo> entry : taskTree.getTaskMap().entrySet()) {
            if (entry.getValue().getNoOfOccurencesInTree() > 0) {
                taskMapCopy.put(entry.getKey(), entry.getValue().getNoOfOccurencesInTree());
            }
            else {
                taskMapCopy.put(entry.getKey(), 1);
            }
        }

        if (doTrace) {
            dumpTaskMap(taskMapCopy);
        }

        TaskSpec task = null;

        Matcher taskMatcher = taskPattern.matcher(taskTreeSpec);

        while (taskMatcher.find()) {

            task = parseTask(taskMatcher);
            
            if (task != null) {
                assertTaskAndChildrenInMapAndRemove(task, taskMapCopy);
            }
        }

        assertTrue("more tasks in map, than expected", taskMapCopy.isEmpty());
    }

    /**
     * <p>
     * TODO: comment
     * </p>
     *
     * @param oracle
     * @param result
     */
    public void assertTaskNodesEqual(ITaskTreeNode expected, ITaskTreeNode checked) {
        if (expected == null) {
            assertNull("null", checked);
        }
        else {
            assertNotNull(expected.toString(), checked);
            
            assertEquals(expected.toString() + ": types do not match",
                         expected.getClass(), checked.getClass());
            assertEquals(expected.toString() + ": names do not match",
                         expected.getName(), checked.getName());
            
            List<ITaskTreeNode> expectedChildren = expected.getChildren();
            List<ITaskTreeNode> checkedChildren = checked.getChildren();
            
            if ((expectedChildren != null) && (expectedChildren.size() > 0)) {
                assertNotNull(expected.toString() + ": children not there", checkedChildren);
                assertEquals(expected.toString() + ": different number of children",
                             expectedChildren.size(), checkedChildren.size());
                
                if (expected instanceof ISequence) {
                    for (int i = 0; i < expectedChildren.size(); i++) {
                        assertTaskNodesEqual(expectedChildren.get(i), checkedChildren.get(i));
                    }
                }
                else {
                    for (int i = 0; i < expectedChildren.size(); i++) {
                        boolean found = false;
                        for (int j = 0; j < checkedChildren.size(); j++) {
                            try {
                                assertTaskNodesEqual
                                    (expectedChildren.get(i), checkedChildren.get(j));
                                found = true;
                                break;
                            }
                            catch (AssertionError e) {
                                // try next
                            }
                        }
                        
                        assertTrue("one of the children not found", found);
                    }
                }
            }
            else {
                assertTrue(expected.toString() + ": unexpected children",
                           (checkedChildren == null) || (checkedChildren.size() == 0));
            }
        }
    }

    /**
     * TODO: comment
     * 
     * @param taskTree
     */
    public void dumpAsCheckString(ITaskTree taskTree) {
        dumpNodeAsCheckString(taskTree.getRoot(), new int[4], "");
    }

    /**
     * TODO: comment
     * 
     * @param root
     * @param string
     */
    private void dumpNodeAsCheckString(ITaskTreeNode node, int[] typeCounters, String indent) {
        System.out.print("       \"");
        System.out.print(indent);

        if (node instanceof ISequence) {
            System.out.print("Sequence sequence");
            System.out.print(typeCounters[0]++);
            System.out.println(" {\" +");
        }
        else if (node instanceof IIteration) {
            System.out.print("Iteration iteration");
            System.out.print(typeCounters[1]++);
            System.out.println(" {\" +");
        }
        else if (node instanceof ISelection) {
            System.out.print("Selection selection");
            System.out.print(typeCounters[2]++);
            System.out.println(" {\" +");
        }
        else if (node instanceof IEventTask) {
            if (((IEventTask) node).getEventType() instanceof TextInput) {
                System.out.print("TextInputEvent textInput");
                System.out.print(typeCounters[3]++);
                System.out.print(" \"");
                System.out.print(((TextInput) ((IEventTask) node).getEventType()).getEnteredText());
                System.out.print("\"");
            }
            else {
                System.out.print("Event ");
                System.out.print(((IEventTask) node).getEventType().getName());
            }
            System.out.print(" {}\" +");
        }
        else {
            fail("unknown type of node in task tree " + node);
        }

        for (ITaskTreeNode child : node.getChildren()) {
            dumpNodeAsCheckString(child, typeCounters, indent + "  ");
        }

        if (!(node instanceof IEventTask)) {
            System.out.print("       \"");
            System.out.print(indent);
            System.out.print("}\" +");
        }

        System.out.println();
    }

    /**
     * TODO: comment
     * 
     * @param taskTree
     */
    public void dumpFullTaskTree(ITaskTree taskTree) throws FileNotFoundException {
        PrintWriter out = null;
        try {
            out = new PrintWriter(new FileOutputStream("taskTree.txt"));
            dumpFullNode(taskTree.getRoot(), out, "", 0);
        }
        finally {
            if (out != null) {
                out.close();
            }
        }

    }

    /**
     *
     */
    private void dumpFullNode(ITaskTreeNode node, PrintWriter out, String indent, int index) {
        if (node instanceof ISequence) {
            if (index > 0) {
                out.println();
            }
            out.print(indent);
            out.print(node.toString());
            out.println(" {");
        }
        else if (node instanceof IIteration) {
            if (index > 0) {
                out.println();
            }
            out.print(indent);
            out.print(node.toString());
            out.println(" {");
        }
        else if (node instanceof ISelection) {
            if (index > 0) {
                out.println();
            }
            out.print(indent);
            out.print(node.toString());
            out.println(" {");
        }
        else if (node instanceof IEventTask) {
            out.print(indent);
            out.print(((IEventTask) node).getEventType());
            out.print(" ");
            out.print(((IEventTask) node).getEventTarget().getStringIdentifier());
//            if (((IEventTask) node).getEventTarget() instanceof IGUIElement) {
//              out.print(" ");
//              out.print(((IGUIElement) ((IEventTask) node).getEventTarget()).getSpecification());
//            }
        }
        else {
            fail("unknown type of node in task tree " + node);
        }

        int i = 0;
        for (ITaskTreeNode child : node.getChildren()) {
            dumpFullNode(child, out, indent + "  ", i++);
        }

        if (!(node instanceof IEventTask)) {
            out.print(indent);
            out.print("}");
        }

        out.println();
    }

    /**
     * 
     */
    private TaskSpec parseTask(Matcher taskMatcher) {
        if ("}".equals(taskMatcher.group(1))) {
            throw new IllegalArgumentException("invalid task specification");
        }
        
        String taskDetails = taskMatcher.group(1);
        
        Matcher matcher = taskDetailsPattern.matcher(taskDetails);
        
        if (!matcher.find()) {
            throw new IllegalArgumentException("could not parse task details");
        }

        TaskSpec task = new TaskSpec();
        task.type = matcher.group(1);
        
        task.name = matcher.group(2);
        if ((matcher.group(4) != null) && (!"".equals(matcher.group(4).trim()))) {
            task.name += " " + matcher.group(4).trim();
        }
        
        if ((matcher.group(5) != null) && (!"".equals(matcher.group(5).trim()))) {
            task.additionalInfo = matcher.group(5).trim();
        }

        if ("".equals(task.name)) {
            task.name = null;
        }

        List<TaskSpec> children = new ArrayList<TaskSpec>();
        while (taskMatcher.find() && !"}".equals(taskMatcher.group(0))) {
            children.add(parseTask(taskMatcher));
        }

        if (children.size() > 0) {
            task.children = children.toArray(new TaskSpec[children.size()]);
        }

        return task;
    }

    /**
     * @param task
     * @param taskMapCopy
     */
    private void assertTaskAndChildrenInMapAndRemove(TaskSpec                    task,
                                                     Map<ITaskTreeNode, Integer> taskMap)
    {
        for (Map.Entry<ITaskTreeNode, Integer> entry : taskMap.entrySet()) {
            if (taskSpecEqualsTask(task, entry.getKey())) {
                if (task.children != null) {
                    for (TaskSpec child : task.children) {
                        assertTaskAndChildrenInMapAndRemove(child, taskMap);
                    }
                }

                int count = taskMap.get(entry.getKey());
                if (count == 1) {
                    taskMap.remove(entry.getKey());
                }
                else {
                    taskMap.put(entry.getKey(), count - 1);
                }
                return;
            }
        }

        fail("expected task " + task.type + " " + task.name + " not included in task map");
    }

    /**
     *
     */
    private boolean taskSpecEqualsTask(TaskSpec taskSpec, ITaskTreeNode task) {
        if (doTrace) {
            System.err.println("comparing " + taskSpec.name + " with");
            dumpTask(task, 0, "");
        }

        if (("Event".equals(taskSpec.type) && (!(task instanceof IEventTask))) ||
            ("TextInputEvent".equals(taskSpec.type) &&
             ((!(task instanceof IEventTask)) ||
              (!(((IEventTask) task).getEventType() instanceof TextInput)))) ||
            ("Sequence".equals(taskSpec.type) && (!(task instanceof ISequence))) ||
            ("Selection".equals(taskSpec.type) && (!(task instanceof ISelection))) ||
            ("Iteration".equals(taskSpec.type) && (!(task instanceof IIteration))))
        {
            if (doTrace) {
                System.err.println("task types do not match: " + taskSpec.type + " != " +
                    task.getClass().getSimpleName() + "\n");
            }
            return false;
        }
        else if (!"Event".equals(taskSpec.type) &&
                 !"TextInputEvent".equals(taskSpec.type) &&
                 !"Sequence".equals(taskSpec.type) &&
                 !"Selection".equals(taskSpec.type) &&
                 !"Iteration".equals(taskSpec.type))
        {
            fail("unknown task type " + taskSpec.type + " --> please extend test case");
        }

        if ("TextInputEvent".equals(taskSpec.type)) {
            TextInput eventType = (TextInput) ((IEventTask) task).getEventType();
            if ((taskSpec.additionalInfo != null) &&
                !"".equals(taskSpec.additionalInfo) &&
                !(taskSpec.additionalInfo.equals(eventType.getEnteredText())))
            {
                if (doTrace) {
                    System.err.println("expected text \"" + taskSpec.additionalInfo +
                                       "\" is not equal to the text " + "provided by the task \"" +
                                       eventType.getEnteredText() + "\"\n");
                }
                return false;
            }
        }
        else if ((task instanceof IEventTask) && (((IEventTask) task).getEventType() != null) &&
                 (!taskSpec.name.equals(((IEventTask) task).getEventType().getName())))
        {
            // simple event names do not match. But what about the event name in
            // combination with the additional info
            String complexName =
                taskSpec.name +
                    (!"".equals(taskSpec.additionalInfo) ? " " + taskSpec.additionalInfo : "");

            if (!complexName.equals(((IEventTask) task).getEventType().getName())) {
                if (doTrace) {
                    System.err.println("event names do not match: " + taskSpec.name + " != " +
                                       ((IEventTask) task).getEventType().getName() + "\n");
                }
                return false;
            }
        }

        if (((taskSpec.children == null) && (task.getChildren().size() > 0)) ||
            ((taskSpec.children != null) && (taskSpec.children.length != task.getChildren().size())))
        {
            if (doTrace) {
                System.err.println
                    ("numbers of children do not match: " +
                     (taskSpec.children == null ? "0" : taskSpec.children.length) + " != " +
                     (task.getChildren() == null ? "0" : task.getChildren().size()) + "\n");
            }
            return false;
        }

        Iterator<ITaskTreeNode> children = task.getChildren().iterator();
        if (taskSpec.children != null) {
            for (TaskSpec child : taskSpec.children) {
                if (!taskSpecEqualsTask(child, children.next())) {
                    if (doTrace) {
                        System.err.println("one of the children does not match\n");
                    }
                    return false;
                }
            }
        }

        if (!children.hasNext()) {
            if (doTrace) {
                System.err.println("nodes match\n");
            }
            return true;
        }
        else {
            if (doTrace) {
                System.err.println("number of children does not match\n");
            }
            return false;
        }
    }

    /**
   *
   */
    private void dumpTaskMap(Map<ITaskTreeNode, Integer> taskMap) {
        System.err.println();
        for (Map.Entry<ITaskTreeNode, Integer> entry : taskMap.entrySet()) {
            dumpTask(entry.getKey(), entry.getValue(), "");
            System.err.println();
        }
    }

    /**
     *
     */
    private void dumpTask(ITaskTreeNode task, int count, String indent) {
        System.err.print(indent);
        System.err.print(task);
        System.err.print(" ");
        System.err.print(task.getDescription());
        System.err.print(" ");

        if (count > 0) {
            System.err.print("(");
            System.err.print(count);
            System.err.print(" occurrences)");
        }

        System.err.println();

        if ((task.getChildren() != null) && (task.getChildren().size() > 0)) {
            for (ITaskTreeNode child : task.getChildren()) {
                dumpTask(child, 0, indent + "  ");
            }
        }
    }
    
    /**
     * TODO comment
     * 
     * @version $Revision: $ $Date: $
     * @author 2011, last modified by $Author: $
     */
    private class TaskSpec {
        public String type;
        public String name;
        public String additionalInfo;
        public TaskSpec[] children;
    }

}
