/*
 * Copyright 2006 JBoss Inc
 * 
 * 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.drools.reteoo.builder;

import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

import org.drools.common.BaseNode;
import org.drools.common.InternalRuleBase;
import org.drools.common.InternalWorkingMemory;
import org.drools.common.RuleBasePartitionId;
import org.drools.reteoo.LeftTupleSource;
import org.drools.reteoo.ObjectSource;
import org.drools.reteoo.ReteooBuilder;
import org.drools.rule.Behavior;
import org.drools.rule.EntryPoint;
import org.drools.rule.Rule;
import org.drools.rule.RuleConditionElement;
import org.drools.time.TemporalDependencyMatrix;

/**
 * A build context for Reteoo Builder
 * 
 * @author etirelli
 */
public class BuildContext {

    // tuple source to attach next node to
    private LeftTupleSource                  tupleSource;

    // object source to attach next node to
    private ObjectSource                     objectSource;

    // object type cache to check for cross products
    private LinkedList                       objectType;

    // offset of the pattern
    private int                              currentPatternOffset;

    // rule base to add rules to
    private InternalRuleBase                 rulebase;
    
    // rule being added at this moment
    private Rule                             rule;

    // working memories attached to the given rulebase
    private InternalWorkingMemory[]          workingMemories;

    // id generator
    private ReteooBuilder.IdGenerator        idGenerator;

    // a build stack to track nested elements
    private LinkedList<RuleConditionElement> buildstack;

    // beta constraints from the last pattern attached
    private List                             betaconstraints;

    // alpha constraints from the last pattern attached
    private List                             alphaConstraints;

    // behaviors from the last pattern attached
    private List<Behavior>                   behaviors;

    // the current entry point
    private EntryPoint                       currentEntryPoint;

    private boolean                          tupleMemoryEnabled;

    private boolean                          objectTypeNodeMemoryEnabled;

    private boolean                          terminalNodeMemoryEnabled;

    /** This one is slightly different as alphaMemory can be adaptive, only turning on for new rule attachments */
    private boolean                          alphaNodeMemoryAllowed;

    /** Stores the list of nodes being added that require partitionIds */
    private List<BaseNode>                   nodes;

    /** Stores the id of the partition this rule will be added to */
    private RuleBasePartitionId              partitionId;

    /** the calculate temporal distance matrix */
    private TemporalDependencyMatrix         temporal;

    public BuildContext(final InternalRuleBase rulebase,
                        final ReteooBuilder.IdGenerator idGenerator) {
        this.rulebase = rulebase;
        this.rule = rule;

        this.idGenerator = idGenerator;

        this.workingMemories = null;

        this.objectType = null;
        this.buildstack = null;

        this.tupleSource = null;
        this.objectSource = null;

        this.currentPatternOffset = 0;

        this.tupleMemoryEnabled = true;

        this.objectTypeNodeMemoryEnabled = true;

        this.currentEntryPoint = EntryPoint.DEFAULT;

        this.nodes = new LinkedList<BaseNode>();

        this.partitionId = null;
    }

    /**
     * @return the currentPatternOffset
     */
    public int getCurrentPatternOffset() {
        return this.currentPatternOffset;
    }

    /**
     * @param currentPatternOffset the currentPatternOffset to set
     */
    public void setCurrentPatternOffset(final int currentPatternIndex) {
        this.currentPatternOffset = currentPatternIndex;
        this.syncObjectTypesWithPatternOffset();
    }

    public void syncObjectTypesWithPatternOffset() {
        if ( this.objectType == null ) {
            this.objectType = new LinkedList();
        }
        while ( this.objectType.size() > this.currentPatternOffset ) {
            this.objectType.removeLast();
        }
    }

    /**
     * @return the objectSource
     */
    public ObjectSource getObjectSource() {
        return this.objectSource;
    }

    /**
     * @param objectSource the objectSource to set
     */
    public void setObjectSource(final ObjectSource objectSource) {
        this.objectSource = objectSource;
    }

    /**
     * @return the objectType
     */
    public LinkedList getObjectType() {
        if ( this.objectType == null ) {
            this.objectType = new LinkedList();
        }
        return this.objectType;
    }

    /**
     * @param objectType the objectType to set
     */
    public void setObjectType(final LinkedList objectType) {
        if ( this.objectType == null ) {
            this.objectType = new LinkedList();
        }
        this.objectType = objectType;
    }

    /**
     * @return the tupleSource
     */
    public LeftTupleSource getTupleSource() {
        return this.tupleSource;
    }

    /**
     * @param tupleSource the tupleSource to set
     */
    public void setTupleSource(final LeftTupleSource tupleSource) {
        this.tupleSource = tupleSource;
    }

    public void incrementCurrentPatternOffset() {
        this.currentPatternOffset++;
    }

    public void decrementCurrentPatternOffset() {
        this.currentPatternOffset--;
        this.syncObjectTypesWithPatternOffset();
    }

    /**
     * Returns context rulebase
     * @return
     */
    public InternalRuleBase getRuleBase() {
        return this.rulebase;
    }

    /**
     * Return the array of working memories associated with the given
     * rulebase.
     * 
     * @return
     */
    public InternalWorkingMemory[] getWorkingMemories() {
        if ( this.workingMemories == null ) {
            this.workingMemories = (InternalWorkingMemory[]) this.rulebase.getWorkingMemories();
        }
        return this.workingMemories;
    }

