/*******************************************************************************
 * Copyright (c) 2009, 2011 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
 *     Yury Chernikov <Yury.Chernikov@borland.com> - Bug 271447 [ui] Bad layout in 'Install available software' dialog
 *******************************************************************************/
package org.eclipse.equinox.internal.p2.ui.dialogs;

import com.ibm.icu.text.Collator;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import org.eclipse.core.runtime.*;
import org.eclipse.equinox.internal.p2.ui.*;
import org.eclipse.equinox.internal.p2.ui.query.IUViewQueryContext;
import org.eclipse.equinox.internal.provisional.p2.repository.RepositoryEvent;
import org.eclipse.equinox.p2.engine.ProvisioningContext;
import org.eclipse.equinox.p2.operations.RepositoryTracker;
import org.eclipse.equinox.p2.repository.IRepository;
import org.eclipse.equinox.p2.repository.IRepositoryManager;
import org.eclipse.equinox.p2.repository.metadata.IMetadataRepositoryManager;
import org.eclipse.equinox.p2.ui.ProvisioningUI;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.fieldassist.ControlDecoration;
import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.wizard.IWizardContainer;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.*;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.*;

/**
 * A RepositorySelectionGroup is a reusable UI component that displays 
 * available repositories and allows the user to select them.  
 * 
 * @since 3.5
 */
public class RepositorySelectionGroup {

	private static final String SITE_NONE = ProvUIMessages.AvailableIUsPage_NoSites;
	private static final String SITE_ALL = ProvUIMessages.AvailableIUsPage_AllSites;
	private static final String SITE_LOCAL = ProvUIMessages.AvailableIUsPage_LocalSites;
	private static final int INDEX_SITE_NONE = 0;
	private static final int INDEX_SITE_ALL = 1;
	private static final int DEC_MARGIN_WIDTH = 2;
	private static final String LINKACTION = "linkAction"; //$NON-NLS-1$

	// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=245569
	private static final int COUNT_VISIBLE_ITEMS = 20;
	IWizardContainer container;
	ProvisioningUI ui;
	IUViewQueryContext queryContext;

	ListenerList listeners = new ListenerList();

	Combo repoCombo;
	Link repoManipulatorLink;
	ControlDecoration repoDec;
	ComboAutoCompleteField repoAutoComplete;
	ProvUIProvisioningListener comboRepoListener;
	IRepositoryManipulationHook repositoryManipulationHook;

	Image info, warning, error;
	URI[] comboRepos; // the URIs shown in the combo, kept in sync with combo items
	HashMap<String, URI> disabledRepoProposals = new HashMap<String, URI>(); // proposal string -> disabled URI 

	public RepositorySelectionGroup(ProvisioningUI ui, IWizardContainer container, Composite parent, IUViewQueryContext queryContext) {
		this.container = container;
		this.queryContext = queryContext;
		this.ui = ui;
		createControl(parent);
	}

	public Control getDefaultFocusControl() {
		return repoCombo;
	}

	public void addRepositorySelectionListener(IRepositorySelectionListener listener) {
		listeners.add(listener);
	}

