/*
 * 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;

import java.util.* ;

import org.apache.jena.datatypes.RDFDatatype ;
import org.apache.jena.datatypes.TypeMapper ;
import org.apache.jena.graph.* ;
import org.apache.jena.graph.impl.LiteralLabel ;
import org.apache.jena.rdf.model.Model ;
import org.apache.jena.rdf.model.ModelFactory ;
import org.apache.jena.rdf.model.RDFNode ;
import org.apache.jena.reasoner.* ;
import org.apache.jena.reasoner.rulesys.impl.* ;
import org.apache.jena.reasoner.transitiveReasoner.TransitiveEngine ;
import org.apache.jena.reasoner.transitiveReasoner.TransitiveGraphCache ;
import org.apache.jena.reasoner.transitiveReasoner.TransitiveReasoner ;
import org.apache.jena.shared.impl.JenaParameters ;
import org.apache.jena.util.OneToManyMap ;
import org.apache.jena.util.PrintUtil ;
import org.apache.jena.util.iterator.ExtendedIterator ;
import org.apache.jena.util.iterator.UniqueFilter ;
import org.apache.jena.vocabulary.RDFS ;
import org.apache.jena.vocabulary.ReasonerVocabulary ;
import org.slf4j.Logger ;
import org.slf4j.LoggerFactory ;

/**
 * An inference graph that uses a mixture of forward and backward
 * chaining rules. The forward rules can create direct deductions from
 * the source data and schema and can also create backward rules. A
 * query is answered by consulting the union of the raw data, the forward
 * derived results and any relevant backward rules (whose answers are tabled
 * for future reference).
 */
public class FBRuleInfGraph  extends BasicForwardRuleInfGraph implements BackwardRuleInfGraphI {
    
    /** Single context for the reasoner, used when passing information to builtins */
    protected BBRuleContext context;
     
    /** A finder that searches across the data, schema, axioms and forward deductions*/
    protected Finder dataFind;
    
    /** The core backward rule engine which includes all the memoized results */
    protected LPBRuleEngine bEngine;
    
    /** The original rule set as supplied */
    protected List<Rule> rawRules;
    
    /** The rule list after possible extension by preprocessing hooks */
    protected List<Rule> rules;
    
    /** Static switch from Basic to RETE implementation of the forward component */
    public static boolean useRETE = true;

    /** Flag, if true then subClass and subProperty lattices will be optimized using TGCs */
    protected boolean useTGCCaching = false;
    
    /** Optional precomputed cache of the subClass/subproperty lattices */
    protected TransitiveEngine transitiveEngine;
    
    /** Optional list of preprocessing hooks  to be run in sequence during preparation time */
    protected List<RulePreprocessHook> preprocessorHooks;
    
    /** Cache of temporary property values inferred through getTemp calls */
    protected TempNodeCache tempNodecache;
    
    /** Table of temp nodes which should be hidden from output listings */
    protected Set<Node> hiddenNodes;

    /** Optional map of property node to datatype ranges */
    protected HashMap<Node, List<RDFDatatype>> dtRange = null;
    
    /** Flag to request datatype range validation be included in the validation step */
    protected boolean requestDatatypeRangeValidation = false;
    
    static Logger logger = LoggerFactory.getLogger(FBRuleInfGraph.class);

//  =======================================================================
//  Constructors

    /**
     * Constructor.
     * @param reasoner the reasoner which created this inf graph instance
     * @param schema the (optional) schema graph to be included
     */
    public FBRuleInfGraph(Reasoner reasoner, Graph schema) {
        super(reasoner, schema);
        constructorInit(schema);    
    }

    /**
     * Constructor.
     * @param reasoner the reasoner which created this inf graph instance
     * @param rules the rules to process
     * @param schema the (optional) schema graph to be included
     */
    public FBRuleInfGraph(Reasoner reasoner, List<Rule> rules, Graph schema) {
        super( reasoner, rules, schema );
        this.rawRules = rules;
        constructorInit( schema ); 
    }
    
