//   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.ArrayList;
import java.util.List;
import java.util.logging.Level;

import de.ugoe.cs.autoquest.eventcore.guimodel.IDialog;
import de.ugoe.cs.autoquest.eventcore.guimodel.IFrame;
import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement;
import de.ugoe.cs.autoquest.tasktrees.taskequality.TaskEquality;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskBuilder;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskFactory;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInstance;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IUserSession;
import de.ugoe.cs.util.console.Console;

/**
 * TODO update comment
 * 
 * <p>
 * This class is responsible for applying temporal relationship rules on a task tree. Through this,
 * a flat task tree is restructured to have more depth but to include more temporal relationships
 * between tasks which are not only a major sequence. I.e. through the application of the
 * rules iterations and selections of tasks are detected. Which kind of temporal relations
 * between tasks are detected depends on the {@link ITaskInstanceScopeRule}s known to
 * this class.
 * </p>
 * <p>The class holds references to the appropriate {@link ITaskInstanceScopeRule}s and calls
 * their {@link ITaskInstanceScopeRule#apply(ITask, ITaskBuilder, ITaskFactory, boolean)}
 * method for each task in the task tree it is needed for. The general behavior of this class is
 * the following:
 * <ol>
 *   <li>
 *     An instance of this class is created using the constructor and calling the
 *     {@link #init()} method afterwards
 *   </li>
 *   <li>
 *     then the {@link #applyRules(ITask, ITaskBuilder, ITaskFactory, boolean)}
 *     method is called for a so far unstructured task
 *   </li>
 *   <li>
 *     the class iterates its internal list of rules and calls their
 *     {@link ITaskInstanceScopeRule#apply(ITask, ITaskBuilder, ITaskFactory, boolean)}
 *     method.
 *   </li>
 *   <li>
 *     the class evaluates the rule application result
 *     <ul>
 *       <li>
 *         if a rule returns a rule application result that is null, the next rule is tried
 *       </li>
 *       <li>
 *         if a rule returns that it would be feasible if more data was available and the rule
 *         application shall not be finalized (see finalize parameter of the applyRules method)
 *         the rule application is broken up 
 *       </li>
 *       <li>
 *         if a rule returns, that it was applied, the same rule is applied again until it returns
 *         null or feasible. For each newly created parent task provided in the rule application
 *         result, the {@link #applyRules(ITask, ITaskBuilder, ITaskFactory, boolean)}
 *         method is called.
 *       </li>
 *     </ul>
 *   </li>
 * </ol>
 * Through this, all rules are tried to be applied at least once to the provided parent task and
 * all parent tasks created during the rule application.
 * </p>
 * 
 * @author Patrick Harms
 */
public class TemporalRelationshipRuleManager {
    
    /**
     * <p>
     * the task factory to be used during rule application
     * </p>
     */
    private ITaskFactory taskFactory;

    /**
     * <p>
     * the task builder to be used during rule application
     * </p>
     */
    private ITaskBuilder taskBuilder;

    /**
     * <p>
     * the temporal relationship rules known to the manager that are executed on whole sessions.
     * The rules are applied in the order they occur in this list.
     * </p>
     */
    private ISessionScopeRule[] sessionScopeRules;

    /**
     * <p>
     * the temporal relationship rules known to the manager that are executed on whole sub trees.
     * The rules are applied in the order they occur in this list.
     * </p>
     */
    private ITaskInstanceScopeRule[] taskScopeRules;

    /**
     * <p>
     * initialize the manager
     * </p>
     * 
     * @param taskFactory             the task factory to be used for instantiating new task tree
     *                                tasks during rule application
     * @param taskBuilder             the task builder to be used for linking tasks
     *                                with each other during rule application
     */
    public TemporalRelationshipRuleManager(ITaskFactory taskFactory, ITaskBuilder taskBuilder) {
        super();
        this.taskFactory = taskFactory;
        this.taskBuilder = taskBuilder;
    }

    /**
     * <p>
     * initialized the temporal relationship rule manager by instantiating the known rules and
     * providing them with a reference to the task equality manager or other information they need.
     * </p>
     */
    public void init() {
        List<Class<? extends IGUIElement>> frameFilter =
            new ArrayList<Class<? extends IGUIElement>>();
        frameFilter.add(IFrame.class);
        frameFilter.add(IDialog.class);
        //frameFilter.add(ICanvas.class);

        sessionScopeRules = new ISessionScopeRule[] {
            new SequenceForTaskDetectionRule
                (TaskEquality.SEMANTICALLY_EQUAL, taskFactory, taskBuilder),
            /*new DefaultTaskSequenceDetectionRule
                (NodeEquality.SYNTACTICALLY_EQUAL, taskFactory, taskTreeBuilder),
            new DefaultTaskSequenceDetectionRule
                (NodeEquality.LEXICALLY_EQUAL, taskFactory, taskTreeBuilder),*/
            /*new TreeScopeWrapperRule
                (new DefaultIterationDetectionRule
                    (NodeEquality.LEXICALLY_EQUAL, taskFactory, taskTreeBuilder)),
            new TreeScopeWrapperRule
                (new DefaultIterationDetectionRule
                    (NodeEquality.SYNTACTICALLY_EQUAL, taskFactory, taskTreeBuilder)),
            new TreeScopeWrapperRule
                (new DefaultIterationDetectionRule
                    (NodeEquality.SEMANTICALLY_EQUAL, taskFactory, taskTreeBuilder))*/
        };
        
        //treeScopeRules.add(new DefaultGuiElementSequenceDetectionRule(frameFilter));

        taskScopeRules = new ITaskInstanceScopeRule[] {
            //new SequenceOnGuiElementDetectionRule(taskFactory, taskTreeBuilder),
            //new EventSequenceOnSameTargetDetectionRule(taskFactory, taskTreeBuilder),
            //new TrackBarSelectionDetectionRule(taskFactory, taskBuilder),
            //new DefaultGuiEventSequenceDetectionRule(taskFactory, taskTreeBuilder),
        };

    }

