/*******************************************************************************
 * Copyright (c) 2011, 2018 WindRiver Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     WindRiver Corporation - initial API and implementation
 *     IBM Corporation - Ongoing development
 *     Ericsson AB (Pascal Rapicault) - Bug 387115 - Allow to export everything
 *     Ericsson AB (Hamdan Msheik) - Bug 398833, 402560, 427195
 *******************************************************************************/
package org.eclipse.equinox.internal.p2.ui.sdk.scheduler.migration;

import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.util.*;
import java.util.List;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.core.runtime.preferences.*;
import org.eclipse.equinox.internal.p2.metadata.query.UpdateQuery;
import org.eclipse.equinox.internal.p2.ui.ProvUI;
import org.eclipse.equinox.internal.p2.ui.ProvUIActivator;
import org.eclipse.equinox.internal.p2.ui.dialogs.*;
import org.eclipse.equinox.internal.p2.ui.model.*;
import org.eclipse.equinox.internal.p2.ui.sdk.scheduler.AutomaticUpdatePlugin;
import org.eclipse.equinox.internal.p2.ui.viewers.*;
import org.eclipse.equinox.p2.core.IProvisioningAgent;
import org.eclipse.equinox.p2.engine.*;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.metadata.VersionRange;
import org.eclipse.equinox.p2.query.*;
import org.eclipse.equinox.p2.repository.artifact.IArtifactRepositoryManager;
import org.eclipse.equinox.p2.repository.metadata.IMetadataRepositoryManager;
import org.eclipse.equinox.p2.ui.ProvisioningUI;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.viewers.*;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.ui.dialogs.FilteredTree;
import org.eclipse.ui.dialogs.PatternFilter;
import org.eclipse.ui.progress.DeferredTreeContentManager;
import org.eclipse.ui.progress.WorkbenchJob;
import org.eclipse.ui.statushandlers.StatusManager;
import org.osgi.framework.BundleContext;
import org.osgi.util.tracker.ServiceTracker;

public class MigrationPage extends WizardPage implements ISelectableIUsPage, Listener {

	protected String currentMessage;
	// protected Button destinationBrowseButton;
	protected CheckboxTreeViewer viewer = null;
	protected Exception finishException;
	protected boolean entryChanged = false;
	protected static IProfileRegistry profileRegistry = null;
	static IProvisioningAgent agent = null;
	protected Button updateToLatest;

	public static final String REMIND_ME_LATER = "remindMeToMigrateLater";

	IProfile profile = null;

	private ProvisioningOperationWizard wizard;
	private ProvisioningUI ui;

	protected IProvisioningAgent otherInstanceAgent = null;
	private Collection<IInstallableUnit> unitsToMigrate;
	private Set<IInstallableUnit> selectedUnitsToMigrate; // selected units to be migrated, initially contains all units
															// not installed in current profile.
	private IProfile toImportFrom = null;
	// private File instancePath = null;
	private URI[] metaURIs = null;
	private URI[] artiURIs = null;

	/**
	 * {@link DelayedFilterCheckboxTree} has a timing bug to prevent restoring the
	 * check state, the methods {@link DeferredTreeContentManager}'s
	 * runClearPlaceholderJob and DelayedFilterCheckboxTree.doCreateRefreshJob().new
	 * JobChangeAdapter() {...}.done(IJobChangeEvent) has timing issue, I can't find
	 * a way to guarantee the first job is executed firstly
	 *
	 */
	final class ImportExportFilteredTree extends FilteredTree {
		ArrayList<Object> checkState = new ArrayList<>();

		ImportExportFilteredTree(Composite parent, int treeStyle, PatternFilter filter, boolean useNewLook) {
			super(parent, treeStyle, filter, useNewLook);
		}

		@Override
		protected TreeViewer doCreateTreeViewer(Composite composite, int style) {
			return new CheckboxTreeViewer(composite, style);
		}

