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

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.Set;
import java.util.stream.Collectors;

import org.apache.jena.rdf.model.Model;
import org.semanticweb.owl.explanation.api.Explanation;
import org.semanticweb.owl.explanation.api.ExplanationGenerator;
import org.semanticweb.owlapi.model.OWLAxiom;
import org.semanticweb.owlapi.model.OWLOntology;
import org.semanticweb.owlapi.profiles.OWL2DLProfile;
import org.semanticweb.owlapi.profiles.OWLProfileReport;
import org.semanticweb.owlapi.profiles.violations.UseOfDefinedDatatypeInDatatypeRestriction;
import org.semanticweb.owlapi.profiles.violations.UseOfDefinedDatatypeInLiteral;
import org.semanticweb.owlapi.profiles.violations.UseOfUndeclaredDatatype;
import org.semanticweb.owlapi.profiles.violations.UseOfUnknownDatatype;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import fr.mines_stetienne.ci.saref.SAREF;
import fr.mines_stetienne.ci.saref.SAREFPipelineException;
import fr.mines_stetienne.ci.saref.managers.OntologyManager;
import fr.mines_stetienne.ci.saref.managers.RepositoryManager;
import fr.mines_stetienne.ci.saref.managers.ThemisManager;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

/**
 * Checks TS 103 673 Clause 9.4.5: OWL Profile, Consistency, and Satisfiability
 * of Classes
 *
 */
public class Clause_9_4_5_Checker extends AbstractClauseChecker {

	private static enum MESSAGE {
		profile, consistent, satisfiable, oops, oopsError;
	}

	/**
	 */
	public Clause_9_4_5_Checker(RepositoryManager repositoryManager) {
		super(repositoryManager, Clause_9_4_5_Checker.class);
	}

	@Override
	public void checkClause() throws SAREFPipelineException {
		final OWLOntology ontology = pipeline.getOntologyManager().loadOntology(version, errorLogger);
		if (ontology == null) {
			return;
		}

		final OWLProfileReport report = new OWL2DLProfile().checkOntology(ontology);
		report.getViolations().removeIf(v -> {
			if (v instanceof UseOfDefinedDatatypeInDatatypeRestriction || v instanceof UseOfDefinedDatatypeInLiteral
					|| v instanceof UseOfUndeclaredDatatype || v instanceof UseOfUnknownDatatype) {
				return true;
			}
			return false;
		});
		if (!report.getViolations().isEmpty()) {
			String violations = report.getViolations().stream().map(Object::toString)
					.collect(Collectors.joining("\n- ", "\n\n- ", "\n\n"));
			logError(getMessage(MESSAGE.profile, violations));
		}

		OntologyManager ontologyManager = pipeline.getOntologyManager();

		Set<Explanation<OWLAxiom>> incExplanation = ontologyManager.getInconsistenceExplanations(ontology, errorLogger);
		if (incExplanation != null && !incExplanation.isEmpty()) {
			String explanation = incExplanation.stream().map(e -> e.getAxioms().toString())
					.collect(Collectors.joining("\n- ", "\n\n- ", "\n\n"));
			logError(getMessage(MESSAGE.consistent, explanation));
		} else {
			ExplanationGenerator<OWLAxiom> generator = ontologyManager.getExplanationGenerator(ontology);
			ontology.classesInSignature().forEach(c -> {
				if (c.isOWLNothing()) {
					return;
				}
				Set<Explanation<OWLAxiom>> incExplanation2 = ontologyManager.getIncoherenceExplanations(generator, c);
				if (!incExplanation2.isEmpty()) {
					String explanation = incExplanation2.stream().map(e -> e.getAxioms().toString())
							.collect(Collectors.joining("\n- ", "\n\n- ", "\n\n"));
					logError(getMessage(MESSAGE.satisfiable, c, explanation));

				}
			});
		}
		try {
			checkOops();
		} catch (Exception ex) {
			logWarning(getMessage(MESSAGE.oopsError), ex);
		}
	}

