/*******************************************************************************
 * Copyright (c) 2000, 2018 IBM Corporation 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
 *     Benjamin Muskalla (b.muskalla@gmx.net) - Bug 149672 [Patch] Create Patch wizard should remember previous settings
 *******************************************************************************/
package org.eclipse.team.internal.ccvs.ui.wizards;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.dialogs.*;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.*;
import org.eclipse.jface.wizard.*;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.*;
import org.eclipse.team.core.Team;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.core.diff.IDiff;
import org.eclipse.team.core.diff.IDiffVisitor;
import org.eclipse.team.core.mapping.provider.ResourceDiffTree;
import org.eclipse.team.core.synchronize.SyncInfoSet;
import org.eclipse.team.internal.ccvs.core.CVSException;
import org.eclipse.team.internal.ccvs.core.ICVSFile;
import org.eclipse.team.internal.ccvs.core.client.Command;
import org.eclipse.team.internal.ccvs.core.client.Command.LocalOption;
import org.eclipse.team.internal.ccvs.core.client.Diff;
import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot;
import org.eclipse.team.internal.ccvs.core.syncinfo.ResourceSyncInfo;
import org.eclipse.team.internal.ccvs.ui.*;
import org.eclipse.team.internal.ccvs.ui.IHelpContextIds;
import org.eclipse.team.internal.ccvs.ui.operations.*;
import org.eclipse.team.internal.ccvs.ui.subscriber.CreatePatchWizardParticipant;
import org.eclipse.team.internal.ccvs.ui.subscriber.WorkspaceSynchronizeParticipant;
import org.eclipse.team.internal.core.subscribers.SubscriberSyncInfoCollector;
import org.eclipse.team.internal.ui.*;
import org.eclipse.team.ui.synchronize.*;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.model.BaseWorkbenchContentProvider;
import org.eclipse.ui.model.WorkbenchLabelProvider;
import org.eclipse.ui.part.PageBook;
import org.eclipse.ui.views.navigator.ResourceComparator;

/**
 * A wizard for creating a patch file by running the CVS diff command.
 */
public class GenerateDiffFileWizard extends Wizard {

	//The initial size of this wizard.
	private final static int INITIAL_WIDTH = 300;
	private final static int INITIAL_HEIGHT = 350;

	public static void run(IWorkbenchPart part, final IResource[] resources, boolean unifiedSelectionEnabled) {
		final String title = CVSUIMessages.GenerateCVSDiff_title;
		final GenerateDiffFileWizard wizard = new GenerateDiffFileWizard(part,resources, unifiedSelectionEnabled);
		wizard.setWindowTitle(title);
		WizardDialog dialog = new WizardDialog(part.getSite().getShell(), wizard);
		dialog.setMinimumPageSize(INITIAL_WIDTH, INITIAL_HEIGHT);
		dialog.open();
	}

	public static void run(IWorkbenchPart part, final IResource[] resources) {
		GenerateDiffFileWizard.run(part,resources,true);
	}

	/**
	 * Page to select a patch file. Overriding validatePage was necessary to allow
	 * entering a file name that already exists.
	 */
	public class LocationPage extends WizardPage {

		/**
		 * The possible locations to save a patch.
		 */
		public final static int CLIPBOARD = 1;
		public final static int FILESYSTEM = 2;
		public final static int WORKSPACE = 3;

		/**
		 * GUI controls for clipboard (cp), filesystem (fs) and workspace (ws).
		 */
		private Button cpRadio;

		private Button fsRadio;
		protected Text fsPathText;
		private Button fsBrowseButton;
		private boolean fsBrowsed = false;

		private Button wsRadio;
		protected Text wsPathText;
		private Button wsBrowseButton;
		private boolean wsBrowsed = false;

		protected CreatePatchWizardParticipant fParticipant;
		private Button chgSelectAll;
		private Button chgDeselectAll;

		/**
		 * State information of this page, updated by the listeners.
		 */
		protected boolean canValidate;
		protected boolean pageValid;
		protected IContainer wsSelectedContainer;
		protected IPath[] foldersToCreate;
		protected int selectedLocation;

		/**
		 * The default values store used to initialize the selections.
		 */
		private final DefaultValuesStore store;


		class LocationPageContentProvider extends BaseWorkbenchContentProvider {
			//Never show closed projects
			boolean showClosedProjects=false;

			@Override
			public Object[] getChildren(Object element) {
				if (element instanceof IWorkspace) {
					// check if closed projects should be shown
					IProject[] allProjects = ((IWorkspace) element).getRoot().getProjects();
					if (showClosedProjects)
						return allProjects;

					ArrayList accessibleProjects = new ArrayList();
					for (int i = 0; i < allProjects.length; i++) {
						if (allProjects[i].isOpen()) {
							accessibleProjects.add(allProjects[i]);
						}
					}
					return accessibleProjects.toArray();
				}

				return super.getChildren(element);
			}
		}

		class WorkspaceDialog extends TitleAreaDialog {

			protected TreeViewer wsTreeViewer;
			protected Text wsFilenameText;
			protected Image dlgTitleImage;

			private boolean modified = false;

			public WorkspaceDialog(Shell shell) {
				super(shell);
			}

			@Override
			protected Control createContents(Composite parent) {
				Control control = super.createContents(parent);
				setTitle(CVSUIMessages.WorkspacePatchDialogTitle);
				setMessage(CVSUIMessages.WorkspacePatchDialogDescription);
				//create title image
				dlgTitleImage = CVSUIPlugin.getPlugin().getImageDescriptor(ICVSUIConstants.IMG_WIZBAN_DIFF).createImage();
				setTitleImage(dlgTitleImage);

				return control;
			}