		@Override
		protected WorkbenchJob doCreateRefreshJob() {
			WorkbenchJob job = super.doCreateRefreshJob();
			job.addJobChangeListener(new JobChangeAdapter() {
				@Override
				public void aboutToRun(IJobChangeEvent event) {
					Display.getDefault().syncExec(() -> {
						Object[] checked = viewer.getCheckedElements();
						if (checkState == null)
							checkState = new ArrayList<>(checked.length);
						for (Object checked1 : checked) {
							if (!viewer.getGrayed(checked1)) {
								if (!checkState.contains(checked1)) {
									checkState.add(checked1);
								}
							}
						}
					});
				}

				@Override
				public void done(IJobChangeEvent event) {
					if (event.getResult().isOK()) {
						Display.getDefault().asyncExec(() -> {
							if (viewer == null || viewer.getTree().isDisposed())
								return;
							if (checkState == null)
								return;

							viewer.setCheckedElements(new Object[0]);
							viewer.setGrayedElements(new Object[0]);
							// Now we are only going to set the check state of the leaf nodes
							// and rely on our container checked code to update the parents properly.
							Iterator<Object> iter = checkState.iterator();
							while (iter.hasNext()) {
								viewer.setChecked(iter.next(), true);
							}

							updatePageCompletion();
						});
					}
				}
			});
			return job;
		}
	}

	class TreeViewerComparator extends ViewerComparator {
		private int sortColumn = 0;
		private int lastSortColumn = 0;
		private boolean ascending = false;
		private boolean lastAscending = false;

		@Override
		public int compare(Viewer viewer1, Object e1, Object e2) {
			IInstallableUnit iu1 = ProvUI.getAdapter(e1, IInstallableUnit.class);
			IInstallableUnit iu2 = ProvUI.getAdapter(e2, IInstallableUnit.class);
			if (iu1 != null && iu2 != null) {
				if (viewer1 instanceof TreeViewer) {
					TreeViewer treeViewer = (TreeViewer) viewer1;
					IBaseLabelProvider baseLabel = treeViewer.getLabelProvider();
					if (baseLabel instanceof ITableLabelProvider) {
						ITableLabelProvider tableProvider = (ITableLabelProvider) baseLabel;
						String e1p = tableProvider.getColumnText(e1, getSortColumn());
						String e2p = tableProvider.getColumnText(e2, getSortColumn());
						// don't suppress this warning as it will cause build-time warning
						// see bug 423628. This should be possible to fix once
						// SWT/JFace adopts generics
						int result = getComparator().compare(e1p, e2p);
						// Secondary column sort
						if (result == 0) {
							e1p = tableProvider.getColumnText(e1, lastSortColumn);
							e2p = tableProvider.getColumnText(e2, lastSortColumn);
							// don't suppress this warning as it will cause build-time warning
							// see bug 423628. This should be possible to fix once
							// SWT/JFace adopts generics
							int result2 = getComparator().compare(e1p, e2p);
							return lastAscending ? result2 : (-1) * result2;
						}
						return isAscending() ? result : (-1) * result;
					}
				}
				// we couldn't determine a secondary sort, call it equal
				return 0;
			}
			return super.compare(viewer1, e1, e2);
		}

		/**
		 * @return Returns the sortColumn.
		 */
		public int getSortColumn() {
			return sortColumn;
		}

		/**
		 * @param sortColumn The sortColumn to set.
		 */
		public void setSortColumn(int sortColumn) {
			if (this.sortColumn != sortColumn) {
				lastSortColumn = this.sortColumn;
				lastAscending = this.ascending;
				this.sortColumn = sortColumn;
			}
		}

		/**
		 * @return Returns the ascending.
		 */
		public boolean isAscending() {
			return ascending;
		}

		/**
		 * @param ascending The ascending to set.
		 */
		public void setAscending(boolean ascending) {
			this.ascending = ascending;
		}
	}

	static {
		BundleContext context = Platform.getBundle(ProvUIActivator.PLUGIN_ID).getBundleContext();
		ServiceTracker<IProvisioningAgent, IProvisioningAgent> tracker = new ServiceTracker<>(context,
				IProvisioningAgent.class, null);
		tracker.open();
		agent = tracker.getService();
		tracker.close();
		if (agent != null)
			profileRegistry = agent.getService(IProfileRegistry.class);
	}

	public MigrationPage(String pageName) {
		super(pageName);
	}

