package de.ugoe.cs.autoquest.tasktrees.temporalrelation;

import java.util.ArrayList;
import java.util.List;

import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTask;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ISequence;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskTreeBuilder;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskTreeNode;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskTreeNodeFactory;

/**
 * TODO: comment or delete
 * 
 * @version $Revision: $ $Date: 18.03.2012$
 * @author 2012, last modified by $Author: patrick$
 */
public class DefaultGuiElementSequenceDetectionRuleBad implements TemporalRelationshipRule {

    /**
     * 
     */
    private List<Class<? extends IGUIElement>> guiElementFilter;

    /**
     * 
     */
    DefaultGuiElementSequenceDetectionRuleBad() {
        this.guiElementFilter = null;
    }

    /**
     * 
     */
    DefaultGuiElementSequenceDetectionRuleBad(List<Class<? extends IGUIElement>> guiElementFilter) {
        this.guiElementFilter = guiElementFilter;
    }

    /*
     * (non-Javadoc)
     * 
     * @see de.ugoe.cs.tasktree.temporalrelation.TemporalRelationshipRule#apply(TaskTreeNode,
     * TaskTreeBuilder, TaskTreeNodeFactory)
     */
    @Override
    public RuleApplicationResult apply(ITaskTreeNode        parent,
                                       ITaskTreeBuilder     builder,
                                       ITaskTreeNodeFactory nodeFactory,
                                       boolean              finalize)
    {
        if (!(parent instanceof ISequence)) {
            return null;
        }

        List<ITaskTreeNode> children = parent.getChildren();
        RuleApplicationResult result = new RuleApplicationResult();
        
        IGUIElement lastGuiElement = null;
        int index = 0;
        while (index <= children.size()) {
            ITaskTreeNode child = index < children.size() ? children.get(index) : null;
            IGUIElement currentGuiElement = getGuiElement(child);
            if ((index > 0) && (!guiElementsEqual(lastGuiElement, currentGuiElement))) {
                ReducableCommonDenominator commonDenominator =
                    getNextReducableCommonDenominator(parent, index - 1);
                
                RuleApplicationStatus status = handleCommonDenominator
                    (commonDenominator, parent, index, lastGuiElement, currentGuiElement, builder,
                     nodeFactory, finalize, result);
                
                if (status != null) {
                    result.setRuleApplicationStatus(status);
                    return result;
                }
                // else go on
            }

            lastGuiElement = currentGuiElement;
            index++;
        }

        return result;
    }

    /**
     * 
     */
    private ReducableCommonDenominator getNextReducableCommonDenominator(ITaskTreeNode parent,
                                                                         int           childIndex)
    {
        ReducableCommonDenominator commonDenominator = null;
        
        // a common denominator can only exist for at least two task tree nodes
        if (childIndex > 0) {
            // start with the last one
            int pos = childIndex;

            commonDenominator = new ReducableCommonDenominator();
            
            // check for further predecessors, if they match the same common denominator
            IGUIElement currentCommonDenominator = null;
            do {
                if (--pos < 0) {
                    currentCommonDenominator = null;
                }
                else {
                    currentCommonDenominator = getCommonDenominator
                        (getGuiElement(parent.getChildren().get(pos)),
                         getGuiElement(parent.getChildren().get(pos + 1)));
                }
                
                if (commonDenominator.commonGuiElement == null) {
                    commonDenominator.commonGuiElement = currentCommonDenominator;
                }
            }
            while ((commonDenominator.commonGuiElement != null) &&
                   (commonDenominator.commonGuiElement.equals(currentCommonDenominator)));
            
            if (commonDenominator.commonGuiElement != null) {
                // pos points to the last element, that has not the same common denominator.
                // This one must be subtracted from the task number as well
                commonDenominator.noOfTasks = childIndex - pos;
            }
            else {
                commonDenominator = null;
            }
        }
        
        return commonDenominator;
    }

