/*
 * Copyright 2020 ETSI
 * 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright notice, 
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice, 
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holder nor the names of its contributors 
 *    may be used to endorse or promote products derived from this software without 
 *    specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package fr.mines_stetienne.ci.saref.managers;

import java.io.IOException;
import java.util.Set;
import java.util.function.Supplier;

import org.semanticweb.HermiT.ReasonerFactory;
import org.semanticweb.owl.explanation.api.Explanation;
import org.semanticweb.owl.explanation.api.ExplanationGenerator;
import org.semanticweb.owl.explanation.api.ExplanationGeneratorFactory;
import org.semanticweb.owl.explanation.impl.blackbox.Configuration;
import org.semanticweb.owl.explanation.impl.blackbox.ContractionStrategy;
import org.semanticweb.owl.explanation.impl.blackbox.DivideAndConquerContractionStrategy;
import org.semanticweb.owl.explanation.impl.blackbox.EntailmentCheckerFactory;
import org.semanticweb.owl.explanation.impl.blackbox.ExpansionStrategy;
import org.semanticweb.owl.explanation.impl.blackbox.InitialEntailmentCheckStrategy;
import org.semanticweb.owl.explanation.impl.blackbox.StructuralTypePriorityExpansionStrategy;
import org.semanticweb.owl.explanation.impl.blackbox.checker.BlackBoxExplanationGeneratorFactory;
import org.semanticweb.owl.explanation.impl.blackbox.checker.InconsistentOntologyExplanationGeneratorFactory;
import org.semanticweb.owl.explanation.impl.blackbox.checker.SatisfiabilityEntailmentCheckerFactory;
import org.semanticweb.owlapi.annotations.HasPriority;
import org.semanticweb.owlapi.apibinding.OWLManager;
import org.semanticweb.owlapi.io.OWLParser;
import org.semanticweb.owlapi.io.OWLParserFactory;
import org.semanticweb.owlapi.io.OWLParserFactoryImpl;
import org.semanticweb.owlapi.model.IRI;
import org.semanticweb.owlapi.model.OWLAxiom;
import org.semanticweb.owlapi.model.OWLClass;
import org.semanticweb.owlapi.model.OWLDataFactory;
import org.semanticweb.owlapi.model.OWLOntology;
import org.semanticweb.owlapi.model.OWLOntologyCreationException;
import org.semanticweb.owlapi.model.OWLOntologyManager;
import org.semanticweb.owlapi.reasoner.OWLReasoner;
import org.semanticweb.owlapi.reasoner.OWLReasonerConfiguration;
import org.semanticweb.owlapi.util.PriorityCollection;
import org.slf4j.Logger;

import fr.mines_stetienne.ci.saref.SAREFErrorLogger;
import fr.mines_stetienne.ci.saref.SAREFPipeline;
import fr.mines_stetienne.ci.saref.SAREFPipeline.Mode;
import fr.mines_stetienne.ci.saref.entities.SAREFExample;
import fr.mines_stetienne.ci.saref.entities.SAREFVersion;
import fr.mines_stetienne.ci.saref.managers.parsers.JenaModelDocumentFormatFactory;
import fr.mines_stetienne.ci.saref.managers.parsers.SourceOntologyParser;

public class OntologyManager extends SAREFErrorLogger {
	public final OWLDataFactory DATA_FACTORY = OWLManager.getOWLDataFactory();
	private final OWLOntologyManager ONTOLOGY_MANAGER = OWLManager.createOWLOntologyManager();

	private static enum MESSAGE {
		loading_error, reasoning_error;
	}

	private final ReasonerFactory reasonerFactory;
	private final InconsistentOntologyExplanationGeneratorFactory inconsistentOntologyExplanationGeneratorFactory;
	private final OWLAxiom axiomInconsistent = DATA_FACTORY.getOWLSubClassOfAxiom(DATA_FACTORY.getOWLThing(),
			DATA_FACTORY.getOWLNothing());
	private final ExplanationGeneratorFactory<OWLAxiom> explanationGeneratorFactory;
	
	/**
	 * Prepare the Apache Jena TDB RDF dataset
	 */
	public OntologyManager(SAREFPipeline pipeline, Logger errorLogger) throws IOException {
		super(pipeline, errorLogger);
		
		PriorityCollection<OWLParserFactory> ontologyParsers = ONTOLOGY_MANAGER.getOntologyParsers();
		ontologyParsers.add(new OntologyParserFactory(pipeline));

		final Supplier<OWLOntologyManager> m = () -> ONTOLOGY_MANAGER;
		reasonerFactory = new ReasonerFactory() {
			@Override
			public OWLReasoner createReasoner(OWLOntology ontology, OWLReasonerConfiguration config) {
				org.semanticweb.HermiT.Configuration configuration = new org.semanticweb.HermiT.Configuration();
				configuration.ignoreUnsupportedDatatypes = true;
				return super.createReasoner(ontology, configuration);
			}
		};
		inconsistentOntologyExplanationGeneratorFactory = new InconsistentOntologyExplanationGeneratorFactory(
				reasonerFactory, ONTOLOGY_MANAGER.getOWLDataFactory(), m, 10000);

		final EntailmentCheckerFactory<OWLAxiom> checker = new SatisfiabilityEntailmentCheckerFactory(reasonerFactory,
				m);
		final ExpansionStrategy<OWLAxiom> expansionStrategy = new StructuralTypePriorityExpansionStrategy<>(
				InitialEntailmentCheckStrategy.PERFORM, m);
		final ContractionStrategy<OWLAxiom> contractionStrategy = new DivideAndConquerContractionStrategy<>();
		final Configuration<OWLAxiom> config = new Configuration<>(checker, expansionStrategy, contractionStrategy,
				null, m);
		explanationGeneratorFactory = new BlackBoxExplanationGeneratorFactory<OWLAxiom>(config);
	}
	
	public OWLOntology loadOntology(SAREFVersion version, Logger logger) {
		IRI iri = IRI.create(version.getIRI());
		OWLOntology ontology = ONTOLOGY_MANAGER.getOntology(iri);
		if(ontology != null) {
			return ontology;
		}
		try {
			return ONTOLOGY_MANAGER.loadOntologyFromOntologyDocument(iri);
		} catch (OWLOntologyCreationException ex) {
			log(logger, getMessage(MESSAGE.loading_error), ex, Mode.DEVELOP, Mode.RELEASE, Mode.PRERELEASE_PORTAL, Mode.RELEASE_PORTAL);
			return null;
		}
	}
	
	public OWLOntology loadOntology(SAREFExample example, Logger logger) {
		IRI iri = IRI.create(example.getIRI());
		OWLOntology ontology = ONTOLOGY_MANAGER.getOntology(iri);
		if(ontology != null) {
			return ontology;
		}
		try {
			return ONTOLOGY_MANAGER.loadOntologyFromOntologyDocument(IRI.create(example.getIRI()));
		} catch (OWLOntologyCreationException ex) {
			log(logger, getMessage(MESSAGE.loading_error), ex, Mode.DEVELOP, Mode.RELEASE, Mode.PRERELEASE_PORTAL, Mode.RELEASE_PORTAL);
			return null;
		}
	}

	public Set<Explanation<OWLAxiom>> getInconsistenceExplanations(OWLOntology ontology, Logger logger) {
		final ExplanationGenerator<OWLAxiom> gen = inconsistentOntologyExplanationGeneratorFactory
				.createExplanationGenerator(ontology);
		try {
			return gen.getExplanations(axiomInconsistent, 10);
		} catch (Exception ex) {
			log(logger, getMessage(MESSAGE.reasoning_error), ex, Mode.DEVELOP, Mode.RELEASE, Mode.PRERELEASE_PORTAL, Mode.RELEASE_PORTAL);
			return null;
		}
	}

	public ExplanationGenerator<OWLAxiom> getExplanationGenerator(OWLOntology ontology) {
		return explanationGeneratorFactory.createExplanationGenerator(ontology);
	}

	public Set<Explanation<OWLAxiom>> getIncoherenceExplanations(ExplanationGenerator<OWLAxiom> explanationGenerator,
			OWLClass owlClass) {
		OWLAxiom axiom = DATA_FACTORY.getOWLSubClassOfAxiom(owlClass, DATA_FACTORY.getOWLNothing());
		return explanationGenerator.getExplanations(axiom, 10);
	}

	@HasPriority(100)
	private static class OntologyParserFactory extends OWLParserFactoryImpl {

		private final SAREFPipeline pipeline;

		public OntologyParserFactory(SAREFPipeline pipeline) {
			super(new JenaModelDocumentFormatFactory());
			this.pipeline = pipeline;
		}

		@Override
		public OWLParser createParser() {
			return new SourceOntologyParser(pipeline);
		}
	}

}