    /**
     * Constructor.
     * @param reasoner the reasoner which created this inf graph instance
     * @param rules the rules to process
     * @param schema the (optional) schema graph to be included
     * @param data the data graph to be processed
     */
    public FBRuleInfGraph( Reasoner reasoner, List<Rule> rules, Graph schema, Graph data ) {
        super(reasoner, rules, schema, data);
        this.rawRules = rules;  
        constructorInit(schema);    
    }


    /**
     * Common pieces of initialization code which apply in all constructor cases.
     */
    private void constructorInit(Graph schema) {
        initLP(schema);  
        tempNodecache = new TempNodeCache(this);
        if (JenaParameters.enableFilteringOfHiddenInfNodes) {
            hiddenNodes = new HashSet<>();
            if (schema != null && schema instanceof FBRuleInfGraph) {
                hiddenNodes.addAll(((FBRuleInfGraph)schema).hiddenNodes);
            }
        }
    }
    
    /**
     * Instantiate the forward rule engine to use.
     * Subclasses can override this to switch to, say, a RETE implementation.
     * @param rules the rule set or null if there are not rules bound in yet.
     */
    @Override
    protected void instantiateRuleEngine(List<Rule> rules) {
        engine = FRuleEngineIFactory.getInstance().createFRuleEngineI(this, rules, useRETE);
    }

    /**
     * Initialize the LP engine, based on an optional schema graph.
     */    
    private void initLP(Graph schema) {
        if (schema != null && schema instanceof FBRuleInfGraph) {
            LPRuleStore newStore = new LPRuleStore();
            newStore.addAll(((FBRuleInfGraph)schema).bEngine.getRuleStore());
            bEngine = new LPBRuleEngine(this, newStore);
        } else {
            bEngine = new LPBRuleEngine(this);
        }
    }
    
    /**
     * Instantiate the optional caches for the subclass/suproperty lattices.
     * Unless this call is made the TGC caching will not be used.
     */
    public void setUseTGCCache() {
        useTGCCaching = true;
        resetTGCCache();
    }
    
    /**
     * Rest the transitive graph caches
     */
    private void resetTGCCache() {
        if (schemaGraph != null) {
            transitiveEngine = new TransitiveEngine(((FBRuleInfGraph)schemaGraph).transitiveEngine);
        } else {
            transitiveEngine = new TransitiveEngine(
                new TransitiveGraphCache(ReasonerVocabulary.directSubClassOf.asNode(), RDFS.subClassOf.asNode()),
                new TransitiveGraphCache(ReasonerVocabulary.directSubPropertyOf.asNode(), RDFS.subPropertyOf.asNode()));
        }
    }
    
//  =======================================================================
//   Interface between infGraph and the goal processing machinery

    
    /**
     * Search the combination of data and deductions graphs for the given triple pattern.
     * This may different from the normal find operation in the base of hybrid reasoners
     * where we are side-stepping the backward deduction step.
     */
    @Override
    public ExtendedIterator<Triple> findDataMatches(Node subject, Node predicate, Node object) {
        return dataFind.find(new TriplePattern(subject, predicate, object));
    }
    
    /**
     * Search the combination of data and deductions graphs for the given triple pattern.
     * This may different from the normal find operation in the base of hybrid reasoners
     * where we are side-stepping the backward deduction step.
     */
    @Override
    public ExtendedIterator<Triple> findDataMatches(TriplePattern pattern) {
        return dataFind.find(pattern);
    }
            
    /**
     * Process a call to a builtin predicate
     * @param clause the Functor representing the call
     * @param env the BindingEnvironment for this call
     * @param rule the rule which is invoking this call
     * @return true if the predicate succeeds
     */
    @Override
    public boolean processBuiltin(ClauseEntry clause, Rule rule, BindingEnvironment env) {
        throw new ReasonerException("Internal error in FBLP rule engine, incorrect invocation of builtin in rule " + rule);
        // TODO: Remove 
//        if (clause instanceof Functor) {
//            context.setEnv(env);
//            context.setRule(rule);
//            return((Functor)clause).evalAsBodyClause(context);
//        } else {
//            throw new ReasonerException("Illegal builtin predicate: " + clause + " in rule " + rule);
//        }
    }
    
