/*
 * Copyright (c) 2016 Uli Bubenheimer
 *
 * 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 org.bubenheimer.android.rulez;

import android.support.annotation.IntDef;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collection;

/**
 * A rule
 */
public final class Rule {

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({TYPE_MATCH_ONCE, TYPE_MATCH_RESET, TYPE_MATCH_ALWAYS})
    public @interface MatchType {}

    /**
     * Specifies to match and fire a rule no more than once
     */
    public static final int TYPE_MATCH_ONCE = 0;

    /**
     * Specifies to re-match and re-fire a rule after its left-hand side no longer matches
     */
    public static final int TYPE_MATCH_RESET = 1;

    /**
     * Specifies to always re-match and re-fire a rule
     */
    public static final int TYPE_MATCH_ALWAYS = 2;

    /**
     * Rule name
     */
    final String name;

    /**
     * Rule match type. Specifies if a rule should match no more than once, or under what
     * conditions it becomes eligible to re-match.
     */
    @MatchType
    final int matchType;

    /**
     * The positive facts of the rule's left-hand side.
     */
    final ArrayList<Integer> conditions = new ArrayList<>();

    /**
     * The negated facts of the rule's left-hand side.
     */
    final ArrayList<Integer> negConditions = new ArrayList<>();

    /**
     * The rule action to execute when the rule fires.
     */
    RuleAction ruleAction;

    /**
     * Create a rule.
     * @param name         the rule name for debugging
     * @param matchType    the rule match type
     */
    public Rule(final String name, @MatchType final int matchType) {
        this.name = name;
        this.matchType = matchType;
    }

    /**
     * @return the rule name for debugging
     */
    public String getName() {
        return name;
    }

    /**
     * Add a conjunction of facts to the rule's left-hand side.
     * @param facts the conjunction of facts
     */
    public void addCondition(final Collection<Fact> facts) {
        int factVector = 0;
        for (final Fact fact : facts) {
            factVector |= 1 << fact.id;
        }
        conditions.add(factVector);
    }

    /**
     * Add a conjunction of negated facts to the rule's left-hand side.
     * @param facts the conjunction of facts
     */
    public void addNegCondition(final Collection<Fact> facts) {
        int factVector = 0;
        for (final Fact fact : facts) {
            factVector |= 1 << fact.id;
        }
        negConditions.add(factVector);
    }

    /**
     * Specifies the rule's executable action (right-hand side)
     * @param ruleAction the rule action
     */
    public void setRuleAction(final RuleAction ruleAction) {
        this.ruleAction = ruleAction;
    }

    @Override
    public String toString() {
        return name;
    }

    /**
     * Evaluates the rule's left-hand side
     * @param state the fact state to use for evaluation
     * @return whether the left-hand side matches the fact state
     */
    boolean eval(final int state) {
        for (final int condition : conditions) {
            if ((state & condition) != condition) {
                return false;
            }
        }
        //TODO optimization possible for the common case of disjoint negated conditions
        for (final int negCondition : negConditions) {
            if ((state & negCondition) == negCondition) {
                return false;
            }
        }
        return true;
    }
}

@@ -821,16 +821,69 @@
 lection;
+%0Aimport java.util.Collections;%0Aimport java.util.List;
 %0A%0A/**%0A *
@@ -2405,38 +2405,24 @@
 he rule name
- for debugging
 %0A     */%0A   
@@ -2468,32 +2468,143 @@
 rn name;%0A    %7D%0A%0A
+    /**%0A     * @return the match type%0A     */%0A    public int getMatchType() %7B%0A        return matchType;%0A    %7D%0A%0A
     /**%0A     * A
@@ -3469,32 +3469,32 @@
 n ruleAction) %7B%0A
-
         this.rul
@@ -3518,24 +3518,740 @@
 ion;%0A    %7D%0A%0A
+    /**%0A     * Retrieve the conjunctions of facts from the rule's left-hand side in the native%0A     * integer format. Not recommended for performance-critical operations.%0A     *%0A     * @return the rule's conjunctions of facts%0A     */%0A    public List%3CInteger%3E getNativeConditions() %7B%0A        return Collections.unmodifiableList(conditions);%0A    %7D%0A%0A    /**%0A     * Retrieve the conjunctions of negated facts from the rule's left-hand side in the native%0A     * integer format. Not recommended for performance-critical operations.%0A     *%0A     * @return the rule's conjunctions of negated facts%0A     */%0A    public List%3CInteger%3E getNativeNegConditions() %7B%0A        return Collections.unmodifiableList(negConditions);%0A    %7D%0A%0A
     @Overrid

Make more extensible