/*
 * 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.FileChooserPanel;
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.postinstall.PostInstallTask;
import org.jboss.installer.postinstall.task.AbstractHttpsEnableConfig;
import org.jboss.installer.validators.PasswordEqualityValidator;
import org.jboss.installer.validators.PathValidator;
import org.jboss.installer.validators.SSLSecurityValidator;

import javax.swing.ButtonGroup;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JRadioButton;
import javax.swing.JTextField;
import java.awt.BorderLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

import static javax.swing.JFileChooser.FILES_ONLY;
import static org.jboss.installer.common.UiResources.createFieldLabelWithColonSuffix;

public abstract class AbstractSSLSecurityScreen extends DefaultScreen {

    public static final String NAME = "SSLSecurityScreen";
    public static final String TITLE_KEY = "ssl_security.title";
    public static final String DESCRIPTION_KEY = "ssl.config.description";
    public static final String PASSWORD_KEY = "ssl.config.password";
    public static final String PASSWORD_CONFIRM_KEY = "ssl.config.password_confirm";
    public static final String VALIDATION_KEYSTORE_PREFIX_KEY = "ssl.config.validation_keystore_prefix";
    public static final String VALIDATION_TRUSTSTORE_PREFIX_KEY = "ssl.config.validation_truststore_prefix";
    public static final String KEYSTORE_LOCATION_LABEL_KEY = "ssl.config.keystore_location.label";
    public static final String TRUSTSTORE_LABEL_KEY = "ssl.config.truststore.label";
    public static final String TRUSTSTORE_LOCATION_LABEL_KEY = "ssl.config.truststore_location.label";
    public static final String TRUSTSTORE_PASSWORD_KEY = "ssl.config.truststore_password";
    public static final String TRUSTSTORE_PASSWORD_CONFIRM_KEY = "ssl.config.truststore_password_confirm";
    public static final String PROTOCOLS_LABEL_KEY = "ssl.config.protocols.label";
    public static final String CIPHER_SUITES_LABEL_KEY = "ssl.config.cipher_suites.label";
    public static final String CIPHER_NAMES_LABEL_KEY = "ssl.config.cipher_names.label";
    public static final String EXISTING_KEYSTORE_LABEL_KEY = "ssl.config.existing_keystore.label";
    public static final String GENERATE_KEYSTORE_LABEL_KEY = "ssl.config.generate_keystore.label";
    public static final String DN_NAME_KEY = "ssl.config.dn_name.label";
    public static final String DN_ORG_UNIT_KEY = "ssl.config.org_unit.label";
    public static final String DN_ORG_KEY = "ssl.config.org.label";
    public static final String DN_CITY_KEY = "ssl.config.city.label";
    public static final String DN_STATE_KEY = "ssl.config.state.label";
    public static final String DN_COUNTRY_CODE_KEY = "ssl.config.country_code.label";
    public static final String VALIDITY_KEY = "ssl.config.validity.label";
    public static final String MUTUAL_KEY = "ssl.config.mutual.label";
    public static final String CLIENT_CERT_FILE_KEY = "ssl.config.client_cert_file.label";
    public static final String CLIENT_CERT_VALIDATE_KEY = "ssl.config.client_cert_validate.label";
    public static final String MODIFIABLE_KEYSTORE_OPERATIONS_LIMITED_TO_STANDALONE_KEY = "ssl.config.modifiable_keystore_operations_limited_to_standalone.label";
    private static final String SSL_CONFIG_ADVANCED_OPTIONS = "ssl.config.advanced.options";
    public static final double COLUMN0_WEIGHT = 0.30;
    public static final double COLUMN1_WEIGHT = 0.35;
    public static final double COLUMN2_WEIGHT = 0.35;
    public static final Insets LEFT_RIGHT_INDENT_INSET = new Insets(0, 10, 10, 10);
    // disable generating keystore
    private static final boolean ALLOW_GENERATE_KEYSTORE = false;
    private static final String[] ALLOWED_PROTOCOLS = new String[]{"TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3"};
    private static final int[] DEFAULT_PROTOCOL_INDICES = {0, 1, 2, 3};
    private final JTextField cipherSuitesField = createTextField(AbstractHttpsEnableConfig.DEFAULT_CIPHER_SUITES_TLS12);
    private final JTextField tls13CipherNamesField = createTextField(AbstractHttpsEnableConfig.DEFAULT_CIPHER_NAMES_TLS13);
    private final JRadioButton existingKeystoreRadioButton = createRadioButton(EXISTING_KEYSTORE_LABEL_KEY, true, true);
    private final JRadioButton generateKeystoreRadioButton = createRadioButton(GENERATE_KEYSTORE_LABEL_KEY, false, true);
    private final FileChooserPanel fileChooser = FileChooserPanel.builder(langUtils, FILES_ONLY, mnemonicUtils).build();
    private final JPasswordField passwordField = createPasswordField();
    private final JPasswordField passwordConfirmField = createPasswordField();
    private final JCheckBox trustStoreCheckBox = createCheckBox(TRUSTSTORE_LABEL_KEY, true, false);
    private final JPasswordField trustStorePasswordField = createPasswordField();
    private final JPasswordField trustStorePasswordConfirmField = createPasswordField();
    private final FileChooserPanel trustStoreFileChooser = FileChooserPanel.builder(langUtils, FILES_ONLY, mnemonicUtils).build();
    private final JPasswordField newKeystorePasswordField = createPasswordField();
    private final JPasswordField newKeystorePasswordConfirmField = createPasswordField();
    private final JTextField dnNameField = createTextField(AbstractHttpsEnableConfig.DN_DEFAULT);
    private final JTextField dnOrgUnitField = createTextField(AbstractHttpsEnableConfig.DN_DEFAULT);
    private final JTextField dnOrgField = createTextField(AbstractHttpsEnableConfig.DN_DEFAULT);
    private final JTextField dnCityField = createTextField(AbstractHttpsEnableConfig.DN_DEFAULT);
    private final JTextField dnStateField = createTextField(AbstractHttpsEnableConfig.DN_DEFAULT);
    private final JTextField dnCountryField = createTextField(AbstractHttpsEnableConfig.DN_DEFAULT);
    private final JTextField validityField = createTextField("");
    private final JCheckBox mutualCheckBox = createCheckBox(MUTUAL_KEY, true, false);
    private final FileChooserPanel clientCertFileChooser = FileChooserPanel.builder(langUtils, FILES_ONLY, mnemonicUtils).build();
    private final JCheckBox clientCertValidateCheckBox = createCheckBox(CLIENT_CERT_VALIDATE_KEY, true, true);
    private final JPasswordField newTrustStorePasswordField = createPasswordField();
    private final JPasswordField newTrustStorePasswordConfirmField = createPasswordField();
    private JLabel advancedOptions = createFieldLabel(SSL_CONFIG_ADVANCED_OPTIONS);
    private JPanel existingStoresPanel = null;
    private JPanel generateStoresPanel = null;
    private final JPanel storesPanel = new JPanel(new BorderLayout());
    private InstallationData installationData;
    private AbstractHttpsEnableConfig config;
    private String descriptionKey;
    private boolean supportsAdvancedModifiableKeyStoreOperations;
    private JList<String> protocolsList;
    private JCheckBox protocolsCheckbox;
    private JCheckBox cipherCheckbox;
    private JCheckBox tls13CipherCheckbox;

    public AbstractSSLSecurityScreen(Screen parent, LanguageUtils langUtils, boolean isActive, String descriptionKey, boolean supportsAdvancedModifiableKeyStoreOperations) {
        this(parent, langUtils, isActive);
        this.descriptionKey = descriptionKey;
        this.supportsAdvancedModifiableKeyStoreOperations = supportsAdvancedModifiableKeyStoreOperations;
    }

    private AbstractSSLSecurityScreen(Screen parent, LanguageUtils langUtils, boolean isActive) {
        super(parent, langUtils, isActive);
    }

    protected abstract PostInstallTask createTask();
    protected abstract AbstractHttpsEnableConfig createConfig(Set<String> protocols, String cipherSuites, String tls13cipherNames,
                                                              String keyStorePath, String keyStorePassword, boolean mutual, String trustStorePath, String trustStorePassword,
                                                              boolean doGenerateStore, String dn, String validity, String clientCertPath, boolean validateCertificate);
    protected abstract Class<? extends AbstractHttpsEnableConfig> getConfigClass();

    @Override
    public JPanel getContent() {
        JPanel content = new JPanel(new GridBagLayout());
        final GridBagConstraints c = initializeConstraints();

        c.gridwidth = 3;
        c.insets = LEFT_RIGHT_INDENT_INSET;
        content.add(createDescription(descriptionKey), c);

        if (ALLOW_GENERATE_KEYSTORE) {
            c.gridy++;
            c.gridwidth = 1;
            c.weightx = COLUMN0_WEIGHT;
            content.add(existingKeystoreRadioButton, c);
            c.gridx = 1;
            c.gridwidth = 2;
            c.weightx = COLUMN1_WEIGHT + COLUMN2_WEIGHT;
            content.add(generateKeystoreRadioButton, c);
            c.gridx = 0;
            c.weightx = 0.0;

            ButtonGroup buttonGroup = new ButtonGroup();
            buttonGroup.add(existingKeystoreRadioButton);
            buttonGroup.add(generateKeystoreRadioButton);

            existingKeystoreRadioButton.addActionListener(actionEvent -> {
                enableExistingKeystoreFields(true);
                enableGenerateNewKeystoreFields(false);

                storesPanel.removeAll();
                storesPanel.add(getExistingStoresPanel(), BorderLayout.CENTER);
                content.revalidate();
            });
            generateKeystoreRadioButton.addActionListener(actionEvent -> {
                enableExistingKeystoreFields(false);
                enableGenerateNewKeystoreFields(true);

                storesPanel.removeAll();
                storesPanel.add(getGenerateStoresPanel(), BorderLayout.CENTER);
                content.revalidate();
            });
        } else {
            c.gridy++;
            content.add(createDescription("ssl.config.keystore_define"), c);
        }

        if (existingKeystoreRadioButton.isSelected()) {
            storesPanel.add(getExistingStoresPanel(), BorderLayout.CENTER);
        } else {
            storesPanel.add(getGenerateStoresPanel(), BorderLayout.CENTER);
        }

        c.gridwidth = 3;
        c.gridy++;
        content.add(storesPanel, c);
        c.gridy++;
        fillEmptySpace(content, c);

        c.gridwidth = 1;
        c.gridy++;
        content.add(advancedOptions, c);

        c.gridwidth = 3;
        c.gridy++;
        content.add(getProtocolsPanel(), c);

        return content;
    }

    @Override
    public void load(InstallationData installationData) {
        this.installationData = installationData;
    }

    @Override
    public ValidationResult validate() {
        if (existingKeystoreRadioButton.isSelected()) {
            // keystore password
            final ValidationResult result = new PasswordEqualityValidator(langUtils).validate(passwordField.getText(),
                    passwordConfirmField.getText(), VALIDATION_KEYSTORE_PREFIX_KEY);
            if (result.getResult() == ValidationResult.Result.ERROR) {
                return result;
            }
            // trust store password
            if (trustStoreCheckBox.isSelected()) {
                final ValidationResult trustStoreResult = new PasswordEqualityValidator(langUtils).validate(trustStorePasswordField.getText(),
                        trustStorePasswordConfirmField.getText(), VALIDATION_TRUSTSTORE_PREFIX_KEY);
                if (trustStoreResult.getResult() == ValidationResult.Result.ERROR) {
                    return trustStoreResult;
                }
            }
        } else if (generateKeystoreRadioButton.isSelected()) {
            // new keystore password
            final ValidationResult newKeyStoreResult = new PasswordEqualityValidator(langUtils).validate(newKeystorePasswordField.getText(),
                    newKeystorePasswordConfirmField.getText(), VALIDATION_KEYSTORE_PREFIX_KEY);
            if (newKeyStoreResult.getResult() == ValidationResult.Result.ERROR) {
                return newKeyStoreResult;
            }
            // new trust store password
            if (mutualCheckBox.isSelected()) {
                final ValidationResult newTrustStoreResult = new PasswordEqualityValidator(langUtils).validate(newTrustStorePasswordField.getText(),
                        newTrustStorePasswordConfirmField.getText(), VALIDATION_TRUSTSTORE_PREFIX_KEY);
                if (newTrustStoreResult.getResult() == ValidationResult.Result.ERROR) {
                    return newTrustStoreResult;
                }
            }
        }

        final Set<String> protocols = readSelectedProtocols();
        final String cipherSuites = readCipherSuites();
        final String tls13cipherNames = readTlS14CipherNames();

        final String keyStorePassword = generateKeystoreRadioButton.isSelected()
                ? newKeystorePasswordField.getText()
                : passwordField.getText();
        final String trustStorePassword = trustStoreCheckBox.isSelected()
                ? trustStorePasswordField.getText()
                : mutualCheckBox.isSelected() ? newTrustStorePasswordField.getText() : null;

        try {
            config = createConfig(
                    protocols,
                    cipherSuites,
                    tls13cipherNames,
                    existingKeystoreRadioButton.isSelected() ? fileChooser.asPath() : null,
                    keyStorePassword,
                    trustStoreCheckBox.isSelected() || mutualCheckBox.isSelected(),
                    trustStoreCheckBox.isSelected() ? trustStoreFileChooser.asPath() : null,
                    trustStorePassword,
                    generateKeystoreRadioButton.isSelected(),
                    buildDNString(),
                    validityField.getText(),
                    mutualCheckBox.isSelected() ? clientCertFileChooser.asPath() : null,
                    clientCertValidateCheckBox.isSelected()
            );
        } catch (FileChooserPanel.InvalidPathException e) {
            return PathValidator.newInstance("generic", langUtils).validate(e.getPath());
        }
        return new SSLSecurityValidator(langUtils, VALIDATION_KEYSTORE_PREFIX_KEY, VALIDATION_TRUSTSTORE_PREFIX_KEY)
                .validate(config, installationData);
    }

    private String readTlS14CipherNames() {
        if (tls13CipherNamesField == null) {
            return null;
        }
        if (!tls13CipherNamesField.isEnabled()) {
            return null;
        }
        if (tls13CipherNamesField.getText() == null || tls13CipherNamesField.getText().trim().isEmpty()) {
            return "";
        }
        return tls13CipherNamesField.getText().trim();
    }

    private String readCipherSuites() {
        if (cipherSuitesField == null) {
            return null;
        }
        if (!cipherSuitesField.isEnabled()) {
            return null;
        }
        if (cipherSuitesField.getText() == null || cipherSuitesField.getText().trim().isEmpty()) {
            return "";
        }
        return cipherSuitesField.getText().trim();
    }

    // return null if not enabled, empty if enabled but nothing selected or selected items
    private Set<String> readSelectedProtocols() {
        if (protocolsList == null) {
            return null;
        }
        if (!protocolsList.isEnabled()) {
            return null;
        }
        if (protocolsList.getSelectedValuesList() == null) {
            return Collections.emptySet();
        }
        return new HashSet<>(protocolsList.getSelectedValuesList());
    }

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

    @Override
    public void record(InstallationData installationData, ScreenManager screenManager) {
        installationData.putConfig(config);
        installationData.addPostInstallTask(createTask());

        final HashMap<String, String> attrs = new HashMap<>();
        if (config.getKeyStorePath() != null) {
            attrs.put(langUtils.getString(KEYSTORE_LOCATION_LABEL_KEY), config.getKeyStorePath());
        }
        if (config.getTrustStorePath() != null) {
            attrs.put(langUtils.getString(TRUSTSTORE_LOCATION_LABEL_KEY), config.getTrustStorePath());
        }
        if (config.getProtocols() != null && !config.getProtocols().isEmpty()) {
            attrs.put(langUtils.getString(PROTOCOLS_LABEL_KEY), String.join(",", config.getProtocols()));
        }
        if (config.getCipherSuites() != null) {
            attrs.put(langUtils.getString(CIPHER_SUITES_LABEL_KEY), config.getCipherSuites());
        }
        if (config.getTls13cipherNames() != null) {
            attrs.put(langUtils.getString(CIPHER_NAMES_LABEL_KEY), config.getTls13cipherNames());
        }
        installationData.updateSummary(getName(), langUtils.getString(getSummaryDescription()), attrs);
    }

    protected abstract String getSummaryDescription();

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

    private void enableExistingKeystoreFields(boolean enable) {
        passwordField.setEnabled(enable);
        passwordConfirmField.setEnabled(enable);
        fileChooser.setEnabled(enable);
        trustStoreCheckBox.setEnabled(enable);
    }

    private void enableExistingTrustStoreFields(boolean enable) {
        trustStoreFileChooser.setEnabled(enable);
        trustStorePasswordField.setEnabled(enable);
        trustStorePasswordConfirmField.setEnabled(enable);
    }

    private void enableGenerateNewKeystoreFields(boolean enable) {
        newKeystorePasswordField.setEnabled(enable);
        newKeystorePasswordConfirmField.setEnabled(enable);
        dnNameField.setEnabled(enable);
        dnOrgUnitField.setEnabled(enable);
        dnOrgField.setEnabled(enable);
        dnCityField.setEnabled(enable);
        dnStateField.setEnabled(enable);
        dnCountryField.setEnabled(enable);
        validityField.setEnabled(enable);
        mutualCheckBox.setEnabled(enable);
    }

    private void enableGenerateNewTrustStoreFields(boolean enable) {
        clientCertFileChooser.setEnabled(enable);
        clientCertValidateCheckBox.setEnabled(enable);
        newTrustStorePasswordField.setEnabled(enable);
        newTrustStorePasswordConfirmField.setEnabled(enable);
    }

    private String buildDNString() {
        return "CN=" + getDNStringOrDefault(dnNameField.getText())
                + ", OU=" + getDNStringOrDefault(dnOrgUnitField.getText())
                + ", O=" + getDNStringOrDefault(dnOrgField.getText())
                + ", L=" + getDNStringOrDefault(dnCityField.getText())
                + ", ST=" + getDNStringOrDefault(dnStateField.getText())
                + ", C=" + getDNStringOrDefault(dnCountryField.getText());
    }

    private String getDNStringOrDefault(String input) {
        if (input == null || input.equals("")) {
            return AbstractHttpsEnableConfig.DN_DEFAULT;
        }
        return input;
    }

    private void addField3Columns(JPanel content, GridBagConstraints c, String labelKey, JComponent textField) {
        String labelText = langUtils.getString(labelKey);
        c.gridy++;
        c.gridx = 0;
        c.gridwidth = 1;
        c.weightx = COLUMN0_WEIGHT;
        final JLabel label = createFieldLabelWithColonSuffix(labelText);
        content.add(label, c);

        c.gridx = 1;
        c.gridwidth = 2;
        c.weightx = COLUMN1_WEIGHT + COLUMN2_WEIGHT;
        content.add(textField, c);
        c.gridx = 0;
        c.gridwidth = 1;
        c.weightx = 0.0;

        if (langUtils.hasString(labelKey+".tooltip")) {
            addToolTip(label, labelKey+".tooltip");
            addToolTip(textField, labelKey+".tooltip");
        }
    }

    private JPanel getProtocolsPanel() {
        final JPanel content = new JPanel(new GridBagLayout());
        final GridBagConstraints c = initializeConstraints();

        c.insets = LEFT_RIGHT_INDENT_INSET;

        c.gridwidth = 1;
        c.gridy++;
        c.weightx = COLUMN0_WEIGHT;
        if (protocolsCheckbox == null) {
            protocolsCheckbox = createCheckBox(PROTOCOLS_LABEL_KEY, true, false);
        }
        content.add(protocolsCheckbox, c);
        c.gridx++;
        c.gridwidth = 2;
        c.weightx = COLUMN1_WEIGHT + COLUMN2_WEIGHT;

        if (protocolsList == null) {
            protocolsList = createMultiSelectionList(ALLOWED_PROTOCOLS);
            protocolsList.setSelectedIndices(DEFAULT_PROTOCOL_INDICES);
            protocolsList.setEnabled(protocolsCheckbox.isSelected());
        }

        protocolsCheckbox.addActionListener((e)-> protocolsList.setEnabled(protocolsCheckbox.isSelected()));


        content.add(protocolsList, c);

        c.gridwidth = 1;
        c.gridy++;
        c.gridx = 0;
        c.weightx = COLUMN0_WEIGHT;
        if (cipherCheckbox == null) {
            cipherCheckbox = createCheckBox(CIPHER_SUITES_LABEL_KEY, true, false);
        }
        content.add(cipherCheckbox, c);
        c.gridx++;
        c.gridwidth = 2;
        c.weightx = COLUMN1_WEIGHT + COLUMN2_WEIGHT;
        content.add(cipherSuitesField, c);
        cipherCheckbox.addActionListener((e)->cipherSuitesField.setEnabled(cipherCheckbox.isSelected()));
        cipherSuitesField.setEnabled(cipherCheckbox.isSelected());

        c.gridwidth = 1;
        c.gridy++;
        c.gridx = 0;
        c.weightx = COLUMN0_WEIGHT;
        if (tls13CipherCheckbox == null) {
            tls13CipherCheckbox = createCheckBox(CIPHER_NAMES_LABEL_KEY, true, false);
        }
        content.add(tls13CipherCheckbox, c);
        c.gridx++;
        c.gridwidth = 2;
        c.weightx = COLUMN1_WEIGHT + COLUMN2_WEIGHT;
        content.add(tls13CipherNamesField, c);
        tls13CipherCheckbox.addActionListener((e)-> tls13CipherNamesField.setEnabled(tls13CipherCheckbox.isSelected()));
        tls13CipherNamesField.setEnabled(tls13CipherCheckbox.isSelected());


        return content;
    }

    private JPanel getExistingStoresPanel() {
        if (existingStoresPanel != null) {
            return existingStoresPanel;
        }

        final JPanel content = new JPanel(new GridBagLayout());
        final GridBagConstraints c = initializeConstraints();

        c.insets = LEFT_RIGHT_INDENT_INSET;

        // existing keystore
        addField3Columns(content, c, PASSWORD_KEY, passwordField);
        addField3Columns(content, c, PASSWORD_CONFIRM_KEY, passwordConfirmField);

        c.gridy++;
        c.gridwidth = 3;
        content.add(createFieldLabel(KEYSTORE_LOCATION_LABEL_KEY), c);
        c.gridy++;
        content.add(fileChooser, c);
        addToolTip(fileChooser, KEYSTORE_LOCATION_LABEL_KEY + ".tooltip");

        // existing truststore (optional)
        c.insets = new Insets(0, 0, 10, 10);
        c.gridy++;
        content.add(trustStoreCheckBox, c);

        c.insets = LEFT_RIGHT_INDENT_INSET;
        addField3Columns(content, c, TRUSTSTORE_PASSWORD_KEY, trustStorePasswordField);
        addField3Columns(content, c, TRUSTSTORE_PASSWORD_CONFIRM_KEY, trustStorePasswordConfirmField);
        c.gridy++;
        c.gridwidth = 3;
        content.add(createFieldLabel(TRUSTSTORE_LOCATION_LABEL_KEY), c);
        c.gridy++;
        content.add(trustStoreFileChooser, c);
        addToolTip(trustStoreFileChooser, TRUSTSTORE_LOCATION_LABEL_KEY + ".tooltip");

        trustStoreCheckBox.addItemListener(itemEvent -> {
            enableExistingTrustStoreFields(trustStoreCheckBox.isSelected());
        });
        enableExistingTrustStoreFields(false);

        return existingStoresPanel = content;
    }

    private JPanel getGenerateStoresPanel() {
        if (generateStoresPanel != null) {
            return generateStoresPanel;
        }

        final JPanel content = new JPanel(new GridBagLayout());
        final GridBagConstraints c = initializeConstraints();

        c.insets = LEFT_RIGHT_INDENT_INSET;

        if (!supportsAdvancedModifiableKeyStoreOperations) {
            c.gridwidth = 3;
            c.gridy++;
            content.add(createFieldLabel(MODIFIABLE_KEYSTORE_OPERATIONS_LIMITED_TO_STANDALONE_KEY), c);
        }

        addField3Columns(content, c, PASSWORD_KEY, newKeystorePasswordField);
        addField3Columns(content, c, PASSWORD_CONFIRM_KEY, newKeystorePasswordConfirmField);

        // * DN
        addField3Columns(content, c, DN_NAME_KEY, dnNameField);
        addSelectingFocusListener(dnNameField);
        addField3Columns(content, c, DN_ORG_UNIT_KEY, dnOrgUnitField);
        addSelectingFocusListener(dnOrgUnitField);
        addField3Columns(content, c, DN_ORG_KEY, dnOrgField);
        addSelectingFocusListener(dnOrgField);
        addField3Columns(content, c, DN_CITY_KEY, dnCityField);
        addSelectingFocusListener(dnCityField);
        addField3Columns(content, c, DN_STATE_KEY, dnStateField);
        addSelectingFocusListener(dnStateField);
        addField3Columns(content, c, DN_COUNTRY_CODE_KEY, dnCountryField);
        addSelectingFocusListener(dnCountryField);

        // * validity  (in days, blank default)
        addField3Columns(content, c, VALIDITY_KEY, validityField);

        // * two-way/mutual? true/false
        c.gridwidth = 3;
        c.gridy++;
        content.add(mutualCheckBox, c);

        c.gridy++;
        content.add(createFieldLabel(CLIENT_CERT_FILE_KEY), c);
        c.gridy++;
        content.add(clientCertFileChooser, c);
        c.gridy++;
        content.add(clientCertValidateCheckBox, c);
        addField3Columns(content, c, TRUSTSTORE_PASSWORD_KEY, newTrustStorePasswordField);
        addField3Columns(content, c, TRUSTSTORE_PASSWORD_CONFIRM_KEY, newTrustStorePasswordConfirmField);

        mutualCheckBox.addItemListener(itemEvent -> {
            enableGenerateNewTrustStoreFields(mutualCheckBox.isSelected());
        });
        enableGenerateNewTrustStoreFields(false);

        return generateStoresPanel = content;
    }

    private void addSelectingFocusListener(JTextField field) {
        field.addFocusListener(new FocusListener() {
            @Override
            public void focusGained(FocusEvent focusEvent) {
                // if default value then select all
                if (AbstractHttpsEnableConfig.DN_DEFAULT.equals(field.getText())) {
                    field.selectAll();
                }
            }

            @Override
            public void focusLost(FocusEvent focusEvent) {
                field.select(0, 0);
            }
        });
    }
}