    /**
     * Adds a new Backward rule as a result of a forward rule process. Only some
     * infgraphs support this.
     */
    @Override
    public void addBRule(Rule brule) {
        if (logger.isDebugEnabled()) {
            logger.debug("Adding rule " + brule);
        }
        bEngine.addRule(brule);
        bEngine.reset();
    }
       
    /**
     * Deletes a new Backward rule as a rules of a forward rule process. Only some
     * infgraphs support this.
     */
    @Override
    public void deleteBRule(Rule brule) {
        if (logger.isDebugEnabled()) {
            logger.debug("Deleting rule " + brule);
        }
        bEngine.deleteRule(brule);
        bEngine.reset();
    }
    
    /**
     * Adds a set of new Backward rules
     */
    public void addBRules(List<Rule> rules) {
        for ( Rule rule : rules )
        {
            //            logger.debug("Adding rule " + rule);
            bEngine.addRule( rule );
        }
        bEngine.reset();
    }
    
    /**
     * Return an ordered list of all registered backward rules. Includes those
     * generated by forward productions.
     */
    public List<Rule> getBRules() {
        return bEngine.getAllRules();
    }
    
    /**
     * Return the originally supplied set of rules, may be a mix of forward
     * and backward rules.
     */
    public List<Rule> getRules() {
        return rules;
    }
      
    /**
     * Set a predicate to be tabled/memoized by the LP engine. 
     */
    public void setTabled(Node predicate) {
        bEngine.tablePredicate(predicate);
        if (traceOn) {
            logger.info("LP TABLE " + predicate);
        }
    }
    
    /**
     * Return a compiled representation of all the registered
     * forward rules.
     */
    private Object getForwardRuleStore() {
        return engine.getRuleStore();
    }
    
    /**
     * Add a new deduction to the deductions graph.
     */
    @Override
    public void addDeduction(Triple t) {
        getCurrentDeductionsGraph().add(t);
        if (useTGCCaching) {
            transitiveEngine.add(t);
        }
    }

    /**
     * Retrieve or create a bNode representing an inferred property value.
     * @param instance the base instance node to which the property applies
     * @param prop the property node whose value is being inferred
     * @param pclass the (optional, can be null) class for the inferred value.
     * @return the bNode representing the property value 
     */
    @Override
    public Node getTemp(Node instance, Node prop, Node pclass) {
        return tempNodecache.getTemp(instance, prop, pclass);
    }
   
//  =======================================================================
//  Core inf graph methods
    
    /**
     * Add a new rule to the rule set. This should only be used by implementations
     * of RuleProprocessHook (which are called during rule system preparation phase).
     * If called at other times the rule won't be correctly transferred into the
     * underlying engines.
     */
    public void addRuleDuringPrepare(Rule rule) {
        if (rules == rawRules) {
            // Ensure the original is preserved in case we need to do a restart
            rules = new ArrayList<>( rawRules );
//            if (rawRules instanceof ArrayList) {
//                rules = (ArrayList<Rule>) ((ArrayList<Rule>)rawRules).clone();
//            } else {
//                rules = new ArrayList<Rule>(rawRules);
//            }
            // Rebuild the forward engine to use the cloned rules
            instantiateRuleEngine(rules);
        }
        rules.add(rule);
    }
    
    /**
     * Add a new preprocessing hook defining an operation that
     * should be run when the preparation phase is underway.
     */
    public void addPreprocessingHook(RulePreprocessHook hook) {
        if (preprocessorHooks == null) {
            preprocessorHooks = new ArrayList<>();
        }
        preprocessorHooks.add(hook);
    }
    
