/**
 *#########################################################################
 *
 * A component of the Gatherer application, part of the Greenstone digital
 * library suite from the New Zealand Digital Library Project at the
 * University of Waikato, New Zealand.
 *
 * <BR><BR>
 *
 * Author: John Thompson, Greenstone Digital Library, University of Waikato
 *
 * <BR><BR>
 *
 * Copyright (C) 1999 New Zealand Digital Library Project
 *
 * <BR><BR>
 *
 * This program 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 2 of the License, or
 * (at your option) any later version.
 *
 * <BR><BR>
 *
 * This program 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.
 *
 * <BR><BR>
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *########################################################################
 */ 
package org.greenstone.gatherer;

import java.awt.*;
import java.awt.event.*;
import java.net.*;
import java.util.*;
import javax.swing.*;


import org.greenstone.gatherer.gui.GLIButton;
import org.greenstone.gatherer.gui.TestingPreparation;

/** Provides a graphic authenticator for network password requests.
 * @author John Thompson, Greenstone Digital Library, University of Waikato
 * @version 2.3
 */
public class GAuthenticator
    extends Authenticator
{
    static public Hashtable authentications = new Hashtable();

    /** Indicates if this authentication prompt been cancelled, and if so rolls-back authentication. */
    private boolean authentication_cancelled = false;
    /** The button used to cancel a prompt. */
    private JButton cancel_button = null;
    /** The button used to submit the login/password. */
    private JButton ok_button = null;
    /** A reference to the dialog prompt created so inner classes can dispose of it. */
    private JDialog dialog = null;
    /** The password is a special starred out password field. */
    private JPasswordField password = null;
    /** The default size of this dialog. */
    static final private Dimension SIZE = new Dimension(470,160);
    
    /** For Wget downloads, we want to avoid the second automatic request for proxy authentication
     * But the settings for that specific case will otherwise interfere with how this GAuthenticator 
     * is to function in the usual situation. For this reason, we use two modes.*/
    static final public int REGULAR = 0;
    static final public int DOWNLOAD = 1;
    static private int operationMode = REGULAR; 

    public static void setMode(int mode) {
	operationMode = mode;
    }
    
    /** Constructor. */
    public GAuthenticator() {
    }

    /** Prompt the user for authentication using a pretty dialog box.
     * @return A <strong>PasswordAuthentication</strong> object containing the login and password valuees the user has submitted.
     * @see org.greenstone.gatherer.GAuthenticator.AuthenticationActionListener
     * @see org.greenstone.gatherer.GAuthenticator.RequestFocusListener
     */
    protected PasswordAuthentication getPasswordAuthentication(String username_str, String password_str) {
	if(operationMode == DOWNLOAD) { // special handling of proxy authentication popup for Wget downloads
	    
	    // Don't prompt if the details were already saved for the same host and port. This is necessary
	    // when running wget because wget requires proxy authentication. And without the following, the
	    // regular proxy authentication would also popup a dialog requesting the same information.
	    
	    String key = getRequestingHost() + ":" + getRequestingPort();
	    String value = (String)authentications.get(key);
	    
	    if(value != null) { // authentication for this host and port was already stored
		// Arguments may be null. If so, retrieve them from the stored value
		if(username_str == null) {
		    username_str = value.substring(0, value.indexOf("@"));
		} 
		if(password_str == null) {
		    password_str = value.substring(value.indexOf("@") + 1);
		}
		operationMode = REGULAR; // reset the state of the Authenticator to normal mode
		return new PasswordAuthentication(username_str, password_str.toCharArray());
	    }
	}
	
	// Component definition.
	dialog = new JDialog (Gatherer.g_man, Dictionary.get("GAuthenticator.Title"), true);
	dialog.setModal(true);
	dialog.setSize(SIZE);
	JPanel content_pane = (JPanel) dialog.getContentPane();
	JLabel title_label = new JLabel(getMessageString());

	JPanel user_panel = new JPanel();
	JLabel username_label = new JLabel(Dictionary.get("GAuthenticator.Username"));
	JTextField username = new JTextField();
	username.setToolTipText(Dictionary.get("GAuthenticator.Username_Tooltip"));
	if (username_str != null) {
	    username.setText(username_str);
	}

	JPanel password_panel = new JPanel();
	JLabel password_label = new JLabel(Dictionary.get("GAuthenticator.Password"));
	password = new JPasswordField();
	password.setEchoChar('*');
	password.setToolTipText(Dictionary.get("GAuthenticator.Password_Tooltip"));
	if (password_str != null) {
	    password.setText(password_str);
	}

	JPanel button_panel = new JPanel();
	ok_button = new GLIButton(Dictionary.get("General.OK"), Dictionary.get("General.OK_Tooltip"));
	cancel_button = new GLIButton(Dictionary.get("General.Cancel"), Dictionary.get("General.Cancel_Tooltip"));
	
	// Connection
	cancel_button.addActionListener(new AuthenticationActionListener(true));
	ok_button.addActionListener(new AuthenticationActionListener(false));
	password.addActionListener(new AuthenticationActionListener(false));
	username.addActionListener(new RequestFocusListener(password));

	// Layout
	user_panel.setLayout(new GridLayout(1,2));
	user_panel.add(username_label);
	user_panel.add(username);

	password_panel.setLayout(new GridLayout(1,2));
	password_panel.add(password_label);
	password_panel.add(password);

	button_panel.setLayout(new GridLayout(1,2));
	button_panel.add(ok_button);
	button_panel.add(cancel_button);

	content_pane.setLayout(new GridLayout(4,1,0,2));
	content_pane.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
	content_pane.add(title_label);
	content_pane.add(user_panel);
	content_pane.add(password_panel);
	content_pane.add(button_panel);

	//username field is not a member variable but local variable
	// dialog is a local variable too. Maybe because they're not members I can't
	// set their component names?
	//dialog.setName("GAuthenticator.Dialog");
	//TestingPreparation.setIndividualSubcomponentNames(dialog, password);
	TestingPreparation.setNamesRecursively(dialog);
	
	// Position the window
	Dimension screen_size = Toolkit.getDefaultToolkit().getScreenSize();
	dialog.setLocation((screen_size.width - SIZE.width) / 2, (screen_size.height - SIZE.height) / 2);
	dialog.setVisible(true);
	if(!authentication_cancelled) {
	    // Store the authentication
	    authentications.put(getRequestingHost() + ":" + getRequestingPort(), username.getText() + "@" + new String(password.getPassword()));
	    return new PasswordAuthentication(username.getText(), password.getPassword());
	} else {
	    return null;
	}
    }

    protected PasswordAuthentication getPasswordAuthentication() {
	return getPasswordAuthentication(null,null);
    }

    /** This is defined so it can be overridden by subclasses (getRequestingPrompt is final). */
    protected String getMessageString()
    {
	return getRequestingPrompt();
    }


    /** Detects actions upon any control that attempt to submit the current details for authentication. */
    private class AuthenticationActionListener
	implements ActionListener {
	/** <i>true</i> if this authentication action cancels the authentication, <i>false</i> otherwise. */
	private boolean cancel_action = false;
	/** Constructor.
	 * @param cancel_action <i>true</i> if this authentication action cancels the authentication, <i>false</i> otherwise.
	 */
	public AuthenticationActionListener(boolean cancel_action) {
	    this.cancel_action = cancel_action;
	}
	/** Any implementation of an ActionListener must include this method so that we can be informed when an action has been performed on our registered controls, allowing us to dispose of the authentication dialog after determining if this is a submit action or a cancel one.
	 * @param event An <strong>ActionEvent</strong> with information about the event that fired this method.
	 */
	public void actionPerformed(ActionEvent event) {
	    authentication_cancelled = cancel_action;
	    dialog.dispose();
	}
    }

    /** This listener detects actions on registered controls, and when they occur ensures the focus is moved to some targetted component. */
    private class RequestFocusListener
	implements ActionListener {
	/*The <strong>Component</strong> you wish to gain focus when an action is performed on a registered control. */
	private Component target = null;
	/** Constructor.
	 * @param target The <strong>Component</strong> you wish to gain focus when an action is performed on a registered control.
	 */
	public RequestFocusListener(Component target) {
	    this.target = target;
	}
	/** Any implementation of an ActionListener must include this method so that we can be informed when an action has been performed on our registered controls, allowing us to request focus in the target control.
	 * @param event An <strong>ActionEvent</strong> with information about the event that fired this method.
	 */
	public void actionPerformed(ActionEvent event) {
	    target.requestFocus();
	}
    }
}
