/*
 * 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.netbeans.modules.db.metadata.model.jdbc;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.netbeans.modules.db.metadata.model.MetadataUtilities;
import org.netbeans.modules.db.metadata.model.api.MetadataException;
import org.netbeans.modules.db.metadata.model.api.Schema;
import org.netbeans.modules.db.metadata.model.spi.CatalogImplementation;

/**
 *
 * @author Andrei Badea
 */
public class JDBCCatalog extends CatalogImplementation {

    private static final Logger LOGGER = Logger.getLogger(JDBCCatalog.class.getName());

    protected final JDBCMetadata jdbcMetadata;
    protected final String name;
    protected final boolean _default;
    protected final String defaultSchemaName;

    protected Schema defaultSchema;
    protected Schema syntheticSchema;
    protected Map<String, Schema> schemas;

    public JDBCCatalog(JDBCMetadata jdbcMetadata, String name, boolean _default, String defaultSchemaName) {
        // defaultSchemaName only makes sense for the default catalog.
        assert defaultSchemaName == null || _default;
        this.jdbcMetadata = jdbcMetadata;
        this.name = name;
        this._default = _default;
        this.defaultSchemaName = defaultSchemaName;
        LOGGER.log(Level.FINE, "Create JDBCCatalog(jdbcMetadata={0}, name={1}, _default={2}, defaultSchemaName={3})",
                new Object[] {jdbcMetadata, name, _default, defaultSchemaName});
    }

    @Override
    public final String getName() {
        return name;
    }

    @Override
    public final boolean isDefault() {
        return _default;
    }

    @Override
    public final Schema getSyntheticSchema() {
        initSchemas();
        return syntheticSchema;
    }

    @Override
    public final Collection<Schema> getSchemas() {
        return initSchemas().values();
    }

    @Override
    public final Schema getSchema(String name) {
        return MetadataUtilities.find(name, initSchemas());
    }

    @Override
    public final void refresh() {
        schemas = null;
    }

    @Override
    public String toString() {
        return "JDBCCatalog[name='" + name + "',default=" + _default + "]"; // NOI18N
    }

    public final Schema getDefaultSchema() {
        initSchemas();
        return defaultSchema;
    }

    protected JDBCSchema createJDBCSchema(String name, boolean _default, boolean synthetic) {
        return new JDBCSchema(this, name, _default, synthetic);
    }

    protected void createSchemas() {
        Map<String, Schema> newSchemas = new LinkedHashMap<String, Schema>();
        try {
            if (jdbcMetadata.getDmd().supportsSchemasInTableDefinitions()) {
                ResultSet rs = jdbcMetadata.getDmd().getSchemas();
                if (rs != null) {
                    int columnCount = rs.getMetaData().getColumnCount();
                    if (columnCount < 2) {
                        LOGGER.fine("DatabaseMetaData.getSchemas() not JDBC 3.0-compliant");
                    }
                    boolean supportsCatalog = jdbcMetadata.getDmd().supportsCatalogsInTableDefinitions();
                    try {
                        while (rs.next()) {
                            String schemaName = MetadataUtilities.trimmed(rs.getString("TABLE_SCHEM")); // NOI18N
                            // Workaround for pre-JDBC 3.0 drivers, where DatabaseMetaData.getSchemas()
                            // only returns a TABLE_SCHEM column.
                            String catalogName = columnCount > 1 && supportsCatalog ? MetadataUtilities.trimmed(rs.getString("TABLE_CATALOG")) : name; // NOI18N
                            LOGGER.log(Level.FINE, "Read schema ''{0}'' in catalog ''{1}''", new Object[] { schemaName, catalogName });
                            LOGGER.log(Level.FINEST, "MetadataUtilities.equals(catalogName=''{0}'', name=''{1}'') returns {2}",
                                    new Object[] {catalogName, name, MetadataUtilities.equals(catalogName, name)});
                            if (MetadataUtilities.equals(catalogName, name)) {
                                if (defaultSchemaName != null && MetadataUtilities.equals(schemaName, defaultSchemaName)) {
                                    defaultSchema = createJDBCSchema(defaultSchemaName, true, false).getSchema();
                                    newSchemas.put(defaultSchema.getName(), defaultSchema);
                                    LOGGER.log(Level.FINE, "Created default schema {0}", defaultSchema);
                                } else {
                                    Schema schema = createJDBCSchema(schemaName, false, false).getSchema();
                                    newSchemas.put(schemaName, schema);
                                    LOGGER.log(Level.FINE, "Created schema {0}", schema);
                                }
                            }
                        }
                    } finally {
                        if (rs != null) {
                            rs.close();
                        }
                    }
                } else {
                    LOGGER.info(this + " returns null from jdbcMetadata.getDmd().getSchemas().");
                }
            }
            if (newSchemas.isEmpty() && !jdbcMetadata.getDmd().supportsSchemasInTableDefinitions()) {
                syntheticSchema = createJDBCSchema(null, _default, true).getSchema();
                if (_default) {
                    defaultSchema = syntheticSchema;
                }
                LOGGER.log(Level.FINE, "Created synthetic schema {0}", syntheticSchema);
            }
        } catch (SQLException e) {
            throw new MetadataException(e);
        }
        schemas = Collections.unmodifiableMap(newSchemas);
    }

    private Map<String, Schema> initSchemas() {
        if (schemas != null) {
            return schemas;
        }
        LOGGER.log(Level.FINE, "Initializing schemas in {0}", this);
        createSchemas();
        return schemas;
    }

    public final JDBCMetadata getJDBCMetadata() {
        return jdbcMetadata;
    }
}
