/*
 * This file is part of muCommander, http://www.mucommander.com
 *
 * muCommander is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * muCommander is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */


package com.mucommander.ui.dialog.auth;

import com.mucommander.auth.CredentialsManager;
import com.mucommander.auth.CredentialsMapping;
import com.mucommander.commons.file.Credentials;
import com.mucommander.commons.file.FileURL;
import com.mucommander.commons.util.StringUtils;
import com.mucommander.commons.util.ui.combobox.EditableComboBox;
import com.mucommander.commons.util.ui.combobox.EditableComboBoxListener;
import com.mucommander.commons.util.ui.combobox.SaneComboBox;
import com.mucommander.commons.util.ui.dialog.DialogToolkit;
import com.mucommander.commons.util.ui.dialog.FocusDialog;
import com.mucommander.commons.util.ui.helper.FocusRequester;
import com.mucommander.commons.util.ui.layout.XAlignedComponentPanel;
import com.mucommander.commons.util.ui.layout.YBoxPanel;
import com.mucommander.text.Translator;
import com.mucommander.ui.layout.InformationPane;
import com.mucommander.ui.main.MainFrame;

import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPasswordField;
import javax.swing.JRadioButton;
import javax.swing.JSeparator;
import javax.swing.JTextField;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;


/**
 * This dialog is used to ask the user for credentials (login/password) to access a particular location and offer him
 * to store them to disk.
 *
 * <p>It uses CredentialsManager to retrieve and display a list of credentials matching the location so
 * they can quickly be recalled.
 *
 * @see CredentialsManager
 * @author Maxence Bernard
 */
public class AuthDialog extends FocusDialog implements ActionListener, EditableComboBoxListener {

    private JButton okButton;
    private JButton cancelButton;

    private JRadioButton guestRadioButton;
    private JRadioButton userRadioButton;

    private JTextField loginField;
    private EditableComboBox loginComboBox;

    private JPasswordField passwordField;

    private JCheckBox saveCredentialsCheckBox;

    private CredentialsMapping selectedCredentialsMapping;
    private boolean guestCredentialsSelected;

    private FileURL fileURL;

    private CredentialsMapping[] credentialsMappings;

    // Dialog size constraints
    private final static Dimension MINIMUM_DIALOG_DIMENSION = new Dimension(320,0);
    private final static Dimension MAXIMUM_DIALOG_DIMENSION = new Dimension(480,10000);