    /**
     * Perform any initial processing and caching. This call is optional. Most
     * engines either have negligable set up work or will perform an implicit
     * "prepare" if necessary. The call is provided for those occasions where
     * substantial preparation work is possible (e.g. running a forward chaining
     * rule system) and where an application might wish greater control over when
     * this preparation is done.
     */
    @Override
    public synchronized void prepare() {
        if (this.isPrepared()) return;
        
        this.setPreparedState(true);
                    
        // Restore the original pre-hookProcess rules
        rules = rawRules;
        
        // Is there any data to bind in yet?
        Graph data = null;
        if (fdata != null) data = fdata.getGraph();
        
        // initilize the deductions graph
        if (fdeductions != null) {
            Graph oldDeductions = (fdeductions).getGraph();
            oldDeductions.clear();
        } else {
            fdeductions = new FGraph( createDeductionsGraph() );
        }
        dataFind = (data == null) ? fdeductions :  FinderUtil.cascade(fdeductions, fdata);
        Finder dataSource = fdata;
        
        // Initialize the optional TGC caches
        if (useTGCCaching) {
            resetTGCCache();
            if (schemaGraph != null) {
                // Check if we can just reuse the copy of the raw 
                if (
                    (transitiveEngine.checkOccurance(TransitiveReasoner.subPropertyOf, data) ||
                     transitiveEngine.checkOccurance(TransitiveReasoner.subClassOf, data) ||
                     transitiveEngine.checkOccurance(RDFS.domain.asNode(), data) ||
                     transitiveEngine.checkOccurance(RDFS.range.asNode(), data) )) {
            
                    // The data graph contains some ontology knowledge so split the caches
                    // now and rebuild them using merged data
                    transitiveEngine.insert(((FBRuleInfGraph)schemaGraph).fdata, fdata);
                }     
            } else {
                if (data != null) {
                    transitiveEngine.insert(null, fdata);
                }
            }
            // Insert any axiomatic statements into the caches
            for ( Rule r : rules )
            {
                if ( r.bodyLength() == 0 )
                {
                    // An axiom
                    for ( int j = 0; j < r.headLength(); j++ )
                    {
                        ClauseEntry head = r.getHeadElement( j );
                        if ( head instanceof TriplePattern )
                        {
                            TriplePattern h = (TriplePattern) head;
                            transitiveEngine.add( h.asTriple() );
                        }
                    }
                }
            }

            transitiveEngine.setCaching(true, true);
//                dataFind = FinderUtil.cascade(subClassCache, subPropertyCache, dataFind);
            dataFind = FinderUtil.cascade(dataFind, transitiveEngine.getSubClassCache(), transitiveEngine.getSubPropertyCache());
            
            // Without the next statement then the transitive closures are not seen by the forward rules
            dataSource = FinderUtil.cascade(dataSource, transitiveEngine.getSubClassCache(), transitiveEngine.getSubPropertyCache());
        }
        
        // Make sure there are no Brules left over from pior runs
        bEngine.deleteAllRules();

        // Call any optional preprocessing hook
        if (preprocessorHooks != null && preprocessorHooks.size() > 0) {
            Graph inserts = GraphMemFactory.createGraphMem();
            for ( RulePreprocessHook hook : preprocessorHooks )
            {
                hook.run( this, dataFind, inserts );
            }
            if (inserts.size() > 0) {
                FGraph finserts = new FGraph(inserts);
                dataSource = FinderUtil.cascade(fdata, finserts);
                dataFind = FinderUtil.cascade(dataFind, finserts);
            }
        }
        
        boolean rulesLoaded = false;
        if (schemaGraph != null) {
            Graph rawPreload = ((InfGraph)schemaGraph).getRawGraph();
            if (rawPreload != null) {
                dataFind = FinderUtil.cascade(dataFind, new FGraph(rawPreload));
            }
            rulesLoaded = preloadDeductions(schemaGraph);
        }
        if (rulesLoaded) {
            engine.fastInit(dataSource);
        } else {
            // No preload so do the rule separation
            addBRules(extractPureBackwardRules(rules));
            engine.init(true, dataSource);
        }
        // Prepare the context for builtins run in backwards engine
        context = new BBRuleContext(this);
    }
    