	protected void createControl(Composite parent) {
		final RepositoryTracker tracker = ui.getRepositoryTracker();
		// Get the possible field error indicators
		info = FieldDecorationRegistry.getDefault().getFieldDecoration(FieldDecorationRegistry.DEC_INFORMATION).getImage();
		warning = FieldDecorationRegistry.getDefault().getFieldDecoration(FieldDecorationRegistry.DEC_WARNING).getImage();
		error = FieldDecorationRegistry.getDefault().getFieldDecoration(FieldDecorationRegistry.DEC_ERROR).getImage();

		// Combo that filters sites
		Composite comboComposite = new Composite(parent, SWT.NONE);
		GridLayout layout = new GridLayout();
		layout.marginTop = 0;
		layout.marginBottom = IDialogConstants.VERTICAL_SPACING;
		layout.numColumns = 3;
		comboComposite.setLayout(layout);
		GridData gd = new GridData(SWT.FILL, SWT.FILL, true, false);
		comboComposite.setLayoutData(gd);
		comboComposite.setFont(parent.getFont());

		Label label = new Label(comboComposite, SWT.NONE);
		label.setText(ProvUIMessages.AvailableIUsPage_RepoFilterLabel);
		label.setFont(comboComposite.getFont());

		repoCombo = new Combo(comboComposite, SWT.DROP_DOWN);
		repoCombo.addSelectionListener(new SelectionListener() {

			public void widgetDefaultSelected(SelectionEvent e) {
				repoComboSelectionChanged();
			}

			public void widgetSelected(SelectionEvent e) {
				repoComboSelectionChanged();
			}

		});
		// Auto complete - install before our own key listeners, so that auto complete gets first shot.
		repoAutoComplete = new ComboAutoCompleteField(repoCombo);
		repoCombo.setVisibleItemCount(COUNT_VISIBLE_ITEMS);
		repoCombo.addKeyListener(new KeyAdapter() {

			public void keyPressed(KeyEvent e) {
				if (e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR)
					addRepository(false);
			}
		});

		// We don't ever want this to be interpreted as a default
		// button event
		repoCombo.addTraverseListener(new TraverseListener() {
			public void keyTraversed(TraverseEvent e) {
				if (e.detail == SWT.TRAVERSE_RETURN) {
					e.doit = false;
				}
			}
		});

		gd = new GridData(SWT.FILL, SWT.CENTER, true, false);
		// breathing room for info dec
		gd.horizontalIndent = DEC_MARGIN_WIDTH * 2;
		repoCombo.setLayoutData(gd);
		repoCombo.setFont(comboComposite.getFont());
		repoCombo.addModifyListener(new ModifyListener() {
			public void modifyText(ModifyEvent event) {
				URI location = null;
				IStatus status = null;
				String text = repoCombo.getText().trim();
				int index = getComboIndex(text);
				// only validate text that doesn't match existing text in combo
				if (index < 0) {
					location = tracker.locationFromString(repoCombo.getText());
					if (location == null) {
						status = tracker.getInvalidLocationStatus(repoCombo.getText());
					} else {
						status = tracker.validateRepositoryLocation(ui.getSession(), location, false, new NullProgressMonitor());
					}
				} else {
					// user typed or pasted an existing location.  Select it.
					repoComboSelectionChanged();
				}
				setRepoComboDecoration(status);
			}
		});

		repoDec = new ControlDecoration(repoCombo, SWT.LEFT | SWT.TOP);
		repoDec.setMarginWidth(DEC_MARGIN_WIDTH);

		DropTarget target = new DropTarget(repoCombo, DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_LINK);
		target.setTransfer(new Transfer[] {URLTransfer.getInstance(), FileTransfer.getInstance()});
		target.addDropListener(new URLDropAdapter(true) {
			/* (non-Javadoc)
			 * @see org.eclipse.equinox.internal.provisional.p2.ui.dialogs.URLDropAdapter#handleURLString(java.lang.String, org.eclipse.swt.dnd.DropTargetEvent)
			 */
			protected void handleDrop(String urlText, DropTargetEvent event) {
				repoCombo.setText(urlText);
				event.detail = DND.DROP_LINK;
				addRepository(false);
			}
		});

		Button button = new Button(comboComposite, SWT.PUSH);
		button.setText(ProvUIMessages.AvailableIUsPage_AddButton);
		button.addSelectionListener(new SelectionListener() {
			public void widgetDefaultSelected(SelectionEvent e) {
				addRepository(true);
			}

			public void widgetSelected(SelectionEvent e) {
				addRepository(true);
			}
		});
		setButtonLayoutData(button);
		button.setFont(comboComposite.getFont());

		// Link to repository manipulator
		repoManipulatorLink = createLink(comboComposite, new Action() {
			public void runWithEvent(Event event) {
				if (repositoryManipulationHook != null)
					repositoryManipulationHook.preManipulateRepositories();
				ui.manipulateRepositories(repoCombo.getShell());
				if (repositoryManipulationHook != null)
					repositoryManipulationHook.postManipulateRepositories();
			}
		}, getLinkLabel());
		gd = new GridData(SWT.END, SWT.FILL, true, false);
		gd.horizontalSpan = 3;
		repoManipulatorLink.setLayoutData(gd);
		repoManipulatorLink.setFont(comboComposite.getFont());

		addComboProvisioningListeners();
		parent.addDisposeListener(new DisposeListener() {
			public void widgetDisposed(DisposeEvent e) {
				removeProvisioningListeners();
			}

		});
	}

