/*******************************************************************************
 * Copyright (c) 2002, 2006 QNX Software Systems and others.
 * 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:
 *     IBM Corporation - initial API and implementation
 *     QNX Software Systems - Initial API and implementation
 *******************************************************************************/

package org.eclipse.cdt.internal.ui.preferences;

import java.io.File;
import java.util.Set;

import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.dialogs.TitleAreaDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.DirectoryDialog;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

public class PathEntryVariableDialog extends TitleAreaDialog {
	
	// UI widgets
	private Button okButton;
	
	private Label variableNameLabel;
	
	private Label variableValueLabel;
	
	private Text variableNameField;
	
	private Text variableValueField;
	
	private Button fileButton;
	
	private Button folderButton;
	
	/**
	 * This dialog type: <code>NEW_VARIABLE</code> or
	 * <code>EXISTING_VARIABLE</code>.
	 */
	private int type;
	
	/**
	 * The type of variable that can be edited in this dialog.
	 * <code>IResource.FILE</code> or <code>IResource.FOLDER</code>
	 */
	private int variableType;
	
	/**
	 * The name of the variable being edited.
	 */
	private String variableName;
	
	/**
	 * The value of the variable being edited.
	 */
	private String variableValue;
	
	/**
	 * The original name of the variable being edited. It is used when testing
	 * if the current variable's name is already in use.
	 */
	private String originalName;
	
	/**
	 * Used to select the proper message depending on the current mode
	 * (new/existing variable).
	 */
	private boolean newVariable;
	
	/**
	 * Set of variable names currently in use. Used when warning the user that
	 * the currently selected name is already in use by another variable.
	 */
	private Set<String> namesInUse;
	
	/**
	 * The current validation status. Its value can be one of the following:<ul>
	 * <li><code>IMessageProvider.NONE</code> (default);</li>
	 * <li><code>IMessageProvider.WARNING</code>;</li>
	 * <li><code>IMessageProvider.ERROR</code>;</li>
	 * </ul>
	 * Used when validating the user input.
	 */
	private int validationStatus;
	
	/**
	 * The current validation message generated by the last
	 * call to a <code>validate</code> method.
	 */
	private String validationMessage;
	
	/**
	 * Whether a variable name has been entered.  
	 */
	private boolean nameEntered = false;
	
	/**
	 * Whether a variable location has been entered.  
	 */
	private boolean locationEntered = false;
	
	/**
	 * The standard message to be shown when there are no problems being
	 * reported.
	 */
	final private String standardMessage;
	
	/**
	 * Constant for defining this dialog as intended to create a new variable
	 * (value = 1).
	 */
	public final static int NEW_VARIABLE = 1;
	
	/**
	 * Constant for defining this dialog as intended to edit an existing
	 * variable (value = 2).
	 */
	public final static int EXISTING_VARIABLE = 2;
	
	/**
	 * Constructs a dialog for editing a new/existing path variable.
	 * 
	 * @param parentShell the parent shell
	 * @param type the dialog type: <code>NEW_VARIABLE</code> or
	 * 	<code>EXISTING_VARIABLE</code>
	 * @param variableType the type of variable that can be edited in 
	 * 	this dialog. <code>IResource.FILE</code> or <code>IResource.FOLDER</code>
	 * @param namesInUse a set of variable names currently in use 
	 */
	public PathEntryVariableDialog(Shell parentShell, int type, int variableType, Set<String> namesInUse) {
		super(parentShell);
		this.type = type;
		this.newVariable = type == NEW_VARIABLE;
		this.variableName = ""; //$NON-NLS-1$
		this.variableValue = ""; //$NON-NLS-1$
		this.variableType = variableType;
		this.namesInUse = namesInUse;
		
		if (newVariable)
			this.standardMessage = PreferencesMessages.PathEntryVariableDialog_message_newVariable; 
		else
			this.standardMessage = PreferencesMessages.PathEntryVariableDialog_message_existingVariable; 
	}
	
	/**
	 * Configures this dialog's shell, setting the shell's text.
	 * 
	 * @see org.eclipse.jface.window.Window#configureShell(Shell)
	 */
	@Override
	protected void configureShell(Shell shell) {
		super.configureShell(shell);
		if (newVariable)
			shell.setText(PreferencesMessages.PathEntryVariableDialog_shellTitle_newVariable); 
		else
			shell
			.setText(PreferencesMessages.PathEntryVariableDialog_shellTitle_existingVariable); 
	}
	