	public MigrationPage(ProvisioningUI ui, ProvisioningOperationWizard wizard, IProfile toImportFrom,
			Collection<IInstallableUnit> unitsToMigrate, boolean firstTime) {
		super("MigrationPageInstance"); //$NON-NLS-1$
		this.wizard = wizard;
		this.ui = ui;
		profile = getSelfProfile();
		this.toImportFrom = toImportFrom;
		this.unitsToMigrate = unitsToMigrate;
		setTitle(firstTime ? ProvUIMessages.MigrationPage_DIALOG_TITLE_FIRSTRUN
				: ProvUIMessages.MigrationPage_DIALOG_TITLE);
		setDescription(NLS.bind(ProvUIMessages.MigrationPage_DIALOG_DESCRIPTION, Platform.getProduct().getName()));
	}

	public MigrationPage(ProvisioningUI ui, ProvisioningOperationWizard wizard, boolean firstTime) {
		super("importfrominstancepage"); //$NON-NLS-1$
		this.wizard = wizard;
		this.ui = ui;
		setTitle(firstTime ? ProvUIMessages.MigrationPage_DIALOG_TITLE_FIRSTRUN
				: ProvUIMessages.MigrationPage_DIALOG_TITLE);
		setDescription(NLS.bind(ProvUIMessages.MigrationPage_DIALOG_DESCRIPTION, Platform.getProduct().getName()));
	}

	protected IProfile getSelfProfile() {
		if (profileRegistry != null) {
			String selfID = System.getProperty("eclipse.p2.profile"); //$NON-NLS-1$
			if (selfID == null)
				selfID = IProfileRegistry.SELF;
			return profileRegistry.getProfile(selfID);
		}
		return null;
	}

	private void createColumns(TreeViewer treeViewer) {
		String[] titles = { ProvUIMessages.Column_Name, ProvUIMessages.Column_Version, ProvUIMessages.Column_Id };
		for (int i = 0; i < titles.length; i++) {
			TreeViewerColumn column = new TreeViewerColumn(treeViewer, SWT.NONE);
			column.getColumn().setText(titles[i]);
			column.getColumn().setResizable(true);
			column.getColumn().setMoveable(true);
			if (titles[i].equals(ProvUIMessages.Column_Name)) {
				column.getColumn().setWidth(300);
				updateTableSorting(i);
			} else {
				column.getColumn().setWidth(200);
			}

			final int columnIndex = i;
			column.getColumn().addSelectionListener(
					SelectionListener.widgetSelectedAdapter(e -> updateTableSorting(columnIndex)));
		}
	}

	protected void updateTableSorting(int columnIndex) {
		TreeViewerComparator comparator = (TreeViewerComparator) viewer.getComparator();
		// toggle direction if it's the same column
		if (columnIndex == comparator.getSortColumn()) {
			comparator.setAscending(!comparator.isAscending());
		}
		comparator.setSortColumn(columnIndex);
		viewer.getTree().setSortColumn(viewer.getTree().getColumn(columnIndex));
		viewer.getTree().setSortDirection(comparator.isAscending() ? SWT.UP : SWT.DOWN);
		viewer.refresh(false);
	}