	private String getLinkLabel() {
		if (ui.getPolicy().getRepositoryPreferencePageId() != null) {
			String pageName = ui.getPolicy().getRepositoryPreferencePageName();
			if (pageName == null)
				pageName = ProvUIMessages.RepositorySelectionGroup_PrefPageName;
			return NLS.bind(ProvUIMessages.RepositorySelectionGroup_PrefPageLink, pageName);
		}
		return ProvUIMessages.RepositorySelectionGroup_GenericSiteLinkTitle;
	}

	private void setButtonLayoutData(Button button) {
		GridData data = new GridData(SWT.FILL, SWT.CENTER, false, false);
		GC gc = new GC(button);
		gc.setFont(JFaceResources.getDialogFont());
		FontMetrics fm = gc.getFontMetrics();
		gc.dispose();
		int widthHint = Dialog.convertHorizontalDLUsToPixels(fm, IDialogConstants.BUTTON_WIDTH);
		Point minSize = button.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
		data.widthHint = Math.max(widthHint, minSize.x);
		button.setLayoutData(data);
	}

	public void setRepositorySelection(int scope, URI location) {
		switch (scope) {
			case AvailableIUGroup.AVAILABLE_ALL :
				fillRepoCombo(SITE_ALL);
				break;
			case AvailableIUGroup.AVAILABLE_LOCAL :
				fillRepoCombo(SITE_LOCAL);
				break;
			case AvailableIUGroup.AVAILABLE_SPECIFIED :
				fillRepoCombo(getSiteString(location));
				break;
			default :
				fillRepoCombo(SITE_NONE);
		}
		setRepoComboDecoration(null);
	}

	public void setRepositoryManipulationHook(IRepositoryManipulationHook hook) {
		this.repositoryManipulationHook = hook;
	}

	protected void setRepoComboDecoration(final IStatus status) {
		if (status == null || status.isOK() || status.getSeverity() == IStatus.CANCEL) {
			repoDec.setShowOnlyOnFocus(true);
			repoDec.setDescriptionText(ProvUIMessages.AvailableIUsPage_RepoFilterInstructions);
			repoDec.setImage(info);
			// We may have been previously showing an error or warning
			// hover.  We will need to dismiss it, but if there is no text
			// typed, don't do this, so that the user gets the info cue
			if (repoCombo.getText().length() > 0)
				repoDec.showHoverText(null);
			return;
		}
		Image image;
		if (status.getSeverity() == IStatus.WARNING)
			image = warning;
		else if (status.getSeverity() == IStatus.ERROR)
			image = error;
		else
			image = info;
		repoDec.setImage(image);
		repoDec.setDescriptionText(status.getMessage());
		repoDec.setShowOnlyOnFocus(false);
		// use a delay to show the validation method because the very next
		// selection or keystroke might fix it
		repoCombo.getDisplay().timerExec(500, new Runnable() {
			public void run() {
				if (repoDec != null && repoDec.getImage() != info)
					repoDec.showHoverText(status.getMessage());
			}
		});

	}