    /**
     * Cause the inference graph to reconsult the underlying graph to take
     * into account changes. Normally changes are made through the InfGraph's add and
     * remove calls are will be handled appropriately. However, in some cases changes
     * are made "behind the InfGraph's back" and this forces a full reconsult of
     * the changed data. 
     */
    @Override
    public void rebind() {
        version++;
        if (bEngine != null) bEngine.reset();
        this.setPreparedState(false);
    }
    
    /**
     * Cause the inference graph to reconsult both the underlying graph and
     * the reasoner ruleset, permits the forward rule set to be dynamically changed.
     * Causes the entire rule engine to be rebuilt from the current ruleset and 
     * reinitialized against the current data. Not needed for normal cases.
     */
    public void rebindAll() {
        rawRules = ((FBRuleReasoner)reasoner).getRules();
        instantiateRuleEngine( rawRules );
        rebind();
    }
    
    /**
     * Set the state of the trace flag. If set to true then rule firings
     * are logged out to the Log at "INFO" level.
     */
    @Override
    public void setTraceOn(boolean state) {
        super.setTraceOn(state);
        bEngine.setTraceOn(state);
    }

    /**
     * Set to true to enable derivation caching
     */
    @Override
    public void setDerivationLogging(boolean recordDerivations) {
        this.recordDerivations = recordDerivations;
        engine.setDerivationLogging(recordDerivations);
        bEngine.setDerivationLogging(recordDerivations);
        if (recordDerivations) {
            derivations = new OneToManyMap<>();
        } else {
            derivations = null;
        }
    }
    
    /**
     * Return the number of rules fired since this rule engine instance
     * was created and initialized. The current implementation only counts
     * forward rules and does not track dynamic backward rules needed for
     * specific queries.
     */
    @Override
    public long getNRulesFired() {
        return engine.getNRulesFired();
    }
    
    /**
     * Extended find interface used in situations where the implementator
     * may or may not be able to answer the complete query. It will
     * attempt to answer the pattern but if its answers are not known
     * to be complete then it will also pass the request on to the nested
     * Finder to append more results.
     * @param pattern a TriplePattern to be matched against the data
     * @param continuation either a Finder or a normal Graph which
     * will be asked for additional match results if the implementor
     * may not have completely satisfied the query.
     */
    @Override
    public ExtendedIterator<Triple> findWithContinuation(TriplePattern pattern, Finder continuation) {
        checkOpen();
        this.requirePrepared();
        ExtendedIterator<Triple> result =bEngine.find(pattern).filterKeep( new UniqueFilter<Triple>());
        if (continuation != null) {
            result = result.andThen(continuation.find(pattern));
        }
        if (filterFunctors) {
//            return result.filterDrop(Functor.acceptFilter);
            return result.filterDrop( t -> FBRuleInfGraph.this.accept( t ) );
        } else {
            return result;
        }
    }
    
    /**
     * Internal variant of find which omits the filters which block illegal RDF data.
     * @param pattern a TriplePattern to be matched against the data
     */
    public ExtendedIterator<Triple> findFull(TriplePattern pattern) {
        checkOpen();
        this.requirePrepared();
       return bEngine.find(pattern).filterKeep( new UniqueFilter<Triple>());
    }
   
    /** 
     * Returns an iterator over Triples.
     * This implementation assumes that the underlying findWithContinuation 
     * will have also consulted the raw data.
     */
    @Override
    public ExtendedIterator<Triple> graphBaseFind(Node subject, Node property, Node object) {
        return findWithContinuation(new TriplePattern(subject, property, object), null);
    }

    /**
     * Basic pattern lookup interface.
     * This implementation assumes that the underlying findWithContinuation 
     * will have also consulted the raw data.
     * @param pattern a TriplePattern to be matched against the data
     * @return a ExtendedIterator over all Triples in the data set
     *  that match the pattern
     */
    @Override
    public ExtendedIterator<Triple> find(TriplePattern pattern) {
        return findWithContinuation(pattern, null);
    }

