/*
 * Copyright 2013 Adam Dubiel, Przemek Hertel.
 *
 * 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 org.polyjdbc.core.schema;

import org.polyjdbc.core.dialect.Dialect;
import org.polyjdbc.core.dialect.MsSqlDialect;
import org.polyjdbc.core.exception.SchemaInspectionException;
import org.polyjdbc.core.transaction.Transaction;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Locale;

class SchemaInspectorImpl implements SchemaInspector {

    private Locale locale;

    private final Transaction transaction;

    private DatabaseMetaData metadata;

    private String catalog;

    private String schemaName;

    private final Dialect dialect;

    SchemaInspectorImpl(Transaction transaction) {
        this(transaction, Locale.ENGLISH);
    }

    SchemaInspectorImpl(Transaction transaction, Locale locale) {
        this(transaction, locale, null, null);
    }

    SchemaInspectorImpl(Transaction transaction, String schemaName, Dialect dialect) {
        this(transaction, Locale.ENGLISH, schemaName, dialect);
    }

    SchemaInspectorImpl(Transaction transaction, Locale locale, String schemaName, Dialect dialect) {
        this.transaction = transaction;
        this.locale = locale;
        this.schemaName = schemaName;
        this.dialect = dialect;
        extractMetadata(transaction);
    }

    private void extractMetadata(Transaction transaction) {
        try {
            Connection connection = transaction.getConnection();
            metadata = connection.getMetaData();
            catalog = connection.getCatalog();
        } catch (SQLException exception) {
            throw new SchemaInspectionException("METADATA_EXTRACTION_ERROR", "Failed to obtain metadata from connection.", exception);
        }
    }

    @Override
    public boolean relationExists(String name) {
        try {
            ResultSet resultSet = metadata.getTables(catalog, schemaName, convertCase(name), new String[]{"TABLE"});
            transaction.registerCursor(resultSet);

            if (schemaName != null) {
                return resultSet.next();
            } else {
                while (resultSet.next()) {
                    String tableSchemaName = resultSet.getString("TABLE_SCHEM");
                    if ( tableSchemaName == null ||
                         tableSchemaName.equalsIgnoreCase("public") ||
                         tableSchemaName.equals("") ||
                        (dialect instanceof MsSqlDialect && tableSchemaName.equalsIgnoreCase("dbo"))
                    ) {
                        return true;
                    }
                }
                return false;
            }
        } catch (SQLException exception) {
            throw new SchemaInspectionException("RELATION_LOOKUP_ERROR", "Failed to obtain relation list when looking for relation " + name, exception);
        }
    }

    @Override
    public boolean indexExists(String relationName, String indexName) {
        try {
            ResultSet resultSet = metadata.getIndexInfo(catalog, schemaName, convertCase(relationName), false, true);
            transaction.registerCursor(resultSet);

            String normalizedIndexName = convertCase(indexName);
            while(resultSet.next()) {
                String rsetIndexName = resultSet.getString("INDEX_NAME");
                if(rsetIndexName !=null && rsetIndexName.equals(normalizedIndexName)) {
                    return true;
                }
            }
            
            return false;
        } catch (SQLException exception) {
            throw new SchemaInspectionException("INDEX_LOOKUP_ERROR", "Failed to obtain relation index list when looking for indexes for relation " + relationName, exception);
        }
    }

    private String convertCase(String identifier) throws SQLException {
        if(metadata.storesLowerCaseIdentifiers()) {
            return identifier.toLowerCase(locale);
        }
        if(metadata.storesUpperCaseIdentifiers()) {
            return identifier.toUpperCase(locale);
        }

        return identifier;
    }

    @Override
    public void close() {
        transaction.commit();
        transaction.close();
    }
}