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

import java.util.List;
import java.util.Stack;

import de.ugoe.cs.autoquest.eventcore.gui.IInteraction;
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;

/**
 * This rule generates sequences of events depending on the event types, more concrete, the
 * {@link IInteraction}s and the return values of their {@link IInteraction#startsLogicalSequence()}
 * and {@link IInteraction#finishesLogicalSequence()}. If a new logical sequence is started by
 * an interaction, then a real sequence is instantiated. The sequence is finished, if an
 * interaction finishes a logical sequence. Examples include keyboard focus changes.
 * 
 * @version $Revision: $ $Date: 18.03.2012$
 * @author 2012, last modified by $Author: patrick$
 */
class SequenceDependingOnEventTypeDetectionRule implements TemporalRelationshipRule {

    /**
     * <p>
     * the task tree node factory to be used for creating substructures for the temporal
     * relationships identified during rule
     * </p>
     */
    private ITaskTreeNodeFactory taskTreeNodeFactory;
    /**
     * <p>
     * the task tree builder to be used for creating substructures for the temporal relationships
     * identified during rule application
     * </p>
     */
    private ITaskTreeBuilder taskTreeBuilder;
    
    /**
     * <p>
     * instantiates the rule with a task tree node factory and builder to be used during rule
     * application.
     * </p>
     * 
     * @param taskTreeNodeFactory the task tree node factory to be used for creating substructures
     *                            for the temporal relationships identified during rule
     *                            application
     * @param taskTreeBuilder     the task tree builder to be used for creating substructures for
     *                            the temporal relationships identified during rule application
     */
    SequenceDependingOnEventTypeDetectionRule(ITaskTreeNodeFactory taskTreeNodeFactory,
                                              ITaskTreeBuilder     taskTreeBuilder)
    {
        this.taskTreeNodeFactory = taskTreeNodeFactory;
        this.taskTreeBuilder = taskTreeBuilder;
    }
    
    /* (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return "SequenceDependingOnEventTypeDetectionRule";
    }

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

        RuleApplicationResult result = new RuleApplicationResult();
        Stack<Integer> sequenceStartingIndex = new Stack<Integer>();

        List<ITaskTreeNode> children = parent.getChildren();
        
        int index = 0;
        while (index < children.size()) {
            ITaskTreeNode child = children.get(index);

            if ((child instanceof IEventTask) &&
                (((IEventTask) child).getEventType() instanceof IInteraction))
            {
                IInteraction eventType = (IInteraction) ((IEventTask) child).getEventType();

                if (eventType.finishesLogicalSequence() && (sequenceStartingIndex.size() > 0)) {
                    index = handleLogicalSequence(sequenceStartingIndex, index, parent, result);
                    // the parents child list will have changed, retrieve it again
                    children = parent.getChildren();
                }

                if (eventType.startsLogicalSequence()) {
                    sequenceStartingIndex.push(index);
                }
            }

            index++;
        }

        if (sequenceStartingIndex.size() > 0) {
            if (!finalize) {
                result.setRuleApplicationStatus(RuleApplicationStatus.FEASIBLE);
            }
            else {
                ITaskTreeNode lastChild = children.get(children.size() - 1);
                
                if (lastChild instanceof IEventTask) {
                    handleLogicalSequence
                        (sequenceStartingIndex, children.size() - 1, parent, result);
                }
            }
        }

        return result;
    }

    /**
     * <p>
     * TODO: comment
     * </p>
     *
     */
    private int handleLogicalSequence(Stack<Integer>        sequenceStartingIndex,
                                      int                   index,
                                      ITaskTreeNode         parent,
                                      RuleApplicationResult result)
    {
        int newIndex = index;
        
        List<ITaskTreeNode> children = parent.getChildren();
        
        IInteraction eventType =
            (IInteraction) ((IEventTask) children.get(index)).getEventType();

        // There are several situations in which this implementation may cause infinite
        // loops. This is because the rule manager will reapply rules until
        // no rule is applied anymore. A sequence identified in a first iteration will
        // be identified as a sequence also in a second iteration. As an example
        // many sequences start with an interaction starting that sequence and end
        // with an interaction ending that sequence. This will be reidentified as
        // further subsequence. It must therefore be assured, that a sequence, that
        // was once identified is not reidentified in a further application of the rule.
        // For this, the implementation performs a kind of dry run. It creates a list of
        // children that would belong to an identified sequence. Only if this list is
        // not a reidentification then a new sequence is created and added to the
        // parent. If it is a reidentification can be identified, if the list of
        // children will contain all children of the parent, or if the list of children
        // only consists of one sequence. Further, an identified sequence must at least
        // have one child.
        
        boolean allChildrenBelongToSubSequence =
            (sequenceStartingIndex.peek() == 0) && (index == (children.size() - 1));
        
        boolean atLeastOneChildToCondense = index - sequenceStartingIndex.peek() > 0;
        
        if (!allChildrenBelongToSubSequence && atLeastOneChildToCondense) {
            int startIndex = sequenceStartingIndex.pop();
            ISequence sequence = taskTreeNodeFactory.createNewSequence();

            for (int j = startIndex; j < index; j++) {
                taskTreeBuilder.addChild(sequence, children.get(startIndex));
                taskTreeBuilder.removeChild((ISequence) parent, startIndex);
            }

            if (!eventType.startsLogicalSequence()) {
                taskTreeBuilder.addChild(sequence, children.get(startIndex));
                taskTreeBuilder.removeChild((ISequence) parent, startIndex);
                newIndex = startIndex;
            }
            else {
                newIndex = startIndex + 1;
            }

            taskTreeBuilder.addChild((ISequence) parent, startIndex, sequence);

            result.addNewlyCreatedParentNode(sequence);
                
            taskTreeBuilder.setDescription(sequence, "logical sequence started by the first event");
                
            result.setRuleApplicationStatus(RuleApplicationStatus.FINISHED);
        }
        
        return newIndex;
    }

}
