/*******************************************************************************
 * Copyright (c) 2005, 2015 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Neil Rickards <neil.rickards@arm.com> - fix for Bug 161026
 *     		[Wizards] ProjectContentsLocationArea uses wrong file separator
 *     Oakland Software Incorporated (Francis Upton) <francisu@ieee.org>
 *		    Bug 224997 [Workbench] Impossible to copy project
 *     Alena Laskavaia - fix for bug 2035
 *******************************************************************************/

package org.eclipse.ui.internal.ide.dialogs;

import java.net.URI;
import java.net.URISyntaxException;

import org.eclipse.core.filesystem.IFileInfo;
import org.eclipse.core.filesystem.URIUtil;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.osgi.util.TextProcessor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.DirectoryDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
import org.eclipse.ui.internal.ide.filesystem.FileSystemConfiguration;
import org.eclipse.ui.internal.ide.filesystem.FileSystemSupportRegistry;
import org.osgi.framework.FrameworkUtil;

/**
 * ProjectContentsLocationArea is a convenience class for area that handle entry
 * of locations using URIs.
 *
 * @since 3.2
 *
 */
public class ProjectContentsLocationArea {
	/**
	 * IErrorMessageReporter is an interface for type that allow message
	 * reporting.
	 *
	 */
	public interface IErrorMessageReporter {
		/**
		 * Report the error message
		 *
		 * @param errorMessage
		 *            String or <code>null</code>. If the errorMessage is
		 *            null then clear any error state.
		 * @param infoOnly
		 *            the message is an informational message, but the dialog
		 *            cannot continue
		 *
		 */
		public void reportError(String errorMessage, boolean infoOnly);
	}

	private static String BROWSE_LABEL = IDEWorkbenchMessages.ProjectLocationSelectionDialog_browseLabel;

	private static final int SIZING_TEXT_FIELD_WIDTH = 250;

	private static final String FILE_SCHEME = "file"; //$NON-NLS-1$

	private static final String SAVED_LOCATION_ATTR = "OUTSIDE_LOCATION"; //$NON-NLS-1$

	private Label locationLabel;

	private Text locationPathField;

	private Button browseButton;

	private IErrorMessageReporter errorReporter;

	private String projectName = IDEResourceInfoUtils.EMPTY_STRING;

	private String userPath = IDEResourceInfoUtils.EMPTY_STRING;

	private Button useDefaultsButton;

	private IProject existingProject;

	private FileSystemSelectionArea fileSystemSelectionArea;

	/**
	 * Create a new instance of the receiver.
	 *
	 * @param reporter
	 * @param composite
	 * @param startProject
	 */
	public ProjectContentsLocationArea(IErrorMessageReporter reporter,
			Composite composite, IProject startProject) {

		errorReporter = reporter;
		projectName = startProject.getName();
		existingProject = startProject;
		createContents(composite, false);
	}

	/**
	 * Set the project to base the contents off of.
	 *
	 * @param existingProject
	 */
	public void setExistingProject(IProject existingProject) {
		projectName = existingProject.getName();
		this.existingProject = existingProject;
	}

	/**
	 * Create a new instance of a ProjectContentsLocationArea.
	 *
	 * @param reporter
	 * @param composite
	 */
	public ProjectContentsLocationArea(IErrorMessageReporter reporter, Composite composite) {
		errorReporter = reporter;

		// If it is a new project always start enabled
		createContents(composite, true);
	}