    /**
     * Flush out all cached results. Future queries have to start from scratch.
     */
    @Override
    public synchronized void reset() {
        version++;
        bEngine.reset();
        this.setPreparedState(false);
    }

    /**
     * Add one triple to the data graph, run any rules triggered by
     * the new data item, recursively adding any generated triples.
     */
    @Override
    public synchronized void performAdd(Triple t) {
        version++;
        fdata.getGraph().add(t);
        if (useTGCCaching) {
            if (transitiveEngine.add(t)) this.setPreparedState(false);
        }
        if (this.isPrepared()) {
            boolean needReset = false;
            if (preprocessorHooks != null && preprocessorHooks.size() > 0) {
                if (preprocessorHooks.size() > 1) {
                    for ( RulePreprocessHook preprocessorHook : preprocessorHooks )
                    {
                        if ( preprocessorHook.needsRerun( this, t ) )
                        {
                            needReset = true;
                            break;
                        }
                    }
                } else {
                    needReset = preprocessorHooks.get(0).needsRerun(this, t);
                }
            }
            if (needReset) {
                this.setPreparedState(false);
            } else {
                engine.add(t);
            }
        }
        bEngine.reset();
    }

    /** 
     * Removes the triple t (if possible) from the set belonging to this graph. 
     */   
    @Override
    public void performDelete(Triple t) {
        version++;
        //boolean removeIsFromBase = fdata.getGraph().contains(t);
        fdata.getGraph().delete(t);
        if (useTGCCaching) {
            if (transitiveEngine.delete(t)) {
                if (this.isPrepared()) {
                    bEngine.deleteAllRules();
                }
                this.setPreparedState(false);
            }
        } 
        // Full incremental remove processing requires reference counting
        // of all deductions. It's not clear the cost of maintaining the
        // reference counts is worth it so the current implementation
        // forces a recompute if any external deletes are performed.
        if (this.isPrepared()) {
            bEngine.deleteAllRules();
            this.setPreparedState(false);
            // Re-enable the code below when/if ref counting is added and remove above
            // if (removeIsFromBase) engine.delete(t);
        }
        bEngine.reset();
    }
    
    /**
     * Return a new inference graph which is a clone of the current graph
     * together with an additional set of data premises. Attempts to the replace
     * the default brute force implementation by one that can reuse some of the
     * existing deductions.
     */
    // This implementatin was incomplete. By commenting it out we revert to
    // the global brute force solution of cloning the full graph
//    public InfGraph cloneWithPremises(Graph premises) {
//        prepare();
//        FBRuleInfGraph graph = new FBRuleInfGraph(getReasoner(), rawRules, this);
//        if (useTGCCaching) graph.setUseTGCCache();
//        graph.setDerivationLogging(recordDerivations);
//        graph.setTraceOn(traceOn);
//        // Implementation note:  whilst current tests pass its not clear that 
//        // the nested passing of FBRuleInfGraph's will correctly handle all
//        // cases of indirectly bound schema data. If we do uncover a problem here
//        // then either include the raw schema in a Union with the premises or
//        // revert of a more brute force version. 
//        graph.rebind(premises);
//        return graph;
//    }
    
    /** 
     * Free all resources, any further use of this Graph is an error.
     */
    @Override
    public void close() {
        if (!closed) {
            bEngine.halt();        
            bEngine = null;
            transitiveEngine = null;
            super.close();
        }
    }
    
//  =======================================================================
//  Generalized validation machinery. Assumes rule set has special validation
//  rules that can be turned on.
   
