package org.dbflute.erflute.db;

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.Driver;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.StringTokenizer;

import org.dbflute.erflute.core.util.Check;
import org.dbflute.erflute.editor.model.settings.JDBCDriverSetting;
import org.dbflute.erflute.preference.PreferenceInitializer;
import org.dbflute.erflute.preference.jdbc.JDBCPathDialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.ui.PlatformUI;

/**
 * @author modified by jflute (originated in ermaster)
 */
public abstract class DBManagerBase implements DBManager {

    private final Set<String> reservedWords;

    public DBManagerBase() {
        DBManagerFactory.addDB(this);
        this.reservedWords = this.getReservedWords();
    }

    @Override
    public String getURL(String serverName, String dbName, int port) {
        String temp = serverName.replaceAll("\\\\", "\\\\\\\\");
        String url = this.getURL().replaceAll("<SERVER NAME>", temp);
        url = url.replaceAll("<PORT>", String.valueOf(port));
        temp = dbName.replaceAll("\\\\", "\\\\\\\\");
        url = url.replaceAll("<DB NAME>", temp);
        return url;
    }

    @Override
    public Class<Driver> getDriverClass(String driverClassName) {
        String path = null;
        try {
            try {
                @SuppressWarnings("unchecked")
                final Class<Driver> clazz = (Class<Driver>) Class.forName(driverClassName);
                return clazz;
            } catch (final ClassNotFoundException e) {
                path = PreferenceInitializer.getJDBCDriverPath(this.getId(), driverClassName);
                if (Check.isEmpty(path)) {
                    throw new IllegalStateException(String.format("JDBC Driver Class \"%s\" is not found", driverClassName));
                }
                final ClassLoader loader = this.getClassLoader(path);
                @SuppressWarnings("unchecked")
                final Class<Driver> clazz = (Class<Driver>) loader.loadClass(driverClassName);
                return clazz;
            }
        } catch (final MalformedURLException | ClassNotFoundException e) {
            final JDBCPathDialog dialog = new JDBCPathDialog(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), this.getId(),
                    driverClassName, path, new ArrayList<JDBCDriverSetting>(), false);
            if (dialog.open() == IDialogConstants.OK_ID) {
                final JDBCDriverSetting newDriverSetting =
                        new JDBCDriverSetting(this.getId(), dialog.getDriverClassName(), dialog.getPath());
                final List<JDBCDriverSetting> driverSettingList = PreferenceInitializer.getJDBCDriverSettingList();
                if (driverSettingList.contains(newDriverSetting)) {
                    driverSettingList.remove(newDriverSetting);
                }
                driverSettingList.add(newDriverSetting);
                PreferenceInitializer.saveJDBCDriverSettingList(driverSettingList);
                return this.getDriverClass(dialog.getDriverClassName());
            }
        }
        return null;
    }

    private ClassLoader getClassLoader(String uri) throws MalformedURLException {
        final StringTokenizer tokenizer = new StringTokenizer(uri, ";");
        final int count = tokenizer.countTokens();
        final URL[] urls = new URL[count];
        for (int i = 0; i < urls.length; i++) {
            urls[i] = new URL("file", "", tokenizer.nextToken());
        }
        return new URLClassLoader(urls, this.getClass().getClassLoader());
    }

    protected abstract String getURL();

    @Override
    public abstract String getDriverClassName();

    protected Set<String> getReservedWords() {
        final Set<String> reservedWords = new HashSet<>();
        final ResourceBundle bundle = ResourceBundle.getBundle(this.getClass().getPackage().getName() + ".reserved_word");
        final Enumeration<String> keys = bundle.getKeys();
        while (keys.hasMoreElements()) {
            reservedWords.add(keys.nextElement().toUpperCase());
        }
        return reservedWords;
    }

    @Override
    public boolean isReservedWord(String str) {
        return reservedWords.contains(str.toUpperCase());
    }

    @Override
    public boolean isSupported(int supportItem) {
        final int[] supportItems = this.getSupportItems();

        for (int i = 0; i < supportItems.length; i++) {
            if (supportItems[i] == supportItem) {
                return true;
            }
        }

        return false;
    }

    @Override
    public boolean doesNeedURLDatabaseName() {
        return true;
    }

    @Override
    public boolean doesNeedURLServerName() {
        return true;
    }

    abstract protected int[] getSupportItems();

    @Override
    public List<String> getImportSchemaList(Connection con) throws SQLException {
        final List<String> schemaList = new ArrayList<>();
        final DatabaseMetaData metaData = con.getMetaData();
        try {
            final ResultSet rs = metaData.getSchemas();
            while (rs.next()) {
                schemaList.add(rs.getString(1));
            }
        } catch (final SQLException ignored) {
            // when schema is not supported
        }
        return schemaList;
    }

    @Override
    public List<String> getSystemSchemaList() {
        return new ArrayList<>();
    }
}

@@ -2039,16 +2039,45 @@
 ception(
+%0A                            
 String.f
@@ -2120,18 +2120,112 @@
 ot found
-%22,
+.%5CrIs %5C%22Preferences%3E ERFlute%3E JDBC Driver%5C%22 set correctly?%22,%0A                                   
  driverC

Fix error message.