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

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;

import java.util.HashMap;
import java.util.Map;

import org.junit.Test;

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.ITaskTreeBuilder;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskTreeNode;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskTreeNodeFactory;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskTreeNodeInfo;
import de.ugoe.cs.autoquest.tasktrees.treeimpl.NodeInfo;
import de.ugoe.cs.autoquest.tasktrees.treeimpl.TaskTreeBuilder;
import de.ugoe.cs.autoquest.tasktrees.treeimpl.TaskTreeNodeFactory;
import de.ugoe.cs.autoquest.test.DummyGUIElement;
import de.ugoe.cs.autoquest.test.DummyInteraction;

/**
 * TODO comment
 * 
 * @version $Revision: $ $Date: 02.04.2012$
 * @author 2012, last modified by $Author: patrick$
 */
public class TaskTreeImplTest {
    
    /** */
    private static final int MAX_TREE_DEPTH = 15;

    /** */
    private ITaskTreeBuilder taskTreeBuilder = new TaskTreeBuilder();

    /** */
    private ITaskTreeNodeFactory taskTreeNodeFactory = new TaskTreeNodeFactory();

    /**
     * @throws Exception
     * 
     */
    @Test
    public void testRandomTrees() throws Exception {
        int noOfTrees = 10;
        int noOfMaxChildren = 8;
        int maxDepth = MAX_TREE_DEPTH;

        for (int i = 0; i < noOfTrees; i++) {
            System.err.println("iteration " + (i + 1) + ":");
            System.err.println("  creating tree");
            Map<ITaskTreeNode, ITaskTreeNodeInfo> treeInfos =
                new HashMap<ITaskTreeNode, ITaskTreeNodeInfo>();
            ITaskTreeNode rootNode = createTree(noOfMaxChildren, maxDepth, treeInfos);
            System.err.println("  creating task tree");
            ITaskTree taskTree = taskTreeNodeFactory.createTaskTree(rootNode);

            System.err.println("  validating task tree");
            assertEquals(rootNode, taskTree.getRoot());
            assertMapsEqual(treeInfos, taskTree.getTaskMap());
        }
    }

    /**
     * TODO: comment
     * 
     * @param treeInfos
     * @param taskMap
     */
    private void assertMapsEqual(Map<ITaskTreeNode, ITaskTreeNodeInfo> map1,
                                 Map<ITaskTreeNode, ITaskTreeNodeInfo> map2)
    {
        try {
            if (map1 == null) {
                assertNull(map2);
                return;
            }

            assertEquals(map1.size(), map2.size());

            for (Map.Entry<ITaskTreeNode, ITaskTreeNodeInfo> entry : map1.entrySet()) {
                ITaskTreeNodeInfo value2 = map2.get(entry.getKey());
                assertNotNull(value2);
                assertEquals(entry.getValue().getTask(), value2.getTask());
                assertEquals(entry.getValue().getNoOfOccurencesInTree(),
                             value2.getNoOfOccurencesInTree());
            }
        }
        catch (AssertionError e) {
            dumpMap(map1);
            dumpMap(map2);
            throw e;
        }
    }

    /**
     * TODO: comment
     * 
     * @param map2
     */
    private void dumpMap(Map<ITaskTreeNode, ITaskTreeNodeInfo> map) {
        System.err.println();

        if (map == null) {
            System.err.println("map is null");
        }
        else {
            System.err.println("map:");
            for (Map.Entry<ITaskTreeNode, ITaskTreeNodeInfo> entry : map.entrySet()) {
                System.err.print("  ");
                System.err.print(entry.getKey());
                for (int i = entry.getKey().toString().length(); i < 49; i++) {
                    System.err.print(" ");
                }
                System.err.print(" : ");
                System.err.println(entry.getValue());
            }
        }

        System.err.println();
    }

