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

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

import de.ugoe.cs.autoquest.tasktrees.treeifc.ITask;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInstance;

/**
 * <p>
 * The task equality rule manager is capable of comparing tasks and task instances based on its
 * internal list of comparison rules. These rules are asked for comparing the two provided tasks or
 * task instance. If a rule returns a task equality other than null, this equality is returned.
 * Otherwise the next rule is asked.
 * </p>
 * 
 * @author Patrick Harms
 */
public class TaskEqualityRuleManager {
    
    /**
     * <p>
     * the singleton instance of this class
     * </p>
     */
    private static final TaskEqualityRuleManager instance = new TaskEqualityRuleManager();

    /**
     * <p>
     * the rules that can be used for comparing tasks
     * </p>
     */
    private List<TaskComparisonRule> mRuleIndex = null;

    /**
     * <p>
     * initializes the task equality rule manager by filling the internal list of comparison rules.
     * </p>
     */
    private TaskEqualityRuleManager() {
        mRuleIndex = new ArrayList<TaskComparisonRule>();
        mRuleIndex.add(new TaskIdentityRule());
        mRuleIndex.add(new GUIEventTaskComparisonRule());
        mRuleIndex.add(new InefficientActionsComparisonRule());
        mRuleIndex.add(new EventTaskComparisonRule());
        mRuleIndex.add(new IterationComparisonRule());
        mRuleIndex.add(new OptionalComparisonRule());
        mRuleIndex.add(new SequenceComparisonRule());
        mRuleIndex.add(new SelectionComparisonRule());
        mRuleIndex.add(new TaskAndIterationComparisonRule());
        mRuleIndex.add(new TaskAndOptionalComparisonRule());
        mRuleIndex.add(new TaskAndSelectionComparisonRule());
    }


    /**
     * <p>
     * returns the singleton instance of this class
     * </p>
     * 
     * @return as described
     */
    public static TaskEqualityRuleManager getInstance() {
        return instance;
    }

    /**
     * <p>
     * this method performs a comparison of the two provided tasks. It iterates its internal
     * comparison rules. If the first rule returns a task equality other than null,
     * this equality is returned. Otherwise the next rule is tried. If no rule returns an equality
     * <code>NodeEquality.UNEQUAL</code> is returned.
     * </p>
     * 
     * @param task1 the first task to be compared
     * @param task2 the second task to be compared
     * 
     * @return as described
     * 
     * @throws IllegalStateException in the case, the object is not correctly initialized
     */
    public TaskEquality compare(ITask task1, ITask task2)
        throws IllegalStateException
    {
        if (mRuleIndex == null) {
            throw new IllegalStateException("not initialized");
        }
        
        // LOG.info("checking for equality of " + task1 + " and " + task2);
        TaskEquality taskEquality = null;

        for (TaskComparisonRule rule : mRuleIndex) {
            if (rule.isApplicable(task1, task2)) {
                taskEquality = rule.compare(task1, task2);
                if (taskEquality != null) {
                    // LOG.warning("used rule " + rule + " for equality check");
                    return taskEquality;
                }
            }
        }

        // LOG.warning("no rule could be applied --> handling tasks as unequal");

        return TaskEquality.UNEQUAL;
    }

    /**
     * <p>
     * this method two tasks with respect to the fiven equality level and returns true, if this
     * level is given.
     * </p>
     * 
     * @param task1         the first task to be compared
     * @param task2         the second task to be compared
     * @param equalityLevel the level of equality to be checked for
     * 
     * @return as described
     * 
     * @throws IllegalStateException in the case, the object is not correctly initialized
     */
    public boolean areAtLeastEqual(ITask task1, ITask task2, TaskEquality equalityLevel) {
        if (equalityLevel == null) {
            throw new IllegalArgumentException("required equality level must not be null");
        }
        
        switch (equalityLevel) {
            case IDENTICAL:
                return areIdentical(task1, task2);
            case LEXICALLY_EQUAL:
                return areLexicallyEqual(task1, task2);
            case SYNTACTICALLY_EQUAL:
                return areSyntacticallyEqual(task1, task2);
            case SEMANTICALLY_EQUAL:
                return areSemanticallyEqual(task1, task2);
            case UNEQUAL:
                return !areSemanticallyEqual(task1, task2);
            default:
                throw new IllegalArgumentException("unknown required equality: " + equalityLevel);
        }
    }