	public void checkOops() throws Exception {
		if (SAREF.IGNORE_OOPS) {
			return;
		}

		ByteArrayOutputStream out = new ByteArrayOutputStream();
		String onto = "";

		Model model = version.getModel();
		model.write(out, "RDF/XML"); // write in string and then replace
		onto = out.toString().replaceAll("]]", "] ]");

		String xmlRequest = "<?xml version='1.0' encoding='UTF-8'?>" + "<OOPSRequest>" + "<OntologyURI></OntologyURI>"
				+ "<OntologyContent><![CDATA[" + onto + "]]></OntologyContent>" + "<Pitfalls></Pitfalls>"
				+ "<OutputFormat>RDF/XML</OutputFormat>" + "</OOPSRequest>";

		Object[] arrayData = new Object[0];

		try {

			OkHttpClient httpClient = new OkHttpClient().newBuilder().build();

			MediaType mediaType = MediaType.parse("application/xml");

			RequestBody body = RequestBody.create(xmlRequest, mediaType);

			Request request = new Request.Builder().url("http://oops.linkeddata.es/rest").method("POST", body)
					.addHeader("Content-Type", "application/xml").build();

			try (Response response = httpClient.newCall(request).execute();) {

				if (response.code() != 200) {
					throw new SAREFPipelineException("response", "Unexpected response code " + response.code());
				}

				String result = response.body().string();

				Document doc = ThemisManager.convertStringToXMLDocument(result);

				NodeList nodeList = doc.getElementsByTagName("rdf:Description");

				ArrayList<String> oopsErrors = new ArrayList<String>();
				for (int temp = 0; temp < nodeList.getLength(); temp++) {
					org.w3c.dom.Node node = nodeList.item(temp);
					if (node.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
						Element element = (Element) node;
						if (((Element) node).getElementsByTagName("oops:hasCode").item(0) != null) {
							if (((Element) node).getElementsByTagName("oops:hasName").item(0) != null) {
								String code = element.getElementsByTagName("oops:hasCode").item(0).getTextContent();
								String name = element.getElementsByTagName("oops:hasName").item(0).getTextContent();
								String description = element.getElementsByTagName("oops:hasDescription").item(0)
										.getTextContent();
								String affected = "";
								NodeList affectedElements = element.getElementsByTagName("oops:hasAffectedElement");
								if (affectedElements != null) {
									for (int k = 0; k < affectedElements.getLength(); k++) {
										if (k == 0) {
											affected += "\"" + affectedElements.item(k).getTextContent() + "\"";
										} else {
											affected += " ; \"" + affectedElements.item(k).getTextContent() + "\"";
										}
									}
								}

								String oopsError = String.format(
										"Code: %s, Name: %s, Description: %s, Affected Elements: %s", code, name,
										description, affected);
								oopsErrors.add(oopsError);
							} else {
								String code = element.getElementsByTagName("oops:hasCode").item(0).getTextContent();
								String description = element.getElementsByTagName("oops:hasDescription").item(0)
										.getTextContent();
								String affected = "";
								NodeList affectedElements = element.getElementsByTagName("oops:hasAffectedElement");
								if (affectedElements != null) {
									for (int k = 0; k < affectedElements.getLength(); k++) {
										if (k == 0) {
											affected += "\"" + affectedElements.item(k).getTextContent() + "\"";
										} else {
											affected += " ; \"" + affectedElements.item(k).getTextContent() + "\"";
										}
									}
								}

								String oopsError = String.format("Code: %s, Description: %s, Affected Elements: %s",
										code, description, affected);
								oopsErrors.add(oopsError);
							}
						}

					}
				}
				if (!oopsErrors.isEmpty()) {
					String data = oopsErrors.stream().map(e -> e.toString())
							.collect(Collectors.joining("\n- ", "\n\n- ", "\n\n"));
					logWarning(getMessage(MESSAGE.oops, data));

				}
			} catch (SocketTimeoutException e) {
				logWarning(getMessage(MESSAGE.oopsError), e);
			}
		} catch (IOException | SAREFPipelineException e) {
			logWarning(getMessage(MESSAGE.oopsError), e);
		}
	}

}