    /**
     * Returns an Id for the next node
     * @return
     */
    public int getNextId() {
        return this.idGenerator.getNextId();
    }

    /**
     * Method used to undo previous id assignment
     */
    public void releaseId(int id) {
        this.idGenerator.releaseId( id );
    }

    /**
     * Adds the rce to the build stack
     * @param rce
     */
    public void push(final RuleConditionElement rce) {
        if ( this.buildstack == null ) {
            this.buildstack = new LinkedList<RuleConditionElement>();
        }
        this.buildstack.addLast( rce );
    }

    /**
     * Removes the top stack element
     * @return
     */
    public RuleConditionElement pop() {
        if ( this.buildstack == null ) {
            this.buildstack = new LinkedList<RuleConditionElement>();
        }
        return this.buildstack.removeLast();
    }

    /**
     * Returns the top stack element without removing it
     * @return
     */
    public RuleConditionElement peek() {
        if ( this.buildstack == null ) {
            this.buildstack = new LinkedList<RuleConditionElement>();
        }
        return this.buildstack.getLast();
    }

    /**
     * Returns a list iterator to iterate over the stacked elements
     * @return
     */
    public ListIterator<RuleConditionElement> stackIterator() {
        if ( this.buildstack == null ) {
            this.buildstack = new LinkedList<RuleConditionElement>();
        }
        return this.buildstack.listIterator( this.buildstack.size() );
    }

    /**
     * @return the betaconstraints
     */
    public List getBetaconstraints() {
        return this.betaconstraints;
    }

    /**
     * @param betaconstraints the betaconstraints to set
     */
    public void setBetaconstraints(final List betaconstraints) {
        this.betaconstraints = betaconstraints;
    }

    public int getNextSequence(String groupName) {
        //List list = new ArrayList();

        Integer seq = (Integer) this.rulebase.getAgendaGroupRuleTotals().get( groupName );
        if ( seq == null ) {
            seq = new Integer( 0 );
        }
        Integer newSeq = new Integer( seq.intValue() + 1 );
        this.rulebase.getAgendaGroupRuleTotals().put( groupName,
                                                      newSeq );

        return newSeq.intValue();
    }

    /**
     * @return
     */
    public List getAlphaConstraints() {
        return alphaConstraints;
    }

    public void setAlphaConstraints(List alphaConstraints) {
        this.alphaConstraints = alphaConstraints;
    }

    public boolean isTupleMemoryEnabled() {
        return this.tupleMemoryEnabled;
    }

    public void setTupleMemoryEnabled(boolean hasLeftMemory) {
        this.tupleMemoryEnabled = hasLeftMemory;
    }

    public boolean isObjectTypeNodeMemoryEnabled() {
        return objectTypeNodeMemoryEnabled;
    }

    public void setObjectTypeNodeMemoryEnabled(boolean hasObjectTypeMemory) {
        this.objectTypeNodeMemoryEnabled = hasObjectTypeMemory;
    }

    public boolean isTerminalNodeMemoryEnabled() {
        return terminalNodeMemoryEnabled;
    }

    public void setTerminalNodeMemoryEnabled(boolean hasTerminalNodeMemory) {
        this.terminalNodeMemoryEnabled = hasTerminalNodeMemory;
    }

    public void setAlphaNodeMemoryAllowed(boolean alphaMemoryAllowed) {
        this.alphaNodeMemoryAllowed = alphaMemoryAllowed;
    }

    public boolean isAlphaMemoryAllowed() {
        return this.alphaNodeMemoryAllowed;
    }

    /**
     * @return the currentEntryPoint
     */
    public EntryPoint getCurrentEntryPoint() {
        return currentEntryPoint;
    }

    /**
     * @param currentEntryPoint the currentEntryPoint to set
     */
    public void setCurrentEntryPoint(EntryPoint currentEntryPoint) {
        this.currentEntryPoint = currentEntryPoint;
    }

    /**
     * @return the behaviours
     */
    public List<Behavior> getBehaviors() {
        return behaviors;
    }

    /**
     * @param behaviors the behaviours to set
     */
    public void setBehaviors(List<Behavior> behaviors) {
        this.behaviors = behaviors;
    }

    /**
     * @return the nodes
     */
    public List<BaseNode> getNodes() {
        return nodes;
    }

    /**
     * @param nodes the nodes to set
     */
    public void setNodes(List<BaseNode> nodes) {
        this.nodes = nodes;
    }

    /**
     * @return the partitionId
     */
    public RuleBasePartitionId getPartitionId() {
        return partitionId;
    }

    /**
     * @param partitionId the partitionId to set
     */
    public void setPartitionId(RuleBasePartitionId partitionId) {
        this.partitionId = partitionId;
    }

    public void setTemporalDistance(TemporalDependencyMatrix temporal) {
        this.temporal = temporal;
    }

    public TemporalDependencyMatrix getTemporalDistance() {
        return this.temporal;
    }

    public LinkedList<RuleConditionElement> getBuildStack() {
        return this.buildstack;
    }

    public Rule getRule() {
        return rule;
    }

    public void setRule(Rule rule) {
        this.rule = rule;
    }

}