	/**
	 * Create the contents of the receiver.
	 *
	 * @param composite
	 * @param defaultEnabled
	 */
	private void createContents(Composite composite, boolean defaultEnabled) {

		int columns = 4;

		// project specification group
		Composite projectGroup = new Composite(composite, SWT.NONE);
		GridLayout layout = new GridLayout();
		layout.numColumns = columns;
		projectGroup.setLayout(layout);
		projectGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

		useDefaultsButton = new Button(projectGroup, SWT.CHECK | SWT.RIGHT);
		useDefaultsButton
				.setText(IDEWorkbenchMessages.ProjectLocationSelectionDialog_useDefaultLabel);
		useDefaultsButton.setSelection(defaultEnabled);
		GridData buttonData = new GridData();
		buttonData.horizontalSpan = columns;
		useDefaultsButton.setLayoutData(buttonData);

		createUserEntryArea(projectGroup, defaultEnabled);

		useDefaultsButton.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				boolean useDefaults = useDefaultsButton.getSelection();

				if (useDefaults) {
					userPath = locationPathField.getText();
					locationPathField.setText(TextProcessor
							.process(getDefaultPathDisplayString()));
				} else {
					locationPathField.setText(TextProcessor.process(userPath));
				}
				String error = checkValidLocation();
				errorReporter.reportError(error,
						error != null && error.equals(IDEWorkbenchMessages.WizardNewProjectCreationPage_projectLocationEmpty));
				setUserAreaEnabled(!useDefaults);
			}
		});
		setUserAreaEnabled(!defaultEnabled);
	}

	/**
	 * Return whether or not we are currently showing the default location for
	 * the project.
	 *
	 * @return boolean
	 */
	public boolean isDefault() {
		return useDefaultsButton.getSelection();
	}

	/**
	 * Create the area for user entry.
	 *
	 * @param composite
	 * @param defaultEnabled
	 */
	private void createUserEntryArea(Composite composite, boolean defaultEnabled) {
		// location label
		locationLabel = new Label(composite, SWT.NONE);
		locationLabel.setText(IDEWorkbenchMessages.ProjectLocationSelectionDialog_locationLabel);

		// project location entry field
		locationPathField = new Text(composite, SWT.BORDER);
		GridData data = new GridData(GridData.FILL_HORIZONTAL);
		data.widthHint = SIZING_TEXT_FIELD_WIDTH;
		data.horizontalSpan = 2;
		locationPathField.setLayoutData(data);

		// browse button
		browseButton = new Button(composite, SWT.PUSH);
		browseButton.setText(BROWSE_LABEL);
		browseButton.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent event) {
				handleLocationBrowseButtonPressed();
			}
		});

		createFileSystemSelection(composite);

		if (defaultEnabled) {
			locationPathField.setText(TextProcessor.process(getDefaultPathDisplayString()));
		} else if (existingProject == null) {
			locationPathField.setText(IDEResourceInfoUtils.EMPTY_STRING);
		} else {
			locationPathField.setText(TextProcessor.process(existingProject
					.getLocation().toOSString()));
		}

		locationPathField.addModifyListener(e -> errorReporter.reportError(checkValidLocation(), false));
	}

	/**
	 * Create the file system selection area.
	 *
	 * @param composite
	 */
	private void createFileSystemSelection(Composite composite) {

		// Always use the default if that is all there is.
		if (FileSystemSupportRegistry.getInstance().hasOneFileSystem()) {
			return;
		}

		new Label(composite, SWT.NONE);

		fileSystemSelectionArea = new FileSystemSelectionArea();
		fileSystemSelectionArea.createContents(composite);
	}

	/**
	 * Return the path we are going to display. If it is a file URI then remove
	 * the file prefix.
	 *
	 * @return String
	 */
	private String getDefaultPathDisplayString() {

		URI defaultURI = null;
		if (existingProject != null) {
			defaultURI = existingProject.getLocationURI();
		}

		// Handle files specially. Assume a file if there is no project to query
		if (defaultURI == null || defaultURI.getScheme().equals(FILE_SCHEME)) {
			return Platform.getLocation().append(projectName).toOSString();
		}
		return defaultURI.toString();

	}

	/**
	 * Set the enablement state of the receiver.
	 *
	 * @param enabled
	 */
	private void setUserAreaEnabled(boolean enabled) {

		locationLabel.setEnabled(enabled);
		locationPathField.setEnabled(enabled);
		browseButton.setEnabled(enabled);
		if (fileSystemSelectionArea != null) {
			fileSystemSelectionArea.setEnabled(enabled);
		}
	}

	/**
	 * Return the browse button. Usually referenced in order to set the layout
	 * data for a dialog.
	 *
	 * @return Button
	 */
	public Button getBrowseButton() {
		return browseButton;
	}

	private IDialogSettings getDialogSettings() {
		IDialogSettings ideDialogSettings = PlatformUI
				.getDialogSettingsProvider(FrameworkUtil.getBundle(ProjectContentsLocationArea.class))
				.getDialogSettings();
		IDialogSettings result = ideDialogSettings.getSection(getClass().getName());
		if (result == null) {
			result = ideDialogSettings.addNewSection(getClass().getName());
		}
		return result;
	}

	/**
	 * Open an appropriate directory browser
	 */
	private void handleLocationBrowseButtonPressed() {

		String selectedDirectory = null;
		String dirName = getPathFromLocationField();

		if (!dirName.equals(IDEResourceInfoUtils.EMPTY_STRING)) {
			IFileInfo info;
			info = IDEResourceInfoUtils.getFileInfo(dirName);

			if (info == null || !(info.exists()))
				dirName = IDEResourceInfoUtils.EMPTY_STRING;
		} else {
			String value = getDialogSettings().get(SAVED_LOCATION_ATTR);
			if (value != null) {
				dirName = value;
			}
		}

		FileSystemConfiguration config = getSelectedConfiguration();
		if (config== null || config.equals(
				FileSystemSupportRegistry.getInstance()
						.getDefaultConfiguration())) {
			DirectoryDialog dialog = new DirectoryDialog(locationPathField
					.getShell(), SWT.SHEET);
			dialog
					.setMessage(IDEWorkbenchMessages.ProjectLocationSelectionDialog_directoryLabel);

			dialog.setFilterPath(dirName);

			selectedDirectory = dialog.open();

		} else {
			URI uri = getSelectedConfiguration().getContributor()
					.browseFileSystem(dirName, browseButton.getShell());
			if (uri != null)
				selectedDirectory = uri.toString();
		}

		if (selectedDirectory != null) {
			updateLocationField(selectedDirectory);
			getDialogSettings().put(SAVED_LOCATION_ATTR, selectedDirectory);
		}
	}

	/**
	 * Update the location field based on the selected path.
	 *
	 * @param selectedPath
	 */
	private void updateLocationField(String selectedPath) {
		locationPathField.setText(TextProcessor.process(selectedPath));
	}

	/**
	 * Return the path on the location field.
	 *
	 * @return the path or the field's text if the path is invalid
	 */
	private String getPathFromLocationField() {
		URI fieldURI;
		try {
			fieldURI = new URI(locationPathField.getText());
		} catch (URISyntaxException e) {
			return locationPathField.getText();
		}
		String path= fieldURI.getPath();
		return path != null ? path : locationPathField.getText();
	}

	/**
	 * Check if the entry in the widget location is valid. If it is valid return
	 * null. Otherwise return a string that indicates the problem.
	 *
	 * @return String
	 */
	public String checkValidLocation() {

		String locationFieldContents = locationPathField.getText();
		if (locationFieldContents.isEmpty()) {
			return IDEWorkbenchMessages.WizardNewProjectCreationPage_projectLocationEmpty;
		}

		URI newPath = getProjectLocationURI();
		if (newPath == null) {
			return IDEWorkbenchMessages.ProjectLocationSelectionDialog_locationError;
		}

		if (existingProject != null) {
			URI projectPath = existingProject.getLocationURI();
			if (projectPath != null && URIUtil.equals(projectPath, newPath)) {
				return IDEWorkbenchMessages.ProjectLocationSelectionDialog_locationIsSelf;
			}
		}

		if (!isDefault()) {
			IStatus locationStatus = ResourcesPlugin.getWorkspace()
					.validateProjectLocationURI(existingProject, newPath);

			if (!locationStatus.isOK()) {
				return locationStatus.getMessage();
			}
		}

		return null;
	}

	/**
	 * Get the URI for the location field if possible.
	 *
	 * @return URI or <code>null</code> if it is not valid.
	 */
	public URI getProjectLocationURI() {

		FileSystemConfiguration configuration = getSelectedConfiguration();
		if (configuration == null) {
			return null;
		}

		return configuration.getContributor().getURI(
				locationPathField.getText());

	}

	/**
	 * Return the selected contributor
	 *
	 * @return FileSystemConfiguration or <code>null</code> if it cannot be
	 *         determined.
	 */
	private FileSystemConfiguration getSelectedConfiguration() {
		if (fileSystemSelectionArea == null) {
			return FileSystemSupportRegistry.getInstance()
					.getDefaultConfiguration();
		}

		return fileSystemSelectionArea.getSelectedConfiguration();
	}

	/**
	 * Set the text to the default or clear it if not using the defaults.
	 *
	 * @param newName
	 *            the name of the project to use. If <code>null</code> use the
	 *            existing project name.
	 */
	public void updateProjectName(String newName) {
		projectName = newName;
		if (isDefault()) {
			locationPathField.setText(TextProcessor
					.process(getDefaultPathDisplayString()));
		}

	}

	/**
	 * Return the location for the project. If we are using defaults then return
	 * the workspace root so that core creates it with default values.
	 *
	 * @return String
	 */
	public String getProjectLocation() {
		if (isDefault()) {
			return Platform.getLocation().toOSString();
		}
		return locationPathField.getText();
	}
}
