/*******************************************************************************
 * Copyright (c) 2007, 2013 Intel 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:
 * Intel Corporation - Initial API and implementation
 * IBM Corporation
 *******************************************************************************/
package org.eclipse.cdt.managedbuilder.ui.properties;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.eclipse.cdt.build.core.scannerconfig.CfgInfoContext;
import org.eclipse.cdt.build.core.scannerconfig.ICfgScannerConfigBuilderInfo2Set;
import org.eclipse.cdt.build.internal.core.scannerconfig.CfgDiscoveredPathManager;
import org.eclipse.cdt.build.internal.core.scannerconfig.CfgScannerConfigUtil;
import org.eclipse.cdt.build.internal.core.scannerconfig2.CfgScannerConfigProfileManager;
import org.eclipse.cdt.core.language.settings.providers.ScannerDiscoveryLegacySupport;
import org.eclipse.cdt.core.model.util.CDTListComparator;
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
import org.eclipse.cdt.core.settings.model.ICResourceDescription;
import org.eclipse.cdt.core.settings.model.util.CDataUtil;
import org.eclipse.cdt.internal.ui.language.settings.providers.LanguageSettingsProvidersPage;
import org.eclipse.cdt.internal.ui.newui.StatusMessageLine;
import org.eclipse.cdt.make.core.MakeCorePlugin;
import org.eclipse.cdt.make.core.scannerconfig.IScannerConfigBuilderInfo2;
import org.eclipse.cdt.make.core.scannerconfig.IScannerConfigBuilderInfo2Set;
import org.eclipse.cdt.make.core.scannerconfig.IScannerInfoCollector;
import org.eclipse.cdt.make.core.scannerconfig.IScannerInfoCollectorCleaner;
import org.eclipse.cdt.make.core.scannerconfig.InfoContext;
import org.eclipse.cdt.make.internal.core.scannerconfig.DiscoveredPathInfo;
import org.eclipse.cdt.make.internal.core.scannerconfig.DiscoveredScannerInfoStore;
import org.eclipse.cdt.make.internal.core.scannerconfig2.DefaultRunSIProvider;
import org.eclipse.cdt.make.internal.core.scannerconfig2.SCProfileInstance;
import org.eclipse.cdt.make.internal.core.scannerconfig2.ScannerConfigProfileManager;
import org.eclipse.cdt.make.ui.dialogs.AbstractDiscoveryPage;
import org.eclipse.cdt.make.ui.dialogs.GCCPerProjectSCDProfilePage;
import org.eclipse.cdt.managedbuilder.core.IConfiguration;
import org.eclipse.cdt.managedbuilder.core.IInputType;
import org.eclipse.cdt.managedbuilder.core.IResourceInfo;
import org.eclipse.cdt.managedbuilder.core.ITool;
import org.eclipse.cdt.managedbuilder.core.IToolChain;
import org.eclipse.cdt.managedbuilder.core.ManagedBuildManager;
import org.eclipse.cdt.managedbuilder.internal.ui.Messages;
import org.eclipse.cdt.ui.CUIPlugin;
import org.eclipse.cdt.ui.newui.CDTPrefUtil;
import org.eclipse.cdt.utils.ui.controls.ControlFactory;
import org.eclipse.cdt.utils.ui.controls.TabFolderLayout;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
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.Table;
import org.eclipse.swt.widgets.TableItem;

/**
 * @noextend This class is not intended to be subclassed by clients.
 * @noinstantiate This class is not intended to be instantiated by clients.
 */
public class DiscoveryTab extends AbstractCBuildPropertyTab implements IBuildInfoContainer {
	/**
	 * @deprecated since CDT 6.1
	 */
	@Deprecated
	protected static final String PREFIX = "ScannerConfigOptionsDialog"; //$NON-NLS-1$

