/*
 *    GeoTools - OpenSource mapping toolkit
 *    http://geotools.org
 *    (C) 2004-2006, GeoTools Project Managment Committee (PMC)
 *    (C) 2004, Institut de Recherche pour le Développement
 *   
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation;
 *    version 2.1 of the License.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 */
package org.geotools.metadata.sql;

// J2SE dependencies
import java.sql.Array;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.geotools.util.logging.Logging;


/**
 * The result of a query for metadata attributes. This object {@linkplain PreparedStatement
 * prepares a statement} once for ever for a given table. When a particular record in this
 * table is fetched, the {@link ResultSet} is automatically constructed. If many attributes
 * are fetched consecutivly for the same record, then the same {@link ResultSet} is reused.
 *
 * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/metadata/src/main/java/org/geotools/metadata/sql/MetadataResult.java $
 * @version $Id: MetadataResult.java 27862 2007-11-12 19:51:19Z desruisseaux $
 * @author Martin Desruisseaux
 *
 * @since 2.1
 *
 * @todo Automatically close the ResultSet after some delay (e.g. 2 minutes).
 */
final class MetadataResult {
    /**
     * The table name, used for formatting error message.
     */
    private final String tableName;

    /**
     * The statements for a specific table.
     */
    private final PreparedStatement statement;

    /**
     * The results, or {@code null} if not yet determined.
     */
    private ResultSet results;

    /**
     * The identifier (usually the primary key) for current results.
     * If the record to fetch doesn't have the same identifier, then
     * the {@link #results} will need to be closed and reconstructed.
     */
    private String identifier;
    
    /**
     * Constructs a metadata result from the specified connection.
     *
     * @param  connection The connection to the database.
     * @param  query The SQL query. The first question mark will be replaced
     *               by the table name.
     * @param  tableName The table name.
     * @throws SQLException if the statement can't be created.
     */
    public MetadataResult(final Connection connection,
                          final String     query,
                          final String     tableName)
            throws SQLException
    {
        this.tableName = tableName;
        final int index = query.indexOf('?');
        if (index < 0) {
            // TODO: localize
            throw new SQLException("Invalid query");
        }
        final StringBuffer buffer = new StringBuffer(query);
        buffer.replace(index, index+1, tableName);
        statement = connection.prepareStatement(buffer.toString());
    }

    /**
     * Returns the result set for the given record.
     *
     * @param  identifier The object identifier, usually the primary key value.
     * @return The result set.
     * @throws SQLException if an SQL operation failed.
     */
    private ResultSet getResultSet(final String identifier) throws SQLException {
        if (results != null) {
            if (this.identifier.equals(identifier)) {
                return results;
            }
            if (results.next()) {
                // TODO: Localize
                Logging.getLogger("org.geotools.metadata.sql")
                      .warning("Duplicate identifier: "+identifier);
            }
            results.close();
            results = null; // In case the 'results = ...' below will fails.
        }
        this.identifier = identifier;
        statement.setString(1, identifier);
        results = statement.executeQuery();
        if (!results.next()) {
            results.close();
            results = null;
            throw new SQLException("Metadata not found: \""+identifier+"\" in table \""+tableName+'"');
            // TODO: localize
        }
        return results;
    }

    /**
     * Returns the attribute value in the given column for the given record.
     *
     * @param  identifier The object identifier, usually the primary key value.
     * @param  columnName The column name of the attribute to search.
     * @return The attribute value.
     * @throws SQLException if an SQL operation failed.
     */
    public Object getObject(final String identifier, final String columnName) throws SQLException {
        return getResultSet(identifier).getObject(columnName);
    }

    /**
     * Returns the attribute value in the given column for the given record.
     *
     * @param  identifier The object identifier, usually the primary key value.
     * @param  columnName The column name of the attribute to search.
     * @return The attribute value.
     * @throws SQLException if an SQL operation failed.
     */
    public Object getArray(final String identifier, final String columnName) throws SQLException {
        final Array array = getResultSet(identifier).getArray(columnName);
        return (array!=null) ? array.getArray() : null;
    }

    /**
     * Returns the attribute value in the given column for the given record.
     *
     * @param  identifier The object identifier, usually the primary key value.
     * @param  columnName The column name of the attribute to search.
     * @return The attribute value.
     * @throws SQLException if an SQL operation failed.
     */
    public int getInt(final String identifier, final String columnName) throws SQLException {
        return getResultSet(identifier).getInt(columnName);
    }

    /**
     * Returns the attribute value in the given column for the given record.
     *
     * @param  identifier The object identifier, usually the primary key value.
     * @param  columnName The column name of the attribute to search.
     * @return The attribute value.
     * @throws SQLException if an SQL operation failed.
     */
    public String getString(final String identifier, final String columnName) throws SQLException {
        return getResultSet(identifier).getString(columnName);
    }

    /**
     * Returns the string value in the first column of the given record.
     * This is used for fetching the name of a code list element.
     *
     * @param  code The object identifier, usually the primary key value.
     * @return The string value found in the first column.
     * @throws SQLException if an SQL operation failed.
     */
    public String getString(final String code) throws SQLException {
        return getResultSet(code).getString(1);
    }

    /**
     * Returns {@code true} if the last value returned by a {@code getFoo} method was null.
     */
    public boolean wasNull() throws SQLException {
        return results.wasNull();
    }

    /**
     * Close this statement and free all resources.
     * After this method has been invoked, this object can't be used anymore.
     *
     * @throws SQLException if an SQL operation failed.
     */
    public void close() throws SQLException {
        if (results != null) {
            results.close();
            results = null;
        }
        statement.close();
    }
}
