/*******************************************************************************
 * Copyright (c) 2010, 2013 SAP AG 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:
 *    Mathias Kinzler (SAP AG) - initial implementation
 *******************************************************************************/
package org.eclipse.egit.ui.internal.fetch;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.egit.core.op.CreateLocalBranchOperation;
import org.eclipse.egit.core.op.ListRemoteOperation;
import org.eclipse.egit.core.op.TagOperation;
import org.eclipse.egit.ui.Activator;
import org.eclipse.egit.ui.JobFamilies;
import org.eclipse.egit.ui.UIPreferences;
import org.eclipse.egit.ui.UIUtils;
import org.eclipse.egit.ui.internal.UIText;
import org.eclipse.egit.ui.internal.branch.BranchOperationUI;
import org.eclipse.egit.ui.internal.dialogs.CheckoutConflictDialog;
import org.eclipse.jface.bindings.keys.KeyStroke;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.fieldassist.ContentProposalAdapter;
import org.eclipse.jface.fieldassist.IContentProposal;
import org.eclipse.jface.fieldassist.IContentProposalProvider;
import org.eclipse.jface.fieldassist.TextContentAdapter;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.jgit.api.CheckoutCommand;
import org.eclipse.jgit.api.CheckoutResult;
import org.eclipse.jgit.api.CheckoutResult.Status;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.CheckoutConflictException;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.TagBuilder;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.FetchResult;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.RemoteConfig;
import org.eclipse.jgit.transport.URIish;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.dnd.TextTransfer;
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.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IWorkbenchCommandConstants;
import org.eclipse.ui.PlatformUI;

/**
 * Fetch a change from Gerrit
 */
public class FetchGerritChangePage extends WizardPage {
	private static final String FETCH_GERRIT_CHANGE_PAGE_SECTION = "FetchGerritChangePage"; //$NON-NLS-1$

	private static final String LAST_URI_POSTFIX = ".lastUri"; //$NON-NLS-1$

	private static final String RUN_IN_BACKGROUND = "runInBackground"; //$NON-NLS-1$

	private final Repository repository;

	private final IDialogSettings settings;

	private final String lastUriKey;

	private Combo uriCombo;

	private List<Change> changeRefs;

	private Text refText;

	private Button createBranch;

	private Button createTag;

	private Button checkout;

	private Button dontCheckout;

	private Label tagTextlabel;

	private Text tagText;

	private Label branchTextlabel;

	private Text branchText;

	private String refName;

	private Composite warningAdditionalRefNotActive;

	private Button activateAdditionalRefs;

	private Button runInBackgroud;

	/**
	 * @param repository
	 * @param refName initial value for the ref field
	 */
	public FetchGerritChangePage(Repository repository, String refName) {
		super(FetchGerritChangePage.class.getName());
		this.repository = repository;
		this.refName = refName;
		setTitle(NLS
				.bind(UIText.FetchGerritChangePage_PageTitle,
						Activator.getDefault().getRepositoryUtil()
								.getRepositoryName(repository)));
		setMessage(UIText.FetchGerritChangePage_PageMessage);
		settings = getDialogSettings();
		lastUriKey = repository + LAST_URI_POSTFIX;
	}

	protected IDialogSettings getDialogSettings() {
		IDialogSettings s = Activator.getDefault().getDialogSettings();
		IDialogSettings section = s
				.getSection(FETCH_GERRIT_CHANGE_PAGE_SECTION);
		if (section == null)
			section = s.addNewSection(FETCH_GERRIT_CHANGE_PAGE_SECTION);
		return section;
	}

