/*
 * 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.domain;

import org.apache.commons.lang3.StringUtils;
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.ValidationResult;
import org.jboss.installer.postinstall.PostInstallTask;
import org.jboss.installer.postinstall.task.SecurityDomainConfig;
import org.jboss.installer.postinstall.task.secdom.JdbcConfig;
import org.jboss.installer.postinstall.task.secdom.PropertiesFileConfig;
import org.jboss.installer.screens.ConfigureRuntimeEnvironmentScreen;
import org.jboss.installer.screens.DefaultScreen;
import org.jboss.installer.validators.CertificateSecurityDomainValidator;
import org.jboss.installer.validators.DataBaseSecurityDomainValidator;
import org.jboss.installer.validators.KerberosSecurityDomainValidator;
import org.jboss.installer.validators.LDAPSecurityDomainValidator;
import org.jboss.installer.validators.PathValidator;
import org.jboss.installer.validators.PropertiesSecurityDomainValidator;

import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JTextField;

import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;

import java.nio.file.Path;
import java.util.HashMap;

import static org.jboss.installer.validators.CertificateSecurityDomainValidator.GENERIC_VALIDATION_EMPTY_FIELD;

public class SecurityDomainScreen extends DefaultScreen {

    public static final String NAME = "SecurityDomainScreen";
    public static final String TITLE_KEY = "security_domain.title";
    public static final String DOMAIN_TYPE_LABEL = "security_domain_screen.domain.type.label";
    public static final String DOMAIN_NAME_LABEL = "security_domain_screen.domain.name.label";
    public static final String DOMAIN_SCREEN_DESCRIPTION = "security_domain_screen.description";
    public static final String AUTHENTICATION_TYPE_NONE_ERROR = "security_domain_screen.domain.type.none_error";
    static final int NONE_INDEX = 0;
    private static final int PROPERTY_REALM_INDEX = 1;
    private static final int JDBC_REALM_INDEX = 2;
    private static final int LDAP_REALM_INDEX = 3;
    private static final int CERT_REALM_INDEX = 4;
    private static final int KERBEROS_REALM_INDEX =5;
    protected static final String SECURITY_DOMAIN_WHITESPACE_ERROR = "security_domain_screen.domain.name.whitespace";

    private final JTextField domainNameField = createTextField("");
    private final JComboBox authType = createDropdown(
            new String[]{asTextKey(SecurityDomainConfig.ConfigType.None),
                    asTextKey(SecurityDomainConfig.ConfigType.Properties),
                    asTextKey(SecurityDomainConfig.ConfigType.Jdbc),
                    asTextKey(SecurityDomainConfig.ConfigType.Ldap),
                    asTextKey(SecurityDomainConfig.ConfigType.Cert),
                    asTextKey(SecurityDomainConfig.ConfigType.Kerberos)
            });
    private final JPanel authPanel = new JPanel(new GridBagLayout());
    private Path baseFolder;
    private PropertiesSecurityDomainPanel propertiesFileDomain;
    private DatabaseSecurityDomainPanel databaseDomain;
    private LdapSecurityDomainPanel ldapDomain;
    private CertificateSecurityDomainPanel certDomain;
    private KerberosSecurityDomainPanel krbDomain;

    public SecurityDomainScreen(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());
        final GridBagConstraints c = this.initializeConstraints();
        c.gridwidth = 2;

        c.insets = DESCRIPTION_INSET;
        content.add(createDescription(DOMAIN_SCREEN_DESCRIPTION), c);

        // security domain name text field
        c.gridwidth = 1;

        addField(content, c, DOMAIN_NAME_LABEL, domainNameField);

        addField(content, c, DOMAIN_TYPE_LABEL, authType);

        c.gridwidth = 2;

        // If panel exists, use selected one to keep user input values once go back from "Next" or "Previous" navigations.
        if (propertiesFileDomain == null) {
            propertiesFileDomain = new PropertiesSecurityDomainPanel(langUtils, baseFolder);
        }
        if (databaseDomain == null) {
            databaseDomain = new DatabaseSecurityDomainPanel(langUtils);
        }
        if (ldapDomain == null) {
            ldapDomain = new LdapSecurityDomainPanel(langUtils);
        }
        if (certDomain == null) {
            certDomain = new CertificateSecurityDomainPanel(langUtils, mnemonicUtils);
        }
        if(krbDomain == null) {
            krbDomain = new KerberosSecurityDomainPanel(langUtils, baseFolder, mnemonicUtils);
        }
        authType.addActionListener(e -> {
            authPanel.removeAll();
            final GridBagConstraints c2 = initializeConstraints();
            switch (getSelectedIndex()) {
                case NONE_INDEX:  // none
                    // selection of "none" fail validation later.
                    break;
                case PROPERTY_REALM_INDEX:  // property
                    authPanel.add(propertiesFileDomain, c2);
                    break;
                case JDBC_REALM_INDEX:  // jdbc
                    authPanel.add(databaseDomain, c2);
                    break;
                case LDAP_REALM_INDEX:  // jdbc
                    authPanel.add(ldapDomain, c2);
                    break;
                case CERT_REALM_INDEX:  // cert
                    authPanel.add(certDomain, c2);
                    break;
                case KERBEROS_REALM_INDEX: // kerberos
                    authPanel.add(krbDomain, c2);
                    break;
                default:
                    throw new IllegalStateException("Unknown authentication mechanism: " + getSelectedIndex());
            }
            content.revalidate();
        });

        c.gridy++;
        content.add(authPanel, c);
        c.gridy++;
        fillEmptySpace(content, c);

        return content;
    }

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

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

    @Override
    public void record(InstallationData installationData) {
        final SecurityDomainConfig config = new SecurityDomainConfig();
        config.setDomainName(domainNameField.getText().trim());

        final HashMap<String, String> attrs = new HashMap<>();

        try {
            if (getSelectedIndex() == PROPERTY_REALM_INDEX) {
                final PropertiesFileConfig propertiesFileConfig = propertiesFileDomain.getConfig();
                config.setPropertiesFileConfig(propertiesFileConfig);
                attrs.putAll(propertiesFileDomain.getAttributes());
            } else if (getSelectedIndex() == JDBC_REALM_INDEX) {
                final JdbcConfig jdbcConfig = databaseDomain.getConfig();
                config.setJdbcFileConfig(jdbcConfig);
                attrs.putAll(databaseDomain.getAttributes());
            } else if (getSelectedIndex() == LDAP_REALM_INDEX) {
                config.setLdapConfig(ldapDomain.getConfig(getDomainName()));
                attrs.putAll(ldapDomain.getAttributes(getDomainName()));
            } else if (getSelectedIndex() == CERT_REALM_INDEX) {
                config.setCertConfig(certDomain.getConfig(getDomainName()));
                attrs.putAll(certDomain.getAttributes(getDomainName()));
            } else if (getSelectedIndex() == KERBEROS_REALM_INDEX) {
                config.setKerberosConfig(krbDomain.getConfig());
                attrs.putAll(krbDomain.getAttributes());
            }
        } catch (FileChooserPanel.InvalidPathException e) {
            // handled in validate
            throw new RuntimeException(e);
        }

        installationData.putConfig(config);
        installationData.addPostInstallTask(PostInstallTask.AddSecurityDomain);

        attrs.put(langUtils.getString(DOMAIN_NAME_LABEL), config.getDomainName());
        attrs.put(langUtils.getString(DOMAIN_TYPE_LABEL), langUtils.getString(asTextKey(config.getConfigType())));
        installationData.updateSummary(SecurityDomainScreen.NAME, langUtils.getString(ConfigureRuntimeEnvironmentScreen.SECURITY_DOMAIN_KEY), attrs);
    }

    private String asTextKey(SecurityDomainConfig.ConfigType configType) {
        switch (configType) {
            case Properties:
                return "security_domain_screen.domain.type.property";
            case Cert:
                return "security_domain_screen.domain.type.cert";
            case Jdbc:
                return "security_domain_screen.domain.type.jdbc";
            case None:
                return "security_domain_screen.domain.type.none";
            case Ldap:
                return "security_domain_screen.domain.type.ldap";
            case Kerberos:
                return "security_domain_screen.domain.type.kerberos";
            default:
                throw new IllegalArgumentException("Unknown Security Domain type: " + configType);
        }
    }

    @Override
    public void rollback(InstallationData installationData) {
        installationData.removePostInstallTask(PostInstallTask.AddSecurityDomain);
        installationData.removeConfig(SecurityDomainConfig.class);
    }

    @Override
    public ValidationResult validate() {
        final String domainName = getDomainName().trim();
        if (StringUtils.isEmpty(domainName)) {
            return ValidationResult.error(langUtils.getString(GENERIC_VALIDATION_EMPTY_FIELD, langUtils.getString(DOMAIN_NAME_LABEL)));
        }
        if (StringUtils.containsWhitespace(domainName)) {
            return ValidationResult.error(langUtils.getString(SECURITY_DOMAIN_WHITESPACE_ERROR,
                    langUtils.getString(DOMAIN_NAME_LABEL)));
        }
        if (getSelectedIndex() == NONE_INDEX) {
            return ValidationResult.error(langUtils.getString(AUTHENTICATION_TYPE_NONE_ERROR));
        }
        try {
            if (getSelectedIndex() == PROPERTY_REALM_INDEX) {
                return new PropertiesSecurityDomainValidator(langUtils).validate(propertiesFileDomain.getConfig());
            } else if (getSelectedIndex() == JDBC_REALM_INDEX) {
                return new DataBaseSecurityDomainValidator(langUtils).validate(databaseDomain.getConfig());
            } else if (getSelectedIndex() == LDAP_REALM_INDEX) {
                return new LDAPSecurityDomainValidator(langUtils).validate(ldapDomain.getConfig(domainName));
            } else if (getSelectedIndex() == CERT_REALM_INDEX) {
                return new CertificateSecurityDomainValidator(langUtils).validate(certDomain.getConfig(domainName));
            } else if (getSelectedIndex() == KERBEROS_REALM_INDEX) {
                return new KerberosSecurityDomainValidator(langUtils).validate(krbDomain.getConfig());
            }
            return ValidationResult.ok();
        } catch (FileChooserPanel.InvalidPathException e) {
            return PathValidator.newInstance("generic", langUtils).validate(e.getPath());
        }
    }


    // used in test
    String getDomainName() {
        return domainNameField.getText();
    }

    int getSelectedIndex() {
        return authType.getSelectedIndex();
    }
}
