// Module    : $RCSfile: TaskTreeChecker.java,v $
// Version   : $Revision: 0.0 $  $Author: patrick $  $Date: 01.04.2012 $
// Project   : TestUtils
// Creation  : 2012 by patrick
// Copyright : Patrick Harms, 2012

package de.ugoe.cs.quest.tasktrees.testutils;

import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

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.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import de.ugoe.cs.quest.eventcore.guimodel.IGUIElement;
import de.ugoe.cs.quest.tasktrees.treeifc.IEventTask;
import de.ugoe.cs.quest.tasktrees.treeifc.IIteration;
import de.ugoe.cs.quest.tasktrees.treeifc.ITaskTreeNodeInfo;
import de.ugoe.cs.quest.tasktrees.treeifc.ISelection;
import de.ugoe.cs.quest.tasktrees.treeifc.ISequence;
import de.ugoe.cs.quest.tasktrees.treeifc.ITaskTree;
import de.ugoe.cs.quest.tasktrees.treeifc.ITaskTreeNode;
import de.ugoe.cs.quest.tasktrees.treeifc.ITextInputEventTask;

/**
 * TODO comment
 * 
 * @version $Revision: $ $Date: 01.04.2012$
 * @author 2012, last modified by $Author: patrick$
 */
public class TaskTreeChecker {
    
    /** */
    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 matcher = Pattern.compile
            ("(\\s*(\\w+)\\s+(\\w+)\\s+((\\w*\\s)*)(\\{))|((\\}))").matcher(taskTreeSpec);

        do {
            if (!matcher.find()) {
                if (!matcher.hitEnd()) {
                    throw new IllegalArgumentException("could not parse task specification");
                }
                else {
                    break;
                }
            }

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

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

    /**
     * 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.err.print("       \"");
        System.err.print(indent);

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

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

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

        System.err.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, "");
        }
        finally {
            if (out != null) {
                out.close();
            }
        }

    }

    /**
     *
     */
    private void dumpFullNode(ITaskTreeNode node, PrintWriter out, String indent) {
        out.print(indent);
        if (node instanceof ISequence) {
            out.println("Sequence");
            out.print(indent);
            out.println("{");
        }
        else if (node instanceof IIteration) {
            out.println("Iteration");
            out.print(indent);
            out.println("{");
        }
        else if (node instanceof ISelection) {
            out.println("Selection");
            out.print(indent);
            out.println("{");
        }
        else if (node instanceof ITextInputEventTask) {
            out.print("TextInputEvent");
            out.print(" ");
            out.println(((ITextInputEventTask) node).getEnteredText());
            out.print(indent);
            out.println("{");
        }
        else if (node instanceof IEventTask) {
            out.print(((IEventTask) node).getEventType().getName());
            out.print(" ");
            out.print(((IEventTask) node).getEventTarget());
            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);
        }

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

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

        out.println();
    }

    /**
     * 
     */
    private TaskSpec parseTask(Matcher tokenMatcher) {
        String firstToken = tokenMatcher.group();

        if ("}".equals(firstToken)) {
            throw new IllegalArgumentException("found a closing bracket at an unexpected place");
        }

        TaskSpec task = new TaskSpec();
        task.type = tokenMatcher.group(2);
        task.name = tokenMatcher.group(3);
        task.additionalInfo = tokenMatcher.group(4).trim();

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

        if (!tokenMatcher.find()) {
            throw new IllegalArgumentException("could not parse task specification");
        }

        firstToken = tokenMatcher.group();

        if (!"}".equals(firstToken)) {
            ArrayList<TaskSpec> children = new ArrayList<TaskSpec>();

            TaskSpec child = null;

            do {
                child = parseTask(tokenMatcher);

                if (child != null) {
                    children.add(child);

                    if (!tokenMatcher.find()) {
                        throw new IllegalArgumentException("could not parse task specification");
                    }

                    firstToken = tokenMatcher.group();

                    if ("}".equals(firstToken)) {
                        break;
                    }
                }

            }
            while (child != null);

            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 ITextInputEventTask))) ||
            ("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)) {
            if (!"".equals(taskSpec.additionalInfo) &&
                !(taskSpec.additionalInfo.equals(((ITextInputEventTask) task).getEnteredText())))
            {
                if (doTrace) {
                    System.err.println("expected text \"" + taskSpec.additionalInfo +
                                       "\" is not equal to the text " + "provided by the task \"" +
                                       ((ITextInputEventTask) task).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;
    }

}
