/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.jena.query;

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

import org.apache.jena.atlas.io.IO ;
import org.apache.jena.atlas.io.IOX;
import org.apache.jena.atlas.logging.Log ;
import org.apache.jena.rdf.model.Model ;
import org.apache.jena.rdf.model.ModelFactory ;
import org.apache.jena.riot.Lang ;
import org.apache.jena.riot.RDFDataMgr;
import org.apache.jena.riot.ReadAnything;
import org.apache.jena.riot.ResultSetMgr ;
import org.apache.jena.riot.resultset.ResultSetLang;
import org.apache.jena.riot.resultset.ResultSetOnClose;
import org.apache.jena.sparql.core.Var;
import org.apache.jena.sparql.engine.QueryIterator ;
import org.apache.jena.sparql.engine.ResultSetStream ;
import org.apache.jena.sparql.exec.RowSet;
import org.apache.jena.sparql.graph.GraphFactory ;
import org.apache.jena.sparql.resultset.* ;

/** ResultSetFactory - make result sets from places other than a query. */

public class ResultSetFactory {
    // See also ResultSetMgr - which post-dates this code.
    // Ideally, read operations here should call ResultSetMgr.
    // The exception is XML from a string and the archaic RDF to ResultSet forms.

    /**
     * Load a result set from file or URL into a result set (memory backed).
     *
     * @param filenameOrURI
     * @return ResultSet
     */
    public static ResultSet load(String filenameOrURI) {
        return load(filenameOrURI, null);
    }

    /**
     * Load a result set from file or URL into a result set (memory backed).
     *
     * @param filenameOrURI
     * @param format
     * @return ResultSet
     */
    public static ResultSet load(String filenameOrURI, ResultsFormat format) {
        if (format == null)
            format = ResultsFormat.guessSyntax(filenameOrURI);
        // Result sets are iterators.
        // The input stream isn't finished with when the result set is created.
        // Close input stream when the result set is finished with
        // (exhausted or closed).
        InputStream in = IO.openFile(filenameOrURI) ;
        ResultSet rs = load(in, format) ;
        Runnable onClose = ()-> {
            try { in.close(); }
            catch (IOException ex) { throw IOX.exception(ex); }
        };
        ResultSet rs2 = new ResultSetOnClose(rs, onClose);
        return rs;
    }

    /**
     * Load a result set from input stream into a result set (memory backed).
     *
     * @param input
     * @param format
     * @return ResultSet
     */
    public static ResultSet load(InputStream input, ResultsFormat format) {
        if (format == null) {
            Log.warn(ResultSet.class, "Null format - defaulting to XML");
            format = ResultsFormat.FMT_RS_XML;
        }

        // Old World - new world
        Lang lang = ResultsFormat.convert(format) ;
        if ( lang != null )
            return ResultSetMgr.read(input, lang) ;

        if (format.equals(ResultsFormat.FMT_TEXT)) {
            Log.warn(ResultSet.class, "Can't read a text result set");
            throw new ResultSetException("Can't read a text result set");
        }

        if (format.equals(ResultsFormat.FMT_RDF_XML)) {
            Model m = ModelFactory.createDefaultModel();
            m.read(input, null);
            return RDFInput.fromRDF(m);
        }

        if (format.equals(ResultsFormat.FMT_RDF_TTL)) {
            Model m = ModelFactory.createDefaultModel();
            m.read(input, null, "TURTLE");
            return RDFInput.fromRDF(m);
        }

        if (format.equals(ResultsFormat.FMT_RDF_N3)) {
            Model m = ModelFactory.createDefaultModel();
            m.read(input, null, "N3");
            return RDFInput.fromRDF(m);
        }

        if (format.equals(ResultsFormat.FMT_RDF_NT)) {
            Model m = ModelFactory.createDefaultModel();
            m.read(input, null, "N-TRIPLES");
            return RDFInput.fromRDF(m);
        }

        Log.warn(ResultSet.class, "Unknown result set syntax: " + format);
        return null;
    }

    /**
     * Load a result set (or any other model) from file or URL
     *
     * @param filenameOrURI
     * @return Model
     */
    public static Model loadAsModel(String filenameOrURI) {
        return loadAsModel(null, filenameOrURI, null);
    }

    /**
     * Load a result set (or any other model) from file or URL
     *
     * @param model
     *            Load into this model (returned)
     * @param filenameOrURI
     * @return Model
     */
    public static Model loadAsModel(Model model, String filenameOrURI) {
        return loadAsModel(model, filenameOrURI, null);
    }

    /**
     * Load a result set (or any other model) from file or URL
     *
     * @param filenameOrURI
     * @param format
     * @return Model
     */
    public static Model loadAsModel(String filenameOrURI, ResultsFormat format) {
        return loadAsModel(null, filenameOrURI, format);
    }