    /**
     * 
     */
    private RuleApplicationStatus handleCommonDenominator(ReducableCommonDenominator commonDenominator,
                                                          ITaskTreeNode              parent,
                                                          int                        currentIndex,
                                                          IGUIElement                lastGuiElement,
                                                          IGUIElement                currentGuiElement,
                                                          ITaskTreeBuilder           builder,
                                                          ITaskTreeNodeFactory       nodeFactory,
                                                          boolean                    finalize,
                                                          RuleApplicationResult      result)
    {
        List<ITaskTreeNode> children = parent.getChildren();
        
        boolean sequenceHasOnlyOneChild = children.size() == 1;
        boolean reachedEndOfSequence = currentIndex == children.size();
        boolean haveCommonDenominator = commonDenominator != null;
        boolean allChildrenShareDenominator =
            haveCommonDenominator && commonDenominator.noOfTasks == children.size();
        boolean nextChildSharesDenominator =
            haveCommonDenominator && !reachedEndOfSequence &&
            isOnGuiElementPath(commonDenominator.commonGuiElement, currentGuiElement);
        
        IGUIElement denominatorOfPreviousAndCurrentChild =
            !reachedEndOfSequence ? getCommonDenominator(lastGuiElement, currentGuiElement) : null;
        
        boolean previousAndCurrentChildHaveDenominator =
            denominatorOfPreviousAndCurrentChild != null;

        if (haveCommonDenominator) {
            if (!reachedEndOfSequence) {
                if (nextChildSharesDenominator) {
                    // the last child, although matching the identified common denominator, may
                    // stand on its own because it is even deeper in the hierarchy, than the
                    // common denominator as well as the common denominator with the next child.
                    // So there need to appropriate subsequences to distinguish the child from
                    // the hierarchy of the next one.
                    if (isOnGuiElementPath(denominatorOfPreviousAndCurrentChild, lastGuiElement)) {
                        return condenseChildToSequencesRepresentingHierarchy
                            (parent, lastGuiElement, denominatorOfPreviousAndCurrentChild,
                             currentIndex - 1, builder, nodeFactory, result);
                    }
                    else {
                        // go on
                        return null;
                    }
                }
                else {
                    condenseTasksToSequencesRepresentingHierarchy
                        (commonDenominator.commonGuiElement, currentGuiElement, parent,
                         currentIndex, commonDenominator.noOfTasks, builder, nodeFactory, result);

                    return RuleApplicationStatus.RULE_APPLICATION_FINISHED;
                }
            }
            else {
                // end of sequence is reached and denominator is found
                if (!allChildrenShareDenominator) {
                    condenseTasksToSequencesRepresentingHierarchy
                        (commonDenominator.commonGuiElement, currentGuiElement, parent,
                         currentIndex, commonDenominator.noOfTasks, builder, nodeFactory, result);

                    return RuleApplicationStatus.RULE_APPLICATION_FINISHED;
                }
                else {
                    // all children share denominator
                    if (finalize) {
                        if (ensureSequencesRepresentingHierarchy
                                (parent, commonDenominator.commonGuiElement,
                                 builder, nodeFactory, result))
                        {
                            return RuleApplicationStatus.RULE_APPLICATION_FINISHED;
                        }
                        else {
                            return condenseChildToSequencesRepresentingHierarchy
                                (parent, lastGuiElement, getGuiElement(parent), currentIndex - 1,
                                 builder, nodeFactory, result);
                        }
                    }
                    else {
                        return RuleApplicationStatus.RULE_APPLICATION_FEASIBLE;
                    }
                }
            }
        }
        else {
            // no common denominator found
            if (!reachedEndOfSequence) {
                if (previousAndCurrentChildHaveDenominator) {
                    // go on
                    return null;
                }
                else {
                    return condenseChildToSequencesRepresentingHierarchy
                        (parent, lastGuiElement, getGuiElement(parent), currentIndex - 1,
                         builder, nodeFactory, result);
                }
            }
            else {
                // last child has its own GUI hierarchy
                if (sequenceHasOnlyOneChild) {
                    if (finalize) {
                        if (ensureSequencesRepresentingHierarchy
                                (parent, lastGuiElement, builder, nodeFactory, result))
                        {
                            return RuleApplicationStatus.RULE_APPLICATION_FINISHED;
                        }
                        else {
                            return RuleApplicationStatus.RULE_NOT_APPLIED;
                        }
                    }
                    else {
                        return RuleApplicationStatus.RULE_APPLICATION_FEASIBLE;
                    }
                }
                else {
                    if (finalize) {
                        return condenseChildToSequencesRepresentingHierarchy
                            (parent, lastGuiElement, getGuiElement(parent), currentIndex - 1,
                             builder, nodeFactory, result);
                    }
                    else {
                        return RuleApplicationStatus.RULE_APPLICATION_FEASIBLE;
                    }
                }
            }
        }
    }

