/**
 * 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.jdbc.results.metadata;

import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.Types;
import java.util.List;

import org.apache.jena.jdbc.JdbcCompatibility;
import org.apache.jena.jdbc.results.JenaResultSet;
import org.apache.jena.jdbc.results.SelectResults;
import org.apache.jena.jdbc.results.metadata.columns.ColumnInfo;
import org.apache.jena.jdbc.results.metadata.columns.SparqlColumnInfo;
import org.apache.jena.jdbc.results.metadata.columns.StringColumn;
import org.apache.jena.query.ResultSet ;
import org.apache.jena.query.ResultSetFactory ;
import org.apache.jena.sparql.core.Var ;
import org.apache.jena.sparql.engine.binding.Binding ;
import org.apache.jena.sparql.resultset.ResultSetPeekable ;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Result Set Metadata for {@link SelectResults} instances
 * 
 */
public class SelectResultsMetadata extends JenaResultsMetadata {

    private static final Logger LOGGER = LoggerFactory.getLogger(SelectResultsMetadata.class);

    private ResultSetPeekable innerResults;

    /**
     * Creates new SELECT results metadata
     * 
     * @param results
     *            JDBC result set
     * @param rset
     *            Underlying SPARQL results
     * @throws SQLException
     */
    public SelectResultsMetadata(JenaResultSet results, ResultSetPeekable rset) throws SQLException {
        super(results, makeColumns(results, rset));
        this.innerResults = rset;
    }

    /**
     * Creates new SELECT results metadata
     * 
     * @param results
     *            JDBC result set
     * @param rset
     *            Underlying SPARQL results
     * @throws SQLException
     */
    public SelectResultsMetadata(JenaResultSet results, ResultSet rset) throws SQLException {
        this(results, ResultSetFactory.makePeekable(rset));
    }

    /**
     * Creates new SELECT results metadata
     * 
     * @param metadata Original metadata
     * @param columns
     *            Column metadata
     * @throws SQLException
     */
    public SelectResultsMetadata(SelectResultsMetadata metadata, ColumnInfo[] columns) throws SQLException {
        super(metadata.getJenaResultSet(), columns);
        this.innerResults = metadata.innerResults;
    }

    /**
     * Makes column information for SELECT results
     * 
     * @param results
     *            Result Set
     * @param rset
     *            Underlying SPARQL results
     * @return Column information
     * @throws SQLException
     *             Thrown if the column information cannot be created
     */
    private static ColumnInfo[] makeColumns(JenaResultSet results, ResultSetPeekable rset) throws SQLException {
        List<String> vars = rset.getResultVars();
        ColumnInfo[] columns = new ColumnInfo[vars.size()];

        int level = JdbcCompatibility.normalizeLevel(results.getJdbcCompatibilityLevel());
        boolean columnsAsStrings = JdbcCompatibility.shouldTypeColumnsAsString(level);
        boolean columnsDetected = JdbcCompatibility.shouldDetectColumnTypes(level);

        Binding b = null;
        if (columnsDetected) {
            if (rset.hasNext()) {
                b = rset.peekBinding();
            } else {
                // If we were supposed to detect columns but there is no data
                // available then we will just fallback to typing everything as
                // strings
                columnsAsStrings = true;
                columnsDetected = false;
            }
        }

        for (int i = 0; i < columns.length; i++) {
            if (!columnsAsStrings && !columnsDetected) {
                // Low compatibility, report columns as being typed as
                // JAVA_OBJECT with ARQ Node as the column class
                columns[i] = new SparqlColumnInfo(vars.get(i), Types.JAVA_OBJECT, columnNullable);
                LOGGER.info("Low JDBC compatibility, column " + vars.get(i) + " is being typed as Node");
            } else if (columnsAsStrings) {
                // Medium compatibility, report columns as being typed as
                // NVARCHAR with String as the column class
                columns[i] = new StringColumn(vars.get(i), columnNullable);
                LOGGER.info("Medium JDBC compatibility, column " + vars.get(i) + " is being typed as String");
            } else if (columnsDetected) {
                // High compatibility, detect columns types based on first row
                // of results
                columns[i] = JdbcCompatibility.detectColumnType(vars.get(i), b.get(Var.alloc(vars.get(i))), true);
                LOGGER.info("High compatibility, column " + vars.get(i) + " was detected as being of type "
                        + columns[i].getClassName());
            } else {
                throw new SQLFeatureNotSupportedException("Unknown JDBC compatibility level was set");
            }
        }

        return columns;
    }
}
