/*
 * 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.* ;
import org.apache.jena.reasoner.rulesys.* ;
import org.apache.jena.util.PrintUtil ;
import org.apache.jena.util.iterator.ClosableIterator ;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * An implementation of the generic RuleContext interface used by
 * the basic forward (BF) rule engine. This provides additional
 * methods specific to the functioning of that engine.
 */
public class BFRuleContext implements RuleContext {
    /** The binding environment which represents the state of the current rule execution. */
    protected BindingStack env;
    
    /** The rule current being executed. */
    protected Rule rule;
    
    /** The enclosing inference graph. */
    protected ForwardRuleInfGraphI graph;
    
    /** A stack of triples which have been added to the graph but haven't yet been processed. */
    protected List<Triple> stack;
    
    /** A temporary list of Triples which will be added to the stack and triples at the end of a rule scan */
    protected List<Triple> pending;

    /** A temporary list of Triples which will be removed from the graph at the end of a rule scan */
    protected List<Triple> deletesPending = new ArrayList<>();

    /** A searchable index into the pending triples */
    protected Graph pendingCache;
    
    protected static Logger logger = LoggerFactory.getLogger(BFRuleContext.class);
    
    /**
     * Constructor.
     * @param graph the inference graph which owns this context.
     */
    public BFRuleContext(ForwardRuleInfGraphI graph) {
        this.graph = graph;
        env = new BindingStack();
        stack = new ArrayList<>();
        pending = new ArrayList<>();
        pendingCache = GraphMemFactory.createGraphMem();
    }
    
    /**
     * Returns the current variable binding environment for the current rule.
     * @return BindingEnvironment
     */
    @Override
    public BindingEnvironment getEnv() {
        return env;
    }
    
    /**
     * Variant of the generic getEnv interface specific to the basic
     * forward rule system.
     * Returns the current variable binding environment for the current rule.
     * @return BindingStack
     */
    public BindingStack getEnvStack() {
        return env;
    }

    /**
     * Returns the graph.
     * @return InfGraph
     */
    @Override
    public InfGraph getGraph() {
        return graph;
    }

    /**
     * Returns the rule.
     * @return Rule
     */
    @Override
    public Rule getRule() {
        return rule;
    }

    /**
     * Sets the rule.
     * @param rule The rule to set
     */
    @Override
    public void setRule(Rule rule) {
        this.rule = rule;
    }

    /**
     * Add a triple to the stack of triples to waiting to be processed by the rule engine.
     */
    public void addTriple(Triple t) {
        if (graph.shouldTrace()) {
            if (rule != null) {
                logger.debug("Adding to stack (" + rule.toShortString() + "): " + PrintUtil.print(t));
            } else {
                logger.debug("Adding to stack : " + PrintUtil.print(t));
            }
        }
        stack.add(t);
    }
    
    /**
     * Add a triple to a temporary "pending" store, ready to be added to added to the
     * deductions graph and the processing stack later.
     * <p>This is needed to prevent concurrent modification exceptions which searching
     * the deductions for matches to a given rule.
     */
    @Override
    public void add(Triple t) {
        if (graph.shouldTrace()) {
            if (rule != null) {
                logger.debug("Adding to pending (" + rule.toShortString() + "): " + PrintUtil.print(t));
            } else {
                logger.debug("Adding to pending : " + PrintUtil.print(t));
            }
        }
        pending.add(t);
        //pendingCache.add(t);
    }
            
    /**
     * Take all the pending triples and add them to both the given graph and
     * to the processing stack.
     */
    public void flushPending() {
        for (Iterator<Triple> i = pending.iterator(); i.hasNext(); ) {
            Triple t = i.next();
            stack.add(t);
            graph.addDeduction(t);
            i.remove();
            // pendingCache.delete(t);
        }
        pending.clear();
        // Flush out pending removes as well
        for ( Triple t : deletesPending )
        {
            graph.delete( t );
        }
        deletesPending.clear();
    }
    
    /**
     * Return true if the triple is already in either the graph or the stack.
     * I.e. it has already been deduced.
     */
    @Override
    public boolean contains(Triple t) {
        // Can't use stackCache.contains because that does not do semantic equality
        return contains(t.getSubject(), t.getPredicate(), t.getObject());
    }
    
    /**
     * Return true if the triple pattern is already in either the graph or the stack.
     * I.e. it has already been deduced.
     */
    @Override
    public boolean contains(Node s, Node p, Node o) {
        // Can't use stackCache.contains because that does not do semantic equality
        ClosableIterator<Triple> it = find(s, p, o);
        boolean result = it.hasNext();
        it.close();
        return result;
    }
    
    /**
     * In some formulations the context includes deductions that are not yet
     * visible to the underlying graph but need to be checked for.
     * However, currently this calls the graph find directly.
     */
    @Override
    public ClosableIterator<Triple> find(Node s, Node p, Node o) {
        //return graph.find(s, p, o).andThen(pendingCache.find(s, p, o));
        return graph.findDataMatches(s, p, o);
    }
    
    /**
     * Return the next triple to be added to the graph, removing it from
     * the stack.
     * @return the Triple or null if there are no more
     */
    public Triple getNextTriple() {
        if (stack.size() > 0) {
            Triple t = stack.remove(stack.size() - 1);
            return t;
        } else {
            return null;
        } 
    }
    
    /**
     * Reset the binding environment back to empty.
     * @param newSize the number of variables needed for processing the new rule
     */
    public void resetEnv(int newSize) {
        env.reset(newSize);
    }
    
    /**
     * Assert a new triple in the deduction graph, bypassing any processing machinery.
     */
    @Override
    public void silentAdd(Triple t) {
        ((SilentAddI)graph).silentAdd(t);
    }

    /**
     * Remove a triple from the deduction graph (and the original graph if relevant).
     */
    @Override
    public void remove(Triple t) {
        deletesPending.add(t);
//        graph.delete(t);
    }

}
