/**
 *
 */
package org.etsi.saref.server.resources;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Iterator;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;

import java.util.function.Consumer;
import java.util.function.Function;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.InternalServerErrorException;
import javax.ws.rs.WebApplicationException;

import org.apache.commons.io.IOUtils;
import org.apache.jena.query.Dataset;
import org.apache.jena.query.QueryExecutionFactory;
import org.apache.jena.query.QuerySolution;
import org.apache.jena.query.ReadWrite;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.RDFNode;
import org.apache.jena.rdfconnection.RDFConnection;
import org.apache.jena.rdfconnection.RDFConnectionFactory;
import org.apache.jena.shared.Lock;
import org.apache.jena.sparql.core.DatasetGraph;
import org.apache.jena.sparql.expr.nodevalue.NodeValueString;
import org.apache.jena.sparql.util.Context;
import org.apache.lucene.store.LockObtainFailedException;
import org.etsi.saref.server.Utils;
import org.etsi.saref.server.entities.SearchResult;
import org.glassfish.jersey.server.mvc.Template;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author Omar Qawasmeh
 *
 *
 */
@Path("/api/search")
public class Search {

	static DatasetManager dsm;
	static Dataset DS;

	private static final Logger LOG = LoggerFactory.getLogger(Search.class);

	private static final int NB_RESULTS = 20;

	private static final String QUERY_COUNT = "PREFIX ex: <http://example.org/>\n"
			+ "PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>\n" + "PREFIX text: <http://jena.apache.org/text#>\n"
			+ "SELECT (COUNT(*) AS ?count)\n" + "WHERE {\n" + "   ( ?s ?score ?literal ?g ) text:query %s .\n"
			+ "  OPTIONAL { ?s rdfs:label ?label }\n" + "  OPTIONAL { ?s rdfs:comment ?comment }\n" + "}";

	private static final String QUERY = "PREFIX ex: <http://example.org/>\n"
			+ "PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>\n" + "PREFIX text: <http://jena.apache.org/text#>\n"
			+ "SELECT DISTINCT ?s ?label ?comment ?score ?g \n" + "WHERE {\n"
			+ "   ( ?s ?score ?literal ?g ) text:query %s .\n" + "  OPTIONAL { ?s rdfs:label ?label }\n"
			+ "  OPTIONAL { ?s rdfs:comment ?comment }\n" + "}\n" + "ORDER BY DESC ( ?score )\n" + "OFFSET %s\n"
			+ "LIMIT %s";

	@GET
	@Produces(MediaType.APPLICATION_JSON)
	public SearchResult doSearch(@QueryParam("q") String keyword,
			@DefaultValue(value = "1") @QueryParam("page") int page)
			throws WebApplicationException, IOException, InterruptedException {

		if (keyword == null) {
			throw new BadRequestException("Expecting query parameter q to be set");
		}
		if (Constants.DScreated == false) {
			createDS();
		}

		// build Node to avoid any code injection
		String kw = new NodeValueString(keyword).asQuotedString();

		SearchResult searchResult = new SearchResult();

		searchResult.search = keyword;

		String query;

		try {
			query = String.format(QUERY_COUNT, kw);
			DS.begin(ReadWrite.READ);

			LOG.trace(query);

			QueryExecutionFactory.create(query, DS).execSelect().forEachRemaining(action -> {
				searchResult.count = action.get("count").asLiteral().getInt();
			});

			searchResult.offset = (page - 1) * NB_RESULTS;
			searchResult.results = new ArrayList<>();

			query = String.format(QUERY, kw, searchResult.offset, searchResult.offset + NB_RESULTS);

			LOG.trace(query);

			QueryExecutionFactory.create(query, DS).execSelect().forEachRemaining(action -> {
				SearchResult.SearchResultSummary result = new SearchResult.SearchResultSummary();
				result.uri = apply(action, "s", node -> node.asResource().getURI());
				result.score = apply(action, "score", node -> node.asLiteral().getFloat());
				result.graph = apply(action, "g", node -> node.asResource().getURI());
				result.label = apply(action, "label", node -> node.asLiteral().getLexicalForm());
				result.comment = apply(action, "comment", node -> node.asLiteral().getLexicalForm());
				searchResult.results.add(result);
			});

			DS.end();
			return searchResult;

		} catch (Exception ex) {
			LOG.warn("exception while evaluating search", ex);
			throw new InternalServerErrorException(ex);
		}

	}

	public void createDS() throws IOException {
		dsm = new DatasetManager(new File("/home/omar/Desktop/saref-pipeline-test/target/"));
		DS = dsm.getDataset();
		Constants.DScreated = true;
	}

	private <R> R apply(QuerySolution sol, String variable, Function<RDFNode, R> function) {
		try {
			return function.apply(sol.get(variable));
		} catch (Exception ex) {
			return null;
		}
	}

}
