/**
 * Copyright (C) 2004-2011 Jive Software. All rights reserved.
 * <p>
 * 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
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * 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.jivesoftware.gui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Principal;
import java.security.UnrecoverableKeyException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.net.ssl.SSLContext;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JPopupMenu;
import javax.swing.JSplitPane;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.text.JTextComponent;

import org.jdesktop.swingx.autocomplete.AutoCompleteDecorator;
import org.jivesoftware.AccountCreationWizard;
import org.jivesoftware.MainWindow;
import org.jivesoftware.Spark;
import org.jivesoftware.resource.Default;
import org.jivesoftware.resource.Res;
import org.jivesoftware.resource.SparkRes;
import org.jivesoftware.smack.AbstractXMPPConnection;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ReconnectionManager;
import org.jivesoftware.smack.SASLAuthentication;
import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.ConnectionConfiguration.DnssecMode;
import org.jivesoftware.smack.parsing.ExceptionLoggingCallback;
import org.jivesoftware.smack.proxy.ProxyInfo;
import org.jivesoftware.smack.sasl.javax.SASLExternalMechanism;
import org.jivesoftware.smack.sasl.javax.SASLGSSAPIMechanism;
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jivesoftware.smack.util.DNSUtil;
import org.jivesoftware.smack.util.TLSUtils;
import org.jivesoftware.smackx.carbons.CarbonManager;
import org.jivesoftware.smackx.chatstates.ChatStateManager;
import org.jivesoftware.smackx.iqregister.AccountManager;
import org.jivesoftware.spark.PluginManager;
import org.jivesoftware.spark.SessionManager;
import org.jivesoftware.spark.SparkManager;
import org.jivesoftware.spark.Workspace;
import org.jivesoftware.spark.component.MessageDialog;
import org.jivesoftware.spark.component.RolloverButton;
import org.jivesoftware.spark.sasl.SASLGSSAPIv3CompatMechanism;
import org.jivesoftware.spark.ui.login.GSSAPIConfiguration;
import org.jivesoftware.spark.ui.login.LoginSettingDialog;

import static java.awt.BorderLayout.*;
import static java.awt.Color.WHITE;
import static javax.swing.SwingConstants.BOTTOM;
import static javax.swing.SwingConstants.CENTER;
import static javax.swing.SwingConstants.LEADING;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.trimToEmpty;
import static org.apache.commons.lang3.StringUtils.trimToNull;
import static org.jivesoftware.XmppProviders.PROVIDERS_A;
import static org.jivesoftware.spark.util.StringUtils.modifyWildcards;
import static org.jivesoftware.sparkimpl.certificates.SparkSSLContextCreator.Options.BOTH;
import static org.jivesoftware.sparkimpl.certificates.SparkSSLContextCreator.Options.ONLY_SERVER_SIDE;

import org.jivesoftware.spark.util.BrowserLauncher;
import org.jivesoftware.spark.util.GraphicUtils;
import org.jivesoftware.spark.util.ResourceUtils;
import org.jivesoftware.spark.util.TaskEngine;
import org.jivesoftware.spark.util.log.Log;
import org.jivesoftware.sparkimpl.certificates.CertificateModel;
import org.jivesoftware.sparkimpl.certificates.SparkSSLContextCreator;
import org.jivesoftware.sparkimpl.certificates.SparkSSLSocketFactory;
import org.jivesoftware.sparkimpl.certificates.SparkTrustManager;
import org.jivesoftware.sparkimpl.certificates.UnrecognizedServerCertificatePanel;
import org.jivesoftware.sparkimpl.plugin.layout.LayoutSettings;
import org.jivesoftware.sparkimpl.plugin.layout.LayoutSettingsManager;
import org.jivesoftware.sparkimpl.plugin.manager.Enterprise;
import org.jivesoftware.sparkimpl.settings.JiveInfo;
import org.jivesoftware.sparkimpl.settings.local.LocalPreferences;
import org.jivesoftware.sparkimpl.settings.local.SettingsManager;
import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.jid.parts.Resourcepart;
import org.jxmpp.util.XmppStringUtils;
import org.minidns.dnsname.DnsName;

/**
 * Dialog to log in a user into the XMPP server.
 * The LoginDialog is used only for login in registered users into the XMPP server.
 * @author KeepToo
 */
public class LoginUIPanel extends JPanel implements KeyListener, FocusListener, CallbackHandler {
    private JFrame loginDialog;
    private final LocalPreferences localPref = SettingsManager.getLocalPreferences();
    private final ArrayList<String> _usernames = new ArrayList<>();
    private final RolloverButton otherUsers = new RolloverButton(SparkRes.getImageIcon(SparkRes.Icon.PANE_DOWN_ARROW_IMAGE));

    /**
     * Creates new form LoginWindow
     */
    public LoginUIPanel() {
        initComponents();
        //SPARK-2115 SPARK-2295 Allow account creation or password changes over an insecure (e.g. unencrypted) connections.
        AccountManager.sensitiveOperationOverInsecureConnectionDefault(true);
        init();
    }