    /**
     * 
     */
    private IGUIElement getCommonDenominator(List<IGUIElement> guiElements) {
        IGUIElement commonDenominator = null;
        
        if (guiElements.size() > 0) {
            List<IGUIElement> commonDenominatorPath = new ArrayList<IGUIElement>();
            
            // create a reference list using the first GUI element
            IGUIElement guiElement = guiElements.get(0);
            while (guiElement != null) {
                if (guiElementMatchesConsideredTypes(guiElement)) {
                    commonDenominatorPath.add(0, guiElement);
                }
                guiElement = guiElement.getParent();
            }
            
            if (commonDenominatorPath.size() == 0) {
                return null;
            }
            
            // for each other GUI element, check the reference list for the first element in the
            // path, that is not common to the current one, and delete it as well as it subsequent
            // siblings
            List<IGUIElement> currentPath = new ArrayList<IGUIElement>();
            for (int i = 1; i < guiElements.size(); i++) {
                currentPath.clear();
                guiElement = guiElements.get(i);
                while (guiElement != null) {
                    if (guiElementMatchesConsideredTypes(guiElement)) {
                        currentPath.add(0, guiElement);
                    }
                    guiElement = guiElement.getParent();
                }
                
                // determine the index of the first unequal path element
                int index = 0;
                while ((index < commonDenominatorPath.size()) && (index < currentPath.size()) &&
                        commonDenominatorPath.get(index).equals(currentPath.get(index)))
                {
                    index++;
                }
                
                // remove all elements from the common denonimator path, that do not match
                while (index < commonDenominatorPath.size()) {
                    commonDenominatorPath.remove(index);
                }
            }
            
            if (commonDenominatorPath.size() > 0) {
                commonDenominator = commonDenominatorPath.get(commonDenominatorPath.size() - 1);
            }
        }
        
        return commonDenominator;
    }

    /**
     * 
     */
    private IGUIElement getCommonDenominator(IGUIElement guiElement1, IGUIElement guiElement2) {
        if ((guiElement1 == null) || (guiElement2 == null)) {
            return null;
        }
    
        List<IGUIElement> allGuiElements = new ArrayList<IGUIElement>();
        allGuiElements.add(guiElement1);
        allGuiElements.add(guiElement2);
        return getCommonDenominator(allGuiElements);
    }

    /**
     * 
     */
    private void condenseTasksToSequencesRepresentingHierarchy(IGUIElement           lastGuiElement,
                                                               IGUIElement           currentGuiElement,
                                                               ITaskTreeNode         parent,
                                                               int                   parentIndex,
                                                               int                   noOfTasks,
                                                               ITaskTreeBuilder      builder,
                                                               ITaskTreeNodeFactory  nodeFactory,
                                                               RuleApplicationResult result)
    {
        List<IGUIElement> lastHierarchy = getGuiElementHierarchy(lastGuiElement);
        List<IGUIElement> currentHierarchy = getGuiElementHierarchy(currentGuiElement);
        int index = parentIndex;
        
        ITaskTreeNode generatedSequence = parent;
        
        for (int i = 0; i < lastHierarchy.size(); i++) {
            // add sequence for each element in the previous hierarchy, that does not occur in the
            // current hierarchy
            if ((currentHierarchy == null) || (i >= currentHierarchy.size()) ||
                (!currentHierarchy.get(i).equals(lastHierarchy.get(i))))
            {
                condenseTasksToSequence(generatedSequence, lastHierarchy.get(i), index,
                                        noOfTasks, builder, nodeFactory, result);
                
                // only in the first iteration, we condense tasks in the parent. Afterwards,
                // we always condense all tasks in the created sequence to a new subsequence.
                // Therefore, adapt all indexes appropriately
                generatedSequence = generatedSequence.getChildren().get(index - noOfTasks);
                index = noOfTasks;
            }
        }
    }