	private static final String GCC_PER_PROJECT_PROFILE = MakeCorePlugin.getUniqueIdentifier()
			+ ".GCCStandardMakePerProjectProfile"; //$NON-NLS-1$
	private static final String MAKEFILE_PROJECT_TOOLCHAIN_ID = "org.eclipse.cdt.build.core.prefbase.toolchain"; //$NON-NLS-1$
	private static final String NAMESPACE = "org.eclipse.cdt.make.ui"; //$NON-NLS-1$
	private static final String POINT = "DiscoveryProfilePage"; //$NON-NLS-1$
	private static final String PROFILE_PAGE = "profilePage"; //$NON-NLS-1$
	private static final String PROFILE_ID = "profileId"; //$NON-NLS-1$
	private static final String PROFILE_NAME = "name"; //$NON-NLS-1$
	private static final int DEFAULT_HEIGHT = 15;
	private static final int[] DEFAULT_SASH_WEIGHTS = new int[] { 10, 20 };
	private Label fTableDefinition;
	private Combo scopeComboBox;
	private Table resTable;
	private Group autoDiscoveryGroup;
	private Button autoDiscoveryCheckBox;
	private Button reportProblemsCheckBox;
	private Combo profileComboBox;
	private Composite profileOptionsComposite;
	private Button clearButton;

	private ICfgScannerConfigBuilderInfo2Set cbi;
	private Map<InfoContext, IScannerConfigBuilderInfo2> baseInfoMap;
	private IScannerConfigBuilderInfo2 buildInfo;
	private CfgInfoContext iContext;
	private List<DiscoveryProfilePageConfiguration> pagesList = null;
	private List<String> visibleProfilesList = null;
	private IPath configPath;
	private AbstractDiscoveryPage[] realPages;
	protected SashForm sashForm;

	private DiscoveryPageWrapper wrapper = null;

	private StatusMessageLine fStatusLine;

