/*
 * 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.graph.* ;
import org.apache.jena.reasoner.* ;
import org.apache.jena.reasoner.rulesys.impl.OWLRuleTranslationHook ;
import org.apache.jena.shared.impl.JenaParameters ;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A hybrid forward/backward implementation of the OWL closure rules.
 */
public class OWLFBRuleReasoner extends FBRuleReasoner {
    
    /** The location of the OWL rule definitions on the class path */
    protected static final String RULE_FILE = "etc/owl-fb.rules";
    
    /** The parsed rules */
    protected static List<Rule> ruleSet;
    
    /** The precomputed axiom closure and compiled rule set */
    protected static FBRuleInfGraph staticPreload; 
    
    protected static Logger logger = LoggerFactory.getLogger(OWLFBRuleReasoner.class);
    
    /**
     * Constructor
     */
    public OWLFBRuleReasoner(ReasonerFactory factory) {
        super(loadRules(), factory);
        
    }
    
    /**
     * Internal constructor, used to generated a partial binding of a schema
     * to a rule reasoner instance.
     */
    private OWLFBRuleReasoner(OWLFBRuleReasoner parent, InfGraph schemaGraph) {
        super(parent.rules, schemaGraph, parent.factory);
    }
    
    /**
     * Return the rule set, loading it in if necessary
     */
    public static List<Rule> loadRules() {
        if (ruleSet == null) ruleSet = loadRules( RULE_FILE );
        return ruleSet;
    }
    
    
    /**
     * Precompute the implications of a schema graph. The statements in the graph
     * will be combined with the data when the final InfGraph is created.
     */
    @Override
    public Reasoner bindSchema(Graph tbox) throws ReasonerException {
        checkArgGraph(tbox);
        if (schemaGraph != null) {
            throw new ReasonerException("Can only bind one schema at a time to an OWLRuleReasoner");
        }
        FBRuleInfGraph graph = new FBRuleInfGraph(this, rules, getPreload(), tbox);
        graph.addPreprocessingHook(new OWLRuleTranslationHook());
        graph.prepare();
        return new OWLFBRuleReasoner(this, graph);
    }
        
    /**
     * Attach the reasoner to a set of RDF data to process.
     * The reasoner may already have been bound to specific rules or ontology
     * axioms (encoded in RDF) through earlier bindRuleset calls.
     * 
     * @param data the RDF data to be processed, some reasoners may restrict
     * the range of RDF which is legal here (e.g. syntactic restrictions in OWL).
     * @return an inference graph through which the data+reasoner can be queried.
     * @throws ReasonerException if the data is ill-formed according to the
     * constraints imposed by this reasoner.
     */
    @Override
    public InfGraph bind(Graph data) throws ReasonerException {
        checkArgGraph(data);
        FBRuleInfGraph graph =  null;
        InfGraph schemaArg = schemaGraph == null ? getPreload() : (FBRuleInfGraph)schemaGraph; 
        List<Rule> baseRules = ((FBRuleInfGraph)schemaArg).getRules();
        graph = new FBRuleInfGraph(this, baseRules, schemaArg);
        graph.addPreprocessingHook(new OWLRuleTranslationHook());
        graph.setDerivationLogging(recordDerivations);
        graph.setTraceOn(traceOn);
        graph.rebind(data);
        graph.setDatatypeRangeValidation(true);
                
        return graph;
    }
    
    /**
     * Get the single static precomputed rule closure.
     */
    @Override
    public synchronized InfGraph getPreload() {
        // Method synchronized - synchronize with other FBRulereasoer sync methods
        // synchronized block - sync on static 
        synchronized (OWLFBRuleReasoner.class) {
            if (staticPreload == null) {
                boolean prior = JenaParameters.enableFilteringOfHiddenInfNodes;
                try {
                    JenaParameters.enableFilteringOfHiddenInfNodes = true;
                    staticPreload = new FBRuleInfGraph(this, rules, null);
                    staticPreload.prepare();
                } finally {
                    JenaParameters.enableFilteringOfHiddenInfNodes = prior;
                }
            }
            return staticPreload;
        }
    }
    
    /**
     * Check an argument graph to make sure it is not an OWL rule graph
     * already and if so log a warning message.
     */
    private void checkArgGraph(Graph g) {
        if (JenaParameters.enableOWLRuleOverOWLRuleWarnings) {
            if (g instanceof InfGraph) {
                if (((InfGraph)g).getReasoner() instanceof OWLFBRuleReasoner) {
                    logger.warn("Creating OWL rule reasoner working over another OWL rule reasoner");
                }
            }
        }
    }
}