    /**
     * TODO: comment
     * 
     * @param noOfMaxChildren
     * @param maxDepth
     * @param treeInfos
     * @return
     */
    private ITaskTreeNode createTree(int                                   maxNoOfChildren,
                                     int                                   maxDepth,
                                     Map<ITaskTreeNode, ITaskTreeNodeInfo> treeInfos)
        throws Exception
    {
        /*
         * for (int i = 0; i < (MAX_TREE_DEPTH + 1 - maxDepth); i++) { System.err.print("  "); }
         */

        ITaskTreeNode tree;

        // integrating the maximum depth here assures, that either something between 0 and 8 will
        // be the type, or if the max depth decreases near 0 only event tasks will be created
        // to finish the tree creation
        int type = (int) (Math.random() * (Math.min(8, maxDepth)));

        switch (type)
        {
            case 0: {
                // System.err.print("creating new event task ");
                tree = createNewEventTask(treeInfos);
                break;
            }
            case 1: {
                // System.err.print("reusing event task ");
                tree = reuseEventTask(treeInfos);
                break;
            }
            case 2: {
                // System.err.println("creating new sequence {");
                tree = createNewSequence(maxNoOfChildren, maxDepth, treeInfos);
                break;
            }
            case 3: {
                // System.err.println("reusing sequence {");
                tree = reuseSequence(maxNoOfChildren, maxDepth, treeInfos);
                break;
            }
            case 4: {
                // System.err.println("creating new selection {");
                tree = createNewSelection(maxNoOfChildren, maxDepth, treeInfos);
                break;
            }
            case 5: {
                // System.err.println("reusing selection {");
                tree = reuseSelection(maxNoOfChildren, maxDepth, treeInfos);
                break;
            }
            case 6: {
                // System.err.println("creating new iteration {");
                tree = createNewIteration(maxNoOfChildren, maxDepth, treeInfos);
                break;
            }
            case 7: {
                // System.err.println("reusing iteration {");
                tree = reuseIteration(maxNoOfChildren, maxDepth, treeInfos);
                break;
            }
            default: {
                // System.err.print("creating new event task per default ");
                tree = createNewEventTask(treeInfos);
            }
        }

        /*
         * if (!(tree instanceof InteractionTask)) { for (int i = 0; i < (MAX_TREE_DEPTH + 1 -
         * maxDepth); i++) { System.err.print("  "); }
         * 
         * System.err.print("} "); }
         * 
         * System.err.println(tree);
         */

        return tree;
    }

    /**
     * TODO: comment
     * 
     * @param treeInfos
     * @return
     */
    private IEventTask createNewEventTask(Map<ITaskTreeNode, ITaskTreeNodeInfo> treeInfos)
        throws Exception
    {
        Thread.sleep(2);
        long id = System.currentTimeMillis();
        IEventTask task =
            taskTreeNodeFactory.createNewEventTask(new DummyInteraction("interaction" + id, 1),
                                                   new DummyGUIElement("elem" + id));

        treeInfos.put(task, new NodeInfo(task));

        return task;
    }

    /**
     * TODO: comment
     * 
     * @param treeInfos
     * @return
     */
    private IEventTask reuseEventTask(Map<ITaskTreeNode, ITaskTreeNodeInfo> treeInfos)
        throws Exception
    {
        int noOfEventTasks = 0;

        for (Map.Entry<ITaskTreeNode, ITaskTreeNodeInfo> entry : treeInfos.entrySet()) {
            if (entry.getKey() instanceof IEventTask) {
                noOfEventTasks++;
            }
        }

        if (noOfEventTasks > 0) {
            noOfEventTasks = (int) (Math.random() * noOfEventTasks);

            for (Map.Entry<ITaskTreeNode, ITaskTreeNodeInfo> entry : treeInfos.entrySet()) {
                if (entry.getKey() instanceof IEventTask) {
                    if (--noOfEventTasks <= 0) {
                        return (IEventTask) entry.getKey();
                    }
                }
            }
        }
        else {
            return createNewEventTask(treeInfos);
        }

        throw new RuntimeException("this is an implementation error");
    }

    /**
     * TODO: comment
     * 
     * @param treeInfos
     * @return
     */
    private ISequence createNewSequence(int                                   maxNoOfChildren,
                                        int                                   maxDepth,
                                        Map<ITaskTreeNode, ITaskTreeNodeInfo> treeInfos)
        throws Exception
    {
        ISequence sequence = taskTreeNodeFactory.createNewSequence();

        int noOfChildren = (int) (Math.random() * maxNoOfChildren);

        for (int i = 0; i < noOfChildren; i++) {
            ITaskTreeNode child = createTree(maxNoOfChildren, maxDepth - 1, treeInfos);

            // through first removing an existing parent it is assured, that a parent is recorded
            // only once. This is needed, because parent may be reused in a tree as well, but we
            // always
            // iterate the whole tree
            ((NodeInfo) treeInfos.get(child)).removeParent(sequence);
            ((NodeInfo) treeInfos.get(child)).addParent(sequence);
            taskTreeBuilder.addChild(sequence, child);
        }

        treeInfos.put(sequence, new NodeInfo(sequence));
        return sequence;
    }

    /**
     * TODO: comment
     * 
     * @param treeInfos
     * @return
     */
    private ISequence reuseSequence(int                                   maxNoOfChildren,
                                    int                                   maxDepth,
                                    Map<ITaskTreeNode, ITaskTreeNodeInfo> treeInfos)
        throws Exception
    {
        int noOfSequences = 0;

        for (Map.Entry<ITaskTreeNode, ITaskTreeNodeInfo> entry : treeInfos.entrySet()) {
            if (entry.getKey() instanceof ISequence) {
                noOfSequences++;
            }
        }

        if (noOfSequences > 0) {
            noOfSequences = (int) (Math.random() * noOfSequences);

            for (Map.Entry<ITaskTreeNode, ITaskTreeNodeInfo> entry : treeInfos.entrySet()) {
                if (entry.getKey() instanceof ISequence) {
                    if (--noOfSequences <= 0) {
                        return (ISequence) entry.getKey();
                    }
                }
            }
        }
        else {
            return createNewSequence(maxNoOfChildren, maxDepth, treeInfos);
        }

        throw new RuntimeException("this is an implementation error");
    }