    /**
     * Test the consistency of the bound data. This normally tests
     * the validity of the bound instance data against the bound
     * schema data. 
     * @return a ValidityReport structure
     */
    @Override
    public ValidityReport validate() {
        checkOpen();
        StandardValidityReport report = new StandardValidityReport();
        // Switch on validation
        Triple validateOn = Triple.create(NodeFactory.createBlankNode(), 
                                ReasonerVocabulary.RB_VALIDATION.asNode(),
                                Functor.makeFunctorNode("on", new Node[] {}));
        // We sneak this switch directly into the engine to avoid contaminating the
        // real data - this is only possible only the forward engine has been prepared
//      add(validateOn);
        this.requirePrepared();
        engine.add(validateOn); 
        // Look for all reports
        TriplePattern pattern = new TriplePattern(null, ReasonerVocabulary.RB_VALIDATION_REPORT.asNode(), null);
        final Model forConversion = ModelFactory.createDefaultModel();
        for (Iterator<Triple> i = findFull(pattern); i.hasNext(); ) {
            Triple t = i.next();
            Node rNode = t.getObject();
            if (rNode.isLiteral()) {
                Object rVal = rNode.getLiteralValue();
                if (rVal instanceof Functor) {
                    Functor rFunc = (Functor)rVal;
                    StringBuffer description = new StringBuffer();
                    String nature = rFunc.getName();
                    String type = rFunc.getArgs()[0].toString();
                    String text = rFunc.getArgs()[1].toString();
                    description.append( text + "\n");
                    description.append( "Culprit = " + PrintUtil.print(t.getSubject()) +"\n");
                    for (int j = 2; j < rFunc.getArgLength(); j++) {
                        description.append( "Implicated node: " + PrintUtil.print(rFunc.getArgs()[j]) + "\n");
                    }
                    RDFNode culprit = forConversion.asRDFNode( t.getSubject() );
                    report.add(nature.equalsIgnoreCase("error"), type, description.toString(), culprit);
                }
            }
        }
        
        if (requestDatatypeRangeValidation) {
            performDatatypeRangeValidation( report );
        }
        return report;
    }
    
    /**
     * Switch on/off datatype range validation
     */
    public void setDatatypeRangeValidation(boolean on) {
        requestDatatypeRangeValidation = on;
    }
    
    /**
     * Run a datatype range check on all literal values of all properties with a range declaration.
     * @param report
     */
    protected void performDatatypeRangeValidation(StandardValidityReport report) {
        HashMap<Node, List<RDFDatatype>> dtRange = getDTRange();
        for ( Node prop : dtRange.keySet() )
        {
            for ( Iterator<Triple> i = find( null, prop, null ); i.hasNext(); )
            {
                Triple triple = i.next();
                report.add( checkLiteral( prop, triple ) );
            }
        }
    }

    /**
     * Check a given literal value for a property against the set of
     * known range constraints for it.
     * @param prop the property node whose range is under scrutiny
     * @param triple the statement whose object value is to be checked.
     * @return null if the range is legal, otherwise a ValidityReport.Report
     * which describes the problem.
     */
    public ValidityReport.Report checkLiteral(Node prop, Triple triple) {
        Node value = triple.getObject();
        List<RDFDatatype> range = getDTRange().get(prop);
        if (range != null) {
            if (value.isBlank()) return null;
            if (!value.isLiteral()) {
                return new ValidityReport.Report(true, "dtRange", 
                    "Property " + prop + " has a typed range but was given a non literal value " + value);
            }
            LiteralLabel ll = value.getLiteral();
            for ( RDFDatatype dt : range )
            {
                if ( !dt.isValidLiteral( ll ) )
                {
                    return new ValidityReport.Report( true, "dtRange", "Property " + prop + " has a typed range " + dt +
                        "that is not compatible with " + value, triple );
                }
            }
        }
        return null;
    }

    /**
     * Return a map from property nodes to a list of RDFDatatype objects
     * which have been declared as the range of that property.
     */
    protected HashMap<Node, List<RDFDatatype>> getDTRange() {
        if (dtRange == null) {
            dtRange = new HashMap<>();
            for (Iterator<Triple> i = find(null, RDFS.range.asNode(), null); i.hasNext(); ) {
                Triple triple = i.next();
                Node prop = triple.getSubject();
                Node rangeValue = triple.getObject();
                if (rangeValue.isURI()) {
                    RDFDatatype dt = TypeMapper.getInstance().getTypeByName(rangeValue.getURI());
                    if (dt != null) {
                        List<RDFDatatype> range = dtRange.get(prop);
                        if (range == null) {
                            range = new ArrayList<>();
                            dtRange.put(prop, range);
                        }
                        range.add(dt);
                    }
                }
            }
        }
        return dtRange;
    }

//  =======================================================================
//  Helper methods

