/*
 * 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
 *
 *   https://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.
 *
 *   SPDX-License-Identifier: Apache-2.0
 */

package org.apache.jena.reasoner;

import java.util.*;

import org.apache.jena.graph.* ;
import org.apache.jena.rdf.model.* ;
import org.apache.jena.reasoner.rulesys.* ;
import org.apache.jena.reasoner.transitiveReasoner.TransitiveReasonerFactory ;
import org.apache.jena.vocabulary.* ;


/**
 * A global registry of known reasoner modules. Modules are identified
 * by a URI and can have associated capability and descriptive
 * information, in RDF. Currently the schema/ontology for such
 * descriptions is mostly open. However, we do ensure that each reasoner
 * URI at least has an associated rdf:type statement to indicate that it
 * is a reasoner.
 * <p>
 * It is up to each reasoner or some associated configuration system
 * to register it in this registry.  </p>
 */
public class ReasonerRegistry {

    /** Single global instance of the registry */
    protected static ReasonerRegistry theRegistry;

    /** Map from reasoner URI to the associated factory */
    protected Map<String, ReasonerFactory> reasonerFactories = new HashMap<>();

    /** Union of the all reasoner capability descriptions */
    protected Model allDescriptions;

    /**
     * Constructor is hidden - go via theRegistry
     */
    private ReasonerRegistry() {
        allDescriptions = ModelFactory.createDefaultModel();
        // Preload the known Jena reasoners
        register(TransitiveReasonerFactory.theInstance());
        register(RDFSRuleReasonerFactory.theInstance());
        register(OWLFBRuleReasonerFactory.theInstance());
        register(GenericRuleReasonerFactory.theInstance());
        register(OWLMicroReasonerFactory.theInstance());
        register(OWLMiniReasonerFactory.theInstance());
    }

    /**
     * Return the single global instance of the registry
     */
    public static ReasonerRegistry theRegistry() {
        if (theRegistry == null) {
            theRegistry = new ReasonerRegistry();
        }
        return theRegistry;
    }

    /**
     * Register a Reasoner.
     * @param factory an factory that can be used to create instances of the reasoner
     */
    public void register(ReasonerFactory factory) {
        reasonerFactories.put(factory.getURI(), factory);
        Model description = factory.getCapabilities();
        if (description != null) {
            allDescriptions.add(description);
        }
        allDescriptions.createResource(factory.getURI())
                        .addProperty(RDF.type, ReasonerVocabulary.ReasonerClass);
    }

    /**
     * Register a Reasoner - simple case with no RDF description.
     * @param factory an factory that can be used to create instances of the reasoner
     * @param reasonerUri the URI used to label the reasoner, expressed as a
     * simple string
     */
    public void register(String reasonerUri, ReasonerFactory factory) {
        reasonerFactories.put(reasonerUri, factory);
        allDescriptions.createResource(reasonerUri)
                       .addProperty(RDF.type, ReasonerVocabulary.ReasonerClass);
    }

    /**
     * Return a composite set of RDF capability descriptions for all registered reasoners.
     * Listing all Resources of type ReasonerClass in the returned model
     * would enumerate all registered reasoners.
     */
    public Model getAllDescriptions() {
        return allDescriptions;
    }

    /**
     * Return information on a given Reasoner.
     * @param uri the URI of the reasoner
     * @return a Resource representing the reasoner whose properties (as obtainable
     * through listProperties etc) give a capability description of the reasoner;
     * returns null if no such reasoner is registered.
     */
    public Resource getDescription(String uri) {
        Resource reasonerURI = allDescriptions.getResource(uri);
        if (allDescriptions.contains(reasonerURI, RDF.type, ReasonerVocabulary.ReasonerClass)) {
            return reasonerURI;
        } else {
            return null;
        }
    }

    /**
     * Return the factory for the given reasoner.
     * @param uri the URI of the reasoner
     * @return the ReasonerFactory instance for this reasoner
     */
    public ReasonerFactory getFactory(String uri) {
        return reasonerFactories.get(uri);
    }

    /**
     * Create and return a new instance of the reasoner identified by
     * the given uri.
     * @param uri the uri of the reasoner to be created, expressed as a simple string
     * @param configuration an optional set of configuration information encoded in RDF this
     * parameter can be null if no configuration information is required.
     * @return a reaoner instance
     * @throws ReasonerException if there is not such reasoner or if there is
     * some problem during instantiation
     */
    public Reasoner create(String uri, Resource configuration) throws ReasonerException {
        ReasonerFactory factory = getFactory(uri);
        if (factory != null) {
            return factory.create(configuration);
        } else {
            throw new ReasonerException("Attempted to instantiate an unknown reasoner: " + uri);
        }
    }