    /**
     * TODO: comment
     * 
     * @param treeInfos
     * @return
     */
    private ISelection createNewSelection(int                                   maxNoOfChildren,
                                          int                                   maxDepth,
                                          Map<ITaskTreeNode, ITaskTreeNodeInfo> treeInfos)
        throws Exception
    {
        ISelection selection = taskTreeNodeFactory.createNewSelection();

        int noOfChildren = (int) (Math.random() * maxNoOfChildren);

        for (int i = 0; i < noOfChildren; i++) {
            ITaskTreeNode child = createTree(maxNoOfChildren, maxDepth - 1, treeInfos);

            // through first removing an existing parent it is assured, that a parent is recorded
            // only once. This is needed, because parent may be reused in a tree as well, but we
            // always
            // iterate the whole tree
            ((NodeInfo) treeInfos.get(child)).removeParent(selection);
            ((NodeInfo) treeInfos.get(child)).addParent(selection);
            taskTreeBuilder.addChild(selection, child);
        }

        treeInfos.put(selection, new NodeInfo(selection));
        return selection;
    }

    /**
     * TODO: comment
     * 
     * @param treeInfos
     * @return
     */
    private ISelection reuseSelection(int                                   maxNoOfChildren,
                                      int                                   maxDepth,
                                      Map<ITaskTreeNode, ITaskTreeNodeInfo> treeInfos)
        throws Exception
    {
        int noOfSelections = 0;

        for (Map.Entry<ITaskTreeNode, ITaskTreeNodeInfo> entry : treeInfos.entrySet()) {
            if (entry.getKey() instanceof ISelection) {
                noOfSelections++;
            }
        }

        if (noOfSelections > 0) {
            noOfSelections = (int) (Math.random() * noOfSelections);

            for (Map.Entry<ITaskTreeNode, ITaskTreeNodeInfo> entry : treeInfos.entrySet()) {
                if (entry.getKey() instanceof ISelection) {
                    if (--noOfSelections <= 0) {
                        return (ISelection) entry.getKey();
                    }
                }
            }
        }
        else {
            return createNewSelection(maxNoOfChildren, maxDepth, treeInfos);
        }

        throw new RuntimeException("this is an implementation error");
    }

    /**
     * TODO: comment
     * 
     * @param treeInfos
     * @return
     */
    private IIteration createNewIteration(int                                   maxNoOfChildren,
                                          int                                   maxDepth,
                                          Map<ITaskTreeNode, ITaskTreeNodeInfo> treeInfos)
        throws Exception
    {
        IIteration iteration = taskTreeNodeFactory.createNewIteration();

        ITaskTreeNode child = createTree(maxNoOfChildren, maxDepth - 1, treeInfos);

        // through first removing an existing parent it is assured, that a parent is recorded
        // only once. This is needed, because parent may be reused in a tree as well, but we always
        // iterate the whole tree
        ((NodeInfo) treeInfos.get(child)).removeParent(iteration);
        ((NodeInfo) treeInfos.get(child)).addParent(iteration);
        taskTreeBuilder.setChild(iteration, child);

        treeInfos.put(iteration, new NodeInfo(iteration));
        return iteration;
    }

    /**
     * TODO: comment
     * 
     * @param treeInfos
     * @return
     */
    private IIteration reuseIteration(int                                   maxNoOfChildren,
                                      int                                   maxDepth,
                                      Map<ITaskTreeNode, ITaskTreeNodeInfo> treeInfos)
        throws Exception
    {
        int noOfIterations = 0;

        for (Map.Entry<ITaskTreeNode, ITaskTreeNodeInfo> entry : treeInfos.entrySet()) {
            if (entry.getKey() instanceof IIteration) {
                noOfIterations++;
            }
        }

        if (noOfIterations > 0) {
            noOfIterations = (int) (Math.random() * noOfIterations);

            for (Map.Entry<ITaskTreeNode, ITaskTreeNodeInfo> entry : treeInfos.entrySet()) {
                if (entry.getKey() instanceof IIteration) {
                    if (--noOfIterations <= 0) {
                        return (IIteration) entry.getKey();
                    }
                }
            }
        }
        else {
            return createNewIteration(maxNoOfChildren, maxDepth, treeInfos);
        }

        throw new RuntimeException("this is an implementation error");
    }

}
