/*
 * 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.sparql.resultset;

import java.util.ArrayList;

import org.apache.jena.atlas.logging.Log;
import org.apache.jena.datatypes.xsd.XSDDatatype;
import org.apache.jena.query.ResultSet;
import org.apache.jena.rdf.model.*;
import org.apache.jena.shared.JenaException;
import org.apache.jena.shared.PropertyNotFoundException;
import org.apache.jena.sparql.core.Var;
import org.apache.jena.sparql.engine.binding.Binding;
import org.apache.jena.sparql.engine.binding.BindingBuilder;
import org.apache.jena.sparql.vocabulary.ResultSetGraphVocab;
import org.apache.jena.vocabulary.RDF;

public class RDFInput extends ResultSetMem {
    /**
     * Process a result set encoded in RDF according to
     * <code>@link{http://www.w3.org/2001/sw/DataAccess/tests/result-set}#</code>
     *
     * @param model
     */
    public RDFInput(Model model) {
        buildFromDumpFormat(model);
    }

    // Convert from RDF model to in-memory result set
    private void buildFromDumpFormat(Model resultsModel) {
        varNames = new ArrayList<>();
        StmtIterator sIter = resultsModel.listStatements(null, RDF.type, ResultSetGraphVocab.ResultSet);
        for ( ; sIter.hasNext() ; ) {
            // For each root
            Statement s = sIter.nextStatement();
            Resource root = s.getSubject();
            buildOneResource(root);
        }
        sIter.close();
        reset();
    }

    private void buildOneResource(Resource root) {
        buildVariables(root);
        int count = buildPreprocess(root);
        if ( root.getModel().contains(null, ResultSetGraphVocab.index, (RDFNode)null) )
            buildRowsOrdered(root, count);
        else
            buildRows(root);
    }

    private void buildVariables(Resource root) {
        // Variables
        StmtIterator rVarsIter = root.listProperties(ResultSetGraphVocab.resultVariable);
        for ( ; rVarsIter.hasNext() ; ) {
            String varName = rVarsIter.nextStatement().getString();
            varNames.add(varName);
        }
        rVarsIter.close();
    }

    private int buildPreprocess(Resource root) {
        StmtIterator solnIter = root.listProperties(ResultSetGraphVocab.solution);
        int rows = 0;
        int indexed = 0;
        for ( ; solnIter.hasNext() ; ) {
            Resource soln = solnIter.nextStatement().getResource();
            rows++;
            if ( soln.hasProperty(ResultSetGraphVocab.index) )
                indexed++;
        }
        solnIter.close();
        if ( indexed > 0 && rows != indexed ) {
            Log.warn(this, "Rows = " + rows + " but only " + indexed + " indexes");
            return rows;
        }
        return rows;
    }

    private void buildRowsOrdered(Resource root, int count) {
        Model m = root.getModel();
        BindingBuilder builder = Binding.builder();
        // Assume one result set per file.
        for ( int index = 1 ;; index++ ) {
            Literal ind = m.createTypedLiteral(index, XSDDatatype.XSDinteger);
            StmtIterator sIter = m.listStatements(null, ResultSetGraphVocab.index, ind);
            if ( !sIter.hasNext() )
                break;
            Statement s = sIter.nextStatement();
            if ( sIter.hasNext() )
                Log.warn(this, "More than one solution: index = " + index);
            Resource soln = s.getSubject();

            Binding rb = buildBinding(builder, soln);
            rows.add(rb);
            sIter.close();
        }
        if ( rows.size() != count )
            Log.warn(this, "Found " + rows.size() + ": expected " + count);
    }

    private void buildRows(Resource root) {
        // Now the results themselves
        int count = 0;
        StmtIterator solnIter = root.listProperties(ResultSetGraphVocab.solution);
        BindingBuilder builder = Binding.builder();
        for ( ; solnIter.hasNext() ; ) {
            Resource soln = solnIter.nextStatement().getResource();
            count++;
            Binding rb = buildBinding(builder, soln);
            rows.add(rb);
        }
        solnIter.close();

        if ( root.hasProperty(ResultSetGraphVocab.size) ) {
            try {
                int size = root.getRequiredProperty(ResultSetGraphVocab.size).getInt();
                if ( size != count )
                    Log.warn(this, "Warning: Declared size = " + size + " : Count = " + count);
            }
            catch (JenaException rdfEx) {}
        }
    }

    private Binding buildBinding(BindingBuilder builder, Resource soln) {
        builder.reset();
        // foreach row
        StmtIterator bindingIter = soln.listProperties(ResultSetGraphVocab.binding);
        for ( ; bindingIter.hasNext() ; ) {
            Resource binding = bindingIter.nextStatement().getResource();

            String var = binding.getRequiredProperty(ResultSetGraphVocab.variable).getString();
            try {
                RDFNode val = binding.getRequiredProperty(ResultSetGraphVocab.value).getObject();
                builder.add(Var.alloc(var), val.asNode());
            }
            catch (PropertyNotFoundException ex) {
                Log.warn(this, "Failed to get value for ?" + var);
            }

            // We include the value even if it is the marker term "rs:undefined"
            // if ( val.equals(ResultSetVocab.undefined))
            // continue ;
            // The ResultSetFormatter code equates null (not found) with
            // rs:undefined. When Jena JUnit testing, it does not matter if the
            // recorded result has the term absent or explicitly undefined.

        }
        bindingIter.close();
        return builder.build();
    }

    /**
     * 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 fromRDF(Model model) {
        return new RDFInput(model);
    }
}