	@Override
	public void createControls(Composite parent) {
		super.createControls(parent);
		wrapper = new DiscoveryPageWrapper(this.page, this);
		usercomp.setLayout(new GridLayout(1, false));

		if (page.isForProject() || page.isForPrefs()) {
			Group scopeGroup = setupGroup(usercomp, Messages.DiscoveryTab_0, 1, GridData.FILL_HORIZONTAL);
			scopeGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
			scopeComboBox = new Combo(scopeGroup, SWT.DROP_DOWN | SWT.READ_ONLY | SWT.BORDER);
			scopeComboBox.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
			scopeComboBox.add(Messages.DiscoveryTab_1);
			scopeComboBox.add(Messages.DiscoveryTab_2);
			scopeComboBox.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					if (cbi == null)
						return;
					cbi.setPerRcTypeDiscovery(scopeComboBox.getSelectionIndex() == 0);
					updateData();
				}
			});
		}

		// Create the sash form
		sashForm = new SashForm(usercomp, SWT.NONE);
		sashForm.setOrientation(SWT.HORIZONTAL);
		sashForm.setLayoutData(new GridData(GridData.FILL_BOTH));

		Composite comp = new Composite(sashForm, SWT.NONE);
		comp.setLayout(new GridLayout(1, true));
		comp.setLayoutData(new GridData(GridData.FILL_BOTH));

		fTableDefinition = new Label(comp, SWT.LEFT);
		fTableDefinition.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

		resTable = new Table(comp, SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER);
		GridData gd = new GridData(GridData.FILL_BOTH);
		gd.widthHint = 150;
		resTable.setLayoutData(gd);
		resTable.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				handleToolSelected();
			}
		});
		initializeProfilePageMap();

		Composite c = new Composite(sashForm, 0);
		c.setLayout(new GridLayout(1, false));
		c.setLayoutData(new GridData(GridData.FILL_BOTH));

		createScannerConfigControls(c);

		profileOptionsComposite = new Composite(c, SWT.NONE);
		gd = new GridData(GridData.FILL, GridData.FILL, true, true);
		gd.heightHint = Dialog.convertVerticalDLUsToPixels(getFontMetrics(parent), DEFAULT_HEIGHT);
		profileOptionsComposite.setLayoutData(gd);
		profileOptionsComposite.setLayout(new TabFolderLayout());

		fStatusLine = new StatusMessageLine(usercomp, SWT.LEFT, 2);
		setEnablement();

		sashForm.setWeights(DEFAULT_SASH_WEIGHTS);
	}

	private void createScannerConfigControls(Composite parent) {
		autoDiscoveryGroup = setupGroup(parent, Messages.ScannerConfigOptionsDialog_scGroup_label, 2,
				GridData.FILL_HORIZONTAL);

		autoDiscoveryCheckBox = setupCheck(autoDiscoveryGroup,
				Messages.ScannerConfigOptionsDialog_scGroup_enabled_button, 2, GridData.FILL_HORIZONTAL);
		autoDiscoveryCheckBox.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				enableAllControls();
				boolean isSCDEnabled = autoDiscoveryCheckBox.getSelection();
				buildInfo.setAutoDiscoveryEnabled(isSCDEnabled);
				if (isSCDEnabled) {
					String id = visibleProfilesList.get(profileComboBox.getSelectionIndex());
					buildInfo.setSelectedProfileId(id);
					handleDiscoveryProfileChanged();
				}
				showStatusLine();
			}
		});
		reportProblemsCheckBox = setupCheck(autoDiscoveryGroup,
				Messages.ScannerConfigOptionsDialog_scGroup_problemReporting_enabled_button, 2,
				GridData.FILL_HORIZONTAL);
		reportProblemsCheckBox.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				buildInfo.setProblemReportingEnabled(reportProblemsCheckBox.getSelection());
			}
		});

		// Add profile combo box
		setupLabel(autoDiscoveryGroup, Messages.ScannerConfigOptionsDialog_scGroup_selectedProfile_combo, 1,
				GridData.BEGINNING);
		profileComboBox = new Combo(autoDiscoveryGroup, SWT.DROP_DOWN | SWT.READ_ONLY | SWT.BORDER);
		profileComboBox.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
		profileComboBox.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				int x = profileComboBox.getSelectionIndex();
				String s = visibleProfilesList.get(x);
				buildInfo.setSelectedProfileId(s);
				handleDiscoveryProfileChanged();
			}
		});

		// "Clear" label
		@SuppressWarnings("unused")
		Label clearLabel = ControlFactory.createLabel(autoDiscoveryGroup, Messages.DiscoveryTab_ClearDisoveredEntries);

		// "Clear" button
		clearButton = ControlFactory.createPushButton(autoDiscoveryGroup, Messages.DiscoveryTab_Clear);
		GridData gd = (GridData) clearButton.getLayoutData();
		gd.grabExcessHorizontalSpace = true;
		//Bug 331783 - NLS: "Clear" button label in Makefile Project preferences truncated
		//gd.widthHint = 80;
		gd.horizontalAlignment = SWT.RIGHT;

		final Shell shell = parent.getShell();
		clearButton.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent event) {
				String title = Messages.DiscoveryTab_ClearEntries;
				try {
					clearDiscoveredEntries();
					MessageDialog.openInformation(shell, title, Messages.DiscoveryTab_DiscoveredEntriesCleared);
				} catch (CoreException e) {
					MessageDialog.openError(shell, title,
							Messages.DiscoveryTab_ErrorClearingEntries + e.getLocalizedMessage());
				}
			}
		});
	}

	private void enableAllControls() {
		boolean isSCDEnabled = autoDiscoveryCheckBox.getSelection();
		reportProblemsCheckBox.setEnabled(isSCDEnabled);
		profileComboBox.setEnabled(isSCDEnabled);
		profileOptionsComposite.setVisible(isSCDEnabled);
	}

	@Override
	public void updateData(ICResourceDescription rcfg) {
		if (page.isMultiCfg()) {
			setAllVisible(false, null);
			return;
		} else {
			setAllVisible(true, null);
			configPath = rcfg.getPath();
			IConfiguration cfg = getCfg(rcfg.getConfiguration());
			cbi = CfgScannerConfigProfileManager.getCfgScannerConfigBuildInfo(cfg);
			if (!page.isForPrefs() && baseInfoMap == null) {
				try {
					IProject project = cfg.getOwner().getProject();
					IScannerConfigBuilderInfo2Set baseCbi = ScannerConfigProfileManager
							.createScannerConfigBuildInfo2Set(project);
					baseInfoMap = baseCbi.getInfoMap();
				} catch (CoreException e) {
				}
			}
			updateData();
		}
	}

	private void updateData() {
		int selScope = 0;
		String lblText = Messages.DiscoveryTab_5;
		if (!cbi.isPerRcTypeDiscovery()) {
			selScope = 1;
			lblText = Messages.DiscoveryTab_8;
		}
		if (scopeComboBox != null)
			scopeComboBox.select(selScope);
		fTableDefinition.setText(lblText);

		Map<CfgInfoContext, IScannerConfigBuilderInfo2> infoMap = cbi.getInfoMap();
		int pos = resTable.getSelectionIndex();
		resTable.removeAll();
		for (CfgInfoContext cfgInfoContext : infoMap.keySet()) {
			String s = null;
			IResourceInfo rcInfo = cfgInfoContext.getResourceInfo();
			if (rcInfo == null) { // per configuration
				s = cfgInfoContext.getConfiguration().getName();
			} else { // per resource
				if (!configPath.equals(rcInfo.getPath()))
					continue;
				IInputType typ = cfgInfoContext.getInputType();
				if (typ != null)
					s = typ.getName();
				if (s == null) {
					ITool tool = cfgInfoContext.getTool();
					if (tool != null)
						s = tool.getName();
				}
				if (s == null)
					s = Messages.DiscoveryTab_3;
			}
			IScannerConfigBuilderInfo2 bi2 = infoMap.get(cfgInfoContext);
			TableItem ti = new TableItem(resTable, SWT.NONE);
			ti.setText(s);
			ti.setData("cont", cfgInfoContext); //$NON-NLS-1$
			ti.setData("info", bi2); //$NON-NLS-1$
		}
		int len = resTable.getItemCount();
		if (len > 0) {
			setVisibility(null);
			resTable.select((pos < len && pos > -1) ? pos : 0);
			handleToolSelected();
		} else {
			setVisibility(Messages.DiscoveryTab_6);
		}

		setEnablement();
	}

	private void setEnablement() {
		ICConfigurationDescription cfgDescription = page.getResDesc().getConfiguration();
		boolean isEnabled = !LanguageSettingsProvidersPage.isLanguageSettingsProvidersEnabled(getProject())
				|| ScannerDiscoveryLegacySupport.isMbsLanguageSettingsProviderOn(cfgDescription);

		scopeComboBox.setEnabled(isEnabled);
		resTable.setEnabled(isEnabled);
		boolean isSCDEnabled = autoDiscoveryCheckBox.getSelection();
		reportProblemsCheckBox.setEnabled(isEnabled && isSCDEnabled);
		autoDiscoveryCheckBox.setEnabled(isEnabled);
		autoDiscoveryGroup.setEnabled(isEnabled);
		clearButton.setEnabled(isEnabled);

		showStatusLine();
	}

	private void showStatusLine() {
		ICConfigurationDescription cfgDescription = page.getResDesc().getConfiguration();
		boolean isEnabled = !LanguageSettingsProvidersPage.isLanguageSettingsProvidersEnabled(getProject())
				|| ScannerDiscoveryLegacySupport.isMbsLanguageSettingsProviderOn(cfgDescription);
		IStatus status = null;
		if (isEnabled) {
			if (autoDiscoveryCheckBox.getSelection()) {
				status = new Status(IStatus.WARNING, CUIPlugin.PLUGIN_ID,
						"This discovery method is deprecated, use 'Preprocessor Include Paths' instead.");
			}
		} else {
			status = new Status(IStatus.INFO, CUIPlugin.PLUGIN_ID,
					org.eclipse.cdt.internal.ui.newui.Messages.AbstractLangsListTab_MbsProviderNotEnabled);
		}
		fStatusLine.setErrorStatus(status);
	}

	private void setVisibility(String errMsg) {
		autoDiscoveryGroup.setVisible(errMsg == null);
		profileOptionsComposite.setVisible(errMsg == null);
		resTable.setEnabled(errMsg == null);
		if (errMsg != null) {
			String[] ss = errMsg.split("\n"); //$NON-NLS-1$
			for (String line : ss)
				new TableItem(resTable, SWT.NONE).setText(line);
		}
	}

	private String getProfileName(String id) {
		int x = id.lastIndexOf("."); //$NON-NLS-1$
		return (x == -1) ? id : id.substring(x + 1);
	}

	/**
	 * @param toolchain to check
	 * @return if this toolchain is a toolchain created by Makefile project "Other Toolchain".
	 * Note that name of this toolchain set to "No ToolChain" in plugin.xml.
	 */
	private boolean isMakefileProjectToolChain(IToolChain toolchain) {
		return toolchain != null && (toolchain.getId().equals(MAKEFILE_PROJECT_TOOLCHAIN_ID)
				|| isMakefileProjectToolChain(toolchain.getSuperClass()));
	}

	private void handleToolSelected() {
		if (resTable.getSelectionCount() == 0)
			return;

		performOK(false);

		TableItem ti = resTable.getSelection()[0];
		buildInfo = (IScannerConfigBuilderInfo2) ti.getData("info"); //$NON-NLS-1$
		String selectedProfileId = buildInfo.getSelectedProfileId();
		iContext = (CfgInfoContext) ti.getData("cont"); //$NON-NLS-1$
		boolean autodiscoveryEnabled2 = buildInfo.isAutoDiscoveryEnabled();
		if (autodiscoveryEnabled2) {
			IConfiguration cfg = iContext.getConfiguration();
			ICConfigurationDescription cfgDescription = ManagedBuildManager.getDescriptionForConfiguration(cfg);
			autodiscoveryEnabled2 = ScannerDiscoveryLegacySupport.isLegacyScannerDiscoveryOn(cfgDescription);
		}
		autoDiscoveryCheckBox.setSelection(
				autodiscoveryEnabled2 && !selectedProfileId.equals(ScannerConfigProfileManager.NULL_PROFILE_ID));
		reportProblemsCheckBox.setSelection(buildInfo.isProblemReportingEnabled());

		profileComboBox.removeAll();
		List<String> profilesList = buildInfo.getProfileIdList();
		Collections.sort(profilesList, CDTListComparator.getInstance());

		if (realPages != null && realPages.length > 0) {
			for (AbstractDiscoveryPage realPage : realPages) {
				if (realPage != null) {
					realPage.setVisible(false);
					realPage.dispose();
				}
			}
		}

		boolean needPerRcProfile = cbi.isPerRcTypeDiscovery(); // per file, i.e. per input type
		Set<String> contextProfiles = null;
		if (page.isForPrefs()) {
			// for preference page get all profiles
			contextProfiles = new TreeSet<>(profilesList);
		} else {
			// property page
			if (!needPerRcProfile) {
				// configuration-wide (all in tool-chain)
				IConfiguration cfg = iContext.getConfiguration();
				IToolChain toolchain = cfg != null ? cfg.getToolChain() : null;

				if (toolchain == null) {
					ManagedBuilderUIPlugin.log(new Status(IStatus.ERROR, ManagedBuilderUIPlugin.getUniqueIdentifier(),
							"Toolchain=null while trying to get discovery profile per project")); //$NON-NLS-1$
					return;
				}

				if (isMakefileProjectToolChain(toolchain)) {
					// for generic Makefile project let user choose any profile
					contextProfiles = new TreeSet<>(profilesList);
				} else {
					contextProfiles = CfgScannerConfigUtil.getAllScannerDiscoveryProfileIds(toolchain);
				}
				if (contextProfiles.size() == 0) {
					// GCC profile is a sensible default for user to start with
					contextProfiles.add(GCC_PER_PROJECT_PROFILE);
				}

			} else {
				// per language (i.e. input type)
				ITool tool = iContext.getTool();
				if (tool == null)
					return;

				contextProfiles = CfgScannerConfigUtil.getAllScannerDiscoveryProfileIds(tool);
			}
		}

		visibleProfilesList = new ArrayList<>(contextProfiles);

		realPages = new AbstractDiscoveryPage[visibleProfilesList.size()];
		String[] labels = new String[visibleProfilesList.size()];
		String[] profiles = new String[visibleProfilesList.size()];
		int pos = 0;
		for (int counter = 0; counter < visibleProfilesList.size(); counter++) {
			String profileId = visibleProfilesList.get(counter);

			labels[counter] = profiles[counter] = getProfileName(profileId);
			if (profileId.equals(selectedProfileId))
				pos = counter;
			buildInfo.setSelectedProfileId(profileId); // needs to create page
			for (DiscoveryProfilePageConfiguration p : pagesList) {
				if (p != null && p.profId.equals(profileId)) {
					AbstractDiscoveryPage pg = p.getPage();
					if (pg != null) {
						realPages[counter] = pg;
						String s = p.name;
						if (s != null && s.length() > 0)
							labels[counter] = s;
						pg.setContainer(wrapper);
						pg.createControl(profileOptionsComposite);
						profileOptionsComposite.layout(true);
						break;
					}
				}
			}
		}
		profileComboBox.setItems(normalize(labels, profiles, visibleProfilesList.size()));

		buildInfo.setSelectedProfileId(selectedProfileId);
		if (profileComboBox.getItemCount() > 0)
			profileComboBox.select(pos);
		enableAllControls();
		handleDiscoveryProfileChanged();
	}

	private String[] normalize(String[] labels, String[] ids, int counter) {
		int mode = CDTPrefUtil.getInt(CDTPrefUtil.KEY_DISC_NAMES);
		String[] tmp = new String[counter];
		// Always show either Name + ID, or ID only
		// These cases do not require checking for doubles.
		if (mode == CDTPrefUtil.DISC_NAMING_ALWAYS_BOTH || mode == CDTPrefUtil.DISC_NAMING_ALWAYS_IDS) {
			for (int i = 0; i < counter; i++) {
				tmp[i] = (mode == CDTPrefUtil.DISC_NAMING_ALWAYS_IDS) ? ids[i] : combine(labels[i], ids[i]);
			}
			return tmp;
		}

		// For not-unique names only, either display ID or name + ID
		boolean doubles = false;
		// quick check for at least one double
		outer: for (int i = 0; i < counter; i++) {
			for (int j = 0; j < counter; j++) {
				// sic! i < j, to avoid repeated comparison
				if (i < j && labels[i].equals(labels[j])) {
					doubles = true;
					break outer;
				}
			}
		}
		if (!doubles) { // all names are unique.
			for (int i = 0; i < counter; i++)
				tmp[i] = labels[i];
		} else {
			for (int i = 0; i < counter; i++) {
				doubles = false;
				for (int j = 0; j < counter; j++) {
					if (i != j && labels[i].equals(labels[j])) {
						doubles = true;
						break;
					}
				}
				if (doubles) {
					if (mode == CDTPrefUtil.DISC_NAMING_UNIQUE_OR_IDS)
						tmp[i] = ids[i];
					else
						// replace with Name + Id
						tmp[i] = combine(labels[i], ids[i]);
				} else { // this name is unique - no changes !
					tmp[i] = labels[i];
				}
			}
		}
		return tmp;
	}

	private String combine(String s1, String s2) {
		if (s1.equals(s2))
			return s1;
		else
			return s1 + " (" + s2 + ")"; //$NON-NLS-1$ //$NON-NLS-2$
	}

	private void handleDiscoveryProfileChanged() {
		int pos = profileComboBox.getSelectionIndex();
		if (realPages != null) {
			for (int i = 0; i < realPages.length; i++)
				if (realPages[i] != null)
					realPages[i].setVisible(i == pos);
		}
	}

	/**
	 *
	 */
	private void initializeProfilePageMap() {
		GCCPerProjectSCDProfilePage.isSIConsoleEnabled = DefaultRunSIProvider.isConsoleEnabled();

		pagesList = new ArrayList<>(5);
		IExtensionPoint point = Platform.getExtensionRegistry().getExtensionPoint(NAMESPACE, POINT);
		if (point == null)
			return;
		IConfigurationElement[] infos = point.getConfigurationElements();
		for (IConfigurationElement info : infos) {
			if (info.getName().equals(PROFILE_PAGE)) {
				pagesList.add(new DiscoveryProfilePageConfiguration(info));
			}
		}
	}

	/**
	 * Create a profile page only on request
	 *
	 * @author vhirsl
	 */
	protected static class DiscoveryProfilePageConfiguration {
		IConfigurationElement fElement;
		private String profId, name;

		protected DiscoveryProfilePageConfiguration(IConfigurationElement element) {
			fElement = element;
			profId = fElement.getAttribute(PROFILE_ID);
			name = fElement.getAttribute(PROFILE_NAME);
		}

		protected String getName() {
			return name;
		}

		private AbstractDiscoveryPage getPage() {
			try {
				return (AbstractDiscoveryPage) fElement.createExecutableExtension("class"); //$NON-NLS-1$
			} catch (CoreException e) {
				return null;
			}
		}
	}

	@Override
	public void performApply(ICResourceDescription src, ICResourceDescription dst) {
		if (page.isMultiCfg())
			return;
		ICfgScannerConfigBuilderInfo2Set cbi1 = CfgScannerConfigProfileManager
				.getCfgScannerConfigBuildInfo(getCfg(src.getConfiguration()));
		ICfgScannerConfigBuilderInfo2Set cbi2 = CfgScannerConfigProfileManager
				.getCfgScannerConfigBuildInfo(getCfg(dst.getConfiguration()));
		cbi2.setPerRcTypeDiscovery(cbi1.isPerRcTypeDiscovery());

		Map<CfgInfoContext, IScannerConfigBuilderInfo2> m1 = cbi1.getInfoMap();
		Map<CfgInfoContext, IScannerConfigBuilderInfo2> m2 = cbi2.getInfoMap();
		for (CfgInfoContext ic : m2.keySet()) {
			if (m1.keySet().contains(ic)) {
				IScannerConfigBuilderInfo2 bi1 = m1.get(ic);
				try {
					cbi2.applyInfo(ic, bi1);
				} catch (CoreException e) {
					ManagedBuilderUIPlugin.log(e);
				}
			} else {
				IStatus status = new Status(IStatus.ERROR, ManagedBuilderUIPlugin.getUniqueIdentifier(),
						Messages.DiscoveryTab_7);
				ManagedBuilderUIPlugin.log(status);
			}
		}

		clearChangedDiscoveredInfos();

		DefaultRunSIProvider.setConsoleEnabled(GCCPerProjectSCDProfilePage.isSIConsoleEnabled);
	}

	@Override
	protected void performOK() {
		performOK(true);
		DefaultRunSIProvider.setConsoleEnabled(GCCPerProjectSCDProfilePage.isSIConsoleEnabled);
	}

	private void performOK(boolean ok) {
		if (page.isMultiCfg())
			return;
		if (buildInfo == null)
			return;
		String savedId = buildInfo.getSelectedProfileId();
		if (realPages != null) {
			for (int i = 0; i < realPages.length; i++) {
				if (realPages[i] != null) {
					String s = visibleProfilesList.get(i);
					buildInfo.setSelectedProfileId(s);
					realPages[i].performApply();
					realPages[i].setVisible(false);
				}
			}
		}
		buildInfo.setSelectedProfileId(savedId);
		handleDiscoveryProfileChanged();
		if (ok)
			clearChangedDiscoveredInfos();
	}

	private void clearChangedDiscoveredInfos() {
		IProject project = getProject();
		List<CfgInfoContext> changedContexts = checkChanges();
		for (CfgInfoContext c : changedContexts) {
			CfgDiscoveredPathManager.getInstance().removeDiscoveredInfo(project, c);
			// MakeCorePlugin.getDefault().getDiscoveryManager().removeDiscoveredInfo(c.getProject(), c);
		}
	}

	private List<CfgInfoContext> checkChanges() {
		if (cbi == null || baseInfoMap == null)
			return new ArrayList<>(0);

		Map<CfgInfoContext, IScannerConfigBuilderInfo2> cfgInfoMap = cbi.getInfoMap();
		HashMap<InfoContext, Object> baseCopy = new HashMap<>(baseInfoMap);
		List<CfgInfoContext> list = new ArrayList<>();
		for (Map.Entry<CfgInfoContext, IScannerConfigBuilderInfo2> entry : cfgInfoMap.entrySet()) {
			CfgInfoContext cic = entry.getKey();
			InfoContext c = cic.toInfoContext();
			if (c == null)
				continue;

			IScannerConfigBuilderInfo2 changed = entry.getValue();
			IScannerConfigBuilderInfo2 old = (IScannerConfigBuilderInfo2) baseCopy.remove(c);

			if (old == null) {
				list.add(cic);
			} else if (!settingsEqual(changed, old)) {
				list.add(cic);
			}
		}

		if (baseCopy.size() != 0) {
			IConfiguration cfg = cbi.getConfiguration();
			for (InfoContext c : baseCopy.keySet()) {
				CfgInfoContext cic = CfgInfoContext.fromInfoContext(cfg, c);
				if (cic != null)
					list.add(cic);
			}
		}

		return list;
	}

	private boolean settingsEqual(IScannerConfigBuilderInfo2 info1, IScannerConfigBuilderInfo2 info2) {
		if (!CDataUtil.objectsEqual(info1.getSelectedProfileId(), info2.getSelectedProfileId()))
			return false;
		if (!CDataUtil.objectsEqual(info1.getBuildOutputFilePath(), info2.getBuildOutputFilePath()))
			return false;
		if (!CDataUtil.objectsEqual(info1.getContext(), info2.getContext()))
			return false;
		if (!CDataUtil.objectsEqual(info1.getSelectedProfileId(), info2.getSelectedProfileId()))
			return false;
		if (info1.isAutoDiscoveryEnabled() != info2.isAutoDiscoveryEnabled()
				|| info1.isBuildOutputFileActionEnabled() != info2.isBuildOutputFileActionEnabled()
				|| info1.isBuildOutputParserEnabled() != info2.isBuildOutputParserEnabled()
				|| info1.isProblemReportingEnabled() != info2.isProblemReportingEnabled())
			return false;
		if (!listEqual(info1.getProfileIdList(), info2.getProfileIdList()))
			return false;
		if (!listEqual(info1.getProviderIdList(), info2.getProviderIdList()))
			return false;
		return true;
	}

	private boolean listEqual(List<String> l1, List<String> l2) {
		if (l1 == null && l2 == null)
			return true;
		if (l1 == null || l2 == null)
			return false;
		if (l1.size() != l2.size())
			return false;
		// both lists have items in the same order ?
		// since it's most probable, try it first.
		if (l1.equals(l2))
			return true;
		// order may differ...
		for (String s : l1)
			if (!l2.contains(s))
				return false;
		return true;
	}

	@Override
	public boolean canBeVisible() {
		if (page.isMultiCfg()) {
			setAllVisible(false, null);
			return false;
		}
		setAllVisible(true, null);
		if (page.isForProject() || page.isForPrefs())
			return true;
		// Hide this page for folders and files
		// if Discovery scope is "per configuration", not "per resource"
		IConfiguration cfg = getCfg(page.getResDesc().getConfiguration());
		ICfgScannerConfigBuilderInfo2Set _cbi = CfgScannerConfigProfileManager.getCfgScannerConfigBuildInfo(cfg);
		return _cbi.isPerRcTypeDiscovery();
	}

	/**
	 * IBuildInfoContainer methods - called from dynamic pages
	 */
	@Override
	public IScannerConfigBuilderInfo2 getBuildInfo() {
		return buildInfo;
	}

	@Override
	public CfgInfoContext getContext() {
		return iContext;
	}

	@Override
	public IProject getProject() {
		return page.getProject();
	}

	@Override
	public ICConfigurationDescription getConfiguration() {
		return getResDesc().getConfiguration();
	}

	@Override
	protected void performDefaults() {
		if (page.isMultiCfg())
			return;
		cbi.setPerRcTypeDiscovery(true);
		for (CfgInfoContext cic : cbi.getInfoMap().keySet()) {
			try {
				cbi.applyInfo(cic, null);
			} catch (CoreException e) {
			}
		}
		updateData();

		DefaultRunSIProvider.setConsoleEnabled(false);
	}

	@Override
	protected void updateButtons() {
		// Do nothing. No buttons to update.
	}

	private void clearDiscoveredEntries() throws CoreException {
		CfgInfoContext cfgInfoContext = getContext();

		IConfiguration cfg = cfgInfoContext.getConfiguration();
		if (cfg == null) {
			cfg = cfgInfoContext.getResourceInfo().getParent();
		}
		if (cfg == null) {
			Status status = new Status(IStatus.ERROR, ManagedBuilderUIPlugin.getUniqueIdentifier(),
					"Unexpected cfg=null while trying to clear discovery entries"); //$NON-NLS-1$
			throw new CoreException(status);
		}

		IProject project = (IProject) cfg.getOwner();

		DiscoveredPathInfo pathInfo = new DiscoveredPathInfo(project);
		InfoContext infoContext = cfgInfoContext.toInfoContext();

		// 1. Remove scanner info from .metadata/.plugins/org.eclipse.cdt.make.core/Project.sc
		DiscoveredScannerInfoStore dsiStore = DiscoveredScannerInfoStore.getInstance();
		dsiStore.saveDiscoveredScannerInfoToState(project, infoContext, pathInfo);

		// 2. Remove scanner info from CfgDiscoveredPathManager cache and from the Tool
		CfgDiscoveredPathManager cdpManager = CfgDiscoveredPathManager.getInstance();
		cdpManager.removeDiscoveredInfo(project, cfgInfoContext);

		// 3. Remove scanner info from SI collector
		ICfgScannerConfigBuilderInfo2Set info2 = CfgScannerConfigProfileManager.getCfgScannerConfigBuildInfo(cfg);
		Map<CfgInfoContext, IScannerConfigBuilderInfo2> infoMap2 = info2.getInfoMap();
		IScannerConfigBuilderInfo2 buildInfo2 = infoMap2.get(cfgInfoContext);
		if (buildInfo2 != null) {
			ScannerConfigProfileManager scpManager = ScannerConfigProfileManager.getInstance();
			String selectedProfileId = buildInfo2.getSelectedProfileId();
			SCProfileInstance profileInstance = scpManager.getSCProfileInstance(project, infoContext,
					selectedProfileId);

			IScannerInfoCollector collector = profileInstance.getScannerInfoCollector();
			if (collector instanceof IScannerInfoCollectorCleaner) {
				((IScannerInfoCollectorCleaner) collector).deleteAll(project);
			}
			buildInfo2 = null;
		}
	}
}
