/*
 * Copyright 2016 Ecole des Mines de Saint-Etienne.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package fr.emse.ci.sparqlext.iterator.library;

import fr.emse.ci.sparqlext.SPARQLExt;
import fr.emse.ci.sparqlext.engine.QueryExecutor;
import fr.emse.ci.sparqlext.iterator.IteratorStreamFunctionBase;
import fr.emse.ci.sparqlext.utils.ContextUtils;
import fr.emse.ci.sparqlext.utils.EvalUtils;

import java.util.*;
import java.util.function.Consumer;
import org.apache.jena.graph.Node;

import org.apache.jena.query.QuerySolution;
import org.apache.jena.query.ResultSet;
import org.apache.jena.rdf.model.RDFNode;
import org.apache.jena.sparql.expr.ExprEvalException;
import org.apache.jena.sparql.expr.ExprList;
import org.apache.jena.sparql.expr.NodeValue;
import org.apache.jena.sparql.expr.nodevalue.NodeValueNode;
import org.apache.jena.sparql.util.Context;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;

/**
 * Iterator function
 * <a href="http://w3id.org/sparql-generate/iter/call-select">iter:call-select</a>
 * takes as input a IRI to a SPARQL-Select document, runs it on the current
 * Dataset, and binds the given variables to the output of the select query, in
 * order.
 *
 * <ul>
 * <li>Param 1: (select) is the IRI to a SPARQL-Select document</li>
 * <li>Param 2 to n: are the call parameters corresponding to the SPARQL-Select
 * query signature</li>
 * </ul>
 *
 * Multiple variables may be bound: variable <em>n</em> is bound to the value of
 * the
 * <em>n</em><sup>th</sup> project variable of the select query.
 *
 * @author Maxime Lefrançois <maxime.lefranç[email protected]>
 */
public class ITER_Call_Select extends IteratorStreamFunctionBase {

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

    public static final String URI = SPARQLExt.ITER + "call-select";

    @Override
    public void exec(
            final List<NodeValue> args,
            final Consumer<List<List<NodeValue>>> collectionListNodeValue) {
        if (args.isEmpty()) {
            LOG.debug("There should be at least one IRI parameter.");
            throw new ExprEvalException("There should be at least one IRI parameter.");
        }
        if (!args.get(0).isIRI()) {
            LOG.debug("The first parameter must be a IRI.");
            throw new ExprEvalException("The first parameter must be a IRI.");
        }
        String queryName = args.get(0).asNode().getURI();

        final List<List<Node>> callParameters = new ArrayList<>();
        callParameters.add(EvalUtils.eval(args.subList(1, args.size())));

        final Context context = ContextUtils.fork(getContext()).setSelectOutput((result) -> {
            List<List<NodeValue>> list = getListNodeValues(result);
            collectionListNodeValue.accept(list);
        }).fork();
        final QueryExecutor queryExecutor = ContextUtils.getQueryExecutor(context);
        queryExecutor.execSelectFromName(queryName, callParameters, context);
    }

    @Override
    public void checkBuild(ExprList args) {
    }

    private List<List<NodeValue>> getListNodeValues(ResultSet result) {
        List<String> resultVars = result.getResultVars();
        List<List<NodeValue>> listNodeValues = new ArrayList<>();
        while (result.hasNext()) {
            List<NodeValue> nodeValues = new ArrayList<>();
            QuerySolution sol = result.next();
            for (String var : resultVars) {
                RDFNode rdfNode = sol.get(var);
                if (rdfNode != null) {
                    NodeValue n = new NodeValueNode(rdfNode.asNode());
                    nodeValues.add(n);
                } else {
                    nodeValues.add(null);
                }
            }
            listNodeValues.add(nodeValues);
        }
        return listNodeValues;
    }

}