/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2021 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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.jboss.installer.screens;

import org.jboss.installer.common.UiResources;
import org.jboss.installer.core.DatabaseDriver;
import org.jboss.installer.core.InstallationData;
import org.jboss.installer.core.LanguageUtils;
import org.jboss.installer.core.Screen;
import org.jboss.installer.core.ScreenManager;
import org.jboss.installer.core.ValidationResult;
import org.jboss.installer.panels.DatasourcePropertiesPanel;
import org.jboss.installer.postinstall.PostInstallTask;
import org.jboss.installer.postinstall.task.JDBCDriverConfig;
import org.jboss.installer.postinstall.task.DatasourceConfig;
import org.jboss.installer.core.DatasourceConnectionUtils;
import org.jboss.installer.validators.DatasourceValidator;
import org.jboss.installer.validators.PasswordEqualityValidator;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JTextField;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.util.HashMap;
import java.util.List;

public class DatasourceScreen extends DefaultScreen {

    public static final String NAME = "DatasourceScreen";
    public static final String TITLE_KEY = "datasource.title";
    public static final String DESCRIPTION_KEY = "datasource.description";
    public static final String DATASOURCE_NAME_KEY = "datasource.name";
    public static final String DATASOURCE_JNDI_KEY = "datasource.jndi";
    public static final String MIN_POOL_SIZE_KEY = "datasource.min_pool_size";
    public static final String MAX_POOL_SIZE_KEY = "datasource.max_pool_size";
    public static final String SECURITY_TYPE_KEY = "datasource.security_type";
    public static final String SECURITY_TYPE_USER_KEY = "datasource.security_type.user";
    public static final String SECURITY_TYPE_DOMAIN_KEY = "datasource.security_type.domain";
    public static final String USER_USERNAME_KEY = "datasource.security_type.user.username";
    public static final String USER_PASSWORD_KEY = "datasource.security_type.user.password";
    public static final String USER_CONFIRM_PASSWORD_KEY = "datasource.security_type.user.confirm_password";
    public static final String DATASOURCE_TYPE_KEY = "datasource.type";
    public static final String DATASOURCE_CONNECTION_URL_KEY = "datasource.type.datasource.connection_url";
    public static final String XA_USER_KEY = "datasource.type.datasource_xa.recovery_user";
    public static final String XA_PASSWORD_KEY = "datasource.type.datasource_xa.recovery_password";
    public static final String CONFIRM_XA_PASSWORD_KEY = "datasource.type.datasource_xa.confirm_recovery_password";
    public static final String TEST_CONNECTION_KEY = "datasource.test_connection";
    public static final int USER_SECURITY_TYPE = 1;
    public static final int XA_DATASOURCE_TYPE = 1;

    private final JLabel securityTypeLabel = createFieldLabel(langUtils.getString(SECURITY_TYPE_DOMAIN_KEY) + ":", false);
    private final JLabel usernameLabel = createFieldLabel(USER_USERNAME_KEY);
    private final JLabel passwordLabel = createFieldLabel(USER_PASSWORD_KEY);
    private final JLabel confirmPasswordLabel = createFieldLabel(USER_CONFIRM_PASSWORD_KEY);
    private final JLabel connectionUrlLabel = createFieldLabel(DATASOURCE_CONNECTION_URL_KEY);
    private final JLabel xaUsernameLabel = createFieldLabel(XA_USER_KEY);
    private final JLabel xaPasswordLabel = createFieldLabel(XA_PASSWORD_KEY);
    private final JLabel confirmXaPasswordLabel = createFieldLabel(CONFIRM_XA_PASSWORD_KEY);
    private final JTextField datasourceNameField = createTextField("myNewDatasource");
    private final JTextField jndiNameField = createTextField("");
    private final JFormattedTextField minPoolSizeField = createPoolSizeField(0);
    private final JFormattedTextField maxPoolSizeField = createPoolSizeField(20);
    private final JComboBox securityTypeDropdown = createDropdown(new String[]{SECURITY_TYPE_DOMAIN_KEY, SECURITY_TYPE_USER_KEY});
    private final JTextField securityDomainNameField = createTextField("mySecurityDomain");
    private final JTextField usernameField = createTextField("");
    private final JPasswordField passwordField = createPasswordField();
    private final JPasswordField confirmPasswordField = createPasswordField();
    private final JComboBox datasourceTypeDropdown = createDropdown(new String[]{"Datasource", "XA Datasource"}, false);
    private final JTextField connectionURLField = createTextField("");
    private final JTextField xaUsernameField = createTextField("");
    private final JPasswordField xaPasswordField = createPasswordField();
    private final JPasswordField confirmXaPasswordField = createPasswordField();
    private  List<String> jarPaths;
    private DatabaseDriver driver;
    private DatabaseDriver previousDriver;
    private DatasourcePropertiesPanel propertiesPanel;
    private DatasourceConfig config;
    private JButton testConnectionButton = createButton(TEST_CONNECTION_KEY,
            actionEvent ->
                    new DatasourceConnectionUtils(langUtils).testDatabaseConnection(UiResources.readTextValue(usernameField),
                            UiResources.readTextValue(passwordField),
                            UiResources.readTextValue(connectionURLField),
                            jarPaths,
                            propertiesPanel.getXaPropertyList(),
                            driver,
                            datasourceTypeDropdown.getSelectedIndex() == XA_DATASOURCE_TYPE)
    );

