/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2007 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * Initial developer: Jeremie Laurent, Yann Petiot, Frederic Rinaldi
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.security.auth.callback;

import java.awt.GridLayout;
import java.io.IOException;

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.callback.UnsupportedCallbackException;
import javax.swing.BoxLayout;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JTextField;

/**
 * Uses a Swing dialog window to query the user for answers to authentication
 * questions. This can be used by a JAAS application to instantiate a
 * CallbackHandler
 * @author Jeremie Laurent, Yann Petiot, Frederic Rinaldi
 * @author Florent Benoit. Integration in the JOnAS 3.1.2 tree
 */
public class DialogCallbackHandler implements CallbackHandler {

    /**
     * The text field where the user will put its login.
     */
    private JTextField loginField = null;

    /**
     * The password field where the user will put its password.
     */
    private JTextField passwordField = null;

    /**
     * If the button cancel is clicked, the user won't be prompted no more.
     */
    private boolean cancelled = false;

    /**
     * The title of the dialog box.
     */
    private String title = "Login Dialog";

    /**
     * The label for the username label.
     */
    private String username = "Username  ";

    /**
     * The label for the password label.
     */
    private String password = "Password  ";

    /**
     * The label for the login button.
     */
    private String loginButton = "Login !";

    /**
     * The label for the cancel button.
     */
    private String cancelButton = "Cancel";

    /**
     * Maximum length for fields.
     */
    private static final int MAX_FIELD_LENGTH = 20;

    /**
     * the length of the password textfield.
     */
    private int passwordLength = MAX_FIELD_LENGTH;

    /**
     * the length for the username textfield.
     */
    private int usernameLength = MAX_FIELD_LENGTH;

    /**
     * the character which will be displayed in the password textfield if
     * echoCharOn is true.
     */
    private char echoChar = '*';

    /**
     * whether the echo is on or not : display the echoChar in the password
     * textfield.
     */
    private boolean echoCharOn = false;

    /**
     * an array of String where are described the label of the buttons.
     */
    private String[] connectOptionNames;

    /**
     * The constructor to create a callback dialog with the default parent
     * window.
     */
    public DialogCallbackHandler() {
        int i = 0;
        connectOptionNames = new String[2];
        connectOptionNames[i] = loginButton;
        connectOptionNames[++i] = cancelButton;
    }

    /**
     * The constructor to create a callback dialog with the default parent
     * window.
     * @param title the title of the dialog box
     */
    public DialogCallbackHandler(final String title) {
        this();
        this.title = title;
    }

    /**
     * The constructor to create a callback dialog with the default parent
     * window.
     * @param title the title of the dialog box
     * @param username the label of the username label
     * @param password the label of the password label
     */
    public DialogCallbackHandler(final String title,
                                 final String username,
                                 final String password) {
        this();
        this.title = title;
        this.username = username;
        this.password = password;
    }

    /**
     * The constructor to create a callback dialog with the default parent
     * window.
     * @param title the title of the dialog box
     * @param username the label of the username label
     * @param password the label of the password label
     * @param loginButton the label of the login button
     * @param cancelButton the label of the cancel button
     * @param usernameLength the length of the username field
     * @param passwordLength the length of the password field
     * @param echoChar the character to display when entering the password
     *        implies that echoCharOn = true
     */
    public DialogCallbackHandler(final String title,
                                 final String username,
                                 final String password,
                                 final String loginButton,
                                 final String cancelButton,
                                 final int usernameLength,
                                 final int passwordLength,
                                 final char echoChar) {
        this(title, username, password);
        this.echoCharOn = true;
        this.echoChar = echoChar;
        this.loginButton = loginButton;
        this.cancelButton = cancelButton;
        this.passwordLength = passwordLength;
        this.usernameLength = usernameLength;
        this.echoChar = echoChar;
    }

    /**
     * /** This method allows to create a new instance of JDialog initialised
     * with the parameters.
     * @param isEchoOn the value of passwordCallback.isEchoOn() called in handle
     */
    private void dialogInit(final boolean isEchoOn) {

        // to determine whether the echo char must be displayed or not
        // we use a "OR" between the isEchoOn=passwordCallback.isEchoOn() and
        // the echoCharOn=the value of the variable echoCharOn set in the
        // OptionPaneCallbackHandler constructor.
        echoCharOn = (isEchoOn || echoCharOn);
        dialogInit();
    }

