/*******************************************************************************
 * Copyright (c) 2011 GEBIT Solutions.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Carsten Pfeiffer(GEBIT Solutions) - initial implementation
 *******************************************************************************/
package org.eclipse.egit.ui.internal.dialogs;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.egit.ui.internal.UIText;
import org.eclipse.jface.dialogs.TrayDialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jgit.transport.CredentialItem;
import org.eclipse.jgit.transport.CredentialItem.CharArrayType;
import org.eclipse.jgit.transport.CredentialItem.StringType;
import org.eclipse.jgit.transport.CredentialItem.YesNoType;
import org.eclipse.jgit.transport.URIish;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

/**
 * This dialog is used in order to display information about
 * authentication problems, and/or request authentication input
 * from the user.
 *
 * The {@link CredentialItem CredentialItems} passed in the constructor
 * are updated to reflect the user-specified values as soon as the dialog
 * is confirmed.
 */
public class CustomPromptDialog extends TrayDialog {
	private CredentialItem[] credentialItems;
	private List<Control> editingControls;
	private URIish uri;
	private String title;
	private static final String KEY_ITEM = "item"; //$NON-NLS-1$

	/**
	 * Creates an instance of the dialog that will present the given
	 * CredentialItems and let the user supply values for them
	 * @param shell the shell for this dialog
	 * @param uri the uri for which the information shall be displayed
	 * @param title an optional title for this dialog
	 * @param items the items to display and edit
	 */
	public CustomPromptDialog(Shell shell, URIish uri, String title, CredentialItem... items) {
		super(shell);
		this.uri = uri;
		this.title = title;
		setShellStyle(getShellStyle() | SWT.SHELL_TRIM);
		credentialItems = items;
	}

	@Override
	protected void configureShell(Shell newShell) {
		super.configureShell(newShell);
		if (title != null) {
			newShell.setText(title);
		}
	}

	/**
	 * Returns the credential items that were passed into the constructor.
	 * @return the credential items
	 */
	public CredentialItem[] getCredentialItems() {
		return credentialItems;
	}

	@Override
	protected Control createDialogArea(Composite parent) {
		editingControls = new ArrayList<Control>(credentialItems.length);

		Composite main = (Composite) super.createDialogArea(parent);
		GridLayout mainLayout = (GridLayout) main.getLayout();
		mainLayout.numColumns = 2;

		Label infoLabel = new Label(main, SWT.NONE);
		GridDataFactory.defaultsFor(infoLabel).span(2, 1).applyTo(infoLabel);
		String tempInfoText = hasEditingItems()
				? UIText.CustomPromptDialog_provide_information_for
				: UIText.CustomPromptDialog_information_about;
		infoLabel.setText(NLS.bind(tempInfoText, uri.toString()));

		for (CredentialItem item : credentialItems) {
			Label label = new Label(main, SWT.NONE);
			label.setText(item.getPromptText());
			GridDataFactory.defaultsFor(label).applyTo(label);

			if (item instanceof CharArrayType || item instanceof StringType) {
				Text text = new Text(main, SWT.BORDER | (item.isValueSecure() ? SWT.PASSWORD : SWT.NONE));
				GridDataFactory.defaultsFor(text).applyTo(text);
				text.setData(KEY_ITEM, item);
				editingControls.add(text);
			} else if (item instanceof YesNoType) {
				Button checkBox = new Button(main, SWT.CHECK);
				GridDataFactory.defaultsFor(checkBox).applyTo(checkBox);
				editingControls.add(checkBox);
			} else {
				// unknown type, not editable
				Label dummy = new Label(main, SWT.NONE);
				GridDataFactory.fillDefaults().applyTo(dummy);
			}
		}

		return main;
	}

	/**
	 * Returns <code>true</code> if there's anything to edit for the user
	 * @return <code>true</code> if the user needs to supply some data
	 */
	private boolean hasEditingItems() {
		for (CredentialItem item : credentialItems) {
			if (item instanceof StringType) {
				return true;
			}
			if (item instanceof CharArrayType) {
				return true;
			}
			if (item instanceof YesNoType) {
				return true;
			}
		}
		return false;
	}

	@Override
	protected void okPressed() {
		updateValues();

		super.okPressed();
	}

	/**
	 * Updates all {@link CredentialItem CredentialItems} with the
	 * user-supplied values.
	 */
	private void updateValues() {
		for (Control control : editingControls) {
			if (control instanceof Text) {
				Text textControl = (Text) control;
				CredentialItem itemToUpdate = (CredentialItem) textControl.getData(KEY_ITEM);
				String value = textControl.getText();
				updateValue(itemToUpdate, value);
			} else if (control instanceof Button) {
				Button checkBoxControl = (Button) control;
				CredentialItem itemToUpdate = (CredentialItem) checkBoxControl.getData(KEY_ITEM);
				boolean value = checkBoxControl.getSelection();
				updateValue(itemToUpdate, value);
			}
		}
	}

	/**
	 * Updates the value of the given {@link CredentialItem}.
	 * @param itemToUpdate the item to update
	 * @param value the new value
	 */
	protected void updateValue(CredentialItem itemToUpdate, String value) {
		if (itemToUpdate instanceof CharArrayType) {
			((CharArrayType) itemToUpdate).setValueNoCopy(value.toCharArray());
		} else if (itemToUpdate instanceof StringType) {
			((StringType) itemToUpdate).setValue(value);
		} else {
			throw new IllegalArgumentException("Cannot handle item of type " + itemToUpdate.getClass()); //$NON-NLS-1$
		}
	}

	/**
	 * Updates the value of the given {@link CredentialItem}.
	 * @param itemToUpdate the item to update
	 * @param value the new value
	 */
	protected void updateValue(CredentialItem itemToUpdate, boolean value) {
		if (itemToUpdate instanceof YesNoType) {
			((YesNoType) itemToUpdate).setValue(value);
		} else {
			throw new IllegalArgumentException("Cannot handle item of type " + itemToUpdate.getClass()); //$NON-NLS-1$
		}
	}
}