    /**
     * Load a result set (or any other model) from file or URL. Does not have to
     * be a result set (e.g. CONSTRUCt results) but it does interpret the
     * ResultSetFormat possibilities.
     *
     * @param model
     *            Load into this model (returned)
     * @param filenameOrURI
     * @param format
     * @return Model
     * @deprecated This functions will become be internal.
     */
    @Deprecated
    public static Model loadAsModel(Model model, String filenameOrURI, ResultsFormat format) {
        if (model == null)
            model = GraphFactory.makeDefaultModel();

        if (format == null)
            format = ResultsFormat.guessSyntax(filenameOrURI);

        if (format == null) {
            Log.warn(ResultSet.class, "Null format - defaulting to XML");
            format = ResultsFormat.FMT_RS_XML;
        }

        if (format.equals(ResultsFormat.FMT_TEXT)) {
            Log.error(ResultSet.class, "Can't read a text result set");
            throw new ResultSetException("Can't read a text result set");
        }

        if (format.equals(ResultsFormat.FMT_RS_XML) || format.equals(ResultsFormat.FMT_RS_JSON)) {
            SPARQLResult x = ReadAnything.read(filenameOrURI);
            if (x.isResultSet())
                RDFOutput.encodeAsRDF(model, x.getResultSet());
            else if ( x.isBoolean() )
                RDFOutput.encodeAsRDF(model, x.getBooleanResult());
            else
                throw new ResultSetException("Not a result set");
            return model;
        }

        if (ResultsFormat.isRDFGraphSyntax(format)) {
            RDFDataMgr.read(model, filenameOrURI);
            return model;
        }

        Log.error(ResultSet.class, "Unknown result set syntax: " + format);
        return null;
    }

    /**
     * Read in any kind of result kind (result set, boolean, graph) Guess the
     * syntax based on filename/URL extension.
     */
    public static SPARQLResult result(String filenameOrURI) {
        return ReadAnything.read(filenameOrURI);
    }

    /**
     * Read XML which is the format of the SPARQL result set format.
     *
     * @param in
     *            InputStream
     * @return ResultSet
     */
    public static ResultSet fromXML(InputStream in) {
        return ResultSetMgr.read(in, ResultSetLang.RS_XML);
    }

    /**
     * Read from an input stream which is the format of the SPARQL result set
     * format in JSON.
     *
     * @param in
     *            InputStream
     * @return ResultSet
     */
    public static ResultSet fromJSON(InputStream in) {
        return ResultSetMgr.read(in, ResultSetLang.RS_JSON);
    }

    /**
     * Turns an RDF model, with properties and classes from the result set
     * vocabulary, into a SPARQL result set. The result set formed is a copy in
     * memory.
     *
     * @param model
     * @return ResultSet
     */
    public static ResultSet makeResults(Model model) {
        return new RDFInput(model);
    }

    /**
     * Turns an RDF model, with properties and classes from the result set
     * vocabulary, into a SPARQL result set which is rewindable (has a
     * .reset()operation). The result set formed is a copy in memory.
     *
     * @param model
     * @return ResultSetRewindable
     */
    public static ResultSetRewindable makeRewindable(Model model) {
        return new RDFInput(model);
    }

    /**
     * Turn an existing result set into a rewindable one.
     * May take a copy but this is not guaranteed
     * Uses up the result set passed in which is no longer valid as a ResultSet.
     *
     * @param resultSet
     * @return ResultSetRewindable
     */
    public static ResultSetRewindable makeRewindable(ResultSet resultSet) {
        if ( resultSet instanceof ResultSetRewindable ) {
            ResultSetRewindable rsw = (ResultSetRewindable)resultSet;
            rsw.reset();
            return rsw;
        }
        return new ResultSetMem(resultSet);
    }

    /**
     * Turn a row set into a rewindable ResultSet.
     * Uses up the result set passed in which is no longer valid as a RowSet.
     *
     * @param rowSet
     * @return ResultSetRewindable
     */
    public static ResultSetRewindable makeRewindable(RowSet rowSet) {
        return makeRewindable(ResultSet.adapt(rowSet));
    }

    /**
     * Turns an existing result set into one with peeking capabilities
     * <p>
     * Using the returned result set consumes the result set passed in, the
     * underlying result set must be at the start in order to be made peeking.
     * If you create such a result set you should avoid accessing the underlying
     * result set directly as this may cause results to be missed or put the
     * returned peekable result set into an invalid state.
     * </p>
     * <p> Note that rewindable results may typically also be peekable so may
     * be more broadly applicable if you can afford the cost of loading all the
     * results into memory. </p>
     *
     * @param resultSet
     *            Result set to wrap
     * @return Peekable results
     */
    public static ResultSetPeekable makePeekable(ResultSet resultSet) {
        return new ResultSetPeeking(resultSet);
    }

    /** Return a closable resultset for a {@link QueryExecution}.
     * The {@link QueryExecution} must be for a {@code SELECT} query.
     * <p>
     * Example:
     * <pre>
     *   QueryExecution qExec = QueryExecutionFactory.create(...);
     *   try (ResultSetCloseable rs = ResultSetFactory.closableResultSet(qExec) ) {
     *       ...
     * }
     * </pre>
     *
     * @param queryExecution {@code QueryExecution} must be for a {@code SELECT} query.
     * @return ResultSetCloseable
     */
    public static ResultSetCloseable closeableResultSet(QueryExecution queryExecution) {
        return ResultSetCloseable.closeableResultSet(queryExecution);
    }

    /**
     * Take a copy of a result set - the result set returns is an in-memory
     * copy. It is not attached to the original query execution object which can
     * be closed.
     *
     * @param results
     * @return ResultSet
     */
    public static ResultSetRewindable copyResults(ResultSet results) {
        return new ResultSetMem(results);
    }

    /**
     * Build a result set from one of ARQ's lower level query iterator.
     *
     * @param queryIterator
     * @param vars
     *            List of variables, by name, for the result set
     * @return ResultSet
     */
    public static ResultSet create(QueryIterator queryIterator, List<String> vars) {
        return ResultSetStream.create(Var.varList(vars), queryIterator);
    }
}