	public void createControl(Composite parent) {
		Clipboard clipboard = new Clipboard(parent.getDisplay());
		String clipText = (String) clipboard.getContents(TextTransfer
				.getInstance());
		String defaultUri = null;
		String defaultCommand = null;
		String defaultChange = null;
		if (clipText != null) {
			final String pattern = "git fetch (\\w+:\\S+) (refs/changes/\\d+/\\d+/\\d+) && git (\\w+) FETCH_HEAD"; //$NON-NLS-1$
			Matcher matcher = Pattern.compile(pattern).matcher(clipText);
			if (matcher.matches()) {
				defaultUri = matcher.group(1);
				defaultChange = matcher.group(2);
				defaultCommand = matcher.group(3);
			}
		}
		Composite main = new Composite(parent, SWT.NONE);
		main.setLayout(new GridLayout(2, false));
		GridDataFactory.fillDefaults().grab(true, true).applyTo(main);
		new Label(main, SWT.NONE)
				.setText(UIText.FetchGerritChangePage_UriLabel);
		uriCombo = new Combo(main, SWT.DROP_DOWN);
		GridDataFactory.fillDefaults().grab(true, false).applyTo(uriCombo);
		uriCombo.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				changeRefs = null;
			}
		});
		new Label(main, SWT.NONE)
				.setText(UIText.FetchGerritChangePage_ChangeLabel);
		refText = new Text(main, SWT.BORDER);
		if (defaultChange != null)
			refText.setText(defaultChange);
		GridDataFactory.fillDefaults().grab(true, false).applyTo(refText);
		addRefContentProposalToText(refText);

		Group checkoutGroup = new Group(main, SWT.SHADOW_ETCHED_IN);
		checkoutGroup.setLayout(new GridLayout(2, false));
		GridDataFactory.fillDefaults().span(2, 1).grab(true, false)
				.applyTo(checkoutGroup);
		checkoutGroup.setText(UIText.FetchGerritChangePage_AfterFetchGroup);

		// radio: create local branch
		createBranch = new Button(checkoutGroup, SWT.RADIO);
		GridDataFactory.fillDefaults().span(2, 1).applyTo(createBranch);
		createBranch.setText(UIText.FetchGerritChangePage_LocalBranchRadio);
		createBranch.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				checkPage();
			}
		});

		branchTextlabel = new Label(checkoutGroup, SWT.NONE);
		GridDataFactory.defaultsFor(branchTextlabel).exclude(false)
				.applyTo(branchTextlabel);
		branchTextlabel.setText(UIText.FetchGerritChangePage_BranchNameText);
		branchText = new Text(checkoutGroup, SWT.SINGLE | SWT.BORDER);
		GridDataFactory.fillDefaults().grab(true, false).applyTo(branchText);
		branchText.addModifyListener(new ModifyListener() {
			public void modifyText(ModifyEvent e) {
				checkPage();
			}
		});

		// radio: create tag
		createTag = new Button(checkoutGroup, SWT.RADIO);
		GridDataFactory.fillDefaults().span(2, 1).applyTo(createTag);
		createTag.setText(UIText.FetchGerritChangePage_TagRadio);
		createTag.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				checkPage();
			}
		});

		tagTextlabel = new Label(checkoutGroup, SWT.NONE);
		GridDataFactory.defaultsFor(tagTextlabel).exclude(true)
				.applyTo(tagTextlabel);
		tagTextlabel.setText(UIText.FetchGerritChangePage_TagNameText);
		tagText = new Text(checkoutGroup, SWT.SINGLE | SWT.BORDER);
		GridDataFactory.fillDefaults().exclude(true).grab(true, false)
				.applyTo(tagText);
		tagText.addModifyListener(new ModifyListener() {
			public void modifyText(ModifyEvent e) {
				checkPage();
			}
		});

		// radio: checkout FETCH_HEAD
		checkout = new Button(checkoutGroup, SWT.RADIO);
		GridDataFactory.fillDefaults().span(2, 1).applyTo(checkout);
		checkout.setText(UIText.FetchGerritChangePage_CheckoutRadio);
		checkout.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				checkPage();
			}
		});

		// radio: don't checkout
		dontCheckout = new Button(checkoutGroup, SWT.RADIO);
		GridDataFactory.fillDefaults().span(2, 1).applyTo(checkout);
		dontCheckout.setText(UIText.FetchGerritChangePage_UpdateRadio);
		dontCheckout.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				checkPage();
			}
		});

		if ("checkout".equals(defaultCommand)) //$NON-NLS-1$
			checkout.setSelection(true);
		else
			createBranch.setSelection(true);

		warningAdditionalRefNotActive = new Composite(main, SWT.NONE);
		GridDataFactory.fillDefaults().span(2, 1).grab(true, false)
				.exclude(true).applyTo(warningAdditionalRefNotActive);
		warningAdditionalRefNotActive.setLayout(new GridLayout(2, false));
		warningAdditionalRefNotActive.setVisible(false);

		activateAdditionalRefs = new Button(warningAdditionalRefNotActive,
				SWT.CHECK);
		activateAdditionalRefs
				.setText(UIText.FetchGerritChangePage_ActivateAdditionalRefsButton);
		activateAdditionalRefs
				.setToolTipText(UIText.FetchGerritChangePage_ActivateAdditionalRefsTooltip);

		refText.addModifyListener(new ModifyListener() {
			public void modifyText(ModifyEvent e) {
				Change change = Change.fromRef(refText.getText());
				if (change != null) {
					branchText.setText(NLS
							.bind(UIText.FetchGerritChangePage_SuggestedRefNamePattern,
									change.getChangeNumber(),
									change.getPatchSetNumber()));
					tagText.setText(branchText.getText());
				} else {
					branchText.setText(""); //$NON-NLS-1$
					tagText.setText(""); //$NON-NLS-1$
				}
				checkPage();
			}
		});

		runInBackgroud = new Button(main, SWT.CHECK);
		GridDataFactory.fillDefaults().span(2, 1).align(SWT.BEGINNING, SWT.END)
				.grab(true, true)
				.applyTo(runInBackgroud);
		runInBackgroud.setText(UIText.FetchGerritChangePage_RunInBackground);

		// get all available URIs from the repository
		SortedSet<String> uris = new TreeSet<String>();
		try {
			for (RemoteConfig rc : RemoteConfig.getAllRemoteConfigs(repository
					.getConfig())) {
				if (rc.getURIs().size() > 0)
					uris.add(rc.getURIs().get(0).toPrivateString());
				for (URIish u : rc.getPushURIs())
					uris.add(u.toPrivateString());

			}
		} catch (URISyntaxException e) {
			Activator.handleError(e.getMessage(), e, false);
			setErrorMessage(e.getMessage());
		}
		for (String aUri : uris)
			uriCombo.add(aUri);
		if (defaultUri != null)
			uriCombo.setText(defaultUri);
		else
			selectLastUsedUri();
		restoreRunInBackgroundSelection();
		refText.setFocus();
		Dialog.applyDialogFont(main);
		setControl(main);
		checkPage();
	}

	private void storeLastUsedUri(String uri) {
		settings.put(lastUriKey, uri.trim());
	}

	private void selectLastUsedUri() {
		String lastUri = settings.get(lastUriKey);
		if (lastUri != null) {
			int i = uriCombo.indexOf(lastUri);
			if (i != -1) {
				uriCombo.select(i);
				return;
			}
		}
		uriCombo.select(0);
	}

	private void storeRunInBackgroundSelection() {
		settings.put(RUN_IN_BACKGROUND, runInBackgroud.getSelection());
	}

	private void restoreRunInBackgroundSelection() {
		runInBackgroud.setSelection(settings.getBoolean(RUN_IN_BACKGROUND));
	}

	@Override
	public void setVisible(boolean visible) {
		super.setVisible(visible);
		if (visible && refName != null)
			refText.setText(refName);
	}

	private void checkPage() {
		boolean createBranchSelected = createBranch.getSelection();
		branchText.setEnabled(createBranchSelected);
		branchText.setVisible(createBranchSelected);
		branchTextlabel.setVisible(createBranchSelected);
		GridData gd = (GridData) branchText.getLayoutData();
		gd.exclude = !createBranchSelected;
		gd = (GridData) branchTextlabel.getLayoutData();
		gd.exclude = !createBranchSelected;

		boolean createTagSelected = createTag.getSelection();
		tagText.setEnabled(createTagSelected);
		tagText.setVisible(createTagSelected);
		tagTextlabel.setVisible(createTagSelected);
		gd = (GridData) tagText.getLayoutData();
		gd.exclude = !createTagSelected;
		gd = (GridData) tagTextlabel.getLayoutData();
		gd.exclude = !createTagSelected;
		branchText.getParent().layout(true);

		boolean showActivateAdditionalRefs = false;
		showActivateAdditionalRefs = (checkout.getSelection() || dontCheckout
				.getSelection())
				&& !Activator
						.getDefault()
						.getPreferenceStore()
						.getBoolean(
								UIPreferences.RESOURCEHISTORY_SHOW_ADDITIONAL_REFS);

		gd = (GridData) warningAdditionalRefNotActive.getLayoutData();
		gd.exclude = !showActivateAdditionalRefs;
		warningAdditionalRefNotActive.setVisible(showActivateAdditionalRefs);
		warningAdditionalRefNotActive.getParent().layout(true);

		setErrorMessage(null);
		try {
			if (refText.getText().length() > 0) {
				Change change = Change.fromRef(refText.getText());
				if (change == null) {
					setErrorMessage(UIText.FetchGerritChangePage_MissingChangeMessage);
					return;
				}
			} else {
				setErrorMessage(UIText.FetchGerritChangePage_MissingChangeMessage);
				return;
			}

			boolean emptyRefName = (createBranchSelected && branchText
					.getText().length() == 0)
					|| (createTagSelected && tagText.getText().length() == 0);
			if (emptyRefName) {
				setErrorMessage(UIText.FetchGerritChangePage_ProvideRefNameMessage);
				return;
			}

			boolean existingRefName = (createBranchSelected && repository
					.getRef(branchText.getText()) != null)
					|| (createTagSelected && repository.getRef(tagText
							.getText()) != null);
			if (existingRefName) {
				setErrorMessage(NLS.bind(
						UIText.FetchGerritChangePage_ExistingRefMessage,
						branchText.getText()));
				return;
			}
		} catch (IOException e1) {
			// ignore here
		} finally {
			setPageComplete(getErrorMessage() == null);
		}
	}

	private List<Change> getRefsForContentAssist()
			throws InvocationTargetException, InterruptedException {
		if (changeRefs == null) {
			final String uriText = uriCombo.getText();
			getWizard().getContainer().run(true, true,
					new IRunnableWithProgress() {
						public void run(IProgressMonitor monitor)
								throws InvocationTargetException,
								InterruptedException {
							ListRemoteOperation listOp;
							try {
								listOp = new ListRemoteOperation(
										repository,
										new URIish(uriText),
										Activator
												.getDefault()
												.getPreferenceStore()
												.getInt(UIPreferences.REMOTE_CONNECTION_TIMEOUT));
							} catch (URISyntaxException e) {
								throw new InvocationTargetException(e);
							}

							listOp.run(monitor);
							changeRefs = new ArrayList<Change>();
							for (Ref ref : listOp.getRemoteRefs()) {
								Change change = Change.fromRef(ref.getName());
								if (change != null)
									changeRefs.add(change);
							}
							Collections.sort(changeRefs,
									new Comparator<Change>() {
										public int compare(Change o1, Change o2) {
											// change number descending
											int changeDiff = o2.changeNumber
													.compareTo(o1.changeNumber);
											if (changeDiff == 0)
												// patch set number descending
												changeDiff = o2
														.getPatchSetNumber()
														.compareTo(
																o1.getPatchSetNumber());
											return changeDiff;
										}
									});
						}
					});
		}
		return changeRefs;
	}

	boolean doFetch() {

		final RefSpec spec = new RefSpec().setSource(refText.getText())
				.setDestination(Constants.FETCH_HEAD);
		final String uri = uriCombo.getText();
		final boolean doCheckout = checkout.getSelection();
		final boolean doCreateTag = createTag.getSelection();
		final boolean doCreateBranch = createBranch.getSelection();
		final boolean doActivateAdditionalRefs = (checkout.getSelection() || dontCheckout
				.getSelection()) && activateAdditionalRefs.getSelection();
		final String textForTag = tagText.getText();
		final String textForBranch = branchText.getText();

		storeRunInBackgroundSelection();

		if (runInBackgroud.getSelection()) {
			Job job = new Job(UIText.FetchGerritChangePage_GetChangeTaskName) {
				@Override
				protected IStatus run(IProgressMonitor monitor) {
					internalDoFetch(spec, uri, doCheckout, doCreateTag,
							doCreateBranch, doActivateAdditionalRefs,
							textForTag, textForBranch, monitor);
					return org.eclipse.core.runtime.Status.OK_STATUS;
				}

				@Override
				public boolean belongsTo(Object family) {
					if (JobFamilies.FETCH.equals(family))
						return true;
					return super.belongsTo(family);
				}
			};
			job.setUser(true);
			job.schedule();
			return true;
		} else {
			try {
			getWizard().getContainer().run(true, true,
					new IRunnableWithProgress() {
						public void run(IProgressMonitor monitor)
								throws InvocationTargetException,
								InterruptedException {
							try {
								internalDoFetch(spec, uri, doCheckout,
										doCreateTag, doCreateBranch,
										doActivateAdditionalRefs, textForTag,
										textForBranch, monitor);
							} catch (RuntimeException e) {
								throw e;
							} catch (Exception e) {
								throw new InvocationTargetException(e);
							} finally {
								monitor.done();
							}
						}
					});
			} catch (InvocationTargetException e) {
				Activator.handleError(e.getCause().getMessage(), e.getCause(),
						true);
				return false;
			} catch (InterruptedException e) {
				// just return
			}
			return true;
		}
	}

	private void internalDoFetch(RefSpec spec, String uri, boolean doCheckout,
			boolean doCreateTag, boolean doCreateBranch,
			boolean doActivateAdditionalRefs, String textForTag,
			String textForBranch, IProgressMonitor monitor) {

		int totalWork = 1;
		if (doCheckout)
			totalWork++;
		if (doCreateTag || doCreateBranch)
			totalWork++;
		monitor.beginTask(
				UIText.FetchGerritChangePage_GetChangeTaskName,
				totalWork);

		try {
			RevCommit commit = fetchChange(uri, spec,
					monitor);

			if (doCreateTag)
				createTag(spec, textForTag, commit, monitor);

			if (doCreateBranch)
				createBranch(textForBranch, commit, monitor);

			if (doCheckout || doCreateTag)
				checkout(commit, monitor);

			if (doActivateAdditionalRefs)
				activateAdditionalRefs();

			storeLastUsedUri(uri);

		} catch (Exception e) {
			Activator
					.handleError(e.getCause().getMessage(), e.getCause(), true);
		} finally {
			monitor.done();
		}
	}

	private RevCommit fetchChange(String uri, RefSpec spec,
			IProgressMonitor monitor) throws CoreException, URISyntaxException,
			IOException {
		int timeout = Activator.getDefault().getPreferenceStore()
				.getInt(UIPreferences.REMOTE_CONNECTION_TIMEOUT);

		List<RefSpec> specs = new ArrayList<RefSpec>(1);
		specs.add(spec);

		String taskName = NLS
				.bind(UIText.FetchGerritChangePage_FetchingTaskName,
						spec.getSource());
		monitor.setTaskName(taskName);
		FetchResult fetchRes = new FetchOperationUI(repository,
				new URIish(uri), specs, timeout, false).execute(monitor);

		monitor.worked(1);
		return new RevWalk(repository).parseCommit(fetchRes.getAdvertisedRef(
				spec.getSource()).getObjectId());
	}

	private void createTag(final RefSpec spec, final String textForTag,
			RevCommit commit, IProgressMonitor monitor) throws CoreException {
		monitor.setTaskName(UIText.FetchGerritChangePage_CreatingTagTaskName);
		final TagBuilder tag = new TagBuilder();
		PersonIdent personIdent = new PersonIdent(repository);

		tag.setTag(textForTag);
		tag.setTagger(personIdent);
		tag.setMessage(NLS.bind(
				UIText.FetchGerritChangePage_GeneratedTagMessage,
				spec.getSource()));
		tag.setObjectId(commit);
		new TagOperation(repository, tag, false).execute(monitor);
		monitor.worked(1);
	}

	private void createBranch(final String textForBranch, RevCommit commit,
			IProgressMonitor monitor) throws CoreException, GitAPIException {
		monitor.setTaskName(UIText.FetchGerritChangePage_CreatingBranchTaskName);
		CreateLocalBranchOperation bop = new CreateLocalBranchOperation(
				repository, textForBranch, commit);
		bop.execute(monitor);
		CheckoutCommand co = new Git(repository).checkout();
		try {
			co.setName(textForBranch).call();
		} catch (CheckoutConflictException e) {
			final CheckoutResult result = co.getResult();

			if (result.getStatus() == Status.CONFLICTS) {
				final Shell shell = getWizard().getContainer().getShell();

				shell.getDisplay().asyncExec(new Runnable() {
					public void run() {
						new CheckoutConflictDialog(shell, repository, result
								.getConflictList()).open();
					}
				});
			}
		}
		monitor.worked(1);
	}

	private void checkout(RevCommit commit, IProgressMonitor monitor)
			throws CoreException {
		monitor.setTaskName(UIText.FetchGerritChangePage_CheckingOutTaskName);
		BranchOperationUI.checkout(repository, commit.name()).run(monitor);

		monitor.worked(1);
	}

	private void activateAdditionalRefs() {
		// do this in the UI thread as it results in a
		// refresh() on the history page
		PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
			public void run() {
				Activator
						.getDefault()
						.getPreferenceStore()
						.setValue(
								UIPreferences.RESOURCEHISTORY_SHOW_ADDITIONAL_REFS,
								true);
			}
		});
	}

	private void addRefContentProposalToText(final Text textField) {
		KeyStroke stroke = UIUtils
				.getKeystrokeOfBestActiveBindingFor(IWorkbenchCommandConstants.EDIT_CONTENT_ASSIST);
		if (stroke != null)
			UIUtils.addBulbDecorator(textField, NLS.bind(
					UIText.FetchGerritChangePage_ContentAssistTooltip,
					stroke.format()));

		IContentProposalProvider cp = new IContentProposalProvider() {
			public IContentProposal[] getProposals(String contents, int position) {
				List<IContentProposal> resultList = new ArrayList<IContentProposal>();

				// make the simplest possible pattern check: allow "*"
				// for multiple characters
				String patternString = contents;
				// ignore spaces in the beginning
				while (patternString.length() > 0
						&& patternString.charAt(0) == ' ')
					patternString = patternString.substring(1);

				// we quote the string as it may contain spaces
				// and other stuff colliding with the Pattern
				patternString = Pattern.quote(patternString);

				patternString = patternString.replaceAll("\\x2A", ".*"); //$NON-NLS-1$ //$NON-NLS-2$

				// make sure we add a (logical) * at the end
				if (!patternString.endsWith(".*")) //$NON-NLS-1$
					patternString = patternString + ".*"; //$NON-NLS-1$

				// let's compile a case-insensitive pattern (assumes ASCII only)
				Pattern pattern;
				try {
					pattern = Pattern.compile(patternString,
							Pattern.CASE_INSENSITIVE);
				} catch (PatternSyntaxException e) {
					pattern = null;
				}

				List<Change> proposals;
				try {
					proposals = getRefsForContentAssist();
				} catch (InvocationTargetException e) {
					Activator.handleError(e.getMessage(), e, false);
					return null;
				} catch (InterruptedException e) {
					return null;
				}

				if (proposals != null)
					for (final Change ref : proposals) {
						if (pattern != null
								&& !pattern.matcher(
										ref.getChangeNumber().toString())
										.matches())
							continue;
						IContentProposal propsal = new ChangeContentProposal(
								ref);
						resultList.add(propsal);
					}

				return resultList.toArray(new IContentProposal[resultList
						.size()]);
			}
		};

		ContentProposalAdapter adapter = new ContentProposalAdapter(textField,
				new TextContentAdapter(), cp, stroke, null);
		// set the acceptance style to always replace the complete content
		adapter.setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE);
	}

	private final static class Change {
		private final String refName;

		private final Integer changeNumber;

		private final Integer patchSetNumber;

		static Change fromRef(String refName) {
			try {
				if (!refName.startsWith("refs/changes/")) //$NON-NLS-1$
					return null;
				String[] tokens = refName.substring(13).split("/"); //$NON-NLS-1$
				if (tokens.length != 3)
					return null;
				Integer changeNumber = Integer.valueOf(tokens[1]);
				Integer patchSetNumber = Integer.valueOf(tokens[2]);
				return new Change(refName, changeNumber, patchSetNumber);
			} catch (NumberFormatException e) {
				// if we can't parse this, just return null
				return null;
			} catch (IndexOutOfBoundsException e) {
				// if we can't parse this, just return null
				return null;
			}
		}

		private Change(String refName, Integer changeNumber,
				Integer patchSetNumber) {
			this.refName = refName;
			this.changeNumber = changeNumber;
			this.patchSetNumber = patchSetNumber;
		}

		public String getRefName() {
			return refName;
		}

		public Integer getChangeNumber() {
			return changeNumber;
		}

		public Integer getPatchSetNumber() {
			return patchSetNumber;
		}

		/* (non-Javadoc)
		 * @see java.lang.Object#toString()
		 */
		@Override
		public String toString() {
			return refName;
		}
	}

	private final static class ChangeContentProposal implements
			IContentProposal {
		private final Change myChange;

		ChangeContentProposal(Change change) {
			myChange = change;
		}

		public String getContent() {
			return myChange.getRefName();
		}

		public int getCursorPosition() {
			return 0;
		}

		public String getDescription() {
			return NLS.bind(
					UIText.FetchGerritChangePage_ContentAssistDescription,
					myChange.getPatchSetNumber(), myChange.getChangeNumber());
		}

		public String getLabel() {
			return NLS
					.bind("{0} - {1}", myChange.getChangeNumber(), myChange.getPatchSetNumber()); //$NON-NLS-1$
		}

		/* (non-Javadoc)
		 * @see java.lang.Object#toString()
		 */
		@Override
		public String toString() {
			return getContent();
		}
	}
}