    /**
     * <p>
     * applies the known rules to the provided sessions. For the creation of further tasks,
     * the provided builder and task factory are utilized. The method expects, that no more data
     * is available and, therefore, finalizes the rule application.
     * </p>
     * 
     * @param taskFactory  the task factory to be used for instantiating new tasks.
     */
    public void applyRules(List<IUserSession> sessions) {
        applyRules(sessionScopeRules, sessions, "");
    }

    /**
     * <p>
     * applies the known rules to the provided parent task. For the creation of further tasks,
     * the provided builder and task factory are utilized. If the finalize parameter is true, the
     * rule application is finalized as far as possible without waiting for further data. If it is
     * false, the rule application is broken up at the first rule returning, that its application
     * would be feasible. The method calls itself for each parent task created through the rule
     * application. In this case, the finalize parameter is always true.
     * </p>
     * 
     * @param parent       the parent task to apply the rules on
     * @param finalize     used to indicate, if the rule application shall break up if a rule would
     *                     be feasible if further data was available, or not.
     * @param logIndent    simply used for logging purposes to indent the log messages depending
     *                     on the recursion depth of calling this method.
     */
    private int applyRules(ISessionScopeRule[] rules,
                           List<IUserSession>  sessions,
                           String              logIndent)
    {
        Console.traceln
            (Level.FINER, logIndent + "applying rules for " + sessions.size() + " sessions");

        int noOfRuleApplications = 0;

        for (ISessionScopeRule rule : rules) {
            RuleApplicationResult result;
            do {
                Console.traceln(Level.FINER, logIndent + "trying rule " + rule);
                result = rule.apply(sessions);

                if ((result != null) &&
                    (result.getRuleApplicationStatus() == RuleApplicationStatus.FINISHED))
                {
                    Console.traceln(Level.FINE, logIndent + "applied rule " + rule);
                    noOfRuleApplications++;
                    
                    //dumpTask(parent, "");

                    for (ITaskInstance newParent : result.getNewlyCreatedTaskInstances()) {
                        noOfRuleApplications +=
                            applyRules(taskScopeRules, newParent, logIndent + "  ");
                    }
                }
            }
            while ((result != null) &&
                   (result.getRuleApplicationStatus() == RuleApplicationStatus.FINISHED));

        }

        if (noOfRuleApplications <= 0) {
            Console.traceln(Level.FINE, logIndent + "no rules applied --> no temporal " +
                            "relationship generated");
        }

        return noOfRuleApplications;
    }

    /**
     * <p>
     * applies the known rules to the provided parent task. For the creation of further tasks,
     * the provided builder and task factory are utilized. If the finalize parameter is true, the
     * rule application is finalized as far as possible without waiting for further data. If it is
     * false, the rule application is broken up at the first rule returning, that its application
     * would be feasible. The method calls itself for each parent task created through the rule
     * application. In this case, the finalize parameter is always true.
     * </p>
     * 
     * @param parent       the parent task to apply the rules on
     * @param finalize     used to indicate, if the rule application shall break up if a rule would
     *                     be feasible if further data was available, or not.
     * @param logIndent    simply used for logging purposes to indent the log messages depending
     *                     on the recursion depth of calling this method.
     */
    private int applyRules(ITaskInstanceScopeRule[] rules,
                           ITaskInstance                taskInstance,
                           String                       logIndent)
    {
        Console.traceln(Level.FINER, logIndent + "applying rules on " + taskInstance);

        int noOfRuleApplications = 0;

        for (ITaskInstanceScopeRule rule : rules) {
            RuleApplicationResult result;
            do {
                Console.traceln
                    (Level.FINER, logIndent + "trying rule " + rule + " on " + taskInstance);
                result = rule.apply(taskInstance);

                if ((result != null) &&
                    (result.getRuleApplicationStatus() == RuleApplicationStatus.FINISHED))
                {
                    Console.traceln
                        (Level.FINE, logIndent + "applied rule " + rule + " on " + taskInstance);
                    noOfRuleApplications++;
                    
                    //dumpTask(parent, "");

                    for (ITaskInstance newParent : result.getNewlyCreatedTaskInstances()) {
                        noOfRuleApplications +=
                            applyRules(taskScopeRules, newParent, logIndent + "  ");
                    }
                }
            }
            while ((result != null) &&
                   (result.getRuleApplicationStatus() == RuleApplicationStatus.FINISHED));
        }

        if (noOfRuleApplications <= 0) {
            Console.traceln(Level.FINE, logIndent + "no rules applied --> no temporal " +
                            "relationship generated");
        }

        return noOfRuleApplications;
    }

    /**
     *
     */
    /*private void dumpTask(ITask task, String indent) {
        StringBuffer message = new StringBuffer();
        message.append(indent);
        message.append(task);
        if (task.getDescription() != null) {
            message.append('(');
            message.append(task.getDescription());
            message.append(')');
        }
        
        Console.traceln(Level.FINER, message.toString());
        
        if ((task.getChildren() != null) && (task.getChildren().size() > 0)) {
            for (ITask child : task.getChildren()) {
                dumpTask(child, indent + "  ");
            }
        }
    }*/

}