	/*
	 * Fill the repo combo and use the specified string
	 * as the selection.  If the selection is null, then the
	 * current selection should be preserved if applicable.
	 */
	void fillRepoCombo(final String selection) {
		RepositoryTracker tracker = ui.getRepositoryTracker();
		URI[] sites = tracker.getKnownRepositories(ui.getSession());
		boolean hasLocalSites = getLocalSites().length > 0;
		final String[] items;
		if (hasLocalSites) {
			// None, All, repo1, repo2....repo n, Local
			comboRepos = new URI[sites.length + 3];
			items = new String[sites.length + 3];
		} else {
			// None, All, repo1, repo2....repo n
			comboRepos = new URI[sites.length + 2];
			items = new String[sites.length + 2];
		}
		items[INDEX_SITE_NONE] = SITE_NONE;
		items[INDEX_SITE_ALL] = SITE_ALL;
		for (int i = 0; i < sites.length; i++) {
			items[i + 2] = getSiteString(sites[i]);
			comboRepos[i + 2] = sites[i];
		}
		if (hasLocalSites)
			items[items.length - 1] = SITE_LOCAL;
		if (sites.length > 0)
			sortRepoItems(items, comboRepos, hasLocalSites);
		Runnable runnable = new Runnable() {
			public void run() {
				if (repoCombo == null || repoCombo.isDisposed())
					return;
				String repoToSelect = selection;
				if (repoToSelect == null) {
					// If the combo is open and something is selected, use that index if we
					// weren't given a string to select.
					int selIndex = repoCombo.getSelectionIndex();
					if (selIndex >= 0)
						repoToSelect = repoCombo.getItem(selIndex);
					else
						repoToSelect = repoCombo.getText();
				}
				repoCombo.setItems(items);
				repoAutoComplete.setProposalStrings(getComboProposals());
				boolean selected = false;
				for (int i = 0; i < items.length; i++)
					if (items[i].equals(repoToSelect)) {
						selected = true;
						if (repoCombo.getListVisible())
							repoCombo.select(i);
						repoCombo.setText(repoToSelect);
						break;
					}
				if (!selected) {
					if (repoCombo.getListVisible())
						repoCombo.select(INDEX_SITE_NONE);
					repoCombo.setText(SITE_NONE);
				}
				repoComboSelectionChanged();
			}
		};
		if (Display.getCurrent() == null)
			repoCombo.getDisplay().asyncExec(runnable);
		else
			runnable.run();
	}

	String getSiteString(URI uri) {
		String nickname = getMetadataRepositoryManager().getRepositoryProperty(uri, IRepository.PROP_NICKNAME);
		if (nickname != null && nickname.length() > 0)
			return NLS.bind(ProvUIMessages.AvailableIUsPage_NameWithLocation, new Object[] {nickname, ProvUIMessages.RepositorySelectionGroup_NameAndLocationSeparator, URIUtil.toUnencodedString(uri)});
		return URIUtil.toUnencodedString(uri);
	}

	private Link createLink(Composite parent, IAction action, String text) {
		Link link = new Link(parent, SWT.PUSH);
		link.setText(text);

		link.addListener(SWT.Selection, new Listener() {
			public void handleEvent(Event event) {
				IAction linkAction = getLinkAction(event.widget);
				if (linkAction != null) {
					linkAction.runWithEvent(event);
				}
			}
		});
		link.setToolTipText(action.getToolTipText());
		link.setData(LINKACTION, action);
		return link;
	}

	IAction getLinkAction(Widget widget) {
		Object data = widget.getData(LINKACTION);
		if (data == null || !(data instanceof IAction)) {
			return null;
		}
		return (IAction) data;
	}

	private void sortRepoItems(String[] strings, URI[] locations, boolean hasLocalSites) {
		int sortStart = 2;
		int sortEnd = hasLocalSites ? strings.length - 2 : strings.length - 1;
		if (sortStart >= sortEnd)
			return;
		final HashMap<URI, String> uriToString = new HashMap<URI, String>();
		for (int i = sortStart; i <= sortEnd; i++) {
			uriToString.put(locations[i], strings[i]);
		}
		final Collator collator = Collator.getInstance(Locale.getDefault());
		Comparator<String> stringComparator = new Comparator<String>() {
			public int compare(String a, String b) {
				return collator.compare(a, b);
			}
		};
		Comparator<URI> uriComparator = new Comparator<URI>() {
			public int compare(URI a, URI b) {
				return collator.compare(uriToString.get(a), uriToString.get(b));
			}
		};

		Arrays.sort(strings, sortStart, sortEnd, stringComparator);
		Arrays.sort(locations, sortStart, sortEnd, uriComparator);
	}

	private URI[] getLocalSites() {
		// use our current visibility flags plus the local filter
		int flags = ui.getRepositoryTracker().getMetadataRepositoryFlags() | IRepositoryManager.REPOSITORIES_LOCAL;
		return getMetadataRepositoryManager().getKnownRepositories(flags);
	}

