package eu.dnetlib.ariadneplus.virtuoso;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.apache.jena.graph.NodeFactory;
import org.apache.jena.graph.Triple;
import org.apache.jena.query.Query;
import org.apache.jena.query.QueryExecutionFactory;
import org.apache.jena.query.QueryFactory;
import org.apache.jena.query.ResultSet;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.Resource;
import org.apache.jena.rdf.model.ResourceFactory;
import org.apache.jena.riot.RDFDataMgr;
import org.apache.jena.riot.RDFFormat;
import org.apache.jena.sparql.engine.http.QueryEngineHTTP;
import org.apache.jena.sparql.engine.http.QueryExceptionHTTP;
import org.apache.jena.util.iterator.ExtendedIterator;
import org.assertj.core.util.Lists;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.springframework.core.io.ClassPathResource;
import virtuoso.jena.driver.VirtGraph;
import virtuoso.jena.driver.VirtModel;
import virtuoso.jena.driver.VirtuosoQueryExecution;
import virtuoso.jena.driver.VirtuosoQueryExecutionFactory;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

/**
 * Created by Alessia Bardi on 11/07/2017.
 *
 * @author Alessia Bardi
 */
@Ignore
@RunWith(JUnit4.class)
public class VirtuosoTest {

	private String testGraph = "virt:test";
	private String connectionString = "jdbc:virtuoso://localhost:1111";
	private String testUser = "dba";
	private String testPwd = "dba";
	private String defaultURIBaseURl = "http://test/";
	private String sparqlEndpoint = "http://localhost:8890/sparql";

	@Before
	public void prepare() {
		VirtGraph graph = new VirtGraph(testGraph, connectionString, testUser, testPwd);
		assertNotNull(graph);
		graph.clear();
	}

	/**
	 * See also: https://stackoverflow.com/questions/24576190/enable-reasoning-for-sparql-request-via-jena
	 */
	@Test
	public void testInference1() {
		String q = "DEFINE input:inference 'ariadneplus_rules' CONSTRUCT {<%s> ?p ?o . } WHERE { <%s> ?p ?o .}";
		String subjectURL = "http://clarin.eu/clarin-eric-datatables/centres";
		String query = String.format(q, subjectURL, subjectURL, subjectURL, subjectURL);
		System.out.println(query);
		final QueryEngineHTTP request = new QueryEngineHTTP(sparqlEndpoint, query);
		Model res = request.execConstruct();
		RDFDataMgr.write(System.out, res, RDFFormat.RDFXML_PLAIN);
		res.close();
		request.close();
	}

	@Test
	public void testLongQuery() throws IOException {
		String q = IOUtils.toString(getResourceAsStream("/eu/dnetlib/ariadneplus/sparql/longQuery.sparql"));
		try (CloseableHttpClient httpclient = HttpClients.createDefault()) {

			HttpPost httpPost = new HttpPost(sparqlEndpoint);
			List<NameValuePair> nvps = Lists.newArrayList();
			nvps.add(new BasicNameValuePair("query", q));
			httpPost.setEntity(new UrlEncodedFormEntity(nvps));

			try (CloseableHttpResponse response2 = httpclient.execute(httpPost)) {
				System.out.println(response2.getStatusLine());
				HttpEntity entity2 = response2.getEntity();
				String s = IOUtils.toString(entity2.getContent());
				System.out.println("OUT: " + s);
				// do something useful with the response body
				// and ensure it is fully consumed
				EntityUtils.consume(entity2);
			}
		}
	}

	@Test(expected = QueryExceptionHTTP.class)
	public void testLongQueryFails() throws IOException {
		String q = IOUtils.toString(getResourceAsStream("/eu/dnetlib/ariadneplus/sparql/longQuery.sparql"));
		final QueryEngineHTTP request = new QueryEngineHTTP(sparqlEndpoint, q);
		Model res = request.execConstruct();
		RDFDataMgr.write(System.out, res, RDFFormat.RDFXML_PLAIN);
		res.close();
		request.close();
	}

	@Test
	public void testRemote() {
		String q = "DEFINE input:inference 'ariadneplus_rules' CONSTRUCT {<%s> ?p ?o . } WHERE { <%s> ?p ?o .}";
		//String q = "CONSTRUCT {<%s> ?p ?o . } WHERE { <%s> ?p ?o .}";
		String subjectURL = "http://clarin.eu/clarin-eric-datatables/centres";
		String query = String.format(q, subjectURL, subjectURL, subjectURL, subjectURL);
		System.out.println(query);
		VirtGraph g = new VirtGraph(connectionString, testUser, testPwd);
		g.setReadFromAllGraphs(true);
		VirtuosoQueryExecution vqe = VirtuosoQueryExecutionFactory.create(query, g);
		Model resultModel = vqe.execConstruct();
		RDFDataMgr.write(System.out, resultModel, RDFFormat.RDFXML_PLAIN);
		vqe.close();
		g.close();
	}