    public AuthDialog(MainFrame mainFrame, FileURL fileURL, boolean authFailed, String errorMessage) {
        super(mainFrame.getJFrame(), Translator.get("auth_dialog.title"), mainFrame.getJFrame());
	
        Container contentPane = getContentPane();
        contentPane.setLayout(new BorderLayout());

        YBoxPanel yPanel = new YBoxPanel();

        if(authFailed) {
            yPanel.add(new InformationPane(Translator.get("auth_dialog.authentication_failed"), errorMessage, errorMessage==null?Font.PLAIN:Font.BOLD, InformationPane.ERROR_ICON));
            yPanel.addSpace(5);
            yPanel.add(new JSeparator());
        }

        yPanel.addSpace(5);
        
        this.fileURL = fileURL;

        // Retrieve guest credentials (if any)
        Credentials guestCredentials = fileURL.getGuestCredentials();
        // Fetch credentials from the specified FileURL (if any) and use them only if they're different from the guest ones
        Credentials urlCredentials = fileURL.getCredentials();
        if(urlCredentials!=null && guestCredentials!=null && urlCredentials.equals(guestCredentials))
            urlCredentials = null;
        // Retrieve a list of credentials matching the URL from CredentialsManager
        credentialsMappings = CredentialsManager.getMatchingCredentials(fileURL);

        XAlignedComponentPanel compPanel = new XAlignedComponentPanel(10);

        // Connect as Guest/User radio buttons, displayed only if the URL has guest credentials
        if(guestCredentials!=null) {
            guestRadioButton = new JRadioButton(StringUtils.capitalize(guestCredentials.getLogin()));
            guestRadioButton.addActionListener(this);
            compPanel.addRow(Translator.get("auth_dialog.connect_as"), guestRadioButton, 0);

            userRadioButton = new JRadioButton(Translator.get("user"));
            userRadioButton.addActionListener(this);
            compPanel.addRow("", userRadioButton, 15);

            ButtonGroup buttonGroup = new ButtonGroup();
            buttonGroup.add(guestRadioButton);
            buttonGroup.add(userRadioButton);
        }
        // If not, display an introduction label ("please enter a login and password")
        else {
            yPanel.add(new JLabel(Translator.get("auth_dialog.desc")));
            yPanel.addSpace(15);
        }

        // Server URL for which the user has to authenticate
        compPanel.addRow(Translator.get("auth_dialog.server"), new JLabel(fileURL.toString(false)), 10);

        // Login field: create either a text field or an editable combo box, depending on whether
        // CredentialsManager returned matches (-> combo box) or not (-> text field).
        int nbCredentials = credentialsMappings.length;
        JComponent loginComponent;
        if(nbCredentials>0) {
            // Editable combo box
            loginComboBox = new EditableComboBox();
            this.loginField = loginComboBox.getTextField();

            // Add credentials to the combo box's choices
            for(int i=0; i<nbCredentials; i++)
                loginComboBox.addItem(credentialsMappings[i].getCredentials().getLogin());

            loginComboBox.addEditableComboBoxListener(this);

            loginComponent = loginComboBox;
        }
		else {
            // Simple text field
            loginField = new JTextField();
            loginComponent = loginField;
        }

        compPanel.addRow(Translator.get("login"), loginComponent, 5);

        // Create password field
        this.passwordField = new JPasswordField();
        passwordField.addActionListener(this);
        compPanel.addRow(Translator.get("password"), passwordField, 10);

        // Contains the credentials to set in the login and password text fields
        Credentials selectedCredentials = null;
        // Whether the 'save credentials' checkbox should be enabled
        boolean saveCredentialsCheckBoxSelected = false;

        // If the provided URL contains credentials, use them
        if(urlCredentials!=null) {
            selectedCredentials = urlCredentials;
        }
        // Else if CredentialsManager had matching credentials, use the best ones  
        else if(nbCredentials>0) {
            CredentialsMapping bestCredentialsMapping = credentialsMappings[0];

            selectedCredentials = bestCredentialsMapping.getCredentials();
            saveCredentialsCheckBoxSelected = bestCredentialsMapping.isPersistent();
        }

        yPanel.add(compPanel);

        this.saveCredentialsCheckBox = new JCheckBox(Translator.get("auth_dialog.store_credentials"), saveCredentialsCheckBoxSelected);
        yPanel.add(saveCredentialsCheckBox);

        yPanel.addSpace(5);
        contentPane.add(yPanel, BorderLayout.CENTER);

        // If we have some existing credentials for this location...
        if(selectedCredentials!=null) {
            // Prefill the login and password fields with the selected credentials
            loginField.setText(selectedCredentials.getLogin());
            passwordField.setText(selectedCredentials.getPassword());

            // Select the text fields' so their content can be erased just by typing the replacement string
            loginField.selectAll();
            passwordField.selectAll();

            // Select the 'Connect as User' radio button if there is one
            if(userRadioButton!=null)
                userRadioButton.setSelected(true);
        }
        else {
            // Prefill the login field with the current user's name (ticket #185)
            loginField.setText(System.getProperty("user.name"));

            // Select the 'Connect as Guest' radio button if there is one
            if(guestRadioButton!=null) {
                guestRadioButton.setSelected(true);

                loginField.setEnabled(false);
                passwordField.setEnabled(false);
                saveCredentialsCheckBox.setEnabled(false);
            }
        }

        // Add OK/Cancel buttons
        this.okButton = new JButton(Translator.get("ok"));
        this.cancelButton = new JButton(Translator.get("cancel"));
        contentPane.add(DialogToolkit.createOKCancelPanel(okButton, cancelButton, getRootPane(), this), BorderLayout.SOUTH);

        // Set the component that will receive the initial focus
        setInitialFocusComponent(guestRadioButton==null?loginField:guestRadioButton.isSelected()?guestRadioButton:loginField);

        // Set minimum dimension
        setMinimumSize(MINIMUM_DIALOG_DIMENSION);

        // Set minimum dimension
        setMaximumSize(MAXIMUM_DIALOG_DIMENSION);
    }