    // Should be called only from the Event Dispatcher Thread.
    private void init() {
        // Most Swing components are not thread safe, and _must_ be executed on the Event Dispatcher Thread.
        if (SmackConfiguration.DEBUG && !EventQueue.isDispatchThread()) {
            throw new IllegalStateException("Must be called on the Event Dispatcher Thread (but was not)");
        }

        ResourceUtils.resButton(cbSavePassword, Res.getString("checkbox.save.password"));
        ResourceUtils.resButton(cbAutoLogin, Res.getString("checkbox.auto.login"));
        ResourceUtils.resButton(btnSignUp, Res.getString("label.accounts"));
        ResourceUtils.resButton(cbLoginInvisible, Res.getString("checkbox.login.as.invisible"));
        ResourceUtils.resButton(cbAnonymous, Res.getString("checkbox.login.anonymously"));
        ResourceUtils.resButton(btnReset, Res.getString("label.passwordreset"));
        configureVisibility();

        lblProgress.setVisible(false);
        cbSavePassword.setOpaque(false);
        cbAutoLogin.setOpaque(false);
        cbLoginInvisible.setOpaque(false);
        cbAnonymous.setOpaque(false);

        // Add KeyListener
        tfUsername.addKeyListener(this);
        tfPassword.addKeyListener(this);
        tfDomain.addKeyListener(this);

        tfPassword.addFocusListener(this);
        tfUsername.addFocusListener(this);
        tfDomain.addFocusListener(this);
        // autocomplete for popular XMPP domains
        AutoCompleteDecorator.decorate(tfDomain, PROVIDERS_A, false);

        // Add a clear button for username, password and domain fields
        tfUsername.putClientProperty("JTextField.showClearButton",true);
        tfDomain.putClientProperty("JTextField.showClearButton",true);
        tfPassword.putClientProperty("JTextField.showClearButton",true);
        //reset ui
        tfDomain.putClientProperty("JTextField.placeholderText", Res.getString("hint.login.domain"));
        tfPassword.putClientProperty("JTextField.placeholderText", Res.getString("hint.login.password"));
        tfUsername.putClientProperty("JTextField.placeholderText", Res.getString("hint.login.username"));

        otherUsers.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                getPopup().show(otherUsers, e.getX(), e.getY());
            }
        });

        // Make same size
        GraphicUtils.makeSameSize(tfUsername, tfPassword);

        // Set progress bar description
        lblProgress.setText(Res.getString("message.authenticating"));
        lblProgress.setVerticalTextPosition(JLabel.BOTTOM);
        lblProgress.setHorizontalTextPosition(JLabel.CENTER);
        lblProgress.setHorizontalAlignment(JLabel.CENTER);

        // Set Resources
        ResourceUtils.resButton(btnLogin, Res.getString("button.login"));
        ResourceUtils.resButton(btnAdvanced, Res.getString("button.advanced"));

        File file = new File(Spark.getSparkUserHome(), "/user/");
        File[] userprofiles = file.listFiles(File::isDirectory);
        userprofiles = userprofiles != null ? userprofiles : new File[]{};
        for (File f : userprofiles) {
            if (f.getName().contains("@")) {
                _usernames.add(f.getName());
            } else {
                Log.error("Profile contains wrong format: " + f.getAbsolutePath());
            }
        }
        // If users account > 1 then show account list button "otherUsers"
        if (_usernames.size() > 1) {
            tfUsername.putClientProperty("JTextField.trailingComponent", otherUsers);
        }
        // When user leaves the username field check if this is a JID and extract the domain
        tfUsername.addFocusListener(new FocusAdapter() {
            @Override
            public void focusLost(FocusEvent e) {
                String jidOrLocalpart = tfUsername.getText(); // get unescaped username field value
                if (jidOrLocalpart.contains("@")) {
                    String[] parts = jidOrLocalpart.split("@");
                    setUsername(parts[0]);
                    setServerName(parts[1]);
                }
            }
        });

        // Check Settings
        cbAutoLogin.setSelected(localPref.isAutoLogin());
        cbLoginInvisible.setSelected(localPref.isLoginAsInvisible());
        cbAnonymous.setSelected(localPref.isLoginAnonymously());
        useSSO(localPref.isSSOEnabled());
        setComponentsAvailable(true);

        loadCredentials();
    }

    private void loadCredentials() {
        // Handle arguments
        setUsername(Spark.getArgumentValue("username"));
        setPassword(Spark.getArgumentValue("password"));
        setServerName(Spark.getArgumentValue("server"));
        if (validateDialog()) {
            TaskEngine.getInstance().submit(this::login);
            return;
        }

        // Load previous login and username
        setUsername(localPref.getLastUsername());
        setServerName(localPref.getServer());
        cbSavePassword.setSelected(localPref.isSavePassword());
        if (localPref.isSavePassword()) {
            String encryptedPassword = localPref.getPasswordForUser(getBareJid());
            setPassword(encryptedPassword);
        }
        // If autologin then sign in with the last time credentials
        if (validateDialog() && cbAutoLogin.isSelected()) {
            TaskEngine.getInstance().submit(this::login);
            return;
        }

        // Pre-populate server domain
        if (isBlank(getServerName())) {
            String lockedDownURL = Default.getString(Default.HOST_NAME);
            if (!isBlank(lockedDownURL)) {
                setServerName(lockedDownURL);
            } else {
                // Pick the computer's domain from Active Directory. XMPP domain may be there or on its subdomain.
                String userDnsDomain = System.getenv("USERDNSDOMAIN");
                if (!isBlank(userDnsDomain)) {
                    setServerName(userDnsDomain);
                }
            }
        }
        // Pre-populate username from OS user
        if (isBlank(getUsername())) {
            String osUsername = System.getProperty("user.name");
            if (!isBlank(osUsername)) {
                setUsername(osUsername);
            }
        }
    }

    // Should be called only from the Event Dispatcher Thread.
    private void configureVisibility() {
        // Most Swing components are not thread safe, and _must_ be executed on the Event Dispatcher Thread.
        if (SmackConfiguration.DEBUG && !EventQueue.isDispatchThread()) {
            throw new IllegalStateException("Must be called on the Event Dispatcher Thread (but was not)");
        }

        int height = filler3.getPreferredSize().height;
        if (Default.getBoolean(Default.HIDE_SAVE_PASSWORD_AND_AUTO_LOGIN) || !localPref.isPswdAutologin()) {
            pnlCheckboxes.remove(cbAutoLogin);
            pnlCheckboxes.remove(cbSavePassword);
            height = height + 20;
        }
        // Add option to hide "Login as invisible" selection on the login screen
        if (Default.getBoolean(Default.HIDE_LOGIN_AS_INVISIBLE) || !localPref.isInvisibleLogin()) {
            pnlCheckboxes.remove(cbLoginInvisible);
            height = height + 10;
        }

        // Add option to hide "Login anonymously" selection on the login screen
        if (Default.getBoolean(Default.HIDE_LOGIN_ANONYMOUSLY) || !localPref.isAnonymousLogin()) {
            pnlCheckboxes.remove(cbAnonymous);
            height = height + 10;
        }

        if (Default.getBoolean(Default.ACCOUNT_DISABLED) || !localPref.isAccountsReg()) {
            pnlBtns.remove(btnSignUp);
            height = height + 15;
        }

        if (!Default.getBoolean(Default.PASSWORD_RESET_ENABLED)) {
            pnlBtns.remove(btnReset);
        }

        if (Default.getBoolean(Default.ADVANCED_DISABLED) || !localPref.isAdvancedConfig()) {
            pnlBtns.remove(btnAdvanced);
            height = height + 15;
        }
        filler3.setPreferredSize(new Dimension(220, height));
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {
        pnlLeft = new JPanel();
        filler1 = new Box.Filler(new Dimension(0, 50), new Dimension(230, 50), new Dimension(32767, 50));
        lblLogo = new JLabel();
        jLabel1 = new JLabel();
        jLabel2 = new JLabel();
        pnlCenter = new JPanel();
        filler3 = new Box.Filler(new Dimension(0, 0), new Dimension(210, 20), new Dimension(0, 32767));
        pnlInputs = new JPanel();
        tfUsername = new JTextField();
        tfDomain = new JTextField();
        tfPassword = new JPasswordField();
        pnlCheckboxes = new JPanel();
        cbSavePassword = new JCheckBox();
        cbAutoLogin = new JCheckBox();
        cbLoginInvisible = new JCheckBox();
        cbAnonymous = new JCheckBox();
        pnlBtns = new JPanel();
        btnLogin = new JButton();
        btnSignUp = new JButton();
        btnAdvanced = new JButton();
        btnReset = new JButton();

        setLayout(new BorderLayout());

        pnlLeft.setPreferredSize(new Dimension(230, 0));
        pnlLeft.add(filler1);

        lblLogo.setHorizontalAlignment(CENTER);
        lblLogo.setIcon(new ImageIcon(getClass().getResource("/images/spark-64x64.png")));
        lblLogo.setPreferredSize(new Dimension(230, 80));
        lblLogo.setRequestFocusEnabled(false);
        pnlLeft.add(lblLogo);

        jLabel1.setFont(new Font("Tahoma", Font.PLAIN, 18));
        jLabel1.setHorizontalAlignment(CENTER);
        jLabel1.setPreferredSize(new Dimension(230, 22));
        pnlLeft.add(jLabel1);

        jLabel2.setText("Instant Messenger");
        pnlLeft.add(jLabel2);

        lblProgress.setHorizontalAlignment(CENTER);
        lblProgress.setIcon(new ImageIcon(getClass().getResource("/images/ripple.gif")));
        lblProgress.setHorizontalTextPosition(CENTER);
        lblProgress.setPreferredSize(new Dimension(230, 90));
        lblProgress.setRequestFocusEnabled(false);
        lblProgress.setVerticalTextPosition(BOTTOM);
        pnlLeft.add(lblProgress);

        add(pnlLeft, WEST);

        pnlCenter.setBackground(WHITE);
        pnlCenter.setMinimumSize(new Dimension(0, 0));
        pnlCenter.setPreferredSize(new Dimension(230, 0));
        FlowLayout flowLayout1 = new FlowLayout();
        flowLayout1.setAlignOnBaseline(true);
        pnlCenter.setLayout(flowLayout1);
        pnlCenter.add(filler3);

        pnlInputs.setBackground(WHITE);
        pnlInputs.setPreferredSize(new Dimension(230, 110));

        tfUsername.setPreferredSize(new Dimension(220, 30));
        tfUsername.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent evt) {
                tfUsernameMousePressed(evt);
            }
        });
        pnlInputs.add(tfUsername);

        tfDomain.setPreferredSize(new Dimension(220, 30));
        pnlInputs.add(tfDomain);

        tfPassword.setPreferredSize(new Dimension(220, 30));
        pnlInputs.add(tfPassword);

        pnlCenter.add(pnlInputs);

        pnlCheckboxes.setBackground(WHITE);
        pnlCheckboxes.setLayout(new BoxLayout(pnlCheckboxes, BoxLayout.Y_AXIS));

        cbSavePassword.setBackground(WHITE);
        cbSavePassword.setPreferredSize(new Dimension(220, 20));
        cbSavePassword.addActionListener(this::cbSavePasswordActionPerformed);
        pnlCheckboxes.add(cbSavePassword);

        cbAutoLogin.setBackground(WHITE);
        cbAutoLogin.setPreferredSize(new Dimension(220, 20));
        cbAutoLogin.addActionListener(this::cbAutoLoginActionPerformed);
        pnlCheckboxes.add(cbAutoLogin);

        cbLoginInvisible.setBackground(WHITE);
        cbLoginInvisible.setPreferredSize(new Dimension(220, 20));
        pnlCheckboxes.add(cbLoginInvisible);

        cbAnonymous.setBackground(WHITE);
        cbAnonymous.setPreferredSize(new Dimension(220, 20));
        cbAnonymous.addActionListener(this::cbAnonymousActionPerformed);
        pnlCheckboxes.add(cbAnonymous);

        pnlCenter.add(pnlCheckboxes);

        pnlBtns.setBackground(WHITE);
        pnlBtns.setPreferredSize(new Dimension(240, 120));

        btnLogin.setBackground(new Color(241, 100, 34));
        btnLogin.setForeground(WHITE);
        btnLogin.setEnabled(false);
        btnLogin.setPreferredSize(new Dimension(230, 30));
        btnLogin.addActionListener(this::btnLoginActionPerformed);
        pnlBtns.add(btnLogin);

        btnSignUp.setBackground(WHITE);
        btnSignUp.setBorderPainted(false);
        btnSignUp.setHorizontalTextPosition(LEADING);
        btnSignUp.setOpaque(false);
        btnSignUp.setPreferredSize(new Dimension(110, 28));
        btnSignUp.addActionListener(this::btnCreateAccountActionPerformed);
        pnlBtns.add(btnSignUp);

        btnAdvanced.setBackground(WHITE);
        btnAdvanced.setBorderPainted(false);
        btnAdvanced.setHorizontalTextPosition(LEADING);
        btnAdvanced.setOpaque(false);
        btnAdvanced.setPreferredSize(new Dimension(110, 28));
        btnAdvanced.addActionListener(this::btnAdvancedActionPerformed);
        pnlBtns.add(btnAdvanced);

        btnReset.setBackground(WHITE);
        btnReset.setBorderPainted(false);
        btnReset.setHorizontalTextPosition(LEADING);
        btnReset.setOpaque(false);
        btnReset.setPreferredSize(new Dimension(230, 28));
        btnReset.addActionListener(this::btnResetActionPerformed);
        pnlBtns.add(btnReset);

        pnlCenter.add(pnlBtns);

        add(pnlCenter, BorderLayout.CENTER);
    }// </editor-fold>//GEN-END:initComponents

    private void btnLoginActionPerformed(ActionEvent evt) {//GEN-FIRST:event_btnLoginActionPerformed
        TaskEngine.getInstance().submit(this::login);
    }//GEN-LAST:event_btnLoginActionPerformed

    private void btnCreateAccountActionPerformed(ActionEvent evt) {//GEN-FIRST:event_btnCreateAccountActionPerformed
        // pre-fill the user domain from the login page i.e. if it already tried to login
        DomainBareJid xmppDomain = JidCreate.domainBareFromOrNull(getServerName());
        startAccountRegistrationWizard(xmppDomain);
    }//GEN-LAST:event_btnCreateAccountActionPerformed

    public void startAccountRegistrationWizard(DomainBareJid xmppDomain) {
        AccountCreationWizard createAccountPanel = new AccountCreationWizard();
        createAccountPanel.setServer(xmppDomain);
        createAccountPanel.invoke(loginDialog);
        if (createAccountPanel.isRegistered()) {
            setUsername(createAccountPanel.getUsernameWithoutEscape());
            setPassword(createAccountPanel.getPassword());
            setServerName(createAccountPanel.getServer());
            validateDialog();
            btnLoginActionPerformed(null);
        }
    }//GEN-LAST:event_btnCreateAccountActionPerformed

    private void btnAdvancedActionPerformed(ActionEvent evt) {//GEN-FIRST:event_btnAdvancedActionPerformed
        final LoginSettingDialog loginSettingsDialog = new LoginSettingDialog();
        loginSettingsDialog.invoke(loginDialog);
        useSSO(localPref.isSSOEnabled());
    }//GEN-LAST:event_btnAdvancedActionPerformed


    private void cbSavePasswordActionPerformed(ActionEvent evt) {//GEN-FIRST:event_cbSavePasswordActionPerformed
        cbAutoLogin.setEnabled(cbSavePassword.isSelected());
        if (!cbSavePassword.isSelected()) {
            cbAutoLogin.setSelected(false);
        }
    }//GEN-LAST:event_cbSavePasswordActionPerformed

    private void cbAutoLoginActionPerformed(ActionEvent evt) {//GEN-FIRST:event_cbAutoLoginActionPerformed
        if ((cbAutoLogin.isSelected() && (!localPref.isSSOEnabled()))) {
            cbSavePassword.setSelected(true);
        }
    }//GEN-LAST:event_cbAutoLoginActionPerformed

    private void cbAnonymousActionPerformed(ActionEvent evt) {//GEN-FIRST:event_cbAnonymousActionPerformed
        tfUsername.setEnabled(!cbAnonymous.isSelected());
        tfPassword.setEnabled(!cbAnonymous.isSelected());
        validateDialog();
    }//GEN-LAST:event_cbAnonymousActionPerformed

    private void tfUsernameMousePressed(MouseEvent evt) {//GEN-FIRST:event_tfUsernameMousePressed
        if (SwingUtilities.isRightMouseButton(evt)) {
            getPopup().show(tfUsername, evt.getX(), evt.getY());
        }
    }//GEN-LAST:event_tfUsernameMousePressed

    private void btnResetActionPerformed(ActionEvent evt) {//GEN-FIRST:event_btnResetActionPerformed
        BrowserLauncher.openURL(Default.getString(Default.PASSWORD_RESET_URL));
    }//GEN-LAST:event_btnResetActionPerformed


    // Variables declaration - do not modify//GEN-BEGIN:variables
    private JButton btnAdvanced;
    private JButton btnSignUp;
    private JButton btnLogin;
    private JButton btnReset;
    private JCheckBox cbAnonymous;
    private JCheckBox cbAutoLogin;
    private JCheckBox cbLoginInvisible;
    private JCheckBox cbSavePassword;
    private Box.Filler filler1;
    private Box.Filler filler3;
    private JLabel jLabel1;
    private JLabel jLabel2;
    private JLabel lblLogo;
    public final JLabel lblProgress = new JLabel();
    private JPanel pnlBtns;
    private JPanel pnlCenter;
    private JPanel pnlCheckboxes;
    private JPanel pnlInputs;
    private JPanel pnlLeft;
    private JTextField tfDomain;
    private JPasswordField tfPassword;
    private JTextField tfUsername;
    // End of variables declaration//GEN-END:variables

    /**
     * Invokes the LoginDialog to be visible.
     */
    public void invoke() {
        // Before creating any connections. Update proxy if needed.
        updateProxyConfig();
        loginDialog = new JFrame(JiveInfo.getName());

        // Construct Dialog
        EventQueue.invokeLater(() -> {
            loginDialog.setIconImage(SparkManager.getApplicationImage().getImage());
            loginDialog.setContentPane(this);
            loginDialog.pack();
            loginDialog.setSize(550, 400);
            // Center dialog on screen
            GraphicUtils.centerWindowOnScreen(loginDialog);

            // Show dialog
            loginDialog.addWindowListener(new WindowAdapter() {
                @Override
                public void windowClosing(WindowEvent e) {
                    // If the user quits, just shut down the application.
                    System.exit(0);
                }
            });
            if (!isBlank(getUsername())) {
                tfPassword.requestFocus();
            }

            if (!localPref.isStartedHidden() || !localPref.isAutoLogin()) {
                // Make dialog top most.
                loginDialog.setVisible(true);
            }
        });
    }

    protected void afterLogin() {
        Log.debug("Performing post-login tasks");
        // The Event Dispatcher Thread should only be used for fast tasks. The tasks in this method potentially are not.
        if (SmackConfiguration.DEBUG && EventQueue.isDispatchThread()) {
            throw new IllegalStateException("Must NOT be called on the Event Dispatcher Thread (but was)");
        }
        // Make certain Enterprise features persist across future logins
        persistEnterprise();
        // Load plugins before Workspace initialization to avoid any UI delays during plugin rendering, but after
        // Enterprise initialization, which can pull in additional plugin configuration (eg: blacklist).
        PluginManager.getInstance().loadPlugins();
        Log.debug("Finished post-login tasks");
    }

    protected XMPPTCPConnectionConfiguration retrieveConnectionConfiguration() {
        String loginUsername = getUsername();
        String loginServer = getServerName();
        String loginPassword = getPassword();
        ConnectionConfiguration.SecurityMode securityMode = localPref.getSecurityMode();
        boolean useDirectTls = localPref.isDirectTls();
        boolean hostAndPortConfigured = localPref.isHostAndPortConfigured();
        int port = localPref.getXmppPort();
        int checkForPort = loginServer.indexOf(":");
        if (checkForPort != -1) {
            if (checkForPort != loginServer.length() - 2) { // : at end of string, so no port
                String portString = loginServer.substring(checkForPort + 1);
                // Set new port.
                port = Integer.parseInt(portString);
            }
            // strip the port from loginServer hostname
            loginServer = loginServer.substring(0, checkForPort);
        }

        ProxyInfo proxyInfo = getProxyInfo();
        DomainBareJid xmppDomain = JidCreate.domainBareFromOrThrowUnchecked(loginServer);
        final XMPPTCPConnectionConfiguration.Builder builder = XMPPTCPConnectionConfiguration.builder()
                .setUsernameAndPassword(loginUsername, loginPassword)
                .setXmppDomain(xmppDomain)
                .setPort(port)
                .setSendPresence(false)
                .setCompressionEnabled(localPref.isCompressionEnabled())
                .setSecurityMode(securityMode);
        if (localPref.isDebuggerEnabled()) {
            builder.enableDefaultDebugger();
        }
        if (hostAndPortConfigured) {
            builder.setHost(localPref.getXmppHost());
        }
        if (proxyInfo != null) {
            builder.setProxyInfo(proxyInfo);
        }
        configureConnectionTls(builder, securityMode, useDirectTls, hostAndPortConfigured, loginServer);
        // SPARK-1747: Don't use the GSS-API SASL mechanism when SSO is disabled.
        SASLAuthentication.unregisterSASLMechanism(SASLGSSAPIMechanism.class.getName());
        SASLAuthentication.unregisterSASLMechanism(SASLGSSAPIv3CompatMechanism.class.getName());
        // Add the mechanism only when SSO is enabled (which allows us to register the correct one).
        if (localPref.isSSOEnabled()) {
            // SPARK-1740: Register a mechanism that's compatible with Smack 3, when requested.
            if (localPref.isSaslGssapiSmack3Compatible()) {
                // SPARK-1747: Don't use the GSSAPI mechanism when SSO is disabled.
                SASLAuthentication.registerSASLMechanism(new SASLGSSAPIv3CompatMechanism());
            } else {
                SASLAuthentication.registerSASLMechanism(new SASLGSSAPIMechanism());
            }
        }

        if (localPref.isLoginAnonymously() && !localPref.isSSOEnabled()) {
            //later login() is called without arguments
            builder.performSaslAnonymousAuthentication();
        }

//        if(!isBlank(localPref.getTrustStorePath())) {
//        	config.setTruststorePath(localPref.getTrustStorePath());
//        	config.setTruststorePassword(localPref.getTrustStorePassword());
//        }
        return builder.build();
    }

    private ProxyInfo getProxyInfo() {
        if (!localPref.isProxyEnabled()) {
            return null;
        }
        ProxyInfo.ProxyType pType = localPref.getProtocol().equals("SOCKS") ? ProxyInfo.ProxyType.SOCKS5 : ProxyInfo.ProxyType.HTTP;
        String pHost = trimToNull(localPref.getHost());
        int pPort = localPref.getPort();
        String pUser = trimToNull(localPref.getProxyUsername());
        String pPass = trimToNull(localPref.getProxyPassword());
        if (pHost != null && pPort != 0) {
            ProxyInfo proxyInfo = new ProxyInfo(pType, pHost, pPort, pUser, pPass);
            return proxyInfo;
        }
        Log.error("No proxy info found but proxy type is enabled!");
        return null;
    }

    private void configureConnectionTls(XMPPTCPConnectionConfiguration.Builder builder, ConnectionConfiguration.SecurityMode securityMode, boolean useDirectTls, boolean hostPortConfigured, String serverName) {
        if (securityMode != ConnectionConfiguration.SecurityMode.disabled) {
            if (localPref.isDisableHostnameVerification()) {
                TLSUtils.disableHostnameVerificationForTlsCertificates(builder);
            }
            if (!useDirectTls) {
                // This use STARTTLS which starts initially plain connection to upgrade it to TLS.
                // It will use the same port as plain connections which is 5222.
                SparkSSLContextCreator.Options options = localPref.isAllowClientSideAuthentication() ? BOTH : ONLY_SERVER_SIDE;
                try {
                    SSLContext context = SparkSSLContextCreator.setUpContext(options);
                    builder.setSslContextFactory(() -> context);
                    builder.setSecurityMode(securityMode);
                    builder.setCustomX509TrustManager(new SparkTrustManager());
                } catch (NoSuchAlgorithmException | KeyManagementException | UnrecoverableKeyException | KeyStoreException | NoSuchProviderException e) {
                    Log.warning("Could not establish secured connection", e);
                }
            } else { // useDirectTls
                if (!hostPortConfigured) {
                    // SMACK does not support XEP-0368, and does not apply a port change, if the host is not changed too.
                    // Here, we force the host to be set (by doing a DNS lookup), and force the port to 5223 (which is the
                    // default Direct TLS port).
                    DnsName serverNameDnsName = DnsName.from(serverName);
                    List<InetAddress> resolvedAddresses = DNSUtil.getDNSResolver().lookupHostAddress(serverNameDnsName, null, DnssecMode.disabled);
                    if (resolvedAddresses.isEmpty()) {
                        throw new RuntimeException("Could not resolve " + serverNameDnsName);
                    }
                    builder.setHost(resolvedAddresses.get(0).getHostName());
                    builder.setPort(5223);
                }
                SparkSSLContextCreator.Options options = localPref.isAllowClientSideAuthentication() ? BOTH : ONLY_SERVER_SIDE;
                builder.setSocketFactory(new SparkSSLSocketFactory(options));
                // SMACK does not recognize a Direct TLS socket as being secure, which will cause a failure when
                // the 'required' Security Mode is defined. Here, we work around this by replacing that security mode with an
                // 'if-possible' setting.
                builder.setSecurityMode(ConnectionConfiguration.SecurityMode.ifpossible);
            }
            SASLAuthentication.registerSASLMechanism(new SASLExternalMechanism());
        }
    }

    /**
     * Returns the username the user defined.
     *
     * Should be called only from the Event Dispatcher Thread.
     */
    private String getUsername() {
        return XmppStringUtils.escapeLocalpart(tfUsername.getText().trim());
    }

    private void setUsername(String username) {
        tfUsername.setText(trimToEmpty(XmppStringUtils.unescapeLocalpart(username)));
    }


    /**
     * Returns the resulting bareJID from username and server
     *
     * Should be called only from the Event Dispatcher Thread.
     */
    private String getBareJid() {
        return getUsername() + "@" + getServerName();
    }

    /**
     * Returns the password specified by the user.
     *
     * Should be called only from the Event Dispatcher Thread.
     */
    private String getPassword() {
        return new String(tfPassword.getPassword()).trim();
    }

    private void setPassword(String password) {
        tfPassword.setText(trimToEmpty(password));
    }

    /**
     * Returns the server name specified by the user.
     *
     * Should be called only from the Event Dispatcher Thread.
     */
    private String getServerName() {
        return tfDomain.getText().trim();
    }

    private void setServerName(String xmppDomain) {
        tfDomain.setText(trimToEmpty(xmppDomain));
    }


    private JPopupMenu getPopup() {
        JPopupMenu popup = new JPopupMenu();
        for (final String key : _usernames) {
            JMenuItem menu = new JMenuItem(key);
            BareJid jid = JidCreate.bareFromOrNull(key);
            if (jid == null) {
                continue;
            }
            final String username = jid.getLocalpartOrNull().toString();
            final String host = jid.getDomain().toString();
            menu.addActionListener(e -> {
                setUsername(username);
                setServerName(host);
                String passwordForUser = localPref.getPasswordForUser(getBareJid());
                setPassword(passwordForUser);
                // if the account had a password stored, then we should save it again
                if (!isBlank(passwordForUser)) {
                    cbSavePassword.setSelected(true);
                }
                validateDialog();
            });
            popup.add(menu);
        }
        return popup;
    }

    /**
     * KeyListener implementation.
     */
    @Override
    public void keyTyped(KeyEvent e) {
        if (e.getKeyChar() == KeyEvent.VK_ENTER)
            if (btnLogin.isEnabled()) {
                TaskEngine.getInstance().submit(this::login);
            }
    }

    @Override
    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_RIGHT
                && ((JTextField) e.getSource()).getCaretPosition() == ((JTextField) e.getSource()).getText().length()) {
            getPopup().show(otherUsers, 0, 0);
        }
    }

    @Override
    public void keyReleased(KeyEvent e) {
        validateDialog();
    }

    /**
     * Checks the users input and enables/disables the login button depending on
     * state.
     *
     * Should be called only from the Event Dispatcher Thread.
     */
    private boolean validateDialog() {
        // Most Swing components are not thread safe, and _must_ be executed on the Event Dispatcher Thread.
        if (SmackConfiguration.DEBUG && !EventQueue.isDispatchThread()) {
            throw new IllegalStateException("Must be called on the Event Dispatcher Thread (but was not)");
        }
        boolean loginIsPossible = !isBlank(getServerName())
            && (!isBlank(getUsername()) || cbAnonymous.isSelected())
            && (!isBlank(getPassword()) || localPref.isSSOEnabled());
        btnLogin.setEnabled(loginIsPossible);
        return loginIsPossible;
    }

    @Override
    public void focusGained(FocusEvent e) {
        Object o = e.getSource();
        if (o instanceof JTextComponent) {
            ((JTextComponent) o).selectAll();
        }
    }

    @Override
    public void focusLost(FocusEvent e) {
    }

    /**
     * Enables/Disables the editable components in the login screen.
     *
     * Should be called only from the Event Dispatcher Thread.
     *
     * @param available true to enable components, otherwise false to disable.
     */
    private void setComponentsAvailable(boolean available)
    {
        // Most Swing components are not thread safe, and _must_ be executed on the Event Dispatcher Thread.
        if (SmackConfiguration.DEBUG && !EventQueue.isDispatchThread()) {
            throw new IllegalStateException("Must be called on the Event Dispatcher Thread (but was not)");
        }

        cbSavePassword.setEnabled(available);
        cbAutoLogin.setEnabled(available);
        cbLoginInvisible.setEnabled(available);
        cbAnonymous.setEnabled(available);
        btnAdvanced.setEnabled(available);
        btnSignUp.setEnabled(available);

        // Need to set both editable and enabled for best behavior.
        tfUsername.setEditable(available);
        tfUsername.setEnabled(available && !cbAnonymous.isSelected());

        tfPassword.setEditable(available);
        tfPassword.setEnabled(available && !cbAnonymous.isSelected());

        boolean domainEditable = !Default.getBoolean(Default.HOST_NAME_CHANGE_DISABLED) && localPref.isHostNameChange();
        tfDomain.setEditable(available && domainEditable);
        tfDomain.setEnabled(available && domainEditable);

        if (available) {
            // Reapply focus to password field
            tfPassword.requestFocus();
        }
    }

    /**
     * Displays the progress bar.
     *
     * Should be called only from the Event Dispatcher Thread.
     *
     * @param visible true to display progress bar, false to hide it.
     */
    private void setProgressBarVisible(boolean visible) {
        // Most Swing components are not thread safe, and _must_ be executed on the Event Dispatcher Thread.
        if (SmackConfiguration.DEBUG && !EventQueue.isDispatchThread()) {
            throw new IllegalStateException("Must be called on the Event Dispatcher Thread (but was not)");
        }

        lblProgress.setVisible(visible);
    }

    @Override
    public Dimension getPreferredSize() {
        final Dimension dim = super.getPreferredSize();
        dim.height = 230;
        return dim;
    }

    public void useSSO(boolean use) {
        if (use) {
            tfUsername.setVisible(true);
            tfPassword.setVisible(false);
            cbSavePassword.setVisible(false);
            cbSavePassword.setSelected(false);
            tfDomain.setVisible(true);
            cbAutoLogin.setVisible(true);
            cbLoginInvisible.setVisible(true);
            cbAnonymous.setVisible(false);

            System.setProperty("java.security.krb5.debug", String.valueOf(Log.isDebugEnabled()));
            System.setProperty("sun.security.krb5.debug", String.valueOf(Log.isDebugEnabled()));

            String ssoMethod = localPref.getSSOMethod();
            System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
            GSSAPIConfiguration config = new GSSAPIConfiguration(ssoMethod.equals("file"));
            Configuration.setConfiguration(config);

            String princName = localPref.getLastUsername();
            String princRealm = null;
            try {
                LoginContext lc = new LoginContext("com.sun.security.jgss.krb5.initiate");
                lc.login();
                Subject mySubject = lc.getSubject();
                for (Principal p : mySubject.getPrincipals()) {
                    //TODO: check if principal is a kerberos principal first...
                    String name = p.getName();
                    int indexOne = name.indexOf("@");
                    if (indexOne != -1) {
                        princName = name.substring(0, indexOne);
                        princRealm = name.substring(indexOne + 1);
                    }
                }
            } catch (LoginException le) {
                Log.debug(le.getMessage());
            }

            String ssoKdc;
            switch (ssoMethod) {
                case "dns":
                    if (princRealm != null) { //princRealm is null if we got a LoginException above.
                        ssoKdc = getDnsKdc(princRealm);
                        System.setProperty("java.security.krb5.realm", princRealm);
                        System.setProperty("java.security.krb5.kdc", ssoKdc);
                    }
                    break;
                case "manual":
                    princRealm = localPref.getSSORealm();
                    ssoKdc = localPref.getSSOKDC();
                    System.setProperty("java.security.krb5.realm", princRealm);
                    System.setProperty("java.security.krb5.kdc", ssoKdc);
                    break;
                case "file":
                default:
                    //Assume "file" method.  We don't have to do anything special,
                    //java takes care of it for us. Unset the props if they are set
                    System.clearProperty("java.security.krb5.realm");
                    System.clearProperty("java.security.krb5.kdc");
                    break;
            }

            String userName = localPref.getLastUsername();
            setUsername(!isBlank(userName) ? userName : princName);
        } else {
            cbAutoLogin.setVisible(true);
            tfUsername.setVisible(true);
            tfPassword.setVisible(true);
            cbSavePassword.setVisible(true);
            tfDomain.setVisible(true);
            cbLoginInvisible.setVisible(true);
            cbAnonymous.setVisible(true);

            Configuration.setConfiguration(null);
        }
    }

    /**
     * Login to the specified server using username, password, and workgroup.
     * Handles error representation as well as logging.
     *
     * Should _not_ be called from the Event Dispatcher Thread.
     */
    private void login() {
        Log.debug("Start login");
        if (localPref.isDebuggerEnabled()) {
            SmackConfiguration.DEBUG = true;
        }

        // The Event Dispatcher Thread should only be used for fast tasks. Network IO is not one of those.
        if (SmackConfiguration.DEBUG && EventQueue.isDispatchThread()) {
            throw new IllegalStateException("Must NOT be called on the Event Dispatcher Thread (but was)");
        }

        SmackConfiguration.setDefaultReplyTimeout(localPref.getTimeOut() * 1000);

        // Persist information
        String username = getUsername();
        String serverName = getServerName();
        //FIXME sometimes when the auto login enabled the
        if (isBlank(username) || isBlank(serverName)) {
            Log.error("No Username or Server");
            loginDialog.setVisible(true);
            String errorMessage = "No Username or Server. Available props are " + localPref;
            MessageDialog.showErrorDialog(loginDialog, errorMessage, null);
            return;
        }
        EntityBareJid bareJid = JidCreate.entityBareFromOrNull(getBareJid());
        String password = getPassword();

        localPref.setLastUsername(username);
        // Check to see if the password should be saved or cleared from file.
        localPref.setPasswordForUser(bareJid, cbSavePassword.isSelected() ? password : null);
        localPref.setSavePassword(cbSavePassword.isSelected());
        localPref.setAutoLogin(cbAutoLogin.isSelected());
        localPref.setServer(serverName);
        localPref.setLoginAsInvisible(cbLoginInvisible.isSelected());
        localPref.setLoginAnonymously(cbAnonymous.isSelected());

        try {
            EventQueue.invokeAndWait(() -> {
                setComponentsAvailable(false);
                setProgressBarVisible(true);
            });
        } catch (InterruptedException | InvocationTargetException e) {
            Log.error("Exception while modifying UI components during login.", e);
            return;
        }

        try {
            // TODO: SPARK-2140 - add support to Spark for stream management. Challenges expected around reconnection logic!
            XMPPTCPConnection.setUseStreamManagementDefault(false);
            AbstractXMPPConnection connection = new XMPPTCPConnection(retrieveConnectionConfiguration());
            connection.setParsingExceptionCallback(new ExceptionLoggingCallback());

            connection.connect();

            if (localPref.isLoginAnonymously() && !localPref.isSSOEnabled()) {
                // ConnectionConfiguration.performSaslAnonymousAuthentication() used earlier in connection configuration builder,
                // so now we can just login()
                connection.login();
            } else {
                String resource = localPref.getResource();
                if (Default.getBoolean(Default.USE_HOSTNAME_AS_RESOURCE) || localPref.isUseHostnameAsResource()) {
                    try {
                        resource = InetAddress.getLocalHost().getHostName();
                    } catch (UnknownHostException e) {
                        Log.warning("Cannot set hostname as resource - unable to retrieve hostname.", e);
                    }
                } else if (Default.getBoolean(Default.USE_VERSION_AS_RESOURCE) || localPref.isUseVersionAsResource()) {
                    resource = JiveInfo.getName() + " " + JiveInfo.getVersion();
                }
                
                localPref.setResource(resource);
                Resourcepart resourcepart = Resourcepart.from(modifyWildcards(resource).trim());
                connection.login(username, password, resourcepart);
            }
            Log.debug("Logged in!");

            final SessionManager sessionManager = SparkManager.getSessionManager();
            sessionManager.setServerAddress(connection.getXMPPServiceDomain());
            sessionManager.initializeSession(connection, username, password);
            sessionManager.setJID(connection.getUser());

            final ReconnectionManager reconnectionManager = ReconnectionManager.getInstanceFor(connection);
            reconnectionManager.setFixedDelay(localPref.getReconnectDelay());
            reconnectionManager.setReconnectionPolicy(ReconnectionManager.ReconnectionPolicy.FIXED_DELAY);
            reconnectionManager.enableAutomaticReconnection();

            final CarbonManager carbonManager = CarbonManager.getInstanceFor(connection);
            if (carbonManager.isSupportedByServer()) {
                carbonManager.enableCarbons();
            }
            // Initialize chat state notification mechanism in smack
            ChatStateManager.getInstance(connection);
        } catch (Exception xee) {
            Log.error("Exception in Login:", xee);
            final String errorMessage;
            if (localPref.isSSOEnabled()) {
                errorMessage = Res.getString("title.advanced.connection.sso.unable");
            } else if (xee.getMessage() != null && xee.getMessage().contains("not-authorized")) {
                errorMessage = Res.getString("message.invalid.username.password");
            } else if (xee.getMessage() != null && (xee.getMessage().contains("java.net.UnknownHostException:") || xee.getMessage().contains("Network is unreachable") || xee.getMessage().contains("java.net.ConnectException: Connection refused:"))) {
                errorMessage = Res.getString("message.server.unavailable");
            } else if (xee.getMessage() != null && xee.getMessage().contains("Hostname verification of certificate failed")) {
                errorMessage = Res.getString("message.cert.hostname.verification.failed");
            } else if (xee.getMessage() != null && xee.getMessage().contains("unable to find valid certification path to requested target")) {
                errorMessage = Res.getString("message.cert.verification.failed");
            } else if (xee.getMessage() != null && xee.getMessage().contains("StanzaError: conflict")) {
                errorMessage = Res.getString("label.conflict.error");
            } else if (xee instanceof SmackException) {
                errorMessage = xee.getLocalizedMessage();
            } else {
                errorMessage = Res.getString("message.unrecoverable.error");
            }

            EventQueue.invokeLater(() -> {
                setEnabled(true);
                setComponentsAvailable(true);
                setProgressBarVisible(false);
                // Show error dialog
                loginDialog.setVisible(true);
                    if (xee.getMessage() != null && xee.getMessage().contains("Self Signed certificate")) {
                        // Handle specific case: if server certificate is self-signed, but self-signed certs are not allowed, show a popup allowing the user to override.
                        // Prompt user if they'd like to add the failed chain to the trust store.
                        final Object[] options = {
                            Res.getString("yes"),
                            Res.getString("no")
                        };

                        final int userChoice = JOptionPane.showOptionDialog(this,
                                Res.getString("dialog.certificate.ask.allow.self-signed"),
                                Res.getString("title.certificate"),
                                JOptionPane.YES_NO_OPTION,
                                JOptionPane.WARNING_MESSAGE,
                                null,
                                options,
                                options[1]);

                        if (userChoice == JOptionPane.YES_OPTION) {
                            // Toggle the preference.
                            localPref.setAcceptSelfSigned(true);
                            // Attempt to login again.
                            TaskEngine.getInstance().submit(this::login);
                        }
                    } else {
                        final X509Certificate[] lastFailedChain = SparkTrustManager.getLastFailedChain();
                        final SparkTrustManager sparkTrustManager = (SparkTrustManager) SparkTrustManager.getTrustManagerList()[0];
                        // Handle specific case: if path validation failed because of an unrecognized CA, show popup allowing the user to add the certificate.
                        if (lastFailedChain != null && ((xee.getMessage() != null && xee.getMessage().contains("Certificate not in the TrustStore")) || !sparkTrustManager.containsTrustAnchorFor(lastFailedChain))) {
                            // Prompt user if they'd like to add the failed chain to the trust store.
                            final CertificateModel certModel = new CertificateModel(lastFailedChain[0]);
                            final Object[] options = {
                                Res.getString("yes"),
                                Res.getString("no")
                            };

                            final int userChoice = JOptionPane.showOptionDialog(this,
                                    new UnrecognizedServerCertificatePanel(certModel),
                                    Res.getString("title.certificate"),
                                    JOptionPane.YES_NO_OPTION,
                                    JOptionPane.WARNING_MESSAGE,
                                    null,
                                    options,
                                    options[1]);

                            if (userChoice == JOptionPane.YES_OPTION) {
                                // Add the certificate chain to the truststore.
                                sparkTrustManager.addChain(lastFailedChain);
                                // Attempt to login again.
                                TaskEngine.getInstance().submit(this::login);
                            }
                        } else {
                            // For anything else, show a generic error dialog.
                            MessageDialog.showErrorDialog(loginDialog, errorMessage, xee);
                        }
                    }
            });
        }

        afterLogin();

        EventQueue.invokeLater(()-> {
            lblProgress.setText(Res.getString("message.connecting.please.wait"));
            // Startup Spark
            startSpark();
        });
    }

    @Override
    public void handle(Callback[] callbacks) throws IOException {
        for (Callback callback : callbacks) {
            if (callback instanceof NameCallback) {
                NameCallback ncb = (NameCallback) callback;
                ncb.setName(getUsername());
            } else if (callback instanceof PasswordCallback) {
                PasswordCallback pcb = (PasswordCallback) callback;
                pcb.setPassword(getPassword().toCharArray());
            } else {
                Log.error("Unknown callback requested: " + callback.getClass().getSimpleName());
            }
        }
    }

    /**
     * Initializes Spark's main window.
     *
     * Should only be called from the Event Dispatcher Thread
     */
    private void startSpark() {
        Log.debug("Starting Spark's main window.");
        // Most Swing components are not thread safe, and _must_ be executed on the Event Dispatcher Thread.
        if (SmackConfiguration.DEBUG && !EventQueue.isDispatchThread()) {
            throw new IllegalStateException("Must be called on the Event Dispatcher Thread (but was not)");
        }
        // Invoke the MainWindow.
        try {
            final MainWindow mainWindow = MainWindow.getInstance();
            // Creates the Spark  Workspace and add to MainWindow
            Workspace workspace = Workspace.getInstance();
            LayoutSettings settings = LayoutSettingsManager.getLayoutSettings();
            LocalPreferences pref = SettingsManager.getLocalPreferences();
            if (pref.isDockingEnabled()) {
                JSplitPane splitPane = mainWindow.getSplitPane();
                workspace.getCardPanel().setMinimumSize(null);
                splitPane.setLeftComponent(workspace.getCardPanel());
                SparkManager.getChatManager().getChatContainer().setMinimumSize(null);
                splitPane.setRightComponent(SparkManager.getChatManager().getChatContainer());
                int dividerLoc = settings.getSplitPaneDividerLocation();
                if (dividerLoc == -1) {
                    dividerLoc = 240;
                }
                mainWindow.getSplitPane().setDividerLocation(dividerLoc);
                mainWindow.getContentPane().add(splitPane, BorderLayout.CENTER);
            } else {
                mainWindow.getContentPane().add(workspace.getCardPanel(), BorderLayout.CENTER);
            }

            final Rectangle mainWindowBounds = settings.getMainWindowBounds();
            if (mainWindowBounds == null || mainWindowBounds.width <= 0 || mainWindowBounds.height <= 0) {
                // Use Default size
                mainWindow.setSize(500, 520);
                // Center Window on Screen
                GraphicUtils.centerWindowOnScreen(mainWindow);
            } else {
                mainWindow.setBounds(mainWindowBounds);
            }

            if (loginDialog != null) {
                if (loginDialog.isVisible()) {
                    mainWindow.setVisible(true);
                }
                loginDialog.dispose();
            }
            // Build the layout in the workspace
            workspace.buildLayout();
            Log.debug("Finished starting Spark's main window.");
        } catch (Exception e) {
            Log.error("An exception occurred while trying to open Spark's main window.", e);
        }
    }

    /**
     * Updates System properties with Proxy configuration.
     */
    private void updateProxyConfig() {
        if (!isBlank(Default.getString(Default.PROXY_PORT)) && !isBlank(Default.getString(Default.PROXY_HOST))) {
            String port = Default.getString(Default.PROXY_PORT);
            String host = Default.getString(Default.PROXY_HOST);
            System.setProperty("socksProxyHost", host);
            System.setProperty("socksProxyPort", port);
            return;
        }

        boolean proxyEnabled = localPref.isProxyEnabled();
        if (proxyEnabled) {
            String host = localPref.getHost();
            String port = String.valueOf(localPref.getPort());
            String username = localPref.getProxyUsername();
            String password = localPref.getProxyPassword();
            String protocol = localPref.getProtocol();
            if (protocol.equals("SOCKS")) {
                System.setProperty("socksProxyHost", host);
                System.setProperty("socksProxyPort", port);
                if (!isBlank(username) && !isBlank(password)) {
                    System.setProperty("java.net.socks.username", username);
                    System.setProperty("java.net.socks.password", password);
                }
            } else {
                System.setProperty("http.proxyHost", host);
                System.setProperty("http.proxyPort", port);
                System.setProperty("https.proxyHost", host);
                System.setProperty("https.proxyPort", port);
                if (!isBlank(username) && !isBlank(password)) {
                    System.setProperty("http.proxyUser", username);
                    System.setProperty("http.proxyPassword", password);
                }
            }
        }
    }

    /**
     * Use DNS to lookup a KDC
     *
     * @param realm The realm to look up
     * @return the KDC hostname
     */
    private String getDnsKdc(String realm) {
        //Assumption: the KDC will be found with the SRV record
        // _kerberos._udp.$realm
        try {
            Hashtable<String, String> env = new Hashtable<>();
            env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
            DirContext context = new InitialDirContext(env);
            Attributes dnsLookup = context.getAttributes("_kerberos._udp." + realm, new String[]{"SRV"});

            ArrayList<Integer> priorities = new ArrayList<>();
            HashMap<Integer, List<String>> records = new HashMap<>();
            for (Enumeration<?> e = dnsLookup.getAll(); e.hasMoreElements();) {
                Attribute record = (Attribute) e.nextElement();
                for (Enumeration<?> e2 = record.getAll(); e2.hasMoreElements();) {
                    String sRecord = (String) e2.nextElement();
                    String[] sRecParts = sRecord.split(" ");
                    Integer pri = Integer.valueOf(sRecParts[0]);
                    if (priorities.contains(pri)) {
                        List<String> recs = records.get(pri);
                        if (recs == null) {
                            recs = new ArrayList<>();
                        }
                        recs.add(sRecord);
                    } else {
                        priorities.add(pri);
                        List<String> recs = new ArrayList<>();
                        recs.add(sRecord);
                        records.put(pri, recs);
                    }
                }
            }
            Collections.sort(priorities);
            List<String> l = records.get(priorities.get(0));
            String toprec = l.get(0);
            String[] sRecParts = toprec.split(" ");
            return sRecParts[3];
        } catch (NamingException e) {
            return "";
        }
    }

    private void persistEnterprise() {
        new Enterprise();
        //Check that the ClientControl plugin Openfire is installed on the server.
        if(Enterprise.isSparkManagerInstalled()){
            localPref.setAccountsReg(Enterprise.containsFeature(Enterprise.ACCOUNTS_REG_FEATURE));
            localPref.setAdvancedConfig(Enterprise.containsFeature(Enterprise.ADVANCED_CONFIG_FEATURE));
            localPref.setHostNameChange(Enterprise.containsFeature(Enterprise.HOST_NAME_FEATURE));
            localPref.setInvisibleLogin(Enterprise.containsFeature(Enterprise.INVISIBLE_LOGIN_FEATURE));
            localPref.setAnonymousLogin(Enterprise.containsFeature(Enterprise.ANONYMOUS_LOGIN_FEATURE));
            localPref.setPswdAutologin(Enterprise.containsFeature(Enterprise.SAVE_PASSWORD_FEATURE));
            if (Enterprise.containsFeature(Enterprise.HOSTNAME_AS_RESOURCE_FEATURE) != Enterprise.containsFeature(Enterprise.VERSION_AS_RESOURCE_FEATURE)) {
                localPref.setUseHostnameAsResource(Enterprise.containsFeature(Enterprise.HOSTNAME_AS_RESOURCE_FEATURE));
                localPref.setUseVersionAsResource(Enterprise.containsFeature(Enterprise.VERSION_AS_RESOURCE_FEATURE));
            }
            localPref.setFileTransferIbbOnly(Enterprise.containsFeature(Enterprise.IBB_FEATURE));
        }
    }

}
