/*
 * This file is part of muCommander, http://www.mucommander.com
 * Copyright (C) 2002-2012 Maxence Bernard
 *
 * 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.ui.combobox.EditableComboBox;
import com.mucommander.ui.combobox.EditableComboBoxListener;
import com.mucommander.ui.combobox.SaneComboBox;
import com.mucommander.ui.dialog.DialogToolkit;
import com.mucommander.ui.dialog.FocusDialog;
import com.mucommander.ui.helper.FocusRequester;
import com.mucommander.ui.layout.InformationPane;
import com.mucommander.ui.layout.XAlignedComponentPanel;
import com.mucommander.ui.layout.YBoxPanel;
import com.mucommander.ui.main.MainFrame;

import javax.swing.*;
import java.awt.*;
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 final JButton btnOk;
    private final JButton btnCancel;

    private JRadioButton guestRadioButton;
    private JRadioButton userRadioButton;

    private final JTextField loginField;
    private EditableComboBox<String> loginComboBox;

    private final JPasswordField passwordField;

    private final JCheckBox cbSaveCredentials;

    private CredentialsMapping selectedCredentialsMapping;
    private boolean guestCredentialsSelected;

    private final FileURL fileURL;

    private final 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, i18n("auth_dialog.title"), mainFrame);
	
        Container contentPane = getContentPane();
        contentPane.setLayout(new BorderLayout());

        YBoxPanel yPanel = new YBoxPanel();

        if (authFailed) {
            yPanel.add(new InformationPane(i18n("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 && 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) {
            addGuestCredentials(guestCredentials, compPanel);
        } else {
            // If not, display an introduction label ("please enter a login and password")
            yPanel.add(new JLabel(i18n("auth_dialog.desc")));
            yPanel.addSpace(15);
        }

        // Server URL for which the user has to authenticate
        compPanel.addRow(i18n("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 (CredentialsMapping credentialsMapping : credentialsMappings) {
                loginComboBox.addItem(credentialsMapping.getCredentials().getLogin());
            }

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

        compPanel.addRow(i18n("login"), loginComponent, 5);

        // create password field
        this.passwordField = new JPasswordField();
        passwordField.addActionListener(this);
        compPanel.addRow(i18n("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 (nbCredentials > 0) {
            // Else if CredentialsManager had matching credentials, use the best ones
            CredentialsMapping bestCredentialsMapping = credentialsMappings[0];

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

        yPanel.add(compPanel);

        this.cbSaveCredentials = new JCheckBox(i18n("auth_dialog.store_credentials"), saveCredentialsCheckBoxSelected);
        yPanel.add(cbSaveCredentials);

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

        // If we have some existing credentials for this location...
        if (selectedCredentials != null) {
            // Pre-fill 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 {
            // Pre-fill 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);
                cbSaveCredentials.setEnabled(false);
            }
        }

        // Add OK/Cancel buttons
        this.btnOk = new JButton(i18n("ok"));
        this.btnCancel = new JButton(i18n("cancel"));
        contentPane.add(DialogToolkit.createOKCancelPanel(btnOk, btnCancel, 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);
    }

    private void addGuestCredentials(Credentials guestCredentials, XAlignedComponentPanel compPanel) {
        guestRadioButton = new JRadioButton(StringUtils.capitalize(guestCredentials.getLogin()));
        guestRadioButton.addActionListener(this);
        compPanel.addRow(i18n("auth_dialog.connect_as"), guestRadioButton, 0);

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

        ButtonGroup buttonGroup = new ButtonGroup();
        buttonGroup.add(guestRadioButton);
        buttonGroup.add(userRadioButton);
    }


    /**
     * 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 = cbSaveCredentials.isSelected();
            selectedCredentialsMapping = new CredentialsMapping(enteredCredentials, fileURL, isPersistent);

            // Look for an existing matching CredentialsMapping instance to re-use the realm which may contain
            // connection properties.
            for (CredentialsMapping cm : credentialsMappings) {
                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;
                }
            }
        }
    }


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

        if (source == btnOk || source == loginField || source == passwordField) {
            setCredentialMapping();
            dispose();
        } else if (source == btnCancel) {
            dispose();
        } else if (source == guestRadioButton) {
            loginField.setEnabled(false);
            passwordField.setEnabled(false);
            cbSaveCredentials.setEnabled(false);
        } else if(source == userRadioButton) {
            loginField.setEnabled(true);
            passwordField.setEnabled(true);
            cbSaveCredentials.setEnabled(true);

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


    @Override
    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 (cbSaveCredentials != null) {
            cbSaveCredentials.setSelected(selectedCredentialsMapping.isPersistent());
        }
    }

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

    @Override
    public void textFieldCancelled(EditableComboBox source) {
    }
}