    /**
     * Returns the <Code>CredentialsMapping</code> corresponding to the credentials selected by the user, either
     * entered in the login and password fields, or the guest credentials.
     *
     * @return the credentials entered by the user, <code>null</code> if the dialog was cancelled
     */
    public CredentialsMapping getCredentialsMapping() {
        return selectedCredentialsMapping;
    }

    /**
     * Returns <code>true</code> if the user chose the guest credentials (radio button) in the dialog.
     * If <code>true</code>, {@link #getCredentialsMapping()} will return the guest credentials.
     *
     * @return <code>true</code> if the user chose the guest credentials (radio button) in the dialog
     */
    public boolean guestCredentialsSelected() {
        return guestCredentialsSelected;
    }

    /**
     * Called when the dialog has been validated by the user, when the OK button has been pressed or when enter has
     * been pressed in a text field.
     */
    private void setCredentialMapping() {
        if(guestRadioButton!=null && guestRadioButton.isSelected()) {
            guestCredentialsSelected = true;
            selectedCredentialsMapping = new CredentialsMapping(fileURL.getGuestCredentials(), fileURL, false);
        }
        else {
            Credentials enteredCredentials = new Credentials(loginField.getText(), new String(passwordField.getPassword()));
            guestCredentialsSelected = false;

            boolean isPersistent = saveCredentialsCheckBox.isSelected();
            selectedCredentialsMapping = new CredentialsMapping(enteredCredentials, fileURL, isPersistent);

            // Look for an existing matching CredentialsMapping instance to re-use the realm which may contain
            // connection properties.
            int nbCredentials = credentialsMappings.length;
            CredentialsMapping cm;
            for(int i=0; i<nbCredentials; i++) {
                cm = credentialsMappings[i];
                if(cm.getCredentials().equals(enteredCredentials, true)) {  // Comparison must be password-sensitive
                    // Create a new CredentialsMapping instance in case the 'isPersistent' flag has changed.
                    // (original credentials may have originally been added as 'volatile' and then made persistent by
                    // ticking the checkbox, or vice-versa)
                    selectedCredentialsMapping = new CredentialsMapping(cm.getCredentials(), cm.getRealm(), isPersistent);
                    break;
                }
            }
        }
    }


    ////////////////////////////
    // ActionListener methods //
    ////////////////////////////

    public void actionPerformed(ActionEvent e) {
        Object source = e.getSource();

        if(source==okButton || source==loginField || source==passwordField) {
            setCredentialMapping();
            dispose();
        }
        else if(source==cancelButton) {
            dispose();
        }
        else if(source==guestRadioButton) {
            loginField.setEnabled(false);
            passwordField.setEnabled(false);
            saveCredentialsCheckBox.setEnabled(false);
        }
        else if(source==userRadioButton) {
            loginField.setEnabled(true);
            passwordField.setEnabled(true);
            saveCredentialsCheckBox.setEnabled(true);

            loginField.selectAll();
            FocusRequester.requestFocus(loginField);
        }
    }


    /////////////////////////////////////////////
    // EditableComboBoxListener implementation //
    /////////////////////////////////////////////

    public void comboBoxSelectionChanged(SaneComboBox source) {
        CredentialsMapping selectedCredentialsMapping = credentialsMappings[loginComboBox.getSelectedIndex()];
        Credentials selectedCredentials = selectedCredentialsMapping.getCredentials();
        loginField.setText(selectedCredentials.getLogin());
        passwordField.setText(selectedCredentials.getPassword());

        // Enable/disable 'save credentials' checkbox depending on whether the selected credentials are persistent or not
        if(saveCredentialsCheckBox!=null)
            saveCredentialsCheckBox.setSelected(selectedCredentialsMapping.isPersistent());
    }

    public void textFieldValidated(EditableComboBox source) {
        setCredentialMapping();
        dispose();
    }

    public void textFieldCancelled(EditableComboBox source) {
    }
}
