/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.jena.reasoner.rulesys.impl;

import java.util.*;

import org.apache.jena.graph.* ;
import org.apache.jena.reasoner.rulesys.Node_RuleVariable ;

/**
 * Frame in the LPInterpreter's control stack used to represent matching
 * to the results of a tabled predicate. Conventionally the system state which
 * finds and tables the goal results is called the generator and states which
 * require those results are called consumers.
 * <p>
 * This is used in the inner loop of the interpreter and so is a pure data structure
 * not an abstract data type and assumes privileged access to the interpreter state.
 * </p>
 */
public class ConsumerChoicePointFrame extends GenericTripleMatchFrame 
                implements LPAgendaEntry, LPInterpreterState {
        
    /** The generator whose tabled results we are selecting over */
    protected Generator generator;
    
    /** The index in the generator's result set that we have reached so far. */
    protected int resultIndex;
    
    /** The preserved permanent registers for the pickled interpreter */
    protected Node[] pVars;

    /** The preserved trail variables for the picked interpreter */
    protected Node_RuleVariable[] trailVars;

    /** The preserved trail bound values for the picked interpreter */
    protected Node[] trailValues;
    
    /** The length of the preserved trail */
    protected int trailLength;
    
    /** The generator or top iterator we are producting results for */
    protected LPInterpreterContext context;
        
    /**
     * Constructor.
     * @param interpreter the parent interpreter whose state is to be preserved here, its arg stack
     * defines the parameters for the target goal
     */
    public ConsumerChoicePointFrame(LPInterpreter interpreter) {
        init(interpreter);
    }
    
    /**
     * Initialize the choice point state.
     * @param interpreter the parent interpreter whose state is to be preserved here, its arg stack
     * defines the parameters for the target goal
     */
    @Override
    public void init(LPInterpreter interpreter) {
        super.init(interpreter);
        context = interpreter.getContext();
        generator = interpreter.getEngine().generatorFor(goal);
        generator.addConsumer(this);
        resultIndex = 0;
    }
    
    /**
     * Preserve the state of an interpreter into this frame.
     */
    public void preserveState( List<Node> trail ) {
        // Save the trail state
        int trailLen = trail.size();
        if (trailLen > trailLength) {
            trailValues = new Node[trailLen];
            trailVars = new Node_RuleVariable[trailLen];
        }
        trailLength = trailLen;
        for (int i = 0; i < trailLen; i++) {
            Node_RuleVariable var = (Node_RuleVariable) trail.get(i);
            trailVars[i] = var;
            trailValues[i] = var.getRawBoundValue();
        }
        // Save the permanent variables
        Node[] currentPVars = envFrame.pVars;
        if (currentPVars != null) {
            if (pVars == null || pVars.length < currentPVars.length) {
                pVars = new Node[currentPVars.length];
            }
            System.arraycopy(currentPVars, 0, pVars, 0, currentPVars.length);
        }
    }
    
    /**
     * Restore the state of an interpreter from this frame
     */
    public void restoreState(LPInterpreter interp) {
        interp.unwindTrail(0);
        for (int i = 0; i < trailLength; i++) {
            interp.bind(trailVars[i], trailValues[i]);
        }
        if (pVars != null) {
            System.arraycopy(pVars, 0, envFrame.pVars, 0, pVars.length);
        }
    }
    
    /**
     * Find the next result triple and bind the result vars appropriately.
     * @param interpreter the calling interpreter whose trail should be used
     * @return FAIL if there are no more matches and the generator is closed, SUSPEND if
     * there are no more matches but the generator could generate more, SATISFIED if
     * a match has been found.
     */
    public synchronized StateFlag nextMatch(LPInterpreter interpreter) {
        while (resultIndex < generator.results.size()) {
            Triple result = (Triple) generator.results.get(resultIndex++);
            // Check if we have finished with this generator
            if (resultIndex >= generator.results.size() && generator.isComplete()) {
                generator.removeConsumer(this);
            }
            if (bindResult(result, interpreter)) {
                return StateFlag.SATISFIED;
            }            
        }
        if (generator.isComplete()) {
            setFinished();
            generator.removeConsumer(this);
            return StateFlag.FAIL;
        } else {
            return StateFlag.SUSPEND;
        }
    }
    
    /**
     * Return true if this choice point could usefully be restarted.
     */
    @Override
    public boolean isReady() {
        return generator.numResults() > resultIndex;
    }
    
    /**
     * Called by generator when there are more results available.
     */
    public void setReady() {
        context.setReady(this);
    }
    
    @Override
    public void close() {    	
    	super.close();
    	if (generator != null) {
            if (generator.interpreter != null) {
                // If this is a table entry then we have to remove it to avoid leaving prematurely closed generator in the table
                generator.interpreter.getEngine().removeTabledGenerator(generator);
                generator.interpreter.close();
            }
            generator.removeConsumer(this);
    		// .. but NOT 
    		//generator.setComplete();
    		// as it seems to cause other tests to fail
    	}
    }
    
    /**
     * Notify that this consumer choice point has finished consuming all
     * the results of a closed generator.
     */
    public void setFinished() {
        context.notifyFinished(this);
    }
    
    /**
     * Reactivate this choice point to return new results.
     */
    @Override
    public void pump() {
        if (context instanceof Generator) {
            ((Generator)context).pump(this);
        } else {
            // The top level iterator is in charge and will restore and run this choice point itself
        }
    }
    
    /**
     * Return the generator associated with this entry (might be the entry itself)
     */
    @Override
    public Generator getGenerator() {
        return generator;
    }
    
    /**
     * Return the interpeter context which is reading the results of this consumer.
     */
    public LPInterpreterContext getConsumingContext() {
        return context;
    }

}