    /**
     * Return a property Node which represents the direct version of a
     * transitively closed property. See <code>makeDirect(String)</code>;
     * this method makes a new Node with the direct respelling of
     * <code>node</code>s URI.
     */
    public static Node makeDirect(Node node) {
        return NodeFactory.createURI( makeDirect( node.getURI() ) );
    }

    /**
     * Return a URI string which represents the direct version of a
     * transitively closed property URI
     *
     * <p>Not clear what the right thing to do here is. Should not just invent
     * a new local name in the same namespace because that might be a controlled
     * namespace like RDF or RDFS. Can't even just extend the namespace slightly
     * because that would be violating the web principles of namespace ownership.
     * On the other hand, this solution results in staggeringly clumsy names.</p>
     *
     * @param uri the URI representing the transitive property
     */
    public static String makeDirect( String uri )
        { return "urn:x-hp-direct-predicate:" + uri.replace( ':' ,'_' ); }

    /** Prebuilt standard configuration for the default RDFS reasoner. */
    protected static Reasoner theRDFSReasoner = null;

    /**
     * Return a prebuilt standard configuration for the default RDFS reasoner
     */
     public static Reasoner getRDFSReasoner() {
         if (theRDFSReasoner == null) theRDFSReasoner = RDFSRuleReasonerFactory.theInstance().create(null);
         return theRDFSReasoner;
     }


    /** Prebuilt standard configuration for the default RDFS reasoner. */
    protected static Reasoner theRDFSSimpleReasoner = null;

    /**
     * Return a prebuilt simplified configuration for the default RDFS reasoner
     */
     public static Reasoner getRDFSSimpleReasoner() {
         if (theRDFSSimpleReasoner == null) {
             theRDFSSimpleReasoner = RDFSRuleReasonerFactory.theInstance().create(null);
             theRDFSSimpleReasoner.setParameter(ReasonerVocabulary.PROPsetRDFSLevel, ReasonerVocabulary.RDFS_SIMPLE);
         }
         return theRDFSSimpleReasoner;
     }

    /** Prebuilt standard configuration for the default subclass/subproperty transitive closure reasoner. */
    protected static Reasoner theTRANSITIVEReasoner;

    /**
     * Return a prebuilt standard configuration for the default subclass/subproperty transitive closure reasoner.
     */
    public static Reasoner getTransitiveReasoner() {
        if (theTRANSITIVEReasoner == null) theTRANSITIVEReasoner = TransitiveReasonerFactory.theInstance().create(null);
        return theTRANSITIVEReasoner;
    }

    /** Prebuilt standard configuration OWL reasoner */
    protected static Reasoner theOWLReasoner;

    /**
     * Prebuilt standard configuration for the default OWL reasoner. This configuration is
     * hybrid forward/backward reasoner.
     */
    public static Reasoner getOWLReasoner() {
        if (theOWLReasoner == null) theOWLReasoner = OWLFBRuleReasonerFactory.theInstance().create(null);
        return theOWLReasoner;
    }

    /** Prebuilt micro configuration OWL reasoner */
    protected static Reasoner theOWLMicroReasoner;

    /**
     * Prebuilt standard configuration a micro-OWL reasoner. This just supports property axioms,
     * and class inheritance sufficient to power the OntAPI.
     */
    public static Reasoner getOWLMicroReasoner() {
        if (theOWLMicroReasoner == null) theOWLMicroReasoner = OWLMicroReasonerFactory.theInstance().create(null);
        return theOWLMicroReasoner;
    }

    /** Prebuilt mini configuration OWL reasoner */
    protected static Reasoner theOWLMiniReasoner;

    /**
     * Prebuilt mini configuration for the default OWL reasoner. This omits bNode
     * introduction rules which has significant performance gain in some cases and
     * avoids breaking the find contract.
     */
    public static Reasoner getOWLMiniReasoner() {
        if (theOWLMiniReasoner == null) theOWLMiniReasoner = OWLMiniReasonerFactory.theInstance().create(null);
        return theOWLMiniReasoner;
    }

}