    /**
     * This method allows to create a new instance of JDialog initialised with
     * the parameters.
     */
    private void dialogInit() {

        // for the login
        JLabel userNameLabel = new JLabel(username, JLabel.RIGHT);
        loginField = new JTextField("");

        // for the password
        JLabel passwordLabel = new JLabel(password, JLabel.RIGHT);

        // because of the bug of Java (in SDK 1.3) it isn't possible to display
        // password without an echo character in a password field, we have to
        // create a JTextField or a JPasswordField depending on echoCharOn.
        if (!echoCharOn) { // password needs to be hidden
            passwordField = new JPasswordField(passwordLength);
            ((JPasswordField) passwordField).setEchoChar(echoChar);
        } else {
            passwordField = new JTextField(passwordLength);
        }

        // creation of a JPanel to put the other panels on
        JPanel connectionPanel = new JPanel(false);
        connectionPanel.setLayout(new BoxLayout(connectionPanel, BoxLayout.X_AXIS));

        // to this panel we add the labels
        JPanel namePanel = new JPanel(false);
        namePanel.setLayout(new GridLayout(0, 1));
        namePanel.add(userNameLabel);
        namePanel.add(passwordLabel);

        // to this panel we add the fields
        JPanel fieldPanel = new JPanel(false);
        fieldPanel.setLayout(new GridLayout(0, 1));
        fieldPanel.add(loginField);
        fieldPanel.add(passwordField);

        // we add the 2 panels created in the first one
        connectionPanel.add(namePanel);
        connectionPanel.add(fieldPanel);

        // Connect or quit
        //System.out.println("Displaying dialog...");
        int choice = JOptionPane.showOptionDialog(null, // the default frame
                connectionPanel, // the object to display
                title, // the title string
                JOptionPane.OK_CANCEL_OPTION, // the options available
                JOptionPane.INFORMATION_MESSAGE, // the kind of message
                null, // the icon to display
                connectOptionNames, // the possible choices
                loginField); // the default selection

        if (choice == JOptionPane.OK_OPTION) {
            cancelled = false;
        } else if (choice == JOptionPane.CANCEL_OPTION) {
            // why is this value never got ?
            // JOptionPane.CANCEL_OPTION=2 but choice=1
            cancelled = true;
        } else if (choice == JOptionPane.CLOSED_OPTION) {
            cancelled = true;
        } else {
            cancelled = true; // another bug of Java
        }

        if (cancelled) {
            loginField.setText("Invalid");
            passwordField.setText("Invalid");
        }
    }

    /**
     * Invoke an array of Callbacks. <p>
     * @param callbacks an array of <code>Callback</code> objects which
     *        contain the information requested by an underlying security
     *        service to be retrieved or displayed.
     * @exception java.io.IOException if an input or output error occurs. <p>
     * @exception UnsupportedCallbackException if the implementation of this
     *            method does not support one or more of the Callbacks specified
     *            in the <code>callbacks</code> parameter.
     */
    public void handle(final Callback[] callbacks) throws IOException, UnsupportedCallbackException {

        // if the user clicked on the cancel button the the system won't
        // prompt him its informations
        if (cancelled) {
            return;
        }

        // recreate a new JDialog with the property to display or not
        // the password echo
        int i = 0;
        boolean found = false;
        while ((i < callbacks.length) && !found) {
            if (callbacks[i] instanceof PasswordCallback) {
                found = true;
                dialogInit(((PasswordCallback) callbacks[i]).isEchoOn());
            }
            // maybe we'll have to create a ObjectCallback object see later.
            i++;
        }
        // if there's no instance of PasswordCallback in the parameter
        // callbacks then there's a problem here !
        // TO DO : a new dialogInit to integrate an ObjectCallback for example
        // (for pki...)
        // while waiting...
        if (!found) {
            dialogInit();
        }

        // get the informations of the user
        for (i = 0; i < callbacks.length; i++) {

            if (callbacks[i] instanceof NameCallback) {
                ((NameCallback) callbacks[i]).setName(loginField.getText());
            } else if (callbacks[i] instanceof PasswordCallback) {
                ((PasswordCallback) callbacks[i]).setPassword((passwordField.getText()).toCharArray());
            } else {
                throw new UnsupportedCallbackException(callbacks[i], "Unrecognized Callback");
            }
        }
    }
}
