//   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.usability.rules.patterns;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;

import de.ugoe.cs.autoquest.tasktrees.treeifc.ISelection;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITask;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskModel;
import de.ugoe.cs.autoquest.usability.taskmodel.filter.ITaskModelFilter;
import de.ugoe.cs.autoquest.usability.taskmodel.filter.TaskModelFilter;
import de.ugoe.cs.autoquest.usability.taskmodel.filter.types.TaskTypeFilter;

/**
 * <p>
 * A interaction pattern is a simple approach to describe the structure of usage behaviour.
 * </p>
 * 
 * @author Alexander Deicke
 */
public class InteractionPattern {

    /**
     * <p>
     * {@link TaskModelFilterAccessor}, which is used to filter a task model after different {@link ITask}s
     * </p>
     */
    private ITaskModelFilter taskTreeFilter = new TaskModelFilter();

    /**
     * <p>
     * Type of root task. Determines the order in which sub task appear.
     * </p>
     */
    private TaskTypeFilter rootTask;

    /**
     * <p>
     * Helper objects, which decide whether or not a defined pattern condition holds.
     * </p>
     */
    private List<InteractionPatternVisitor> patternVisitors;

    /**
     * <p>
     * Flag, which indicates if the interaction pattern was found within a given task model.
     * </p>
     */
    private boolean present = false;

    /**
     * <p>
     * Constructor. Creates a new interaction pattern for a given root task and a collection of
     * {@link InteractionPatternVisitor}s.
     * </p>
     * 
     * @param rootTask
     *            Type of root task, which determines the order in which sub task appear.
     * @param patternVisitors
     *            {@link InteractionPatternVisitor}s, which decide whether or not a defined pattern
     *            condition holds
     */
    public InteractionPattern(TaskTypeFilter rootTask,
                              List<InteractionPatternVisitor> patternVisitors)
    {
        this.patternVisitors = patternVisitors;
        this.rootTask = rootTask;
    }

    /**
     * 
     * <p>
     * Checks if a interaction pattern is contained in a given task model.
     * </p>
     * 
     * @param taskModel
     *            {@link ITaskModel}, which might contain the interaction pattern
     * @return true, iff interaction pattern is contained
     */
    public boolean containedIn(ITaskModel taskModel) {
        List<ITask> allConcernedTasks = filterAllConcernedTasksFrom(taskModel);
        for (ITask concernedTask : allConcernedTasks) {
            checkTask(concernedTask);
            if (this.present)
                break;
        }
        return this.present;
    }

    /**
     * 
     * <p>
     * Returns all tasks of the given task model matching the provided interaction pattern.
     * </p>
     * 
     * @param taskModel
     *            {@link ITaskModel}, which might contain the interaction pattern
     * @return all tasks matched
     */
    public Collection<ITask> matchingTasks(ITaskModel taskModel) {
        List<ITask> allConcernedTasks = filterAllConcernedTasksFrom(taskModel);
        for (Iterator<ITask> iterator = allConcernedTasks.iterator(); iterator.hasNext();) {
            ITask concernedTask = iterator.next();
            checkTask(concernedTask);
            if (this.present)
                this.present = false;
            else
                iterator.remove();
        }
        return allConcernedTasks;
    }

    /**
     * 
     * <p>
     * Checks a single {@link ITask} for the interaction pattern.
     * </p>
     * 
     * @param task
     *            task, which might contain the interaction pattern
     */
    private void checkTask(ITask task) {
        applyAllVisitors(task);
        if (allVisitorsArePresent()) {
            this.present = true;
        }
        else {
            resetAllVisitors();
        }
    }

    /**
     * 
     * <p>
     * Checks if a interaction pattern is contained in a given task.
     * </p>
     * 
     * @param task
     *            task, which might contain the interaction pattern
     * @return true, iff interaction pattern is contained
     */
    public boolean containedIn(ITask task) {
        checkTask(task);
        return this.present;
    }

    /**
     * 
     * <p>
     * Method applys all {@link InteractionPatternVisitor}s, to check single interaction pattern
     * conditions.
     * </p>
     * 
     * @param task
     *            task, which might contain the interaction pattern
     */
    private void applyAllVisitors(ITask task) {
        Optional<InteractionPatternVisitor> previousVisitor = Optional.absent();
        for (InteractionPatternVisitor visitor : patternVisitors) {
            if (appliedOnSelectionNode(previousVisitor)) {
                for (ITask selection : previousVisitor.get().getRetainedSelectionNodes()) {
                    selection.accept(visitor);
                }
            }
            else {
                previousVisitor = Optional.of(visitor);
                task.accept(visitor);
            }
        }
    }

    /**
     * 
     * <p>
     * Checks, if a {@link InteractionPatternVisitor} was applied on a {@link ISelection} task.
     * </p>
     * 
     * @param interactionPatternVisitor
     *            {@link InteractionPatternVisitor}
     * @return true, iff {@link InteractionPatternVisitor} was applied on {@link ISelection} task
     */
    private boolean appliedOnSelectionNode(Optional<InteractionPatternVisitor> interactionPatternVisitor)
    {
        return interactionPatternVisitor.isPresent() &&
            interactionPatternVisitor.get().hasExcludedSelectionNodes();
    }

    /**
     * <p>
     * Filters given task model after root task of interaction pattern.
     * </p>
     * 
     * @param taskModel
     *            {@link ITaskModel}
     * @return all tasks of task model, which matches root task of interaction pattern
     */
    private List<ITask> filterAllConcernedTasksFrom(ITaskModel taskModel) {
        return this.taskTreeFilter.filter(taskModel, this.rootTask).tasksMatchedFilter();
    }

    /**
     * <p>
     * Checks, if all interaction pattern condition are evaluated to true.
     * </p>
     * 
     * @return true, iff all interaction pattern condition are true
     */
    private boolean allVisitorsArePresent() {
        Iterable<InteractionPatternVisitor> allPresent =
            Iterables.filter(this.patternVisitors, new Predicate<InteractionPatternVisitor>() {

                public boolean apply(InteractionPatternVisitor visitor) {
                    return visitor.isPresent();
                }

            });
        int cnt = Iterables.size(allPresent);
        return cnt == this.patternVisitors.size();
    }

    /**
     * <p>
     * Resets all interaction pattern condition.
     * </p>
     * 
     */
    private void resetAllVisitors() {
        for (InteractionPatternVisitor visitor : this.patternVisitors) {
            visitor.reset();
        }

    }

}