    /**
     * 
     */
    private boolean ensureSequencesRepresentingHierarchy(ITaskTreeNode         parent,
                                                         IGUIElement           elementToRepresent,
                                                         ITaskTreeBuilder      builder,
                                                         ITaskTreeNodeFactory  nodeFactory,
                                                         RuleApplicationResult result)
    {
        boolean applied = false;
        IGUIElement currentlyConsideredElement = elementToRepresent;
        IGUIElement elementRepresentedByParent = getGuiElement(parent);

        while ((currentlyConsideredElement != null) &&
               (!currentlyConsideredElement.equals(elementRepresentedByParent)))
        {
            condenseTasksToSequence(parent, currentlyConsideredElement, parent.getChildren().size(),
                                    parent.getChildren().size(), builder, nodeFactory, result);
            applied = true;
            currentlyConsideredElement = currentlyConsideredElement.getParent();
        }
        
        if (currentlyConsideredElement != null) {
            applied |= updateDescription(parent, currentlyConsideredElement, builder);
        }
        
        return applied;
    }

    /**
     * 
     */
    private RuleApplicationStatus condenseChildToSequencesRepresentingHierarchy(ITaskTreeNode         parent,
                                                                                IGUIElement           childGuiElement,
                                                                                IGUIElement           parentGuiElement,
                                                                                int                   childIndex,
                                                                                ITaskTreeBuilder      builder,
                                                                                ITaskTreeNodeFactory  nodeFactory,
                                                                                RuleApplicationResult result)
    {
        ITaskTreeNode child = parent.getChildren().get(childIndex);
        boolean childIsSequence = child instanceof ISequence;
        boolean childHasGuiElement = childGuiElement != null;
        
        if (childIsSequence) {
            if (childHasGuiElement) {
                if (updateDescription(child, childGuiElement, builder)) {
                    return RuleApplicationStatus.RULE_APPLICATION_FINISHED;
                }
            }
        }
        else {
            boolean applied = false;
            IGUIElement currentlyConsideredElement = childGuiElement;

            while ((currentlyConsideredElement != null) &&
                   (!currentlyConsideredElement.equals(parentGuiElement)))
            {
                condenseTasksToSequence(parent, currentlyConsideredElement, childIndex + 1, 1,
                                        builder, nodeFactory, result);
                applied = true;
                currentlyConsideredElement = currentlyConsideredElement.getParent();
            }
            
            if (currentlyConsideredElement != null) {
                IGUIElement parentCommonDenominator = getGuiElement(parent);
                if (currentlyConsideredElement.equals(parentCommonDenominator)) {
                    applied |= updateDescription(parent, currentlyConsideredElement, builder);
                }
            }

            if (applied) {
                return RuleApplicationStatus.RULE_APPLICATION_FINISHED;
            }
        }
        
        return null;
    }

    /**
     * <p>
     * condensed a subsequence of the children of the provided parent to a sequence. The
     * subsequence in the list of children defined by the index of the succeeding child
     * (parentIndex) and the number of tasks to be condensed (noOfTasks).
     * </p>
     *
     * @param parent      the parent node whose children are to be condensed
     * @param target      the GUI element in which all condensed interactions take place
     * @param parentIndex the index of the child in the parent, which follows the children
     *                    to be condensed
     * @param noOfTasks   the number of children to be condensed
     * @param builder     the builder to create the new subsequence
     * @param nodeFactory the node factory to instantiate the new subsequence
     * @param result      the result of the rule application to store newly created parent nodes
     */
    private void condenseTasksToSequence(ITaskTreeNode         parent,
                                         IGUIElement           target,          
                                         int                   parentIndex,
                                         int                   noOfTasks,
                                         ITaskTreeBuilder      builder,
                                         ITaskTreeNodeFactory  nodeFactory,
                                         RuleApplicationResult result)
    {
        ISequence newSequence = nodeFactory.createNewSequence();
        updateDescription(newSequence, target, builder);
        
        for (int i = 0; i < noOfTasks; i++) {
            builder.addChild(newSequence, parent.getChildren().get(parentIndex - noOfTasks));
            // remove exactly the same number of children from the parent.
            builder.removeChild((ISequence) parent, parentIndex - noOfTasks);
        }
                
        builder.addChild((ISequence) parent, parentIndex - noOfTasks, newSequence);
        result.addNewlyCreatedParentNode(newSequence);
    }

