/*
 * Copyright 2015 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * 
 *      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.drools.core.common;

import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

import org.drools.core.RuleBaseConfiguration;
import org.drools.core.reteoo.BetaMemory;
import org.drools.core.reteoo.builder.BuildContext;
import org.drools.core.rule.ContextEntry;
import org.drools.core.rule.IndexableConstraint;
import org.drools.core.rule.constraint.BetaNodeFieldConstraint;
import org.drools.core.util.index.IndexFactory;
import org.kie.internal.conf.IndexPrecedenceOption;

import static org.drools.core.util.index.IndexUtil.compositeAllowed;
import static org.drools.core.util.index.IndexUtil.isIndexableForNode;

public abstract class MultipleBetaConstraint implements BetaConstraints {
    protected BetaNodeFieldConstraint[] constraints;
    protected boolean[]                 indexed;
    protected IndexPrecedenceOption     indexPrecedenceOption;
    protected transient boolean         disableIndexing;

    private transient Boolean           leftUpdateOptimizationAllowed;

    public MultipleBetaConstraint() { }

    public MultipleBetaConstraint( BetaNodeFieldConstraint[] constraints,
                                   RuleBaseConfiguration conf,
                                   boolean disableIndexing) {
        this(constraints, conf.getIndexPrecedenceOption(), disableIndexing);
    }

    protected MultipleBetaConstraint( BetaNodeFieldConstraint[] constraints,
                                      IndexPrecedenceOption indexPrecedenceOption,
                                      boolean disableIndexing) {
        this.constraints = constraints;
        this.indexPrecedenceOption = indexPrecedenceOption;
        this.disableIndexing = disableIndexing;
    }

    public final void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        constraints = (BetaNodeFieldConstraint[])in.readObject();
        indexed = (boolean[]) in.readObject();
        indexPrecedenceOption = (IndexPrecedenceOption) in.readObject();
    }

    public final void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(constraints);
        out.writeObject(indexed);
        out.writeObject(indexPrecedenceOption);
    }

    public final void init(BuildContext context, short betaNodeType) {
        RuleBaseConfiguration config = context.getRuleBase().getRuleBaseConfiguration();

        if ( disableIndexing || (!config.isIndexLeftBetaMemory() && !config.isIndexRightBetaMemory()) ) {
            indexed = new boolean[constraints.length];
        } else {
            int depth = config.getCompositeKeyDepth();
            if ( !compositeAllowed( constraints, betaNodeType, config ) ) {
                // UnificationRestrictions cannot be allowed in composite indexes
                // We also ensure that if there is a mixture that standard restriction is first
                depth = 1;
            }
            initIndexes( depth, betaNodeType, config );
        }
    }

    public final void initIndexes(int depth, short betaNodeType, RuleBaseConfiguration config) {
        indexed = isIndexableForNode(indexPrecedenceOption, betaNodeType, depth, constraints, config);
    }

    public final boolean isIndexed() {
        return indexed[0];
    }

    public final int getIndexCount() {
        int count = 0;
        for (boolean i : indexed) {
            if ( i ) {
                count++;
            }
        }
        return count;
    }

    public BetaMemory createBetaMemory(final RuleBaseConfiguration config,
                                       final short nodeType) {
        return IndexFactory.createBetaMemory(config, nodeType, constraints);
    }

    public final BetaNodeFieldConstraint[] getConstraints() {
        return constraints;
    }

    public final ContextEntry[] createContext() {
        ContextEntry[] entries = new ContextEntry[constraints.length];
        for (int i = 0; i < constraints.length; i++) {
            entries[i] = constraints[i].createContextEntry();
        }
        return entries;
    }

    public final boolean isEmpty() {
        return false;
    }

    public boolean isLeftUpdateOptimizationAllowed() {
        if (leftUpdateOptimizationAllowed == null) {
            leftUpdateOptimizationAllowed = calcLeftUpdateOptimizationAllowed();
        }
        return leftUpdateOptimizationAllowed;
    }

    private boolean calcLeftUpdateOptimizationAllowed() {
        for (BetaNodeFieldConstraint constraint : constraints) {
            if ( !(constraint instanceof IndexableConstraint && ((IndexableConstraint)constraint).getConstraintType().isEquality()) ) {
                return false;
            }
        }
        return true;
    }

    public void registerEvaluationContext(BuildContext buildContext) {
        for (int i = 0; i < constraints.length; i++) {
            constraints[i].registerEvaluationContext(buildContext);
        }
    }
}