    public DatasourceScreen(Screen parent, LanguageUtils langUtils, boolean isActive) {
        super(parent, langUtils, isActive);
    }

    @Override
    public String getTitle() {
        return langUtils.getString(TITLE_KEY);
    }

    @Override
    public String getName() {
        return NAME;
    }

    @Override
    public JPanel getContent() {
        JPanel content = new JPanel(new GridBagLayout());
        GridBagConstraints c = initializeConstraints();
        content.setBorder(BorderFactory.createEmptyBorder(0,0,0,10));

        c.insets = DESCRIPTION_INSET;
        c.gridwidth = 3;
        content.add(createDescription(DESCRIPTION_KEY), c);
        c.gridy++;

        c.gridwidth = 1;
        addField(content, c, DATASOURCE_NAME_KEY, datasourceNameField);
        addField(content, c, DATASOURCE_JNDI_KEY, jndiNameField);
        addField(content, c, MIN_POOL_SIZE_KEY, minPoolSizeField);
        addField(content, c, MAX_POOL_SIZE_KEY, maxPoolSizeField);

        addField(content, c, SECURITY_TYPE_KEY, securityTypeDropdown);
        securityTypeDropdown.addActionListener(actionEvent -> {
            setSecurityFieldsVisibility(securityTypeDropdown.getSelectedIndex() == USER_SECURITY_TYPE);
            testConnectionButton.setEnabled(securityTypeDropdown.getSelectedIndex() == USER_SECURITY_TYPE);
        });
        addOptionalField(content, c, securityTypeLabel, securityDomainNameField);
        addToolTip(securityTypeLabel, SECURITY_TYPE_DOMAIN_KEY + ".tooltip");
        addToolTip(securityDomainNameField, SECURITY_TYPE_DOMAIN_KEY + ".tooltip");
        addOptionalField(content, c, usernameLabel, usernameField);
        addToolTip(usernameLabel, USER_USERNAME_KEY + ".tooltip");
        addToolTip(usernameField, USER_USERNAME_KEY + ".tooltip");
        addOptionalField(content, c, passwordLabel, passwordField);
        addToolTip(passwordLabel, USER_PASSWORD_KEY + ".tooltip");
        addToolTip(passwordField, USER_PASSWORD_KEY + ".tooltip");
        addOptionalField(content, c, confirmPasswordLabel, confirmPasswordField);
        addToolTip(confirmPasswordLabel, USER_CONFIRM_PASSWORD_KEY + ".tooltip");
        addToolTip(confirmPasswordField, USER_CONFIRM_PASSWORD_KEY + ".tooltip");

        addField(content, c, DATASOURCE_TYPE_KEY, datasourceTypeDropdown);
        datasourceTypeDropdown.addActionListener(actionEvent -> {
            setDatasourceTypeVisibility(datasourceTypeDropdown.getSelectedIndex() == XA_DATASOURCE_TYPE);
        });
        addOptionalField(content, c, connectionUrlLabel, connectionURLField);
        addToolTip(connectionUrlLabel, DATASOURCE_CONNECTION_URL_KEY + ".tooltip");
        addToolTip(connectionURLField, DATASOURCE_CONNECTION_URL_KEY + ".tooltip");
        addOptionalField(content, c, xaUsernameLabel, xaUsernameField);
        addToolTip(xaUsernameLabel, XA_USER_KEY + ".tooltip");
        addToolTip(xaUsernameField, XA_USER_KEY + ".tooltip");
        addOptionalField(content, c, xaPasswordLabel, xaPasswordField);
        addToolTip(xaPasswordLabel, XA_USER_KEY + ".tooltip");
        addToolTip(xaPasswordField, XA_USER_KEY + ".tooltip");
        addOptionalField(content, c, confirmXaPasswordLabel, confirmXaPasswordField);
        addToolTip(confirmXaPasswordLabel, XA_PASSWORD_KEY + ".tooltip");
        addToolTip(confirmXaPasswordField, XA_PASSWORD_KEY + ".tooltip");

        c.gridwidth = 3;
        c.gridy++;
        // preserve user changes on prev/next
        if (propertiesPanel == null) {
            propertiesPanel = new DatasourcePropertiesPanel(langUtils, driver, mnemonicUtils);
        }
        content.add(propertiesPanel, c);

        c.gridwidth = 1;
        c.gridy++;
        c.gridx = 0;
        c.weightx = 0.35;

        testConnectionButton.setEnabled(securityTypeDropdown.getSelectedIndex() == USER_SECURITY_TYPE);
        content.add(testConnectionButton, c);
        c.gridy++;
        fillEmptySpace(content, c);

        setDatasourceDefaults();
        setSecurityFieldsVisibility(securityTypeDropdown.getSelectedIndex() == USER_SECURITY_TYPE);
        setDatasourceTypeVisibility(datasourceTypeDropdown.getSelectedIndex() == XA_DATASOURCE_TYPE);

        return content;
    }

