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

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

import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskTreeNode;

/**
 * <p>
 * The node equality rule manager is capable of comparing task tree nodes based on its internal list
 * of comparison rules. The current list of rules contains the {@link NodeIdentityRule}, the
 * {@link IterationComparisonRule}, the {@link SequenceComparisonRule}, and
 * {@link SelectionComparisonRule}. These rules are asked for comparing the two provided task tree
 * nodes in the mentioned order. If a rule returns a node equality other than null, this equality is
 * returned. Otherwise the next rule is asked.
 * </p>
 * 
 * @version $Revision: $ $Date: 19.02.2012$
 * @author 2012, last modified by $Author: patrick$
 */
public class NodeEqualityRuleManager {

    /** */
    private List<NodeComparisonRule> mRuleIndex = null;

    /**
     * <p>
     * initializes the node equality rule manager by filling the internal list of comparison rules.
     * This method must be called before any other method is called on the rule manager.
     * </p>
     */
    public void init() {
        mRuleIndex = new ArrayList<NodeComparisonRule>();
        mRuleIndex.add(new NodeIdentityRule());
        mRuleIndex.add(new GUIEventTaskComparisonRule());
        mRuleIndex.add(new EventTaskComparisonRule());
        mRuleIndex.add(new IterationComparisonRule(this));
        mRuleIndex.add(new SequenceComparisonRule(this));
        mRuleIndex.add(new SelectionComparisonRule(this));
        mRuleIndex.add(new NodeAndIterationComparisonRule(this));
        mRuleIndex.add(new NodeAndSelectionComparisonRule(this));
    }

    /**
     * <p>
     * this method performs a comparison of the two provided task tree nodes. It iterates its
     * internal comparison rules. If the first rule returns a node 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 node1 the first task tree node to be compared
     * @param node2 the second task tree node to be compared
     * 
     * @return as described
     * 
     * @throws IllegalStateException in the case, the {@link #init()} method was not called on the
     *                               manager before a call to this method.
     */
    public NodeEquality compare(ITaskTreeNode node1, ITaskTreeNode node2)
        throws IllegalStateException
    {
        if (mRuleIndex == null) {
            throw new IllegalStateException("not initialized");
        }
        
        // LOG.info("checking for equality of " + node1 + " and " + node2);
        NodeEquality nodeEquality = null;

        for (NodeComparisonRule rule : mRuleIndex) {
            if (rule.isApplicable(node1, node2)) {
                nodeEquality = rule.compare(node1, node2);
                if (nodeEquality != null) {
                    // LOG.warning("used rule " + rule + " for equality check");
                    return nodeEquality;
                }
            }
        }

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

        return NodeEquality.UNEQUAL;
    }

    /**
     * <p>
     * TODO: comment
     * </p>
     *
     * @param child1
     * @param child2
     * @param equalityLevel
     * @return
     */
    public boolean areAtLeastEqual(ITaskTreeNode node1,
                                   ITaskTreeNode node2,
                                   NodeEquality  equalityLevel)
    {
        if (equalityLevel == null) {
            throw new IllegalArgumentException("required equality level must not be null");
        }
        
        switch (equalityLevel) {
            case IDENTICAL:
                return areIdentical(node1, node2);
            case LEXICALLY_EQUAL:
                return areLexicallyEqual(node1, node2);
            case SYNTACTICALLY_EQUAL:
                return areSyntacticallyEqual(node1, node2);
            case SEMANTICALLY_EQUAL:
                return areSemanticallyEqual(node1, node2);
            case UNEQUAL:
                return !areSemanticallyEqual(node1, node2);
            default:
                throw new IllegalArgumentException("unknown required equality: " + equalityLevel);
        }
    }

    /**
     * <p>
     * TODO: comment
     * </p>
     *
     * @param child1
     * @param child2
     * @return
     */
    public boolean areIdentical(ITaskTreeNode node1, ITaskTreeNode node2) {
        if (mRuleIndex == null) {
            throw new IllegalStateException("not initialized");
        }
        
        for (NodeComparisonRule rule : mRuleIndex) {
            if (rule.isApplicable(node1, node2) && rule.areLexicallyEqual(node1, node2)) {
                 return true;
            }
        }

        return false;
    }

    /**
     * <p>
     * TODO: comment
     * </p>
     *
     * @param child1
     * @param child2
     * @return
     */
    public boolean areLexicallyEqual(ITaskTreeNode node1, ITaskTreeNode node2) {
        if (mRuleIndex == null) {
            throw new IllegalStateException("not initialized");
        }
        
        for (NodeComparisonRule rule : mRuleIndex) {
            if (rule.isApplicable(node1, node2) && rule.areLexicallyEqual(node1, node2)) {
                 return true;
            }
        }

        return false;
    }

    /**
     * <p>
     * TODO: comment
     * </p>
     *
     * @param child1
     * @param child2
     * @return
     */
    public boolean areSyntacticallyEqual(ITaskTreeNode node1, ITaskTreeNode node2) {
        if (mRuleIndex == null) {
            throw new IllegalStateException("not initialized");
        }
        
        for (NodeComparisonRule rule : mRuleIndex) {
            if (rule.isApplicable(node1, node2) && rule.areSyntacticallyEqual(node1, node2)) {
                 return true;
            }
        }

        return false;
    }

    /**
     * <p>
     * TODO: comment
     * </p>
     *
     * @param child1
     * @param child2
     * @return
     */
    public boolean areSemanticallyEqual(ITaskTreeNode node1, ITaskTreeNode node2) {
        if (mRuleIndex == null) {
            throw new IllegalStateException("not initialized");
        }
        
        for (NodeComparisonRule rule : mRuleIndex) {
            if (rule.isApplicable(node1, node2) && rule.areSemanticallyEqual(node1, node2)) {
                 return true;
            }
        }

        return false;
    }

}