    /**
     * <p>
     * this method checks if the two given tasks are identical. For this, it iterates its internal
     * comparison rules. If the first rule returns true, than this method returns true as well.
     * If no rule returns true, this method returns false.
     * </p>
     * 
     * @param task1 the first task to be compared
     * @param task2 the second task to be compared
     * 
     * @return as described
     * 
     * @throws IllegalStateException in the case, the object is not correctly initialized
     */
    public boolean areIdentical(ITask task1, ITask task2) {
        if (mRuleIndex == null) {
            throw new IllegalStateException("not initialized");
        }
        
        for (TaskComparisonRule rule : mRuleIndex) {
            if (rule.isApplicable(task1, task2) && rule.areLexicallyEqual(task1, task2)) {
                 return true;
            }
        }

        return false;
    }

    /**
     * <p>
     * this method checks if the two given tasks are lexically equal. For this, it iterates its
     * internal comparison rules. If the first rule returns true, than this method returns true
     * as well. If no rule returns true, this method returns false.
     * </p>
     * 
     * @param task1 the first task to be compared
     * @param task2 the second task to be compared
     * 
     * @return as described
     * 
     * @throws IllegalStateException in the case, the object is not correctly initialized
     */
    public boolean areLexicallyEqual(ITask task1, ITask task2) {
        if (mRuleIndex == null) {
            throw new IllegalStateException("not initialized");
        }
        
        for (TaskComparisonRule rule : mRuleIndex) {
            if (rule.isApplicable(task1, task2) && rule.areLexicallyEqual(task1, task2)) {
                 return true;
            }
        }

        return false;
    }

    /**
     * <p>
     * this method checks if the two given tasks are syntactically equal. For this, it iterates its
     * internal comparison rules. If the first rule returns true, than this method returns true
     * as well. If no rule returns true, this method returns false.
     * </p>
     * 
     * @param task1 the first task to be compared
     * @param task2 the second task to be compared
     * 
     * @return as described
     * 
     * @throws IllegalStateException in the case, the object is not correctly initialized
     */
    public boolean areSyntacticallyEqual(ITask task1, ITask task2) {
        if (mRuleIndex == null) {
            throw new IllegalStateException("not initialized");
        }
        
        for (TaskComparisonRule rule : mRuleIndex) {
            if (rule.isApplicable(task1, task2) && rule.areSyntacticallyEqual(task1, task2)) {
                 return true;
            }
        }

        return false;
    }

    /**
     * <p>
     * this method checks if the two given tasks are semantically equal. For this, it iterates its
     * internal comparison rules. If the first rule returns true, than this method returns true
     * as well. If no rule returns true, this method returns false.
     * </p>
     * 
     * @param task1 the first task to be compared
     * @param task2 the second task to be compared
     * 
     * @return as described
     * 
     * @throws IllegalStateException in the case, the object is not correctly initialized
     */
    public boolean areSemanticallyEqual(ITask task1, ITask task2) {
        if (mRuleIndex == null) {
            throw new IllegalStateException("not initialized");
        }
        
        for (TaskComparisonRule rule : mRuleIndex) {
            if (rule.isApplicable(task1, task2) && rule.areSemanticallyEqual(task1, task2)) {
                 return true;
            }
        }

        return false;
    }

    /**
     * <p>
     * this method performs a comparison of the two provided task instances. It iterates its
     * internal comparison rules. If the first rule returns a task instance equality other than
     * null, this equality is returned. Otherwise the next rule is tried. If no rule returns an
     * equality <code>TaskEquality.UNEQUAL</code> is returned.
     * </p>
     * 
     * @param instance1 the first task instance to be compared
     * @param instance2 the second task instance to be compared
     * 
     * @return as described
     * 
     * @throws IllegalStateException in the case, the object is not correctly initialized
     */
    public TaskEquality compare(ITaskInstance instance1, ITaskInstance instance2)
        throws IllegalStateException
    {
        if (mRuleIndex == null) {
            throw new IllegalStateException("not initialized");
        }
        
        // LOG.info("checking for equality of " + instance1 + " and " + instance2);
        TaskEquality instanceEquality = null;

        for (TaskComparisonRule rule : mRuleIndex) {
            if (rule.isApplicable(instance1, instance2)) {
                instanceEquality = rule.compare(instance1, instance2);
                if (instanceEquality != null) {
                    // LOG.warning("used rule " + rule + " for equality check");
                    return instanceEquality;
                }
            }
        }

        // LOG.warning("no rule could be applied --> handling tasks as unequal");

        return TaskEquality.UNEQUAL;
    }

