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

import java.util.ArrayList;
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.Function;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.InternalServerErrorException;
import javax.ws.rs.WebApplicationException;
import org.apache.jena.query.QuerySolution;
import org.apache.jena.rdf.model.RDFNode;
import org.apache.jena.rdfconnection.RDFConnection;
import org.apache.jena.rdfconnection.RDFConnectionFactory;
import org.apache.jena.sparql.expr.nodevalue.NodeValueString;
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("/search.html")
public class Search {

    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.TEXT_HTML)
    @Template(name = "/search")
    public SearchResult doSearch(
            @QueryParam("q") String keyword,
            @DefaultValue(value = "1") @QueryParam("page") int page) throws WebApplicationException {
        if (keyword == null) {
            throw new BadRequestException("Expecting query parameter q to be set");
        }
        // build Node to avoid any code injection
        String kw = new NodeValueString(keyword).asQuotedString();

        SearchResult searchResult = new SearchResult();
        searchResult.search = keyword;

        String query;
        try (RDFConnection conn = RDFConnectionFactory.connect(Utils.DS)) {
            query = String.format(QUERY_COUNT, kw);
            LOG.trace(query);
            conn.querySelect(query, row -> {
                searchResult.count = row.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);
            conn.querySelect(query, row -> {
                SearchResult.SearchResultSummary result = new SearchResult.SearchResultSummary();
                result.uri = apply(row, "s", node -> node.asResource().getURI());
                result.score = apply(row, "score", node -> node.asLiteral().getFloat());
                result.graph = apply(row, "g", node -> node.asResource().getURI());
                result.label = apply(row, "label", node -> node.asLiteral().getLexicalForm());
                result.comment = apply(row, "comment", node -> node.asLiteral().getLexicalForm());
                searchResult.results.add(result);
            });
            return searchResult;
        } catch (Exception ex) {
            LOG.warn("exception while evaluating search", ex);
            throw new InternalServerErrorException(ex);
        }
    }

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

}