			@Override
			protected Control createDialogArea(Composite parent){
				Composite parentComposite = (Composite) super.createDialogArea(parent);

				// create a composite with standard margins and spacing
				Composite composite = new Composite(parentComposite, SWT.NONE);
				GridLayout layout = new GridLayout();
				layout.marginHeight = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN);
				layout.marginWidth = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN);
				layout.verticalSpacing = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_SPACING);
				layout.horizontalSpacing = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_SPACING);
				composite.setLayout(layout);
				composite.setLayoutData(new GridData(GridData.FILL_BOTH));
				composite.setFont(parentComposite.getFont());

				getShell().setText(CVSUIMessages.GenerateDiffFileWizard_9);

				wsTreeViewer = new TreeViewer(composite, SWT.BORDER);
				final GridData gd= new GridData(SWT.FILL, SWT.FILL, true, true);
				gd.widthHint= 550;
				gd.heightHint= 250;
				wsTreeViewer.getTree().setLayoutData(gd);

				wsTreeViewer.setContentProvider(new LocationPageContentProvider());
				wsTreeViewer.setComparator(new ResourceComparator(ResourceComparator.NAME));
				wsTreeViewer.setLabelProvider(new WorkbenchLabelProvider());
				wsTreeViewer.setInput(ResourcesPlugin.getWorkspace());

				//Open to whatever is selected in the workspace field
				IPath existingWorkspacePath = new Path(wsPathText.getText());
				//Ensure that this workspace path is valid
				IResource selectedResource = ResourcesPlugin.getWorkspace().getRoot().findMember(existingWorkspacePath);
				if (selectedResource != null) {
					wsTreeViewer.expandToLevel(selectedResource, 0);
					wsTreeViewer.setSelection(new StructuredSelection(selectedResource));
				}

				final Composite group = new Composite(composite, SWT.NONE);
				layout = new GridLayout(2, false);
				layout.marginWidth = 0;
				group.setLayout(layout);
				group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));

				final Label label = new Label(group, SWT.NONE);
				label.setLayoutData(new GridData());
				label.setText(CVSUIMessages.Fi_le_name__9);

				wsFilenameText = new Text(group,SWT.BORDER);
				wsFilenameText.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));

				setupListeners();

				return parent;
			}

			@Override
			protected Button createButton(Composite parent, int id,
					String label, boolean defaultButton) {
				Button button = super.createButton(parent, id, label,
						defaultButton);
				if (id == IDialogConstants.OK_ID) {
					button.setEnabled(false);
				}
				return button;
			}

			private void validateDialog() {
				String fileName = wsFilenameText.getText();

				if (fileName.equals("")) { //$NON-NLS-1$
					if (modified) {
						setErrorMessage(CVSUIMessages.GenerateDiffFileWizard_2);
						getButton(IDialogConstants.OK_ID).setEnabled(false);
						return;
					} else {
						setErrorMessage(null);
						getButton(IDialogConstants.OK_ID).setEnabled(false);
						return;
					}
				}

				// make sure that the filename is valid
				if (!(ResourcesPlugin.getWorkspace().validateName(fileName,
						IResource.FILE)).isOK() && modified) {
					setErrorMessage(CVSUIMessages.GenerateDiffFileWizard_5);
					getButton(IDialogConstants.OK_ID).setEnabled(false);
					return;
				}

				// Make sure that a container has been selected
				if (getSelectedContainer() == null) {
					setErrorMessage(CVSUIMessages.GenerateDiffFileWizard_0);
					getButton(IDialogConstants.OK_ID).setEnabled(false);
					return;
				} else {
					IWorkspace workspace = ResourcesPlugin.getWorkspace();
					IPath fullPath = wsSelectedContainer.getFullPath().append(
							fileName);
					if (workspace.getRoot().getFolder(fullPath).exists()) {
						setErrorMessage(CVSUIMessages.GenerateDiffFileWizard_FolderExists);
						getButton(IDialogConstants.OK_ID).setEnabled(false);
						return;
					}
				}

				setErrorMessage(null);
				getButton(IDialogConstants.OK_ID).setEnabled(true);
			}

			@Override
			protected void okPressed() {
				IFile file = wsSelectedContainer.getFile(new Path(
						wsFilenameText.getText()));
				if (file != null)
					wsPathText.setText(file.getFullPath().toString());

				validatePage();
				super.okPressed();
			}

			private IContainer getSelectedContainer() {
				Object obj = wsTreeViewer.getStructuredSelection().getFirstElement();
				if (obj instanceof IContainer) {
					wsSelectedContainer = (IContainer) obj;
				} else if (obj instanceof IFile) {
					wsSelectedContainer = ((IFile) obj).getParent();
				}
				return wsSelectedContainer;
			}

			@Override
			protected void cancelPressed() {
				validatePage();
				super.cancelPressed();
			}

			@Override
			public boolean close() {
				if (dlgTitleImage != null)
					dlgTitleImage.dispose();
				return super.close();
			}

			void setupListeners(){
				wsTreeViewer.addSelectionChangedListener(
						event -> {
							IStructuredSelection s = event.getStructuredSelection();
							Object obj=s.getFirstElement();
							if (obj instanceof IContainer)
								wsSelectedContainer = (IContainer) obj;
							else if (obj instanceof IFile){
								IFile tempFile = (IFile) obj;
								wsSelectedContainer = tempFile.getParent();
								wsFilenameText.setText(tempFile.getName());
							}
							validateDialog();
						});

				wsTreeViewer.addDoubleClickListener(
						event -> {
							ISelection s= event.getSelection();
							if (s instanceof IStructuredSelection) {
								Object item = ((IStructuredSelection)s).getFirstElement();
								if (wsTreeViewer.getExpandedState(item))
									wsTreeViewer.collapseToLevel(item, 1);
								else
									wsTreeViewer.expandToLevel(item, 1);
							}
							validateDialog();
						});

				wsFilenameText.addModifyListener(e -> {
					modified = true;
					validateDialog();
				});
			}
		}

		LocationPage(String pageName, String title, ImageDescriptor image, DefaultValuesStore store) {
			super(pageName, title, image);
			setPageComplete(false);
			this.store= store;
			this.canValidate=false;
		}

		/**
		 * Allow the user to finish if a valid file has been entered.
		 */
		protected boolean validatePage() {

			if (!canValidate)
				return false;

			switch (selectedLocation) {
			case WORKSPACE:
				pageValid= validateWorkspaceLocation();
				break;
			case FILESYSTEM:
				pageValid= validateFilesystemLocation();
				break;
			case CLIPBOARD:
				pageValid= true;
				break;
			}

			if ((resources = getSelectedResources()).length == 0) {
				pageValid = false;
				setErrorMessage(CVSUIMessages.GenerateDiffFileWizard_noChangesSelected);
			}

			/**
			 * Avoid draw flicker by clearing error message
			 * if all is valid.
			 */
			if (pageValid) {
				setMessage(null);
				setErrorMessage(null);
			}
			setPageComplete(pageValid);
			return pageValid;
		}

		/**
		 * The following conditions must hold for the file system location
		 * to be valid:
		 * - the path must be valid and non-empty
		 * - the path must be absolute
		 * - the specified file must be of type file
		 * - the parent must exist (new folders can be created via the browse button)
		 */
		private boolean validateFilesystemLocation() {
			final String pathString= fsPathText.getText().trim();
			if (pathString.length() == 0 || !new Path("").isValidPath(pathString)) { //$NON-NLS-1$
				if (fsBrowsed)
					setErrorMessage(CVSUIMessages.GenerateDiffFileWizard_0);
				else
					setErrorMessage(CVSUIMessages.GenerateDiffFileWizard_browseFilesystem);
				return false;
			}

			final File file= new File(pathString);
			if (!file.isAbsolute()) {
				setErrorMessage(CVSUIMessages.GenerateDiffFileWizard_0);
				return false;
			}

			if (file.isDirectory()) {
				setErrorMessage(CVSUIMessages.GenerateDiffFileWizard_2);
				return false;
			}

			if (pathString.endsWith("/") || pathString.endsWith("\\")) {  //$NON-NLS-1$//$NON-NLS-2$
				setErrorMessage(CVSUIMessages.GenerateDiffFileWizard_3);
				return false;
			}

			final File parent= file.getParentFile();
			if (!(parent.exists() && parent.isDirectory())) {
				setErrorMessage(CVSUIMessages.GenerateDiffFileWizard_3);
				return false;
			}
			return true;
		}

		/**
		 * The following conditions must hold for the workspace location to be valid:
		 * - a parent must be selected in the workspace tree view
		 * - the resource name must be valid
		 */
		private boolean validateWorkspaceLocation() {
			//make sure that the field actually has a filename in it - making
			//sure that the user has had a chance to browse the workspace first
			if (wsPathText.getText().equals("")){ //$NON-NLS-1$
				if (selectedLocation ==WORKSPACE && wsBrowsed)
					setErrorMessage(CVSUIMessages.GenerateDiffFileWizard_5);
				else
					setErrorMessage(CVSUIMessages.GenerateDiffFileWizard_4);
				return false;
			}

			//Make sure that all the segments but the last one (i.e. project + all
			//folders) exist - file doesn't have to exist. It may have happened that
			//some folder refactoring has been done since this path was last saved.
			//
			// The path will always be in format project/{folders}*/file - this
			// is controlled by the workspace location dialog and by
			// validatePath method when path has been entered manually.


			IPath pathToWorkspaceFile = new Path(wsPathText.getText());
			IStatus status = ResourcesPlugin.getWorkspace().validatePath(wsPathText.getText(), IResource.FILE);
			if (status.isOK()) {
				//Trim file name from path
				IPath containerPath = pathToWorkspaceFile.removeLastSegments(1);
				IResource container =ResourcesPlugin.getWorkspace().getRoot().findMember(containerPath);
				if (container == null) {
					if (selectedLocation == WORKSPACE)
						setErrorMessage(CVSUIMessages.GenerateDiffFileWizard_4);
					return false;
				} else if (!container.isAccessible()) {
					if (selectedLocation == WORKSPACE)
						setErrorMessage(CVSUIMessages.GenerateDiffFileWizard_ProjectClosed);
					return false;
				} else {
					if (ResourcesPlugin.getWorkspace().getRoot().getFolder(
							pathToWorkspaceFile).exists()) {
						setErrorMessage(CVSUIMessages.GenerateDiffFileWizard_FolderExists);
						return false;
					}
				}
			} else {
				setErrorMessage(status.getMessage());
				return false;
			}

			return true;
		}

		/**
		 * Answers a full path to a file system file or <code>null</code> if the user
		 * selected to save the patch in the clipboard.
		 */
		public File getFile() {
			if (pageValid && selectedLocation == FILESYSTEM) {
				return new File(fsPathText.getText().trim());
			}
			if (pageValid && selectedLocation == WORKSPACE) {
				final String filename= wsPathText.getText().trim();
				IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
				final IFile file= root.getFile(new Path(filename));
				return file.getLocation().toFile();
			}
			return null;
		}

		/**
		 * Answers the workspace string entered in the dialog or <code>null</code> if the user
		 * selected to save the patch in the clipboard or file system.
		 */
		public String getWorkspaceLocation() {

			if (pageValid && selectedLocation == WORKSPACE) {
				final String filename= wsPathText.getText().trim();
				return filename;
			}
			return null;
		}

		/**
		 * Get the selected workspace resource if the patch is to be saved in the
		 * workspace, or null otherwise.
		 */
		public IResource getResource() {
			if (pageValid && selectedLocation == WORKSPACE) {
				IPath pathToWorkspaceFile = new Path(wsPathText.getText().trim());
				//Trim file name from path
				IPath containerPath = pathToWorkspaceFile.removeLastSegments(1);
				return ResourcesPlugin.getWorkspace().getRoot().findMember(containerPath);
			}
			return null;
		}

		/**
		 * Allow the user to chose to save the patch to the workspace or outside
		 * of the workspace.
		 */
		@Override
		public void createControl(Composite parent) {

			final Composite composite= new Composite(parent, SWT.NULL);
			composite.setLayout(new GridLayout());
			setControl(composite);
			initializeDialogUnits(composite);

			// set F1 help
			PlatformUI.getWorkbench().getHelpSystem().setHelp(composite, IHelpContextIds.PATCH_SELECTION_PAGE);

			//Create a location group
			setupLocationControls(composite);

			initializeDefaultValues();

			fParticipant = new CreatePatchWizardParticipant(new ResourceScope(((GenerateDiffFileWizard)this.getWizard()).resources), (GenerateDiffFileWizard) this.getWizard());
			try {
				getAllOutOfSync();
			} catch (CVSException e) {}

			final PixelConverter converter= new PixelConverter(parent);
			createChangesArea(composite, converter);

			createSelectionButtons(composite);

			Dialog.applyDialogFont(parent);

			/**
			 * Ensure the page is in a valid state.
			 */
			/*if (!validatePage()) {
                store.storeRadioSelection(CLIPBOARD);
                initializeDefaultValues();
                validatePage();
            }
            pageValid= true;*/
			validatePage();

			updateEnablements();
			setupListeners();
		}


		private void createSelectionButtons(Composite composite) {
			final Composite buttonGroup = new Composite(composite,SWT.NONE);
			GridLayout layout = new GridLayout();
			layout.numColumns = 2;
			layout.marginWidth = 0;
			layout.marginHeight = 0;
			layout.horizontalSpacing = 0;
			layout.verticalSpacing = 0;
			buttonGroup.setLayout(layout);
			GridData data = new GridData(GridData.HORIZONTAL_ALIGN_END
					| GridData.VERTICAL_ALIGN_CENTER);
			buttonGroup.setLayoutData(data);

			chgSelectAll = createSelectionButton(CVSUIMessages.GenerateDiffFileWizard_SelectAll, buttonGroup);
			chgDeselectAll = createSelectionButton(CVSUIMessages.GenerateDiffFileWizard_DeselectAll, buttonGroup);
		}

		private Button createSelectionButton(String buttonName, Composite buttonGroup) {
			Button button = new Button(buttonGroup,SWT.PUSH);
			button.setText(buttonName);
			GridData data = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
			int widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
			Point minSize = button.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
			data.widthHint = Math.max(widthHint, minSize.x);
			button.setLayoutData(data);
			return button;
		}


		/**
		 * Setup the controls for the location.
		 */
		private void setupLocationControls(final Composite parent) {
			final Composite composite = new Composite(parent, SWT.NULL);
			GridLayout gridLayout = new GridLayout();
			gridLayout.numColumns = 3;
			composite.setLayout(gridLayout);
			composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

			// clipboard
			GridData gd = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
			gd.horizontalSpan = 3;
			cpRadio = new Button(composite, SWT.RADIO);
			cpRadio.setText(CVSUIMessages.Save_To_Clipboard_2);
			cpRadio.setLayoutData(gd);

			// filesystem
			fsRadio = new Button(composite, SWT.RADIO);
			fsRadio.setText(CVSUIMessages.Save_In_File_System_3);

			fsPathText = new Text(composite, SWT.BORDER);
			gd = new GridData(GridData.FILL_HORIZONTAL);
			fsPathText.setLayoutData(gd);

			fsBrowseButton = new Button(composite, SWT.PUSH);
			fsBrowseButton.setText(CVSUIMessages.Browse____4);
			GridData data = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
			int widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
			Point minSize = fsBrowseButton.computeSize(SWT.DEFAULT,
					SWT.DEFAULT, true);
			data.widthHint = Math.max(widthHint, minSize.x);
			fsBrowseButton.setLayoutData(data);

			// workspace
			wsRadio = new Button(composite, SWT.RADIO);
			wsRadio.setText(CVSUIMessages.Save_In_Workspace_7);

			wsPathText = new Text(composite, SWT.BORDER);
			gd = new GridData(GridData.FILL_HORIZONTAL);
			wsPathText.setLayoutData(gd);

			wsBrowseButton = new Button(composite, SWT.PUSH);
			wsBrowseButton.setText(CVSUIMessages.Browse____4);
			data = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
			widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
			minSize = fsBrowseButton
			.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
			data.widthHint = Math.max(widthHint, minSize.x);
			wsBrowseButton.setLayoutData(data);

			// change the cpRadio layout to be of the same height as other rows' layout
			((GridData)cpRadio.getLayoutData()).heightHint = minSize.y;
		}

		private ParticipantPagePane fPagePane;
		private PageBook bottomChild;
		private ISynchronizePageConfiguration fConfiguration;

		private void createChangesArea(Composite parent, PixelConverter converter) {

			int size = fParticipant.getSyncInfoSet().size();
			if (size > getFileDisplayThreshold()) {
				// Create a page book to allow eventual inclusion of changes
				bottomChild = new PageBook(parent, SWT.NONE);
				bottomChild.setLayoutData(SWTUtils.createGridData(SWT.DEFAULT, SWT.DEFAULT, SWT.FILL, SWT.FILL, true, false));
				// Create composite for showing the reason for not showing the changes and a button to show them
				Composite changeDesc = new Composite(bottomChild, SWT.NONE);
				changeDesc.setLayout(SWTUtils.createGridLayout(1, converter, SWTUtils.MARGINS_NONE));
				SWTUtils.createLabel(changeDesc, NLS.bind(CVSUIMessages.CommitWizardCommitPage_1, new String[] { Integer.toString(size), Integer.toString(getFileDisplayThreshold()) }));
				Button showChanges = new Button(changeDesc, SWT.PUSH);
				showChanges.setText(CVSUIMessages.CommitWizardCommitPage_5);
				showChanges.addSelectionListener(new SelectionAdapter() {
					@Override
					public void widgetSelected(SelectionEvent e) {
						showChangesPane();
					}
				});
				showChanges.setLayoutData(new GridData());
				bottomChild.showPage(changeDesc);
			} else {
				final Composite composite= new Composite(parent, SWT.NONE);
				composite.setLayout(SWTUtils.createGridLayout(1, converter, SWTUtils.MARGINS_NONE));
				composite.setLayoutData(SWTUtils.createGridData(SWT.DEFAULT, SWT.DEFAULT, SWT.FILL, SWT.FILL, true, true));

				createPlaceholder(composite);

				Control c = createChangesPage(composite, fParticipant);
				c.setLayoutData(SWTUtils.createHVFillGridData());
			}
		}

		protected void showChangesPane() {
			Control c = createChangesPage(bottomChild, fParticipant);
			bottomChild.setLayoutData(SWTUtils.createGridData(SWT.DEFAULT, SWT.DEFAULT, SWT.FILL, SWT.FILL, true, true));
			bottomChild.showPage(c);
			Dialog.applyDialogFont(getControl());
			((Composite)getControl()).layout();
		}

		private Control createChangesPage(final Composite composite, WorkspaceSynchronizeParticipant participant) {
			fConfiguration= participant.createPageConfiguration();
			fPagePane= new ParticipantPagePane(getShell(), true /* modal */, fConfiguration, participant);
			Control control = fPagePane.createPartControl(composite);
			return control;
		}

		@Override
		public void dispose() {
			if (fPagePane != null)
				fPagePane.dispose();
			if (fParticipant != null)
				fParticipant.dispose();
			super.dispose();
		}

		private int getFileDisplayThreshold() {
			return CVSUIPlugin.getPlugin().getPreferenceStore().getInt(ICVSUIConstants.PREF_COMMIT_FILES_DISPLAY_THRESHOLD);
		}

		private void createPlaceholder(final Composite composite) {
			final Composite placeholder= new Composite(composite, SWT.NONE);
			placeholder.setLayoutData(new GridData(SWT.DEFAULT, convertHorizontalDLUsToPixels(IDialogConstants.VERTICAL_SPACING) /3));
		}
		/**
		 * Initialize the controls with the saved default values which are
		 * obtained from the DefaultValuesStore.
		 */
		private void initializeDefaultValues() {

			selectedLocation= store.getLocationSelection();

			updateRadioButtons();

			/**
			 * Text fields.
			 */
			// We need to ensure that we have a valid workspace path - user
			//could have altered workspace since last time this was saved
			wsPathText.setText(store.getWorkspacePath());
			if(!validateWorkspaceLocation()) {
				wsPathText.setText(""); //$NON-NLS-1$

				//Don't open wizard with an error - instead change selection
				//to clipboard
				if (selectedLocation == WORKSPACE){
					//clear the error message caused by the workspace not having
					//any workspace path entered
					setErrorMessage(null);
					selectedLocation=CLIPBOARD;
					updateRadioButtons();
				}
			}
			// Do the same thing for the filesystem field
			fsPathText.setText(store.getFilesystemPath());
			if (!validateFilesystemLocation()) {
				fsPathText.setText(""); //$NON-NLS-1$
				if (selectedLocation == FILESYSTEM) {
					setErrorMessage(null);
					selectedLocation = CLIPBOARD;
					updateRadioButtons();
				}
			}

		}

		private void updateRadioButtons() {
			/**
			 * Radio buttons
			 */
			cpRadio.setSelection(selectedLocation == CLIPBOARD);
			fsRadio.setSelection(selectedLocation == FILESYSTEM);
			wsRadio.setSelection(selectedLocation == WORKSPACE);
		}

		/**
		 * Setup all the listeners for the controls.
		 */
		private void setupListeners() {

			cpRadio.addListener(SWT.Selection, event -> {
				selectedLocation= CLIPBOARD;
				validatePage();
				updateEnablements();
			});
			fsRadio.addListener(SWT.Selection, event -> {
				selectedLocation= FILESYSTEM;
				validatePage();
				updateEnablements();
			});

			wsRadio.addListener(SWT.Selection, event -> {
				selectedLocation= WORKSPACE;
				validatePage();
				updateEnablements();
			});

			ModifyListener pathTextModifyListener = e -> validatePage();
			fsPathText.addModifyListener(pathTextModifyListener);
			wsPathText.addModifyListener(pathTextModifyListener);

			fsBrowseButton.addListener(SWT.Selection, event -> {
				final FileDialog dialog = new FileDialog(getShell(), SWT.PRIMARY_MODAL | SWT.SAVE);
				if (pageValid) {
					final File file= new File(fsPathText.getText());
					dialog.setFilterPath(file.getParent());
				}
				dialog.setText(CVSUIMessages.Save_Patch_As_5);
				dialog.setFileName(CVSUIMessages.patch_txt_6);
				final String path = dialog.open();
				fsBrowsed = true;
				if (path != null) {
					fsPathText.setText(new Path(path).toOSString());
				}
				validatePage();
			});



			wsBrowseButton.addListener(SWT.Selection, event -> {
				final WorkspaceDialog dialog = new WorkspaceDialog(getShell());
				wsBrowsed = true;
				dialog.open();
				validatePage();
			});


			chgSelectAll.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					initCheckedItems();
					//Only bother changing isPageComplete state if the current state
					//is not enabled
					if (!isPageComplete())
						setPageComplete(validatePage());
				}
			});

			chgDeselectAll.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					ISynchronizePage page = fConfiguration.getPage();
					if (page != null){
						Viewer viewer = page.getViewer();
						if (viewer instanceof CheckboxTreeViewer) {
							CheckboxTreeViewer treeViewer =(CheckboxTreeViewer)viewer;
							treeViewer.setCheckedElements(new Object[0]);
						}
					}
					//Only bother changing isPageComplete state if the current state
					//is enabled
					if (isPageComplete())
						setPageComplete(validatePage());
				}
			});

			ISynchronizePage page = fConfiguration.getPage();
			if (page != null) {
				Viewer viewer = page.getViewer();
				if (viewer instanceof CheckboxTreeViewer) {
					((CheckboxTreeViewer)viewer).addCheckStateListener(event -> setPageComplete(validatePage()));
				}
			}
		}

		protected void initCheckedItems() {
			ISynchronizePage page = fConfiguration.getPage();
			if (page != null) {
				Viewer viewer = page.getViewer();
				if (viewer instanceof CheckboxTreeViewer) {
					TreeItem[] items=((CheckboxTreeViewer)viewer).getTree().getItems();
					for (int i = 0; i < items.length; i++) {
						((CheckboxTreeViewer)viewer).setChecked(items[i].getData(), true);
					}
				}
			}
		}

		protected IResource[] getSelectedResources() {
			ISynchronizePage page = fConfiguration.getPage();
			if (page != null) {
				Viewer viewer = page.getViewer();
				if (viewer instanceof CheckboxTreeViewer) {
					Object[] elements = ((CheckboxTreeViewer)viewer).getCheckedElements();
					IResource[]selectedResources = Utils.getResources(elements);
					ArrayList result = new ArrayList();
					for (int i = 0; i < selectedResources.length; i++) {
						IResource resource = selectedResources[i];
						if (fConfiguration.getSyncInfoSet().getSyncInfo(resource) != null) {
							result.add(resource);
						}
					}
					return (IResource[]) result.toArray(new IResource[result.size()]);
				}
			}
			return new IResource[0];
		}

		/**
		 * Enable and disable controls based on the selected radio button.
		 */
		public void updateEnablements() {
			fsBrowseButton.setEnabled(selectedLocation == FILESYSTEM);
			fsPathText.setEnabled(selectedLocation == FILESYSTEM);
			if (selectedLocation == FILESYSTEM)
				fsBrowsed=false;
			wsPathText.setEnabled(selectedLocation == WORKSPACE);
			wsBrowseButton.setEnabled(selectedLocation == WORKSPACE);
			if (selectedLocation == WORKSPACE)
				wsBrowsed=false;
		}

		public int getSelectedLocation() {
			return selectedLocation;
		}

		private SyncInfoSet getAllOutOfSync() throws CVSException {
			final SubscriberSyncInfoCollector syncInfoCollector = fParticipant.getSubscriberSyncInfoCollector();
			//WaitForChangesJob waits for the syncInfoCollector to get all the changes
			//before checking off the tree items and validating the page
			class WaitForChangesJob extends Job{
				LocationPage fLocationPage;

				public WaitForChangesJob(LocationPage page) {
					super(""); //$NON-NLS-1$
					fLocationPage=page;
				}
				@Override
				public IStatus run(IProgressMonitor monitor) {
					monitor.beginTask(CVSUIMessages.CommitWizard_4, IProgressMonitor.UNKNOWN);
					syncInfoCollector.waitForCollector(monitor);
					Utils.syncExec((Runnable) () -> {
						fLocationPage.initCheckedItems();
						fLocationPage.canValidate=true;
						fLocationPage.validatePage();
					}, getControl());
					monitor.done();
					return Status.OK_STATUS;
				}
			}
			WaitForChangesJob job =new WaitForChangesJob(this);
			//Don't need the job in the UI, make it a system job
			job.setSystem(true);
			job.schedule();
			return fParticipant.getSyncInfoSet();
		}

		public IFile findBinaryFile() {
			try {
				final IFile[] found = new IFile[1];
				fParticipant.getSubscriber().accept(resources, IResource.DEPTH_INFINITE, (IDiffVisitor) diff -> {
					if (isBinaryFile(diff))
						found[0] = getFile(diff);
					return true;
				});
				return found[0];
			} catch (CoreException e) {
				CVSUIPlugin.log(e);
			}
			return null;
		}

		protected boolean isBinaryFile(IDiff diff) {
			IFile file = getFile(diff);
			if (file != null) {
				ICVSFile cvsFile = CVSWorkspaceRoot.getCVSFileFor(file);
				try {
					byte[] bytes = cvsFile.getSyncBytes();
					if (bytes != null) {
						return ResourceSyncInfo.getKeywordMode(bytes).toMode().equals(
								Command.KSUBST_BINARY.toMode());
					}
				} catch (CVSException e) {
					CVSUIPlugin.log(e);
				}
				return (Team.getFileContentManager().getType(file) == Team.BINARY);
			}
			return false;
		}

		protected IFile getFile(IDiff diff) {
			IResource resource = ResourceDiffTree.getResourceFor(diff);
			if (resource instanceof IFile) {
				IFile file = (IFile) resource;
				return file;
			}
			return null;
		}

		public void removeBinaryFiles() {
			try {
				final List nonBinaryFiles = new ArrayList();
				fParticipant.getSubscriber().accept(resources, IResource.DEPTH_INFINITE, (IDiffVisitor) diff -> {
					if (!isBinaryFile(diff)) {
						IFile file = getFile(diff);
						if (file != null)
							nonBinaryFiles.add(file);
					}
					return true;
				});
				resources = (IResource[]) nonBinaryFiles
				.toArray(new IResource[nonBinaryFiles.size()]);
			} catch (CoreException e) {
				CVSUIPlugin.log(e);
			}
		}

	}

	/**
	 * Page to select the options for creating the patch.
	 */
	private class OptionsPage extends WizardPage {

		/**
		 * The possible file format to save a patch.
		 */
		public final static int FORMAT_UNIFIED = 1;
		public final static int FORMAT_CONTEXT = 2;
		public final static int FORMAT_STANDARD = 3;

		/**
    	The possible root of the patch
		 */
		public final static int ROOT_WORKSPACE = 1;
		public final static int ROOT_PROJECT = 2;
		public final static int ROOT_SELECTION = 3;

		private Button unifiedDiffOption;
		private Button unified_workspaceRelativeOption; //multi-patch format
		private Button unified_projectRelativeOption; //full project path
		private Button unified_selectionRelativeOption; //use path of whatever is selected
		private Button contextDiffOption;
		private Button regularDiffOption;
		private final RadioButtonGroup diffTypeRadioGroup = new RadioButtonGroup();
		private final RadioButtonGroup unifiedRadioGroup = new RadioButtonGroup();

		private boolean patchHasCommonRoot=true;
		protected IPath patchRoot=ResourcesPlugin.getWorkspace().getRoot().getFullPath();

		private final DefaultValuesStore store;

		/**
		 * Constructor for PatchFileCreationOptionsPage.
		 * 
		 * @param pageName
		 *            the name of the page
		 * 
		 * @param title
		 *            the title for this wizard page, or <code>null</code> if
		 *            none
		 * @param titleImage
		 *            the image descriptor for the title of this wizard page, or
		 *            <code>null</code> if none
		 * @param store
		 *            the value store where the page stores it's data
		 */
		protected OptionsPage(String pageName, String title,
				ImageDescriptor titleImage, DefaultValuesStore store) {
			super(pageName, title, titleImage);
			this.store = store;
		}

		/*
		 * @see IDialogPage#createControl(Composite)
		 */
		@Override
		public void createControl(Composite parent) {
			Composite composite= new Composite(parent, SWT.NULL);
			GridLayout layout= new GridLayout();
			composite.setLayout(layout);
			composite.setLayoutData(new GridData());
			setControl(composite);

			// set F1 help
			PlatformUI.getWorkbench().getHelpSystem().setHelp(composite, IHelpContextIds.PATCH_OPTIONS_PAGE);

			Group diffTypeGroup = new Group(composite, SWT.NONE);
			layout = new GridLayout();
			diffTypeGroup.setLayout(layout);
			GridData data = new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL);
			diffTypeGroup.setLayoutData(data);
			diffTypeGroup.setText(CVSUIMessages.Diff_output_format_12);

			unifiedDiffOption = new Button(diffTypeGroup, SWT.RADIO);
			unifiedDiffOption.setText(CVSUIMessages.Unified__format_required_by_Compare_With_Patch_feature__13);

			contextDiffOption = new Button(diffTypeGroup, SWT.RADIO);
			contextDiffOption.setText(CVSUIMessages.Context_14);
			regularDiffOption = new Button(diffTypeGroup, SWT.RADIO);
			regularDiffOption.setText(CVSUIMessages.Standard_15);

			diffTypeRadioGroup.add(FORMAT_UNIFIED, unifiedDiffOption);
			diffTypeRadioGroup.add(FORMAT_CONTEXT, contextDiffOption);
			diffTypeRadioGroup.add(FORMAT_STANDARD, regularDiffOption);

			//Unified Format Options
			Group unifiedGroup = new Group(composite, SWT.None);
			layout = new GridLayout();
			unifiedGroup.setLayout(layout);
			data = new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL);
			unifiedGroup.setLayoutData(data);
			unifiedGroup.setText(CVSUIMessages.GenerateDiffFileWizard_10);

			unified_workspaceRelativeOption = new Button(unifiedGroup, SWT.RADIO);
			unified_workspaceRelativeOption.setText(CVSUIMessages.GenerateDiffFileWizard_6);
			unified_workspaceRelativeOption.setSelection(true);

			unified_projectRelativeOption = new Button(unifiedGroup, SWT.RADIO);
			unified_projectRelativeOption.setText(CVSUIMessages.GenerateDiffFileWizard_7);

			unified_selectionRelativeOption = new Button(unifiedGroup, SWT.RADIO);
			unified_selectionRelativeOption.setText(CVSUIMessages.GenerateDiffFileWizard_8);

			unifiedRadioGroup.add(ROOT_WORKSPACE, unified_workspaceRelativeOption);
			unifiedRadioGroup.add(ROOT_PROJECT, unified_projectRelativeOption);
			unifiedRadioGroup.add(ROOT_SELECTION, unified_selectionRelativeOption);

			Dialog.applyDialogFont(parent);

			initializeDefaultValues();

			//add listeners
			unifiedDiffOption.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					setEnableUnifiedGroup(true);
					updateEnablements();
					diffTypeRadioGroup.setSelection(FORMAT_UNIFIED, false);
				}
			});

			contextDiffOption.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					setEnableUnifiedGroup(false);
					updateEnablements();
					diffTypeRadioGroup.setSelection(FORMAT_CONTEXT, false);
				}
			});

			regularDiffOption.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					setEnableUnifiedGroup(false);
					updateEnablements();
					diffTypeRadioGroup.setSelection(FORMAT_STANDARD, false);
				}
			});

			unified_workspaceRelativeOption
			.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					unifiedRadioGroup.setSelection(ROOT_WORKSPACE, false);
				}
			});

			unified_projectRelativeOption
			.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					unifiedRadioGroup.setSelection(ROOT_PROJECT, false);
				}
			});

			unified_selectionRelativeOption
			.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					unifiedRadioGroup.setSelection(ROOT_SELECTION, false);
				}
			});

			calculatePatchRoot();
			updateEnablements();

			// update selection
			diffTypeRadioGroup.selectEnabledOnly();
			unifiedRadioGroup.selectEnabledOnly();
		}

		public int getFormatSelection() {
			return diffTypeRadioGroup.getSelected();
		}

		public int getRootSelection() {
			return unifiedRadioGroup.getSelected();
		}

		private void initializeDefaultValues() {
			// Radio buttons for format
			diffTypeRadioGroup.setSelection(store.getFormatSelection(), true);
			// Radio buttons for patch root
			unifiedRadioGroup.setSelection(store.getRootSelection(), true);

			if (store.getFormatSelection() != FORMAT_UNIFIED) {
				setEnableUnifiedGroup(false);
			}
		}


		protected void updateEnablements() {
			if (!patchHasCommonRoot){
				diffTypeRadioGroup.setEnablement(false, new int[] {
						FORMAT_CONTEXT, FORMAT_STANDARD }, FORMAT_UNIFIED);
				unifiedRadioGroup.setEnablement(false, new int[] {
						ROOT_PROJECT, ROOT_SELECTION }, ROOT_WORKSPACE);
			}

			// temporary until we figure out best way to fix synchronize view
			// selection
			if (!unifiedSelectionEnabled)
				unifiedRadioGroup.setEnablement(false, new int[] {ROOT_SELECTION});
		}

		private void calculatePatchRoot(){
			//check to see if this is a multi select patch, if so disable
			IResource[] tempResources = ((GenerateDiffFileWizard)this.getWizard()).resources;

			//Guard for quick cancellation to avoid ArrayOutOfBounds (see Bug# 117234)
			if (tempResources == null)
				return;

			if (tempResources.length > 1){
				//Check to see is the selected resources are contained by the same parent (climbing
				//parent by parent to the project root)
				//If so, then allow selection relative patches -> set the relative path to the common
				//parent [also allow project relative patches]
				//If parents are different projects, allow only multiproject selection

				patchHasCommonRoot=true;
				int segmentMatch=-1;
				IPath path = tempResources[0].getFullPath().removeLastSegments(1);
				for (int i = 1; i < tempResources.length; i++) {
					int segments=path.matchingFirstSegments(tempResources[i].getFullPath());
					//Keep track of the lowest number of matches that were found - the common
					//path will be this number
					if (segmentMatch == -1 ||
							segmentMatch>segments){
						segmentMatch=segments;
					}
					//However, if no segments for any one resource - break out of the loop
					if (segments == 0){
						patchHasCommonRoot=false;
						break;
					}
				}
				if (patchHasCommonRoot){
					IPath tempPath = path.uptoSegment(segmentMatch);
					/*IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
					while (!root.exists(tempPath) &&
							!tempPath.isRoot()){
						tempPath = tempPath.removeLastSegments(1);
					}*/
					patchRoot=tempPath;
				}
			} else {
				IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();

				//take the file name off the path and use that as the patch root
				//patchRoot = tempResources[0].getFullPath().removeLastSegments(1);
				IPath fullPath = tempResources[0].getFullPath();
				IResource resource = root.findMember(fullPath);

				//keep trimming the path until we find something that can be used as the
				//patch root
				while (resource == null &&
						!(resource instanceof IWorkspaceRoot)){
					fullPath=fullPath.removeLastSegments(1);
					resource=root.findMember(fullPath);
				}
				patchRoot = resource.getFullPath();
				if (resource.getType() == IResource.FILE)
					patchRoot =resource.getFullPath().removeLastSegments(1);
			}
		}
		/**
		 * Return the list of Diff command options configured on this page.
		 */
		public LocalOption[] getOptions() {
			List options = new ArrayList(5);
			/*  if(includeNewFilesOptions.getSelection()) {
                options.add(Diff.INCLUDE_NEWFILES);
            }
            if(!recurseOption.getSelection()) {
                options.add(Command.DO_NOT_RECURSE);
            }*/

			//Add new files for now
			options.add(Diff.INCLUDE_NEWFILES);

			if(unifiedDiffOption.getSelection()) {
				options.add(Diff.UNIFIED_FORMAT);
			} else if(contextDiffOption.getSelection()) {
				options.add(Diff.CONTEXT_FORMAT);
			}

			return (LocalOption[]) options.toArray(new LocalOption[options.size()]);
		}
		protected void setEnableUnifiedGroup(boolean enabled){
			unifiedRadioGroup.setEnablement(enabled, new int[] {
					ROOT_WORKSPACE, ROOT_PROJECT, ROOT_SELECTION });

			//temporary until we figure out best way to fix synchronize view selection
			if (!unifiedSelectionEnabled)
				unifiedRadioGroup.setEnablement(false, new int[] {ROOT_SELECTION});
		}
	}

	/**
	 * Class to retrieve and store the default selected values.
	 */
	private final class DefaultValuesStore {

		private static final String PREF_LAST_SELECTION= "org.eclipse.team.internal.ccvs.ui.wizards.GenerateDiffFileWizard.PatchFileSelectionPage.lastselection"; //$NON-NLS-1$
		private static final String PREF_LAST_FS_PATH= "org.eclipse.team.internal.ccvs.ui.wizards.GenerateDiffFileWizard.PatchFileSelectionPage.filesystem.path"; //$NON-NLS-1$
		private static final String PREF_LAST_WS_PATH= "org.eclipse.team.internal.ccvs.ui.wizards.GenerateDiffFileWizard.PatchFileSelectionPage.workspace.path"; //$NON-NLS-1$
		private static final String PREF_LAST_AO_FORMAT = "org.eclipse.team.internal.ccvs.ui.wizards.GenerateDiffFileWizard.OptionsPage.diff.format"; //$NON-NLS-1$
		private static final String PREF_LAST_AO_ROOT = "org.eclipse.team.internal.ccvs.ui.wizards.GenerateDiffFileWizard.OptionsPage.patch.root"; //$NON-NLS-1$


		private final IDialogSettings dialogSettings;

		public DefaultValuesStore() {
			dialogSettings= CVSUIPlugin.getPlugin().getDialogSettings();
		}

		public int getLocationSelection() {
			int value= LocationPage.CLIPBOARD;
			try {
				value= dialogSettings.getInt(PREF_LAST_SELECTION);
			} catch (NumberFormatException e) {
				// ignore
			}

			switch (value) {
			case LocationPage.FILESYSTEM:
			case LocationPage.WORKSPACE:
			case LocationPage.CLIPBOARD:
				return value;
			default:
				return LocationPage.CLIPBOARD;
			}
		}

		public String getFilesystemPath() {
			final String path= dialogSettings.get(PREF_LAST_FS_PATH);
			return path != null ? path : "";  //$NON-NLS-1$
		}

		public String getWorkspacePath() {
			final String path= dialogSettings.get(PREF_LAST_WS_PATH);
			return path != null ? path : ""; //$NON-NLS-1$
		}


		public int getFormatSelection() {
			int value = OptionsPage.FORMAT_UNIFIED;
			try {
				value = dialogSettings.getInt(PREF_LAST_AO_FORMAT);
			} catch (NumberFormatException e) {
			}

			switch (value) {
			case OptionsPage.FORMAT_UNIFIED:
			case OptionsPage.FORMAT_CONTEXT:
			case OptionsPage.FORMAT_STANDARD:
				return value;
			default:
				return OptionsPage.FORMAT_UNIFIED;
			}
		}

		public int getRootSelection() {
			int value = OptionsPage.ROOT_WORKSPACE;
			try {
				value = dialogSettings.getInt(PREF_LAST_AO_ROOT);
			} catch (NumberFormatException e) {
			}

			switch (value) {
			case OptionsPage.ROOT_WORKSPACE:
			case OptionsPage.ROOT_PROJECT:
			case OptionsPage.ROOT_SELECTION:
				return value;
			default:
				return OptionsPage.ROOT_WORKSPACE;
			}
		}

		public void storeLocationSelection(int defaultSelection) {
			dialogSettings.put(PREF_LAST_SELECTION, defaultSelection);
		}

		public void storeFilesystemPath(String path) {
			dialogSettings.put(PREF_LAST_FS_PATH, path);
		}

		public void storeWorkspacePath(String path) {
			dialogSettings.put(PREF_LAST_WS_PATH, path);
		}

		public void storeOutputFormat(int selection) {
			dialogSettings.put(PREF_LAST_AO_FORMAT, selection);
		}

		public void storePatchRoot(int selection) {
			dialogSettings.put(PREF_LAST_AO_ROOT, selection);
		}
	}

	private LocationPage locationPage;
	private OptionsPage optionsPage;

	protected IResource[] resources;
	private final DefaultValuesStore defaultValuesStore;
	private final IWorkbenchPart part;

	//temporary until we figure out best way to fix synchronize view selection
	protected boolean unifiedSelectionEnabled;

	public GenerateDiffFileWizard(IWorkbenchPart part, IResource[] resources, boolean unifiedSelectionEnabled) {
		super();
		this.part = part;
		this.resources = resources;
		setWindowTitle(CVSUIMessages.GenerateCVSDiff_title);
		initializeDefaultPageImageDescriptor();
		defaultValuesStore= new DefaultValuesStore();
		this.unifiedSelectionEnabled=unifiedSelectionEnabled;
	}

	@Override
	public void addPages() {
		String pageTitle = CVSUIMessages.GenerateCVSDiff_pageTitle;
		String pageDescription = CVSUIMessages.GenerateCVSDiff_pageDescription;
		locationPage = new LocationPage(pageTitle, pageTitle, CVSUIPlugin.getPlugin().getImageDescriptor(ICVSUIConstants.IMG_WIZBAN_DIFF), defaultValuesStore);
		locationPage.setDescription(pageDescription);
		addPage(locationPage);

		pageTitle = CVSUIMessages.Advanced_options_19;
		pageDescription = CVSUIMessages.Configure_the_options_used_for_the_CVS_diff_command_20;
		optionsPage = new OptionsPage(pageTitle, pageTitle, CVSUIPlugin.getPlugin().getImageDescriptor(ICVSUIConstants.IMG_WIZBAN_DIFF), defaultValuesStore);
		optionsPage.setDescription(pageDescription);
		addPage(optionsPage);
	}

	/**
	 * Declares the wizard banner iamge descriptor
	 */
	protected void initializeDefaultPageImageDescriptor() {
		final String iconPath= "icons/full/"; //$NON-NLS-1$
		try {
			final URL installURL = CVSUIPlugin.getPlugin().getBundle().getEntry("/"); //$NON-NLS-1$
			final URL url = new URL(installURL, iconPath + "wizards/newconnect_wiz.gif");	//$NON-NLS-1$
			ImageDescriptor desc = ImageDescriptor.createFromURL(url);
			setDefaultPageImageDescriptor(desc);
		} catch (MalformedURLException e) {
			// Should not happen.  Ignore.
		}
	}

	/* (Non-javadoc)
	 * Method declared on IWizard.
	 */
	@Override
	public boolean needsProgressMonitor() {
		return true;
	}

	/**
	 * Completes processing of the wizard. If this method returns <code>
	 * true</code>, the wizard will close; otherwise, it will stay active.
	 */
	@Override
	public boolean performFinish() {

		final int location= locationPage.getSelectedLocation();

		final File file= location != LocationPage.CLIPBOARD? locationPage.getFile() : null;

		if (!(file == null || validateFile(file))) {
			return false;
		}

		//Is this a multi-patch?
		boolean multiPatch=false;
		if (optionsPage.unifiedDiffOption.getSelection() && optionsPage.unified_workspaceRelativeOption.getSelection())
			multiPatch=true;


		//If not a multipatch, patch should use project relative or selection relative paths[default]?
		boolean useProjectRelativePaths=false;
		if (optionsPage.unifiedDiffOption.getSelection() &&
				optionsPage.unified_projectRelativeOption.getSelection())
			useProjectRelativePaths=true;

		IFile binFile = locationPage.findBinaryFile();
		if (binFile != null) {
			int result = promptToIncludeBinary(binFile);
			if (result == 2)
				return false;
			if (result == 1)
				locationPage.removeBinaryFiles();
		}

		/**
		 * Perform diff operation.
		 */
		try {
			if (file != null) {
				generateDiffToFile(file,multiPatch,useProjectRelativePaths);
			} else {
				generateDiffToClipboard(multiPatch,useProjectRelativePaths);
			}
		} catch (TeamException e) {}

		/**
		 * Refresh workspace if necessary and save default selection.
		 */
		switch (location) {

		case LocationPage.WORKSPACE:
			final String workspaceResource= locationPage.getWorkspaceLocation();
			if (workspaceResource != null){
				defaultValuesStore.storeLocationSelection(LocationPage.WORKSPACE);
				defaultValuesStore.storeWorkspacePath(workspaceResource);
				/* try {
	                workspaceResource.getParent().refreshLocal(IResource.DEPTH_ONE, null);
	            } catch(CoreException e) {
	                CVSUIPlugin.openError(getShell(), CVSUIMessages.GenerateCVSDiff_error, null, e);
	                return false;
	            } */
			} else {
				//Problem with workspace location, open with clipboard next time
				defaultValuesStore.storeLocationSelection(LocationPage.CLIPBOARD);
			}
			break;

		case LocationPage.FILESYSTEM:
			defaultValuesStore.storeFilesystemPath(file.getPath());
			defaultValuesStore.storeLocationSelection(LocationPage.FILESYSTEM);
			break;

		case LocationPage.CLIPBOARD:
			defaultValuesStore.storeLocationSelection(LocationPage.CLIPBOARD);
			break;

		default:
			return false;
		}


		/**
		 * Save default selections of Options Page
		 */

		defaultValuesStore.storeOutputFormat(optionsPage.getFormatSelection());
		defaultValuesStore.storePatchRoot(optionsPage.getRootSelection());

		return true;
	}

	private int promptToIncludeBinary(IFile file) {
		MessageDialog dialog = new MessageDialog(getShell(), CVSUIMessages.GenerateDiffFileWizard_11, null, // accept
				// the default window icon
				NLS.bind(CVSUIMessages.GenerateDiffFileWizard_12, file.getFullPath()), MessageDialog.WARNING, new String[] { IDialogConstants.YES_LABEL,
			IDialogConstants.NO_LABEL, IDialogConstants.CANCEL_LABEL }, 1); // no is the default
		return dialog.open();
	}

	private void generateDiffToClipboard(boolean multiPatch, boolean useProjectRelativePaths) throws TeamException {
		DiffOperation diffop = new ClipboardDiffOperation(part,RepositoryProviderOperation.asResourceMappers(resources, IResource.DEPTH_ZERO),optionsPage.getOptions(),multiPatch, useProjectRelativePaths, optionsPage.patchRoot);
		try {
			diffop.run();
		} catch (InvocationTargetException e) {}
		catch (InterruptedException e) {}
	}

	private void generateDiffToFile(File file, boolean multiPatch, boolean useProjectRelativePaths) throws TeamException {
		DiffOperation diffop = null;
		if (locationPage.selectedLocation == LocationPage.WORKSPACE){
			diffop = new WorkspaceFileDiffOperation(part,RepositoryProviderOperation.asResourceMappers(resources, IResource.DEPTH_ZERO),optionsPage.getOptions(),file, multiPatch, useProjectRelativePaths, optionsPage.patchRoot);
		}
		else {
			diffop = new FileDiffOperation(part,RepositoryProviderOperation.asResourceMappers(resources, IResource.DEPTH_ZERO),optionsPage.getOptions(),file, multiPatch, useProjectRelativePaths, optionsPage.patchRoot);
		}

		try {
			diffop.run();
		} catch (InvocationTargetException e) {}
		catch (InterruptedException e) {}
	}

	public boolean validateFile(File file) {

		if (file == null)
			return false;

		/**
		 * Consider file valid if it doesn't exist for now.
		 */
		if (!file.exists())
			return true;

		/**
		 * The file exists.
		 */
		if (!file.canWrite()) {
			final String title= CVSUIMessages.GenerateCVSDiff_1;
			final String msg= CVSUIMessages.GenerateCVSDiff_2;
			final MessageDialog dialog= new MessageDialog(getShell(), title, null, msg, MessageDialog.ERROR, new String[] { IDialogConstants.OK_LABEL }, 0);
			dialog.open();
			return false;
		}

		final String title = CVSUIMessages.GenerateCVSDiff_overwriteTitle;
		final String msg = CVSUIMessages.GenerateCVSDiff_overwriteMsg;
		final MessageDialog dialog = new MessageDialog(getShell(), title, null, msg, MessageDialog.QUESTION, new String[] { IDialogConstants.YES_LABEL, IDialogConstants.CANCEL_LABEL }, 0);
		dialog.open();
		if (dialog.getReturnCode() != 0)
			return false;

		return true;
	}

	public LocationPage getLocationPage() {
		return locationPage;
	}

	/**
	 * The class maintain proper selection of radio button within the group:
	 * <ul>
	 * <li>Only one button can be selected at the time.</li>
	 * <li>Disabled button can't be selected unless all buttons in the group
	 * are disabled.</li>
	 * </ul>
	 */
	/*private*/ class RadioButtonGroup {

		/**
		 * List of buttons in the group. Both radio groups contain 3 elements.
		 */
		private List buttons = new ArrayList(3);

		/**
		 * Index of the selected button.
		 */
		private int selected = 0;

		/**
		 * Add a button to the group. While adding a new button the method
		 * checks if there is only one button selected in the group.
		 * 
		 * @param buttonCode
		 *            A button's code (eg. <code>ROOT_WORKSPACE</code>). To get
		 *            an index we need to subtract 1 from it.
		 * @param button
		 *            A button to add.
		 */
		public void add(int buttonCode, Button button) {
			if (button != null && (button.getStyle() & SWT.RADIO) != 0) {
				if (button.getSelection() && !buttons.isEmpty()) {
					deselectAll();
					selected = buttonCode - 1;
				}
				buttons.add(buttonCode - 1, button);
			}
		}

		/**
		 * Returns selected button's code.
		 * 
		 * @return Selected button's code.
		 */
		public int getSelected() {
			return selected + 1;
		}

		/**
		 * Set selection to the given button. When
		 * <code>selectEnabledOnly</code> flag is true the returned value can
		 * differ from the parameter when a button we want to set selection to
		 * is disabled and there are other buttons which are enabled.
		 * 
		 * @param buttonCode
		 *            A button's code (eg. <code>ROOT_WORKSPACE</code>). To get
		 *            an index we need to subtract 1 from it.
		 * @return Code of the button to which selection was finally set.
		 */
		public int setSelection(int buttonCode, boolean selectEnabledOnly) {
			deselectAll();

			((Button) buttons.get(buttonCode - 1)).setSelection(true);
			selected = buttonCode - 1;
			if (selectEnabledOnly)
				selected = selectEnabledOnly() - 1;
			return getSelected();
		}

		/**
		 * Make sure that only an enabled radio button is selected.
		 * 
		 * @return A code of the selected button.
		 */
		public int selectEnabledOnly() {
			deselectAll();

			Button selectedButton = (Button) buttons.get(selected);
			if (!selectedButton.isEnabled()) {
				// if the button is disabled, set selection to an enabled one
				for (Iterator iterator = buttons.iterator(); iterator.hasNext();) {
					Button b = (Button) iterator.next();
					if (b.isEnabled()) {
						b.setSelection(true);
						selected = buttons.indexOf(b);
						return selected + 1;
					}
				}
				// if none found, reset the initial selection
				selectedButton.setSelection(true);
			} else {
				// because selection has been cleared, set it again
				selectedButton.setSelection(true);
			}
			// return selected button's code so the value can be stored
			return getSelected();
		}

		/**
		 * Enable or disable given buttons.
		 * 
		 * @param enabled
		 *            Indicates whether to enable or disable the buttons.
		 * @param buttonsToChange
		 *            Buttons to enable/disable.
		 * @param defaultSelection
		 *            The button to select if the currently selected button
		 *            becomes disabled.
		 */
		public void setEnablement(boolean enabled, int[] buttonsToChange,
				int defaultSelection) {

			// enable (or disable) given buttons
			for (int i = 0; i < buttonsToChange.length; i++) {
				((Button) this.buttons.get(buttonsToChange[i] - 1))
				.setEnabled(enabled);
			}
			// check whether the selected button is enabled
			if (!((Button) this.buttons.get(selected)).isEnabled()) {
				if (defaultSelection != -1)
					// set the default selection and check if it's enabled
					setSelection(defaultSelection, true);
				else
					// no default selection is given, select any enabled button
					selectEnabledOnly();
			}
		}

		/**
		 * Enable or disable given buttons with no default selection. The selection
		 * will be set to an enabled button using the <code>selectEnabledOnly</code> method.
		 * 
		 * @param enabled Indicates whether to enable or disable the buttons.
		 * @param buttonsToChange Buttons to enable/disable.
		 */
		public void setEnablement(boolean enabled, int[] buttonsToChange) {
			// -1 means that no default selection is given
			setEnablement(enabled, buttonsToChange, -1);
		}

		/**
		 * Deselect all buttons in the group.
		 */
		private void deselectAll() {
			// clear all selections
			for (Iterator iterator = buttons.iterator(); iterator.hasNext();)
				((Button) iterator.next()).setSelection(false);
		}
	}

}
