/*
 * 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.*;
import org.netbeans.modules.db.metadata.model.spi.SchemaImplementation;

/**
 *
 * @author Andrei Badea
 */
public class JDBCSchema extends SchemaImplementation {

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

    protected final JDBCCatalog jdbcCatalog;
    protected final String name;
    protected final boolean _default;
    protected final boolean synthetic;

    protected Map<String, Table> tables;
    protected Map<String, View> views;
    protected Map<String, Procedure> procedures;
    protected Map<String, Function> functions;

    public JDBCSchema(JDBCCatalog jdbcCatalog, String name, boolean _default, boolean synthetic) {
        this.jdbcCatalog = jdbcCatalog;
        this.name = name;
        this._default = _default;
        this.synthetic = synthetic;
    }

    @Override
    public final Catalog getParent() {
        return jdbcCatalog.getCatalog();
    }

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

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

    @Override
    public final boolean isSynthetic() {
        return synthetic;
    }

    @Override
    public final Collection<Table> getTables() {
        return initTables().values();
    }

    @Override
    public final Table getTable(String name) {
        return MetadataUtilities.find(name, initTables());
    }

    @Override
    public View getView(String name) {
        return MetadataUtilities.find(name, initViews());
    }

    @Override
    public Collection<View> getViews() {
        return initViews().values();
    }

    @Override
    public Procedure getProcedure(String name) {
        return initProcedures().get(name);
    }

    @Override
    public Collection<Procedure> getProcedures() {
        return initProcedures().values();
    }

    @Override
    public Function getFunction(String name) {
        return initFunctions().get(name);
    }

    @Override
    public Collection<Function> getFunctions() {
        return initFunctions().values();
    }

    @Override
    public void refresh() {
        tables = null;
        views = null;
        procedures = null;
    }

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

    protected JDBCTable createJDBCTable(String name, boolean system) {
        return new JDBCTable(this, name, system);
    }

    protected JDBCProcedure createJDBCProcedure(String procedureName) {
        return new JDBCProcedure(this, procedureName);
    }

    protected JDBCFunction createJDBCFunction(String functionName) {
        return new JDBCFunction(this, functionName);
    }

    protected JDBCView createJDBCView(String viewName) {
        return new JDBCView(this, viewName);
    }

    protected void createTables() {
        LOGGER.log(Level.FINE, "Initializing tables in {0}", this);
        Map<String, Table> newTables = new LinkedHashMap<String, Table>();
        try {
            ResultSet rs = MetadataUtilities.getTables(jdbcCatalog.getJDBCMetadata().getDmd(),
                    jdbcCatalog.getName(), name, "%", new String[]{"TABLE", "SYSTEM TABLE"}); // NOI18N
            if (rs != null) {
                try {
                    while (rs.next()) {
                        String type = MetadataUtilities.trimmed(rs.getString("TABLE_TYPE")); //NOI18N
                        String tableName = MetadataUtilities.trimmed(rs.getString("TABLE_NAME")); // NOI18N
                        Table table = createJDBCTable(tableName, type.contains("SYSTEM")).getTable(); //NOI18N
                        newTables.put(tableName, table);
                        LOGGER.log(Level.FINE, "Created table {0}", table); //NOI18N
                    }
                } finally {
                    rs.close();
                }
            }
        } catch (SQLException e) {
            throw new MetadataException(e);
        }
        tables = Collections.unmodifiableMap(newTables);
    }

    protected void createViews() {
        LOGGER.log(Level.FINE, "Initializing views in {0}", this);
        Map<String, View> newViews = new LinkedHashMap<String, View>();
        try {
            ResultSet rs = MetadataUtilities.getTables(jdbcCatalog.getJDBCMetadata().getDmd(),
                    jdbcCatalog.getName(), name, "%", new String[]{"VIEW"}); // NOI18N
            if (rs != null) {
                try {
                    while (rs.next()) {
                        String viewName = MetadataUtilities.trimmed(rs.getString("TABLE_NAME")); // NOI18N
                        View view = createJDBCView(viewName).getView();
                        newViews.put(viewName, view);
                        LOGGER.log(Level.FINE, "Created view {0}", view); // NOI18N
                    }
                } finally {
                    rs.close();
                }
            }
        } catch (SQLException e) {
            throw new MetadataException(e);
        }
        views = Collections.unmodifiableMap(newViews);
    }

    protected void createProcedures() {
        LOGGER.log(Level.FINE, "Initializing procedures in {0}", this);
        Map<String, Procedure> newProcedures = new LinkedHashMap<String, Procedure>();
        try {
            ResultSet rs = MetadataUtilities.getProcedures(jdbcCatalog.getJDBCMetadata().getDmd(),
                    jdbcCatalog.getName(), name, "%"); // NOI18N
            if (rs != null) {
                try {
                    while (rs.next()) {
                        String procedureName = MetadataUtilities.trimmed(rs.getString("PROCEDURE_NAME")); // NOI18N
                        Procedure procedure = createJDBCProcedure(procedureName).getProcedure();
                        newProcedures.put(procedureName, procedure);
                        LOGGER.log(Level.FINE, "Created procedure {0}", procedure); //NOI18N
                    }
                } finally {
                    rs.close();
                }
            }
        } catch (SQLException e) {
            throw new MetadataException(e);
        }
        procedures = Collections.unmodifiableMap(newProcedures);
    }

    protected void createFunctions() {
        LOGGER.log(Level.FINE, "Initializing functions in {0}", this); //NOI18N
        Map<String, Function> newProcedures = new LinkedHashMap<String, Function>();
        try {
            ResultSet rs = MetadataUtilities.getFunctions(jdbcCatalog.getJDBCMetadata().getDmd(),
                    jdbcCatalog.getName(), name, "%"); // NOI18N
            if (rs != null) {
                try {
                    while (rs.next()) {
                        String functionName = MetadataUtilities.trimmed(rs.getString("FUNCTION_NAME")); // NOI18N
                        Function function = createJDBCFunction(functionName).getFunction();
                        newProcedures.put(functionName, function);
                        LOGGER.log(Level.FINE, "Created function {0}", function); //NOI18N
                    }
                } finally {
                    rs.close();
                }
            }
        } catch (SQLException e) {
            throw new MetadataException(e);
        }
        functions = Collections.unmodifiableMap(newProcedures);
    }

    private Map<String, Table> initTables() {
        if (tables != null) {
            return tables;
        }
        createTables();
        return tables;
    }

    public final JDBCCatalog getJDBCCatalog() {
        return jdbcCatalog;
    }

    private Map<String, View> initViews() {
        if (views != null) {
            return views;
        }
        createViews();
        return views;
    }

    private Map<String, Procedure> initProcedures() {
        if (procedures != null) {
            return procedures;
        }

        createProcedures();
        return procedures;
    }

    private Map<String, Function> initFunctions() {
        if (functions != null) {
            return functions;
        }

        createFunctions();
        return functions;
    }
}