    /**
     * Scan the initial rule set and pick out all the backward-only rules with non-null bodies,
     * and transfer these rules to the backward engine. 
     */
    private static List<Rule> extractPureBackwardRules(List<Rule> rules) {
        List<Rule> bRules = new ArrayList<>();
        for ( Rule r : rules )
        {
            if ( r.isBackward() && r.bodyLength() > 0 )
            {
                bRules.add( r );
            }
        }
        return bRules;
    }

    /**
     * Adds a set of precomputed triples to the deductions store. These do not, themselves,
     * fire any rules but provide additional axioms that might enable future rule
     * firing when real data is added. Used to implement bindSchema processing
     * in the parent Reasoner.
     * @return true if the preload was able to load rules as well
     */
    @Override
    protected boolean preloadDeductions(Graph preloadIn) {
        Graph d = fdeductions.getGraph();
        FBRuleInfGraph preload = (FBRuleInfGraph)preloadIn;
        // If the rule set is the same we can reuse those as well
        if (preload.rules == rules) {
            // Load raw deductions
            for (Iterator<Triple> i = preload.getDeductionsGraph().find(null, null, null); i.hasNext(); ) {
                d.add( i.next() );
            }
            // Load backward rules
            addBRules(preload.getBRules());
            // Load forward rules
            engine.setRuleStore(preload.getForwardRuleStore());
            // Add access to raw data
            return true;
        } else {
            return false;
        }
    }
   
    /**
     * Called to flag that a node should be hidden from external queries.
     */
    public void hideNode(Node n) {
        if (! JenaParameters.enableFilteringOfHiddenInfNodes) return;
        if (hiddenNodes == null) {
            hiddenNodes = new HashSet<>();
        }
        synchronized (hiddenNodes) {
            hiddenNodes.add(n);
        }
    }
    
//  =======================================================================
//  Support for LP engine profiling
    
    /**
     * Reset the LP engine profile.
     * @param enable it true then profiling will continue with a new empty profile table,
     * if false profiling will stop all current data lost.
     */
    public void resetLPProfile(boolean enable) {
        bEngine.resetProfile(enable);
    }
    
    /**
     * Print a profile of LP rules used since the last reset.
     */
    public void printLPProfile() {
        bEngine.printProfile();
    }
    
//  =======================================================================
//  Implement Filter signature
 
    /**
     * Post-filter query results to hide unwanted
     * triples from the glare of publicity. Unwanted triples
     * are triples with Functor literals and triples with hidden nodes
     * as subject or object.
     */
    public boolean accept(Object tin) {
        Triple t = (Triple)tin;
        
        if ((t).getSubject().isLiteral()) return true;
        
        if (JenaParameters.enableFilteringOfHiddenInfNodes && hiddenNodes != null) {
            if (hiddenNodes.contains(t.getSubject()) || hiddenNodes.contains(t.getObject()) || hiddenNodes.contains(t.getPredicate())) {
                return true;
            }
        }
        
        if (filterFunctors) {
            if (Functor.isFunctor(t.getObject())) {
                return true;
            }
        }
        
        return false;

     }
     
//  =======================================================================
//   Inner classes

    /**
     * Structure used to wrap up pre-processed/compiled rule sets.
     */
    public static class RuleStore {
        
        /** The raw rules */
        protected List<Rule> rawRules;
        
        /** The indexed store used by the forward chainer */
        protected Object fRuleStore;
        
        /** The separated backward rules */
        protected List<Rule> bRules;
        
        /** 
         * Constructor.
         */
        public RuleStore(List<Rule> rawRules, Object fRuleStore, List<Rule> bRules) {
            this.rawRules = rawRules;
            this.fRuleStore = fRuleStore;
            this.bRules = bRules;
        }
        
    }
   
    
}