	@Override
	public void createControl(Composite parent) {
		initializeDialogUnits(parent);
		// initializeService();
		Composite composite = new Composite(parent, SWT.NULL);
		GridLayout layout = new GridLayout(1, true);
		layout.horizontalSpacing = 0;
		layout.verticalSpacing = 5;
		composite.setLayout(layout);
		composite.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_FILL | GridData.HORIZONTAL_ALIGN_FILL));

		createContents(composite);

		// can not finish initially, but don't want to start with an error
		// message either
		if (!(validateOptionsGroup())) {
			setPageComplete(false);
		}

		setControl(composite);
		// giveFocusToDestination();
		Dialog.applyDialogFont(composite);
	}

	protected IUColumnConfig[] getColumnConfig() {
		return new IUColumnConfig[] {
				new IUColumnConfig(org.eclipse.equinox.internal.p2.ui.ProvUIMessages.ProvUI_NameColumnTitle,
						IUColumnConfig.COLUMN_NAME, ILayoutConstants.DEFAULT_PRIMARY_COLUMN_WIDTH),
				new IUColumnConfig(org.eclipse.equinox.internal.p2.ui.ProvUIMessages.ProvUI_VersionColumnTitle,
						IUColumnConfig.COLUMN_VERSION, ILayoutConstants.DEFAULT_SMALL_COLUMN_WIDTH),
				new IUColumnConfig(org.eclipse.equinox.internal.p2.ui.ProvUIMessages.ProvUI_IdColumnTitle,
						IUColumnConfig.COLUMN_ID, ILayoutConstants.DEFAULT_COLUMN_WIDTH) };
	}

	protected void createContents(Composite composite) {
		createInstallationTable(composite);
		createAdditionOptions(composite);
	}

	protected void createInstallationTable(final Composite parent) {

		SashForm sashForm = new SashForm(parent, SWT.VERTICAL);
		FillLayout fill = new FillLayout();
		sashForm.setLayout(fill);
		GridData data = new GridData(GridData.FILL_BOTH);
		sashForm.setLayoutData(data);
		Composite sashComposite = new Composite(sashForm, SWT.NONE);
		GridLayout grid = new GridLayout();
		grid.marginWidth = 0;
		grid.marginHeight = 0;
		sashComposite.setLayout(grid);

		PatternFilter filter = getPatternFilter();
		filter.setIncludeLeadingWildcard(true);
		final ImportExportFilteredTree filteredTree = new ImportExportFilteredTree(sashComposite,
				SWT.MULTI | SWT.FULL_SELECTION | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER, filter, true);
		viewer = (CheckboxTreeViewer) filteredTree.getViewer();
		final Tree tree = viewer.getTree();
		tree.setHeaderVisible(true);
		tree.setLinesVisible(false);
		GridData treeDataGrid = new GridData(GridData.FILL_BOTH);
		treeDataGrid.heightHint = convertHeightInCharsToPixels(ILayoutConstants.DEFAULT_TABLE_HEIGHT);
		treeDataGrid.widthHint = convertWidthInCharsToPixels(ILayoutConstants.DEFAULT_TABLE_WIDTH);
		tree.setLayoutData(treeDataGrid);

		viewer.setComparator(new TreeViewerComparator());
		viewer.setComparer(new ProvElementComparer());
		createColumns(viewer);
		final ITreeContentProvider contentProvider = getContentProvider();
		viewer.setContentProvider(contentProvider);
		viewer.setLabelProvider(getLabelProvider());
		viewer.addCheckStateListener(event -> {
			if (!event.getChecked() && filteredTree.checkState != null) {
				ArrayList<Object> toRemove = new ArrayList<>(1);
				// See bug 258117. Ideally we would get check state changes
				// for children when the parent state changed, but we aren't, so
				// we need to remove all children from the additive check state
				// cache.
				if (contentProvider.hasChildren(event.getElement())) {
					Set<Object> unchecked = new HashSet<>();
					Object[] children = contentProvider.getChildren(event.getElement());
					for (Object child : children) {
						unchecked.add(child);
					}
					Iterator<Object> iter = filteredTree.checkState.iterator();
					while (iter.hasNext()) {
						Object current = iter.next();
						if (current != null && unchecked.contains(current)) {
							toRemove.add(current);
						}
					}
				} else {
					for (Object element : filteredTree.checkState) {
						if (viewer.getComparer().equals(element, event.getElement())) {
							toRemove.add(element);
							// Do not break out of the loop. We may have duplicate equal
							// elements in the cache. Since the cache is additive, we want
							// to be sure we've gotten everything.
						}
					}
				}
				filteredTree.checkState.removeAll(toRemove);
			}
		});
		parent.addControlListener(new ControlAdapter() {
			private final int[] columnRate = new int[] { 4, 2, 2 };

			@Override
			public void controlResized(ControlEvent e) {
				Rectangle area = parent.getClientArea();
				Point size = tree.computeSize(SWT.DEFAULT, SWT.DEFAULT);
				ScrollBar vBar = tree.getVerticalBar();
				int width = area.width - tree.computeTrim(0, 0, 0, 0).width - vBar.getSize().x;
				if (size.y > area.height + tree.getHeaderHeight()) {
					// Subtract the scrollbar width from the total column width
					// if a vertical scrollbar will be required
					Point vBarSize = vBar.getSize();
					width -= vBarSize.x;
				}
				Point oldSize = tree.getSize();
				TreeColumn[] columns = tree.getColumns();
				int hasUsed = 0, i = 0;
				if (oldSize.x > area.width) {
					// table is getting smaller so make the columns
					// smaller first and then resize the table to
					// match the client area width
					for (; i < columns.length - 1; i++) {
						columns[i].setWidth(width * columnRate[i] / 10);
						hasUsed += columns[i].getWidth();
					}
					columns[columns.length - 1].setWidth(width - hasUsed);
					tree.setSize(area.width, area.height);
				} else {
					// table is getting bigger so make the table
					// bigger first and then make the columns wider
					// to match the client area width
					tree.setSize(area.width, area.height);
					for (; i < columns.length - 1; i++) {
						columns[i].setWidth(width * columnRate[i] / 10);
						hasUsed += columns[i].getWidth();
					}
					columns[columns.length - 1].setWidth(width - hasUsed);
				}
			}
		});

		selectedUnitsToMigrate = identifyUnitsToBeMigrated();

		ICheckStateProvider provider = getViewerDefaultState();
		if (provider != null)
			viewer.setCheckStateProvider(provider);
		else
			viewer.addSelectionChangedListener(event -> updatePageCompletion());
		viewer.getControl().setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, true));
		viewer.setInput(getInput());

		viewer.getTree().addListener(SWT.Selection, event -> {
			if (event.item instanceof TreeItem && event.detail == SWT.CHECK) {
				TreeItem treeItem = (TreeItem) event.item;
				IInstallableUnit iu = ProvUI.getAdapter(event.item.getData(), IInstallableUnit.class);
				if (treeItem.getChecked()) {
					selectedUnitsToMigrate.add(iu);
				} else {
					selectedUnitsToMigrate.remove(iu);
				}
			}
			updatePageCompletion();
		});

		Composite buttons = new Composite(sashComposite, SWT.NONE);
		buttons.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, false));
		buttons.setLayout(new RowLayout(SWT.HORIZONTAL));
		Button selectAll = new Button(buttons, SWT.PUSH);
		selectAll.setText(ProvUIMessages.AbstractPage_ButtonSelectAll);
		selectAll.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
			for (TreeItem item : viewer.getTree().getItems()) {
				if (!item.getChecked()) {
					item.setChecked(true);
					Event event = new Event();
					event.widget = item.getParent();
					event.detail = SWT.CHECK;
					event.item = item;
					event.type = SWT.Selection;
					viewer.getTree().notifyListeners(SWT.Selection, event);
				}
			}
			updatePageCompletion();
		}));
		Button deselectAll = new Button(buttons, SWT.PUSH);
		deselectAll.setText(ProvUIMessages.AbstractPage_ButtonDeselectAll);
		deselectAll.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
			for (TreeItem item : viewer.getTree().getItems()) {
				if (item.getChecked()) {
					item.setChecked(false);
					Event event = new Event();
					event.widget = item.getParent();
					event.detail = SWT.CHECK;
					event.item = item;
					event.type = SWT.Selection;
					viewer.getTree().notifyListeners(SWT.Selection, event);
				}
				viewer.setSubtreeChecked(item.getData(), false);
			}
			updatePageCompletion();
		}));

	}

	private Set<IInstallableUnit> identifyUnitsToBeMigrated() {

		Set<IInstallableUnit> ius = new HashSet<>();
		if (profile != null) {
			for (IInstallableUnit iu : unitsToMigrate) {
				IQueryResult<IInstallableUnit> collector = profile.query(
						QueryUtil.createIUQuery(iu.getId(), new VersionRange(iu.getVersion(), true, null, false)),
						new NullProgressMonitor());
				if (collector.isEmpty()) {
					ius.add(iu);
				}
			}
		}

		return ius;
	}

	protected void createAdditionOptions(Composite parent) {

		Composite composite = new Composite(parent, SWT.BORDER);
		GridLayout layout = new GridLayout();
		layout.numColumns = 1;
		composite.setLayout(layout);
		composite.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, false));

		updateToLatest = new Button(composite, SWT.CHECK);
		updateToLatest.setText(ProvUIMessages.MigrationPage_UPDATE_TO_LATEST);
		updateToLatest.setSelection(loadCustomizedSetting());

	}

	protected PatternFilter getPatternFilter() {
		return new AvailableIUPatternFilter(getColumnConfig());
	}

	protected ICheckStateProvider getViewerDefaultState() {
		return new ICheckStateProvider() {

			@Override
			public boolean isGrayed(Object element) {
				return false;
			}

			@Override
			public boolean isChecked(Object element) {
				IInstallableUnit iu = ProvUI.getAdapter(element, IInstallableUnit.class);
				if (selectedUnitsToMigrate.contains(iu)) {
					return true;
				}
				return false;
			}
		};
	}

	protected ITreeContentProvider getContentProvider() {
		ProvElementContentProvider provider = new ProvElementContentProvider() {
			@Override
			public boolean hasChildren(Object element) {
				if (element instanceof InstalledIUElement)
					return false;
				return super.hasChildren(element);
			}

			@Override
			public Object[] getChildren(Object parent) {
				if (parent instanceof InstalledIUElement)
					return new Object[0];
				return super.getChildren(parent);
			}
		};
		return provider;
	}

	protected boolean determinePageCompletion() {
		currentMessage = null;
		// validate groups in order of priority so error message is the most important
		// one
		boolean complete = validateOptionsGroup();

		// Avoid draw flicker by not clearing the error
		// message unless all is valid.
		if (complete) {
			setErrorMessage(null);
		} else {
			setErrorMessage(currentMessage);
		}

		return complete;
	}

	protected int getBrowseDialogStyle() {
		return SWT.OPEN;
	}

	// TODO remove the implementation of Listener
	@Override
	public void handleEvent(Event event) {
		// Widget source = event.widget;
		//
		// if (source == destinationBrowseButton) {
		// handleDestinationBrowseButtonPressed();
		// }
		// updatePageCompletion();
	}

	/**
	 * Determine if the page is complete and update the page appropriately.
	 */
	protected void updatePageCompletion() {
		boolean pageComplete = determinePageCompletion();
		setPageComplete(pageComplete);
		if (pageComplete) {
			setMessage(null);
		}
	}

	protected boolean validateOptionsGroup() {
		if (viewer == null || viewer.getCheckedElements().length > 0)
			return true;

		currentMessage = getNoOptionsMessage();
		return false;
	}

	protected ProvisioningOperationWizard getProvisioningWizard() {
		return wizard;
	}

	protected ProvisioningUI getProvisioningUI() {
		return ui;
	}

	public boolean hasInstalled(IInstallableUnit iu) {
		IQueryResult<IInstallableUnit> results = profile
				.query(QueryUtil.createIUQuery(iu.getId(), new VersionRange(iu.getVersion(), true, null, false)), null);
		return !results.isEmpty();
	}

	public String getIUNameWithDetail(IInstallableUnit iu) {
		IQueryResult<IInstallableUnit> results = profile
				.query(QueryUtil.createIUQuery(iu.getId(), new VersionRange(iu.getVersion(), true, null, false)), null);
		String text = iu.getProperty(IProfile.PROP_NAME, null);
		text = (text != null) ? text : iu.getId();
		if (!results.isEmpty()) {
			boolean hasHigherVersion = false;
			boolean hasEqualVersion = false;
			for (IInstallableUnit installedIU : results.toSet()) {
				int compareValue = installedIU.getVersion().compareTo(iu.getVersion());
				if (compareValue > 0) {
					hasHigherVersion = true;
					break;
				} else if (compareValue == 0)
					hasEqualVersion = true;
			}
			if (hasHigherVersion)
				return NLS.bind(ProvUIMessages.AbstractImportPage_HigherVersionInstalled, text);
			else if (hasEqualVersion)
				return NLS.bind(ProvUIMessages.AbstractImportPage_SameVersionInstalled, text);
		}
		return text;
	}

	protected void doFinish() throws Exception {
		// do nothing
	}

	@Override
	public boolean canFlipToNextPage() {
		return isPageComplete();
	}

	protected String getDialogTitle() {
		return ProvUIMessages.MigrationPage_DIALOG_TITLE;
	}

	protected Object getInput() {

		IUElementListRoot root = new IUElementListRoot(ui);
		List<AvailableIUElement> elements = new ArrayList<>(unitsToMigrate.size());
		for (IInstallableUnit unit : unitsToMigrate) {
			elements.add(new AvailableIUElement(root, unit, toImportFrom.getProfileId(), false));
		}
		root.setChildren(elements.toArray());
		return root;
	}

	protected String getInvalidDestinationMessage() {
		return "";// ProvUIMessages.ImportFromInstallationPage_INVALID_DESTINATION; //$NON-NLS-1$
	}

	protected String getNoOptionsMessage() {
		return ProvUIMessages.MigrationPage_SELECT_COMPONENT;
	}

	class ImportFromInstallationLabelProvider extends IUDetailsLabelProvider {
		@Override
		public String getColumnText(Object element, int columnIndex) {
			String text = super.getColumnText(element, columnIndex);
			// it's the order of label provider
			if (columnIndex == 0) {
				IInstallableUnit iu = ProvUI.getAdapter(element, IInstallableUnit.class);
				return getIUNameWithDetail(iu);
			}
			return text;
		}

		@Override
		public Color getForeground(Object element) {
			IInstallableUnit iu = ProvUI.getAdapter(element, IInstallableUnit.class);
			if (hasInstalled(iu))
				return Display.getDefault().getSystemColor(SWT.COLOR_GRAY);
			return super.getForeground(element);
		}
	}

	protected ITableLabelProvider getLabelProvider() {
		return new ImportFromInstallationLabelProvider();
	}

	@Override
	public void dispose() {
		super.dispose();
		if (otherInstanceAgent != null) {
			otherInstanceAgent.stop();
			otherInstanceAgent = null;
			toImportFrom = null;
		}
		cleanLocalRepository();
	}

	public void cleanLocalRepository() {
		if (metaURIs != null && metaURIs.length > 0) {
			IProvisioningAgent runningAgent = getProvisioningUI().getSession().getProvisioningAgent();
			IMetadataRepositoryManager manager = runningAgent.getService(IMetadataRepositoryManager.class);
			for (URI uri : metaURIs)
				manager.removeRepository(uri);
			IArtifactRepositoryManager artifactManager = runningAgent.getService(IArtifactRepositoryManager.class);
			for (URI uri : artiURIs)
				artifactManager.removeRepository(uri);
		}
	}

	// Both checkedElements and checkedElementsUpdates and the logic inside the
	// getCheckedIUElements method
	// are used to prevent unnecessary call to getUpdates method due to
	// computational cost.
	@SuppressWarnings("rawtypes")
	private Set checkedElements;
	@SuppressWarnings("rawtypes")
	private Set checkedElementsUpdates;
	private boolean getUpdatesCanceled;

	@Override
	public Object[] getCheckedIUElements() {

		if (isUpdateToLatest()) {

			Object[] latestUpdates = getLatestVersionOfCheckedElements();

			// If the getUpdades operation is cancelled, then set checkedElements and
			// checkedElementsUpdates to null to force the lookup for updates again.
			// Thereafter throw OperationCanceledException.
			if (getUpdatesCanceled) {

				this.checkedElements = null;
				this.checkedElementsUpdates = null;
				throw new OperationCanceledException();
			}
			return latestUpdates;
		}
		return viewer.getCheckedElements();
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	private Object[] getLatestVersionOfCheckedElements() {

		Object[] checkedArray = viewer.getCheckedElements();
		if (this.checkedElements == null) {
			// initialize checkedElements and checkedElementsUpdates for the first time
			this.checkedElements = new HashSet(Arrays.asList(checkedArray));
			this.checkedElementsUpdates = new HashSet(Arrays.asList(getUpdates(checkedArray)));

		} else {
			Set checkedElementsNow = new HashSet(Arrays.asList(checkedArray));
			if (checkedElementsNow.size() != this.checkedElements.size()
					|| (!checkedElementsNow.containsAll(checkedElements))) {
				// only if the set of checkedElements has changed get the update for them
				this.checkedElements = checkedElementsNow; //
				this.checkedElementsUpdates = new HashSet(Arrays.asList(getUpdates(checkedArray)));
			}
		}

		return this.checkedElementsUpdates.toArray();
	}

	@Override
	public Object[] getSelectedIUElements() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void setCheckedElements(Object[] elements) {
		throw new UnsupportedOperationException();
	}

	// Look for update of the current selected installation units and replace the
	// old ons with the updated version
	private Object[] getUpdates(final Object[] _checkedElements) {

		final Collection<IInstallableUnit> toInstall = new ArrayList<>();

		try {
			getContainer().run(false, true, monitor -> {
				SubMonitor sub = SubMonitor.convert(monitor, _checkedElements.length);
				ProvisioningContext context = new ProvisioningContext(
						getProvisioningUI().getSession().getProvisioningAgent());

				for (Object iu : _checkedElements) {

					if (sub.isCanceled()) {
						MigrationPage.this.getUpdatesCanceled = true;
						toInstall.clear();
						sub.done();
						return;
					}

					if (iu instanceof AvailableIUElement) {
						IInstallableUnit unit = ((AvailableIUElement) iu).getIU();
						IuUpdateAndPatches updateAndPatches = filterToInstall(unit,
								updatesFor(unit, context, sub.newChild(1)));
						if (updateAndPatches.update != null) {
							toInstall.add(updateAndPatches.update);
						} else {
							toInstall.add(updateAndPatches.iu); // because it is not yet installed
							toInstall.addAll(updateAndPatches.patches);
						}

					}

					sub.worked(1);
				}
			});
		} catch (InterruptedException e) {
			// Nothing to report if thread was interrupted
		} catch (InvocationTargetException e) {
			ProvUI.handleException(e.getCause(), null, StatusManager.SHOW | StatusManager.LOG);
		}

		return toInstall.toArray();

	}

	public boolean isUpdateToLatest() {
		return updateToLatest.getSelection();
	}

	public ProvisioningContext getProvisioningContext() {
		ProvisioningContext context = new ProvisioningContext(getProvisioningUI().getSession().getProvisioningAgent());
		context.setArtifactRepositories(artiURIs);
		context.setMetadataRepositories(metaURIs);
		return context;
	}

	private static boolean hasHigherFidelity(IInstallableUnit iu, IInstallableUnit currentIU) {
		if (Boolean.parseBoolean(currentIU.getProperty(IInstallableUnit.PROP_PARTIAL_IU))
				&& !Boolean.parseBoolean(iu.getProperty(IInstallableUnit.PROP_PARTIAL_IU)))
			return true;
		return false;
	}

	public Collection<IInstallableUnit> updatesFor(IInstallableUnit toUpdate, ProvisioningContext context,
			IProgressMonitor monitor) {
		// IPlanner planner = (IPlanner)
		// getProvisioningUI().getSession().getProvisioningAgent().getService(IPlanner.SERVICE_NAME);
		// return planner.updatesFor(toUpdate, context, monitor).toSet();

		Map<String, IInstallableUnit> resultsMap = new HashMap<>();

		SubMonitor sub = SubMonitor.convert(monitor, 1000);
		IQueryable<IInstallableUnit> queryable = context.getMetadata(sub.newChild(500));
		for (IInstallableUnit iu : queryable.query(new UpdateQuery(toUpdate), sub.newChild(500))) {
			String key = iu.getId() + "_" + iu.getVersion().toString(); //$NON-NLS-1$
			IInstallableUnit currentIU = resultsMap.get(key);
			if (currentIU == null || hasHigherFidelity(iu, currentIU))
				resultsMap.put(key, iu);
		}
		sub.done();
		return resultsMap.values();
	}

	class IuUpdateAndPatches {
		public IInstallableUnit iu;
		public IInstallableUnit update;
		public Collection<IInstallableUnit> patches;

		IuUpdateAndPatches(IInstallableUnit iu) {
			this.iu = iu;
			this.patches = new ArrayList<>();
		}

	}

	/**
	 *
	 * @param iu      original unit.
	 * @param updates list of updates: patches or true updates.
	 * @return a structure holding the original unit, its most recent update and any
	 *         available patches.
	 */
	private IuUpdateAndPatches filterToInstall(IInstallableUnit iu, Collection<IInstallableUnit> updates) {

		IuUpdateAndPatches updateAndPatches = new IuUpdateAndPatches(iu);

		for (IInstallableUnit update : updates) {

			if (QueryUtil.isPatch(update)) {
				updateAndPatches.patches.add(update);
			} else {
				if (updateAndPatches.update == null
						|| updateAndPatches.update.getVersion().compareTo(update.getVersion()) < 0) {
					updateAndPatches.update = update;
				}
			}
		}

		return updateAndPatches;
	}

	public static boolean loadCustomizedSetting() {
		IScopeContext[] contexts = new IScopeContext[] { InstanceScope.INSTANCE, DefaultScope.INSTANCE,
				BundleDefaultsScope.INSTANCE, ConfigurationScope.INSTANCE };
		boolean updateToLatest = Platform.getPreferencesService().getBoolean(AutomaticUpdatePlugin.PLUGIN_ID,
				"updateToLatest", false, contexts);
		return updateToLatest;
	}

}