	/**
	 * Creates and returns the contents of this dialog (except for the button bar).
	 * 
	 * @see org.eclipse.jface.dialogs.TitleAreaDialog#createDialogArea
	 */
	@Override
	protected Control createDialogArea(Composite parent) {
		// top level composite
		Composite parentComposite = (Composite) super.createDialogArea(parent);
		
		// creates dialog area composite
		Composite contents = createComposite(parentComposite);
		
		// creates and lay outs dialog area widgets 
		createWidgets(contents, parent.getFont());
		
		// validate possibly already incorrect variable definitions
		if (type == EXISTING_VARIABLE) {
			nameEntered = locationEntered = true;
			validateVariableValue();
		}
		
		return contents;
	}
	
	/**
	 * Creates and configures this dialog's main composite.
	 * 
	 * @param parentComposite parent's composite
	 * @return this dialog's main composite
	 */
	private Composite createComposite(Composite parentComposite) {
		// creates a composite with standard margins and spacing
		Composite contents = new Composite(parentComposite, SWT.NONE);
		
		FormLayout layout = new FormLayout();
		
		layout.marginHeight = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN);
		layout.marginWidth = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN);
		
		contents.setLayout(layout);
		contents.setFont(parentComposite.getFont());
		
		if (newVariable)
			setTitle(PreferencesMessages.PathEntryVariableDialog_dialogTitle_newVariable); 
		else
			setTitle(PreferencesMessages.PathEntryVariableDialog_dialogTitle_existingVariable); 
		setMessage(standardMessage);
		return contents;
	}
	
	/**
	 * Creates widgets for this dialog.
	 * 
	 * @param parent the parent composite where to create widgets
	 * @param contents 
	 */
	private void createWidgets(Composite contents, Font font) {
		FormData data;
		
		String nameLabelText = PreferencesMessages.PathEntryVariableDialog_variableName; 
		String valueLabelText = PreferencesMessages.PathEntryVariableDialog_variableValue; 
		
		// variable name label
		variableNameLabel = new Label(contents, SWT.LEFT);
		variableNameLabel.setText(nameLabelText);
		
		data = new FormData();
		variableNameLabel.setLayoutData(data);
		variableNameLabel.setFont(font);
		
		// variable value label
		variableValueLabel = new Label(contents, SWT.LEFT);
		variableValueLabel.setText(valueLabelText);
		
		data = new FormData();
		data.top = new FormAttachment(variableNameLabel,
				convertVerticalDLUsToPixels(10));
		variableValueLabel.setLayoutData(data);
		variableValueLabel.setFont(font);
		
		// the larger label will be used in the left attachments for the fields  
		Label largerLabel = nameLabelText.length() > valueLabelText.length() ? variableNameLabel
				: variableValueLabel;
		
		// variable name field
		variableNameField = new Text(contents, SWT.SINGLE | SWT.BORDER);
		variableNameField.setText(variableName);
		
		data = new FormData();
		data.width = convertWidthInCharsToPixels(50);
		data.left = new FormAttachment(largerLabel,
				convertHorizontalDLUsToPixels(5));
		variableNameField.setLayoutData(data);
		variableNameField.setFont(font);
		variableNameField.setFocus();
		
		variableNameField.addModifyListener(new ModifyListener() {
			public void modifyText(ModifyEvent event) {
				variableNameModified();
			}
		});
		
		// variable value field
		variableValueField = new Text(contents, SWT.SINGLE | SWT.BORDER);
		variableValueField.setText(variableValue);
		
		data = new FormData();
		data.width = convertWidthInCharsToPixels(50);
		data.left = new FormAttachment(largerLabel,
				convertHorizontalDLUsToPixels(5));
		data.top = new FormAttachment(variableNameLabel,
				convertVerticalDLUsToPixels(10));
		variableValueField.setLayoutData(data);
		variableValueField.setFont(font);
		
		variableValueField.addModifyListener(new ModifyListener() {
			public void modifyText(ModifyEvent event) {
				variableValueModified();
			}
		});
		
		// select file path button
		fileButton = new Button(contents, SWT.PUSH);
		fileButton.setText(PreferencesMessages.PathEntryVariableDialog_file); 
		if ((variableType & IResource.FILE) == 0)
			fileButton.setEnabled(false);
		
		data = setButtonFormLayoutData(fileButton);
		data.top = new FormAttachment(variableNameLabel,
				convertVerticalDLUsToPixels(10));
		data.left = new FormAttachment(variableValueField,
				convertHorizontalDLUsToPixels(10));
		data.right = new FormAttachment(100, -5);
		fileButton.setLayoutData(data);
		fileButton.setFont(font);
		
		fileButton.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				selectFile();
			}
		});
		
		// select folder path button
		folderButton = new Button(contents, SWT.PUSH);
		folderButton.setText(PreferencesMessages.PathEntryVariableDialog_folder); 
		if ((variableType & IResource.FOLDER) == 0)
			folderButton.setEnabled(false);
		
		data = setButtonFormLayoutData(folderButton);
		data.top = new FormAttachment(variableValueLabel,
				convertVerticalDLUsToPixels(10));
		data.left = new FormAttachment(variableValueField,
				convertHorizontalDLUsToPixels(10));
		data.right = new FormAttachment(100, -5);
		folderButton.setLayoutData(data);
		folderButton.setFont(font);
		
		folderButton.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				selectFolder();
			}
		});
	}
	
	/**
	 * Sets the <code>FormData</code> on the specified button to be one that is
	 * spaced for the current dialog page units. The method
	 * <code>initializeDialogUnits</code> must be called once before calling this
	 * method for the first time.
	 * 
	 * @param button the button to set the <code>FormData</code>
	 * @return the <code>FormData</code> set on the specified button
	 */
	private FormData setButtonFormLayoutData(Button button) {
		FormData data = new FormData();
		int widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
		data.width = Math.max(widthHint, button.computeSize(SWT.DEFAULT,
				SWT.DEFAULT, true).x);
		button.setLayoutData(data);
		return data;
	}
	
	/**
	 * Fires validations (variable name first) and updates enabled state for the
	 * "Ok" button accordingly.
	 */
	protected void variableNameModified() {
		// updates and validates the variable name
		variableName = variableNameField.getText().trim();
		validationStatus = IMessageProvider.NONE;
		okButton.setEnabled(validateVariableName() && validateVariableValue());
		nameEntered = true;
	}
	
	/**
	 * Fires validations (variable value first) and updates enabled state for the
	 * "Ok" button accordingly.
	 */
	protected void variableValueModified() {
		// updates and validates the variable value
		variableValue = variableValueField.getText().trim();
		validationStatus = IMessageProvider.NONE;
		okButton.setEnabled(validateVariableValue() && validateVariableName());
		locationEntered = true;
	}
	
	/**
	 * Opens a dialog where the user can select a folder path.
	 */
	protected void selectFolder() {
		DirectoryDialog dialog = new DirectoryDialog(getShell());
		dialog.setText(PreferencesMessages.PathEntryVariableDialog_selectFolderTitle); 
		dialog.setMessage(PreferencesMessages.PathEntryVariableDialog_selectFolderMessage); 
		dialog.setFilterPath(variableValue);
		String res = dialog.open();
		if (res != null) {
			variableValue = new Path(res).makeAbsolute().toOSString();
			variableValueField.setText(variableValue);
		}
	}
	
	/**
	 * Opens a dialog where the user can select a file path.
	 */
	protected void selectFile() {
		FileDialog dialog = new FileDialog(getShell());
		dialog.setText(PreferencesMessages.PathEntryVariableDialog_selectFileTitle); 
		dialog.setFilterPath(variableValue);
		String res = dialog.open();
		if (res != null) {
			variableValue = new Path(res).makeAbsolute().toOSString();
			variableValueField.setText(variableValue);
		}
	}
	
	/**
	 * Adds buttons to this dialog's button bar.
	 * 
	 * @see org.eclipse.jface.dialogs.Dialog#createButtonsForButtonBar
	 */
	@Override
	protected void createButtonsForButtonBar(Composite parent) {
		okButton = createButton(parent, IDialogConstants.OK_ID,
				IDialogConstants.OK_LABEL, true);
		okButton.setEnabled(type == EXISTING_VARIABLE);
		
		createButton(parent, IDialogConstants.CANCEL_ID,
				IDialogConstants.CANCEL_LABEL, false);
	}
	
	/**
	 * Validates the current variable name, and updates this dialog's message.
	 * 
	 * @return true if the name is valid, false otherwise
	 */
	private boolean validateVariableName() {
		boolean allowFinish = false;
		
		// if the current validationStatus is ERROR, no additional validation applies
		if (validationStatus == IMessageProvider.ERROR)
			return false;
		
		// assumes everything will be ok
		String message = standardMessage;
		int newValidationStatus = IMessageProvider.NONE;
		
		if (variableName.length() == 0) {
			// the variable name is empty
			if (nameEntered) {
				// a name was entered before and is now empty
				newValidationStatus = IMessageProvider.ERROR;
				message = PreferencesMessages.PathEntryVariableDialog_variableNameEmptyMessage; 
			}
		} else {
			if (namesInUse.contains(variableName)
					&& !variableName.equals(originalName)) {
				// the variable name is already in use
				message = PreferencesMessages.PathEntryVariableDialog_variableAlreadyExistsMessage; 
				newValidationStatus = IMessageProvider.ERROR;
			} else {
				allowFinish = true;
			}
		}
		
		// overwrite the current validation status / message only if everything is ok (clearing them)
		// or if we have a more serious problem than the current one
		if (validationStatus == IMessageProvider.NONE
				|| newValidationStatus == IMessageProvider.ERROR) {
			validationStatus = newValidationStatus;
			validationMessage = message;
		}
		// only set the message here if it is not going to be set in 
		// validateVariableValue to avoid flashing.
		if (allowFinish == false)
			setMessage(validationMessage, validationStatus);
		return allowFinish;
	}
	
	/**
	 * Validates the current variable value, and updates this dialog's message.
	 * 
	 * @return true if the value is valid, false otherwise
	 */
	private boolean validateVariableValue() {
		boolean allowFinish = false;
		
		// if the current validationStatus is ERROR, no additional validation applies
		if (validationStatus == IMessageProvider.ERROR)
			return false;
		
		// assumes everything will be ok
		String message = standardMessage;
		int newValidationStatus = IMessageProvider.NONE;
		
		if (variableValue.length() == 0) {
			// the variable value is empty
			if (locationEntered) {
				// a location value was entered before and is now empty
				newValidationStatus = IMessageProvider.ERROR;
				message = PreferencesMessages.PathEntryVariableDialog_variableValueEmptyMessage; 
			}
		} else if (!Path.EMPTY.isValidPath(variableValue)) {
			// the variable value is an invalid path
			message = PreferencesMessages.PathEntryVariableDialog_variableValueInvalidMessage; 
			newValidationStatus = IMessageProvider.ERROR;
		} else if (!new Path(variableValue).isAbsolute()) {
			// the variable value is a relative path
			message = PreferencesMessages.PathEntryVariableDialog_pathIsRelativeMessage; 
			newValidationStatus = IMessageProvider.ERROR;
		} else if (!new File(variableValue).exists()) {
			// the path does not exist (warning)
			message = PreferencesMessages.PathEntryVariableDialog_pathDoesNotExistMessage; 
			newValidationStatus = IMessageProvider.WARNING;
			allowFinish = true;
		} else {
			allowFinish = true;
		}
		
		// overwrite the current validation status / message only if everything is ok (clearing them)
		// or if we have a more serious problem than the current one
		if (validationStatus == IMessageProvider.NONE
				|| newValidationStatus > validationStatus) {
			validationStatus = newValidationStatus;
			validationMessage = message;
		}
		setMessage(validationMessage, validationStatus);
		return allowFinish;
	}
	
	/**
	 * Returns the variable name.
	 * 
	 * @return the variable name
	 */
	public String getVariableName() {
		return variableName;
	}
	
	/**
	 * Returns the variable value.
	 * 
	 * @return the variable value
	 */
	public String getVariableValue() {
		return variableValue;
	}
	
	/**
	 * Sets the variable name.
	 * 
	 * @param variableName the new variable name
	 */
	public void setVariableName(String variableName) {
		this.variableName = variableName.trim();
		this.originalName = this.variableName;
	}
	
	/**
	 * Sets the variable value.
	 * 
	 * @param variableValue the new variable value
	 */
	public void setVariableValue(String variableValue) {
		this.variableValue = variableValue;
	}
	
	
}