    @Override
    public JComponent getDefaultFocusComponent() {
        return datasourceNameField;
    }

    private void setSecurityFieldsVisibility(boolean flag) {
        usernameField.setVisible(flag);
        passwordField.setVisible(flag);
        confirmPasswordField.setVisible(flag);
        usernameLabel.setVisible(flag);
        passwordLabel.setVisible(flag);
        confirmPasswordLabel.setVisible(flag);

        securityTypeLabel.setVisible(!flag);
        securityDomainNameField.setVisible(!flag);
    }

    private void setDatasourceTypeVisibility(boolean flag) {
        xaUsernameField.setVisible(flag);
        xaPasswordField.setVisible(flag);
        confirmXaPasswordField.setVisible(flag);
        xaUsernameLabel.setVisible(flag);
        xaPasswordLabel.setVisible(flag);
        confirmXaPasswordLabel.setVisible(flag);
        propertiesPanel.setVisible(flag);

        connectionURLField.setVisible(!flag);
        connectionUrlLabel.setVisible(!flag);
    }

    private JFormattedTextField createPoolSizeField(int value) {
        JFormattedTextField jFormattedTextField = new JFormattedTextField(getIntegerValueFormatter(0, Integer.MAX_VALUE));
        jFormattedTextField.setText(String.valueOf(value));
        return jFormattedTextField;
    }

    private void addOptionalField(JPanel content, GridBagConstraints c, JLabel label, JComponent textField) {
        c.insets = RIGHT_INDENT_INSET;
        c.gridy++;
        c.weightx = LEFT_COLUMN_WEIGHT;
        content.add(label, c);

        c.gridx = 1;
        c.weightx = RIGHT_COLUMN_WEIGHT;
        content.add(textField, c);
        ((JTextField) textField).setColumns(30);
        c.gridx = 0;
        c.weightx = 1;
        c.insets = NO_INSET;
    }

    private DatasourceConfig createConfig() {
        DatasourceConfig config = new DatasourceConfig(
                UiResources.readTextValue(datasourceNameField),
                UiResources.readTextValue(jndiNameField),
                UiResources.readTextValue(minPoolSizeField),
                UiResources.readTextValue(maxPoolSizeField));

        if (datasourceTypeDropdown.getSelectedIndex() == 0) {
            config.setConnectionUrl(UiResources.readTextValue(connectionURLField));
        } else {
            config.setXaUsername(UiResources.readTextValue(xaUsernameField));
            config.setXaPassword(xaPasswordField.getText());
            config.setXaProperties(propertiesPanel.getXaPropertyList());
        }

        if (securityTypeDropdown.getSelectedIndex() == 0) {
            config.setSecurityDomain(UiResources.readTextValue(securityDomainNameField));
        } else {
            config.setDsUsername(UiResources.readTextValue(usernameField));
            config.setDsPassword(passwordField.getText());
        }
        return config;
    }