	String[] getComboProposals() {
		int flags = ui.getRepositoryTracker().getMetadataRepositoryFlags() | IRepositoryManager.REPOSITORIES_DISABLED;
		String[] items = repoCombo.getItems();
		// Clear any previously remembered disabled repos
		disabledRepoProposals = new HashMap<String, URI>();
		URI[] disabled = getMetadataRepositoryManager().getKnownRepositories(flags);
		String[] disabledItems = new String[disabled.length];
		for (int i = 0; i < disabledItems.length; i++) {
			disabledItems[i] = getSiteString(disabled[i]);
			disabledRepoProposals.put(disabledItems[i], disabled[i]);
		}
		String[] both = new String[items.length + disabledItems.length];
		System.arraycopy(items, 0, both, 0, items.length);
		System.arraycopy(disabledItems, 0, both, items.length, disabledItems.length);
		return both;
	}

	int getComboIndex(String repoText) {
		// Callers have typically done this already, but just in case
		repoText = repoText.trim();
		// First look for exact match to the combo string.
		// This includes the name, etc.
		if (repoText.length() > 0) {
			String[] items = repoCombo.getItems();
			for (int i = 0; i < items.length; i++)
				if (repoText.equals(items[i])) {
					return i;
				}
		}
		// Look for URI match - the user may have pasted or dragged 
		// in a location that matches one we already know about, even
		// if the text does not match completely.  (slashes, no name, etc.)
		try {
			URI location = URIUtil.fromString(repoText);
			for (int i = 0; i < comboRepos.length; i++)
				if (URIUtil.sameURI(location, comboRepos[i])) {
					return i;
				}
		} catch (URISyntaxException e) {
			// never mind
		}

		// Special case.  The user has typed a URI with a trailing slash.
		// Make a URI without the trailing slash and see if it matches
		// a location we know about.
		// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=268580
		int length = repoText.length();
		if (length > 0 && repoText.charAt(length - 1) == '/') {
			return getComboIndex(repoText.substring(0, length - 1));
		}
		return -1;
	}

	void addComboProvisioningListeners() {
		// We need to monitor repository events so that we can adjust the repo combo.
		comboRepoListener = new ProvUIProvisioningListener(getClass().getName(), ProvUIProvisioningListener.PROV_EVENT_METADATA_REPOSITORY, ui.getOperationRunner()) {
			protected void repositoryAdded(RepositoryEvent e) {
				fillRepoCombo(getSiteString(e.getRepositoryLocation()));
			}

			protected void repositoryRemoved(RepositoryEvent e) {
				fillRepoCombo(null);
			}

			protected void refreshAll() {
				fillRepoCombo(null);
			}
		};
		ProvUI.getProvisioningEventBus(ui.getSession()).addListener(comboRepoListener);
	}

	void removeProvisioningListeners() {
		if (comboRepoListener != null) {
			ProvUI.getProvisioningEventBus(ui.getSession()).removeListener(comboRepoListener);
			comboRepoListener = null;
		}

	}