	@Test
	public void testConnection() {
		VirtGraph graph = new VirtGraph(testGraph, connectionString, testUser, testPwd);
		assertNotNull(graph);
	}

	@Test
	public void testDescribe() {
		//put something in the graph
		Model md = VirtModel.openDatabaseModel(testGraph, connectionString, testUser, testPwd);
		md.read(getResourceAsStream("eu/dnetlib/ariadneplus/virtuoso/test.rdf"), defaultURIBaseURl);
		String resURI = "http://www.ariadneplus-project.eu/ARIADNE/ARIADNECatalog/Dataset/http%3A%2F%2Fregistry.ariadne-infrastructure.eu%2Fdataset%2F25058478";
		//now query for the resource URI
		String describeQuery = "DESCRIBE <" + resURI + ">";
		Query query = QueryFactory.create(describeQuery);
		String sparqlUrl = "http://localhost:8890/sparql";
		final QueryEngineHTTP serviceRequest = QueryExecutionFactory.createServiceRequest(sparqlUrl, query);
		Model resultModel = serviceRequest.execDescribe();
		serviceRequest.close();
		resultModel.write(System.out);
		VirtGraph graph = new VirtGraph(testGraph, connectionString, testUser, testPwd);
		graph.clear();
	}

	@Ignore
	@Test
	public void testDescribeRemote() {
		String resURI = "http://hdl.handle.net/11858/00-203C-0000-0029-F5E8-C";
		//now query for the resource URI
		String describeQuery = "DESCRIBE <" + resURI + ">";
		Query query = QueryFactory.create(describeQuery);
		String sparqlUrl = "https://virtuoso.ariadneplus.d4science.org/sparql";
		final QueryEngineHTTP serviceRequest = QueryExecutionFactory.createServiceRequest(sparqlUrl, query);
		Model resultModel = serviceRequest.execDescribe();
		serviceRequest.close();
		resultModel.write(System.out);
	}

	@Test
	public void testReadForDescribeResource() {
		String sparqlUrl = "http://localhost:8890/sparql";
		String queryForGraphs =
				"SELECT DISTINCT ?g \n"
						+ "WHERE {\n"
						+ "  { ?g <dnet:collectedFrom> <http://ariadneplus.d4science.org/handle/api_________::ariadneplus___::clarin::p_1271859438164> }\n"
						+ "}";
		Query query = QueryFactory.create(queryForGraphs);
		final QueryEngineHTTP serviceRequest = QueryExecutionFactory.createServiceRequest(sparqlUrl, query);
		ResultSet graphs = serviceRequest.execSelect();
		String queryForSubjectsTemplate = "SELECT DISTINCT ?s WHERE {GRAPH <%s> {?s ?p ?o}}";
		int countGraphs = 0;
		while (graphs.hasNext()) {
			countGraphs++;
			Resource g = graphs.next().getResource("g");
			System.out.println("*****GRAPH: " + g.getURI());
			final QueryEngineHTTP serviceRequest2 =
					QueryExecutionFactory.createServiceRequest(sparqlUrl, QueryFactory.create(String.format(queryForSubjectsTemplate, g.getURI())));
			ResultSet subjects = serviceRequest2.execSelect();
			int countSubjects = 0;
			while (subjects.hasNext()) {
				countSubjects++;
				Resource s = subjects.next().getResource("s");
				String describeQuery = "DESCRIBE <" + s.getURI() + ">";
				final QueryEngineHTTP serviceRequest3 = QueryExecutionFactory.createServiceRequest(sparqlUrl, QueryFactory.create(describeQuery));
				Model resultModel = serviceRequest3.execDescribe();
				serviceRequest3.close();
				resultModel.write(System.out);
			}
			serviceRequest2.close();
			System.out.println("Got " + countSubjects + " subjects");
		}
		serviceRequest.close();
		assertEquals("unexpected number of graphs", 4, countGraphs);

	}

	@Test
	public void testClear() {
		VirtGraph graph = new VirtGraph(testGraph, connectionString, testUser, testPwd);
		assertNotNull(graph);
		graph.clear();
		Assert.assertTrue(graph.isEmpty());

	}