    public void setDatasourceDefaults() {
        // if the driver was changed and the datasources page was reloaded, change the defaults
        if (previousDriver == null || previousDriver != driver) {
            jndiNameField.setText(driver.getJndiName());
            connectionURLField.setText(driver.getConnectionUrl());
        }
        previousDriver = driver;
    }

    @Override
    public void load(InstallationData installationData, ScreenManager screenManager) {
        JDBCDriverConfig jdbcConfig = installationData.getConfig(JDBCDriverConfig.class);
        driver = jdbcConfig.getDatabaseDriver();
        jarPaths = jdbcConfig.getResolvedJarList();
    }

    @Override
    public ValidationResult validate() {
        ValidationResult result;
        this.config = createConfig();
        result = new DatasourceValidator(langUtils).validate(config);
        if (result.getResult() != ValidationResult.Result.OK) {
            return result;
        }

        if (securityTypeDropdown.getSelectedIndex() == 1) {
            result = new PasswordEqualityValidator(langUtils).validate(passwordField.getText(), confirmPasswordField.getText());
            if (result.getResult() != ValidationResult.Result.OK) {
                return result;
            }
        }
        if (datasourceTypeDropdown.getSelectedIndex() == 1) {
            result = new PasswordEqualityValidator(langUtils).validate(xaPasswordField.getText(), confirmXaPasswordField.getText());
            if (result.getResult() != ValidationResult.Result.OK) {
                return result;
            }
        }
        return ValidationResult.ok();
    }

    @Override
    public void record(InstallationData installationData) {
        installationData.putConfig(config);
        installationData.addPostInstallTask(PostInstallTask.AddDatasource);

        final HashMap<String, String> attrs = new HashMap<>();
        attrs.put(langUtils.getString(DATASOURCE_NAME_KEY), config.getDsName());
        attrs.put(langUtils.getString(DATASOURCE_JNDI_KEY), config.getJndiName());

        attrs.put(langUtils.getString(MAX_POOL_SIZE_KEY), config.getMaxPool());
        attrs.put(langUtils.getString(MIN_POOL_SIZE_KEY), config.getMinPool());
        attrs.put(langUtils.getString(SECURITY_TYPE_KEY), securityTypeDropdown.getSelectedItem().toString());
        if (securityTypeDropdown.getSelectedIndex() == 0) {
            attrs.put(langUtils.getString(SECURITY_TYPE_DOMAIN_KEY), config.getSecurityDomain());
        } else {
            attrs.put(langUtils.getString(USER_USERNAME_KEY), config.getDsUsername());
        }
        attrs.put(langUtils.getString(DATASOURCE_TYPE_KEY), datasourceTypeDropdown.getSelectedItem().toString());
        if (datasourceTypeDropdown.getSelectedIndex() == 0) {
            attrs.put(langUtils.getString(DATASOURCE_CONNECTION_URL_KEY), config.getConnectionUrl());
        } else {
            attrs.put(langUtils.getString(XA_USER_KEY), config.getXaUsername());
            for (DatasourceConfig.XaProperty xaProperty : config.getXaProperties()) {
                attrs.put(xaProperty.getKey(), xaProperty.getValue());
            }
        }

        installationData.updateSummary(DatasourceScreen.NAME, langUtils.getString(ConfigureRuntimeEnvironmentScreen.DATASOURCE_KEY), attrs);
    }

    @Override
    public void rollback(InstallationData installationData) {
        installationData.removeConfig(DatasourceConfig.class);
        installationData.removePostInstallTask(PostInstallTask.AddDatasource);
        // reset password confirmation to check that the user remembers previously entered value - https://bugzilla.redhat.com/show_bug.cgi?id=1088152#c3
        this.confirmPasswordField.setText("");
        this.confirmXaPasswordField.setText("");
    }
}