	/*
	 *  Add a repository using the text in the combo or launch a dialog if the text
	 *  represents an already known repo.  
	 */
	void addRepository(boolean alwaysPrompt) {
		final RepositoryTracker manipulator = ui.getRepositoryTracker();
		final String selectedRepo = repoCombo.getText().trim();
		int selectionIndex = getComboIndex(selectedRepo);
		final boolean isNewText = selectionIndex < 0;
		// If we are adding something already in the combo, just
		// select that item.
		if (!alwaysPrompt && !isNewText && selectionIndex != repoCombo.getSelectionIndex()) {
			repoComboSelectionChanged();
		} else if (alwaysPrompt) {
			AddRepositoryDialog dialog = new AddRepositoryDialog(repoCombo.getShell(), ui) {

				protected String getInitialLocationText() {
					if (isNewText) {
						// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=293068
						// we need to ensure any embedded nickname is stripped out
						URI loc = manipulator.locationFromString(selectedRepo);
						return loc.toString();
					}
					return super.getInitialLocationText();
				}

				@Override
				protected String getInitialNameText() {
					if (isNewText) {
						URI loc = manipulator.locationFromString(selectedRepo);
						// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=293068
						if (loc != null && manipulator instanceof ColocatedRepositoryTracker) {
							String parsedNickname = ((ColocatedRepositoryTracker) manipulator).getParsedNickname(loc);
							if (parsedNickname != null)
								return parsedNickname;
						}
					}
					return super.getInitialNameText();
				}

			};
			dialog.setTitle(ProvUIMessages.AddRepositoryDialog_Title);
			dialog.open();
			URI location = dialog.getAddedLocation();
			if (location != null)
				fillRepoCombo(getSiteString(location));
		} else if (isNewText) {
			try {
				container.run(false, false, new IRunnableWithProgress() {
					public void run(IProgressMonitor monitor) {
						URI location;
						IStatus status;
						// This might be a disabled repo.  If so, no need to validate further.
						if (disabledRepoProposals.containsKey(selectedRepo)) {
							location = disabledRepoProposals.get(selectedRepo);
							status = Status.OK_STATUS;
						} else {
							location = manipulator.locationFromString(selectedRepo);
							if (location == null)
								status = manipulator.getInvalidLocationStatus(selectedRepo);
							else {
								status = manipulator.validateRepositoryLocation(ui.getSession(), location, false, monitor);
							}
						}
						if (status.isOK() && location != null) {
							String nick = null;
							if (manipulator instanceof ColocatedRepositoryTracker)
								nick = ((ColocatedRepositoryTracker) manipulator).getParsedNickname(location);
							manipulator.addRepository(location, nick, ui.getSession());
							fillRepoCombo(getSiteString(location));
						}
						setRepoComboDecoration(status);
					}
				});
			} catch (InvocationTargetException e) {
				// ignore
			} catch (InterruptedException e) {
				// ignore
			}
		}
	}

	public ProvisioningContext getProvisioningContext() {
		int siteSel = getComboIndex(repoCombo.getText().trim());
		if (siteSel < 0 || siteSel == INDEX_SITE_ALL || siteSel == INDEX_SITE_NONE)
			return new ProvisioningContext(ui.getSession().getProvisioningAgent());
		URI[] locals = getLocalSites();
		// If there are local sites, the last item in the combo is "Local Sites Only"
		// Use all local sites in this case
		// We have to set metadata repositories and artifact repositories in the
		// provisioning context because the artifact repositories are used for
		// sizing.
		if (locals.length > 0 && siteSel == repoCombo.getItemCount() - 1) {
			ProvisioningContext context = new ProvisioningContext(ui.getSession().getProvisioningAgent());
			context.setMetadataRepositories(locals);
			context.setArtifactRepositories(locals);
			return context;
		}
		// A single site is selected.
		ProvisioningContext context = new ProvisioningContext(ui.getSession().getProvisioningAgent());
		context.setMetadataRepositories(new URI[] {comboRepos[siteSel]});
		context.setArtifactRepositories(new URI[] {comboRepos[siteSel]});
		return context;
	}

	void repoComboSelectionChanged() {
		int repoChoice = -1;
		URI repoLocation = null;

		int selection = -1;
		if (repoCombo.getListVisible()) {
			selection = repoCombo.getSelectionIndex();
		} else {
			selection = getComboIndex(repoCombo.getText().trim());
		}
		int localIndex = getLocalSites().length == 0 ? repoCombo.getItemCount() : repoCombo.getItemCount() - 1;
		if (comboRepos == null || selection < 0) {
			selection = INDEX_SITE_NONE;
		}

		if (selection == INDEX_SITE_NONE) {
			repoChoice = AvailableIUGroup.AVAILABLE_NONE;
		} else if (selection == INDEX_SITE_ALL) {
			repoChoice = AvailableIUGroup.AVAILABLE_ALL;
		} else if (selection >= localIndex) {
			repoChoice = AvailableIUGroup.AVAILABLE_LOCAL;
		} else {
			repoChoice = AvailableIUGroup.AVAILABLE_SPECIFIED;
			repoLocation = comboRepos[selection];
		}

		Object[] selectionListeners = listeners.getListeners();
		for (int i = 0; i < selectionListeners.length; i++) {
			((IRepositorySelectionListener) selectionListeners[i]).repositorySelectionChanged(repoChoice, repoLocation);
		}
	}

	IMetadataRepositoryManager getMetadataRepositoryManager() {
		return ProvUI.getMetadataRepositoryManager(ui.getSession());
	}
}