	@Test
	public void testFeedTriple() {
		VirtGraph graph = new VirtGraph(testGraph, connectionString, testUser, testPwd);
		assertNotNull(graph);
		graph.clear();
		Assert.assertTrue(graph.isEmpty());
		Triple t = new Triple(NodeFactory.createURI("http://test/1"), NodeFactory.createURI("http://test/is_friend_of"),
				NodeFactory.createURI("http://test/KevinBacon"));
		graph.performAdd(t);
		assertEquals(1, graph.getCount());
		graph.clear();
	}

	@Test
	public void testFeedRDFFile() {
		Model md = VirtModel.openDatabaseModel(testGraph, connectionString, testUser, testPwd);
		md.read(getResourceAsStream("eu/dnetlib/ariadneplus/virtuoso/test.rdf"), defaultURIBaseURl);
		long size = md.size();
		System.out.println("==Inserted " + size + "triples==");

		VirtGraph graph = new VirtGraph(testGraph, connectionString, testUser, testPwd);
		assertNotNull(graph);
		Assert.assertFalse(graph.isEmpty());

		graph.clear();
	}

	/*
	If we want to be able to update, we need to store the triples in different named graph. This way we can delete the old triples and add the new ones.
	And then we can add triples about the graph (e.g. provenance, last update time, whatever) in another named graph (e.g. 'provenance'),
	where all the info about the named graphs are available.
	 */
	@Test
	public void testUpdate() {
		String namedGraph = "virt:test:theObjIdentifier";
		Model md = VirtModel.openDatabaseModel(namedGraph, connectionString, testUser, testPwd);
		md.read(getResourceAsStream("eu/dnetlib/ariadneplus/virtuoso/test.rdf"), defaultURIBaseURl);

		long size = md.size();
		System.out.println("==Inserted " + size + "triples==");
		md.removeAll();
		Assert.assertTrue(md.isEmpty());

		md.read(getResourceAsStream("eu/dnetlib/ariadneplus/virtuoso/test_less_data.rdf"), defaultURIBaseURl);
		long size2 = md.size();
		System.out.println("==Inserted " + size2 + "triples==");

		md.removeAll();

	}

	@Test
	public void testRemoveAllWildcards() {
		VirtGraph graph = new VirtGraph(testGraph, connectionString, testUser, testPwd);
		assertNotNull(graph);
		graph.clear();
		Assert.assertTrue(graph.isEmpty());
		Triple t1 = new Triple(NodeFactory.createURI("http://test/1"), NodeFactory.createURI("http://test/is_friend_of"),
				NodeFactory.createURI("http://test/KevinBacon"));
		Triple t2 = new Triple(NodeFactory.createURI("http://test/1"), NodeFactory.createURI("http://test/is_friend_of"),
				NodeFactory.createURI("http://test/BonoVox"));
		Triple t3 = new Triple(NodeFactory.createURI("http://test/1"), NodeFactory.createURI("http://test/is_enemy_of"),
				NodeFactory.createURI("http://test/Spiderman"));
		graph.performAdd(t1);
		graph.performAdd(t2);
		graph.performAdd(t3);
		assertEquals(3, graph.getCount());

		Model md = VirtModel.openDatabaseModel(testGraph, connectionString, testUser, testPwd);
		assertEquals(3, md.size());
		md.removeAll(ResourceFactory.createResource("http://test/1"), ResourceFactory.createProperty("http://test/is_enemy_of"), null);
		assertEquals(2, md.size());
	}

	@Test
	public void testSearch() {
		VirtGraph graph = new VirtGraph(testGraph, connectionString, testUser, testPwd);
		assertNotNull(graph);
		graph.clear();
		Assert.assertTrue(graph.isEmpty());
		Triple t1 = new Triple(NodeFactory.createURI("http://test/1"), NodeFactory.createURI("http://test/is_friend_of"),
				NodeFactory.createURI("http://test/KevinBacon"));
		Triple t2 = new Triple(NodeFactory.createURI("http://test/1"), NodeFactory.createURI("http://test/is_friend_of"),
				NodeFactory.createURI("http://test/BonoVox"));
		Triple t3 = new Triple(NodeFactory.createURI("http://test/1"), NodeFactory.createURI("http://test/is_enemy_of"),
				NodeFactory.createURI("http://test/Spiderman"));
		graph.performAdd(t1);
		graph.performAdd(t2);
		graph.performAdd(t3);
		assertEquals(3, graph.getCount());

		final ExtendedIterator<Triple> friends = graph.find(null, NodeFactory.createURI("http://test/is_friend_of"), null);
		while (friends.hasNext()) {
			System.out.println(friends.next());
		}

	}

	private InputStream getResourceAsStream(final String classpath) {
		try {
			final ClassPathResource resource = new ClassPathResource(classpath);
			return resource.getInputStream();
		} catch (IOException e) {
			return null;
		}
	}

}