    /**
     * 
     */
    private boolean updateDescription(ITaskTreeNode    node,
                                      IGUIElement      commonGuiElement,
                                      ITaskTreeBuilder builder)
    {
        String newDescription = "interactions on " + commonGuiElement.getStringIdentifier();

        String currentDescription = node.getDescription();
        
        if ((currentDescription == null) || (currentDescription.indexOf(newDescription) == -1)) {
            if ((currentDescription != null) && (!"".equals(currentDescription))) {
                newDescription = currentDescription + "; " + newDescription;
            }
            
            builder.setDescription(node, newDescription);
            return true;
        }
        else {
            return false;
        }
    }

    /**
     * 
     */
    private IGUIElement getGuiElement(ITaskTreeNode node) {
        if (node != null) {
            List<IGUIElement> terminalGuiElements = new ArrayList<IGUIElement>();
            getTerminalGuiElements(node, terminalGuiElements);
            return getCommonDenominator(terminalGuiElements);
        }
        else {
            return null;
        }
    }
        
    /**
     * 
     */
    private void getTerminalGuiElements(ITaskTreeNode node, List<IGUIElement> terminalGuiElements) {
        if (node instanceof IEventTask) {
            if (((IEventTask) node).getEventTarget() instanceof IGUIElement) {
                IGUIElement terminalGuiElement = (IGUIElement) ((IEventTask) node).getEventTarget();
                terminalGuiElement =
                    searchHierarchyForGuiElementWithConsideredType(terminalGuiElement);
                
                if (terminalGuiElement != null) {
                    terminalGuiElements.add(terminalGuiElement);
                }
            }
        }
        else {
            for (ITaskTreeNode child : node.getChildren()) {
                getTerminalGuiElements(child, terminalGuiElements);
            }
        }
    }

    /**
     * 
     */
    private List<IGUIElement> getGuiElementHierarchy(IGUIElement guiElement) {
        IGUIElement element = guiElement;
        
        if (!guiElementMatchesConsideredTypes(element)) {
            element = searchHierarchyForGuiElementWithConsideredType(element);
        }
        
        List<IGUIElement> hierarchy = new ArrayList<IGUIElement>();
        
        while (element != null) {
            hierarchy.add(0, element);
            element = searchHierarchyForGuiElementWithConsideredType(element.getParent());
        }
        
        if (hierarchy.size() > 0) {
            return hierarchy;
        }
        else {
            return null;
        }
    }

    /**
     * 
     */
    private IGUIElement searchHierarchyForGuiElementWithConsideredType(IGUIElement guiElement) {
        IGUIElement returnValue = guiElement;
        
        while ((returnValue != null) && !guiElementMatchesConsideredTypes(returnValue)) {
            returnValue = returnValue.getParent();
        }
        
        return returnValue;
    }

    /**
     * 
     */
    private boolean guiElementsEqual(IGUIElement guiElement1, IGUIElement guiElement2) {
        if (guiElement1 == null) {
            return guiElement2 == null;
        }
        else {
            return guiElement1.equals(guiElement2);
        }
    }

    /**
     * 
     */
    private boolean isOnGuiElementPath(IGUIElement potentialPathElement, IGUIElement child) {
        IGUIElement guiElement = child;
        
        while (guiElement != null) {
            if (guiElement.equals(potentialPathElement)) {
                return true;
            }
            guiElement = guiElement.getParent();
        }
        
        return false;
    }

    /**
     * 
     */
    private boolean guiElementMatchesConsideredTypes(IGUIElement guiElement) {
        if (guiElementFilter == null) {
            return true;
        }
        else {
            for (Class<? extends IGUIElement> clazz : guiElementFilter) {
                if (clazz.isInstance(guiElement)) {
                    return true;
                }
            }
            
            return false;
        }
    }

    /**
     * 
     */
    static class ReducableCommonDenominator {
        
        /** the GUI element being the common denominator */
        IGUIElement commonGuiElement;
        
        /** the number of tasks that match the common denominator */
        int noOfTasks;

        /* (non-Javadoc)
         * @see java.lang.Object#toString()
         */
        @Override
        public String toString() {
            return noOfTasks + " tasks on " + commonGuiElement;
        }
        
    }
}