    /**
     * <p>
     * this method compares two task instances with respect to the given equality level and returns
     * true, if this level is given.
     * </p>
     * 
     * @param instance1     the first task instance to be compared
     * @param instance2     the second task instance to be compared
     * @param equalityLevel the level of equality to be checked for
     * 
     * @return as described
     * 
     * @throws IllegalStateException in the case, the object is not correctly initialized
     */
    public boolean areAtLeastEqual(ITaskInstance        instance1,
                                   ITaskInstance        instance2,
                                   TaskEquality equalityLevel)
    {
        if (equalityLevel == null) {
            throw new IllegalArgumentException("required equality level must not be null");
        }
        
        switch (equalityLevel) {
            case IDENTICAL:
                return areIdentical(instance1, instance2);
            case LEXICALLY_EQUAL:
                return areLexicallyEqual(instance1, instance2);
            case SYNTACTICALLY_EQUAL:
                return areSyntacticallyEqual(instance1, instance2);
            case SEMANTICALLY_EQUAL:
                return areSemanticallyEqual(instance1, instance2);
            case UNEQUAL:
                return !areSemanticallyEqual(instance1, instance2);
            default:
                throw new IllegalArgumentException("unknown required equality: " + equalityLevel);
        }
    }

    /**
     * <p>
     * this method checks if the two given task instances are identical. For this, it iterates its
     * internal comparison rules. If the first rule returns true, than this method returns true
     * as well. If no rule returns true, this method returns false.
     * </p>
     * 
     * @param instance1 the first task instance to be compared
     * @param instance2 the second task instance to be compared
     * 
     * @return as described
     * 
     * @throws IllegalStateException in the case, the object is not correctly initialized
     */
    public boolean areIdentical(ITaskInstance instance1, ITaskInstance instance2) {
        if (mRuleIndex == null) {
            throw new IllegalStateException("not initialized");
        }
        
        for (TaskComparisonRule rule : mRuleIndex) {
            if (rule.isApplicable(instance1, instance2) &&
                rule.areLexicallyEqual(instance1, instance2))
            {
                return true;
            }
        }

        return false;
    }

    /**
     * <p>
     * this method checks if the two given task instances are lexically equal. For this, it
     * iterates its internal comparison rules. If the first rule returns true, than this method
     * returns true, as well. If no rule returns true, this method returns false.
     * </p>
     * 
     * @param instance1 the first task instance to be compared
     * @param instance2 the second task instance to be compared
     * 
     * @return as described
     * 
     * @throws IllegalStateException in the case, the object is not correctly initialized
     */
    public boolean areLexicallyEqual(ITaskInstance instance1, ITaskInstance instance2) {
        if (mRuleIndex == null) {
            throw new IllegalStateException("not initialized");
        }
        
        for (TaskComparisonRule rule : mRuleIndex) {
            if (rule.isApplicable(instance1, instance2) &&
                rule.areLexicallyEqual(instance1, instance2))
            {
                return true;
            }
        }

        return false;
    }

    /**
     * <p>
     * this method checks if the two given task instances are syntactically equal. For this, it
     * iterates its internal comparison rules. If the first rule returns true, than this method
     * returns true, as well. If no rule returns true, this method returns false.
     * </p>
     * 
     * @param instance1 the first task instance to be compared
     * @param instance2 the second task instance to be compared
     * 
     * @return as described
     * 
     * @throws IllegalStateException in the case, the object is not correctly initialized
     */
    public boolean areSyntacticallyEqual(ITaskInstance instance1, ITaskInstance instance2) {
        if (mRuleIndex == null) {
            throw new IllegalStateException("not initialized");
        }
        
        for (TaskComparisonRule rule : mRuleIndex) {
            if (rule.isApplicable(instance1, instance2) &&
                rule.areSyntacticallyEqual(instance1, instance2))
            {
                return true;
            }
        }

        return false;
    }

    /**
     * <p>
     * this method checks if the two given task instances are semantically equal. For this, it
     * iterates its internal comparison rules. If the first rule returns true, than this method
     * returns true, as well. If no rule returns true, this method returns false.
     * </p>
     * 
     * @param instance1 the first task instance to be compared
     * @param instance2 the second task instance to be compared
     * 
     * @return as described
     * 
     * @throws IllegalStateException in the case, the object is not correctly initialized
     */
    public boolean areSemanticallyEqual(ITaskInstance instance1, ITaskInstance instance2) {
        if (mRuleIndex == null) {
            throw new IllegalStateException("not initialized");
        }
        
        for (TaskComparisonRule rule : mRuleIndex) {
            if (rule.isApplicable(instance1, instance2) &&
                rule.areSemanticallyEqual(instance1, instance2))
            {
                 return true;
            }
        }

        return false;
    }

}
