/*******************************************************************************
 * Copyright (c) 2007, 2018 IBM 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.debug.internal.ui.preferences;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationType;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.internal.core.LaunchManager;
import org.eclipse.debug.internal.ui.AbstractDebugListSelectionDialog;
import org.eclipse.debug.internal.ui.DebugUIPlugin;
import org.eclipse.debug.internal.ui.DefaultLabelProvider;
import org.eclipse.debug.internal.ui.IDebugHelpContextIds;
import org.eclipse.debug.internal.ui.SWTFactory;
import org.eclipse.debug.internal.ui.launchConfigurations.LaunchConfigurationComparator;
import org.eclipse.debug.internal.ui.launchConfigurations.LaunchConfigurationManager;
import org.eclipse.debug.internal.ui.launchConfigurations.LaunchConfigurationsDialog;
import org.eclipse.debug.ui.ILaunchGroup;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Table;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.PropertyPage;
import org.eclipse.ui.dialogs.SelectionDialog;

import com.ibm.icu.text.MessageFormat;

/**
 * Displays default launch configuration settings for a selected resource - associated launch configurations.
 *
 * @see PropertyPage
 * @see ILaunchConfiguration
 * @see LaunchConfigurationsDialog
 *
 * CONTEXTLAUNCHING
 *
 * @since 3.3.0
 */
public class RunDebugPropertiesPage extends PropertyPage {
	/**
	 * Set of configurations to be deleted
	 */
	private Set<ILaunchConfigurationWorkingCopy> fDeletedConfigurations = new HashSet<>();

	/**
	 * Set of original default candidates for the resource
	 */
	private Set<ILaunchConfiguration> fOriginalCandidates;

	/**
	 * Holds configurations that need to be saved when the page closes
	 */
	private Set<ILaunchConfigurationWorkingCopy> fChangedConfigurations = new HashSet<>();

	/**
	 * List of the applicable launch config types for the backing resource
	 */
	private List<ILaunchConfigurationType> fTypeCandidates = null;

	//widgets
	private TableViewer fViewer;
	private Button fNewButton = null;
	private Button fEditButton = null;
	private Button fDuplicateButton = null;
	private Button fDeleteButton = null;

	@Override
	protected Control createContents(Composite parent) {
		PlatformUI.getWorkbench().getHelpSystem().setHelp(parent, IDebugHelpContextIds.RUN_DEBUG_RESOURCE_PROPERTY_PAGE);
		collectConfigCandidates(getResource());
		Composite topComposite = SWTFactory.createComposite(parent, 2, 1, GridData.FILL_BOTH);

		SWTFactory.createWrapLabel(topComposite, DebugPreferencesMessages.DefaultLaunchConfigurationsPropertiesPage_0, 2, 300);
		SWTFactory.createVerticalSpacer(topComposite, 2);
		SWTFactory.createWrapLabel(topComposite, MessageFormat.format(DebugPreferencesMessages.DefaultLaunchConfigurationsPropertiesPage_1, new Object[] { getResource().getName() }), 2, 300);
		fViewer = createViewer(topComposite);

		Composite buttonComp = SWTFactory.createComposite(topComposite, 1, 1, GridData.FILL_VERTICAL);
		GridLayout layout = (GridLayout) buttonComp.getLayout();
		layout.marginHeight = 0;
		fNewButton = SWTFactory.createPushButton(buttonComp, DebugPreferencesMessages.DefaultLaunchConfigurationsPropertiesPage_2, null);
		fNewButton.setToolTipText(DebugPreferencesMessages.DefaultLaunchConfigurationsPropertiesPage_3);
		fNewButton.setEnabled(collectTypeCandidates().length > 0);
		fNewButton.addSelectionListener(new SelectionListener() {
			@Override
			public void widgetDefaultSelected(SelectionEvent e) {}
			@Override
			public void widgetSelected(SelectionEvent e) {
				handleNew();
			}
		});

		fDuplicateButton = SWTFactory.createPushButton(buttonComp, DebugPreferencesMessages.DefaultLaunchConfigurationsPropertiesPage_4, null);
		fDuplicateButton.setToolTipText(DebugPreferencesMessages.DefaultLaunchConfigurationsPropertiesPage_5);
		fDuplicateButton.setEnabled(false);
		fDuplicateButton.addSelectionListener(new SelectionListener() {
			@Override
			public void widgetDefaultSelected(SelectionEvent e) {}
			@Override
			public void widgetSelected(SelectionEvent e) {
				handleCopy();
			}
		});
		fEditButton = SWTFactory.createPushButton(buttonComp, DebugPreferencesMessages.DefaultLaunchConfigurationsPropertiesPage_6, null);
		fEditButton.setToolTipText(DebugPreferencesMessages.DefaultLaunchConfigurationsPropertiesPage_7);
		fEditButton.setEnabled(false);
		fEditButton.addSelectionListener(new SelectionListener() {
			@Override
			public void widgetDefaultSelected(SelectionEvent e) {}
			@Override
			public void widgetSelected(SelectionEvent e) {
				handleEdit();
			}
		});
		fDeleteButton = SWTFactory.createPushButton(buttonComp, DebugPreferencesMessages.DefaultLaunchConfigurationsPropertiesPage_8, null);
		fDeleteButton.setToolTipText(DebugPreferencesMessages.DefaultLaunchConfigurationsPropertiesPage_9);
		fDeleteButton.setEnabled(false);
		fDeleteButton.addSelectionListener(new SelectionListener() {
			@Override
			public void widgetDefaultSelected(SelectionEvent e) {}
			@Override
			public void widgetSelected(SelectionEvent e) {
				handleDelete();
			}
		});

		fViewer.setSelection(new StructuredSelection());
		applyDialogFont(topComposite);
		return topComposite;
	}

	/**
	 * Creates and returns the viewer that will display the possible default configurations.
	 *
	 * @param parent parent composite to create the viewer in
	 * @return viewer viewer that will display possible default configurations
	 */
	protected TableViewer createViewer(Composite parent){
		TableViewer viewer = new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.BORDER);
		viewer.setLabelProvider(new DefaultLabelProvider());
		viewer.setContentProvider(new ArrayContentProvider());
		viewer.setComparator(new LaunchConfigurationComparator());
		Table builderTable = viewer.getTable();
		GridData tableGridData = new GridData(GridData.FILL_BOTH);
		tableGridData.heightHint = 300;
		tableGridData.widthHint = 300;
		builderTable.setLayoutData(tableGridData);
		IResource resource = getResource();
		viewer.setInput(collectConfigCandidates(resource));
		viewer.addSelectionChangedListener(new ISelectionChangedListener() {
			@Override
			public void selectionChanged(SelectionChangedEvent event) {
				ISelection sel = event.getSelection();
				if(sel instanceof IStructuredSelection) {
					IStructuredSelection ss = (IStructuredSelection) sel;
					boolean empty = ss.isEmpty();
					int size = ss.size();
					fEditButton.setEnabled(!empty && size == 1);
					fDuplicateButton.setEnabled(!empty && size == 1);
					fDeleteButton.setEnabled(!empty);
					setErrorMessage(null);
				}
			}
		});
		viewer.addDoubleClickListener(new IDoubleClickListener() {
			@Override
			public void doubleClick(DoubleClickEvent arg0) {
				handleEdit();
			}
		});
		return viewer;
	}

	@Override
	public void dispose() {
		if(fOriginalCandidates != null) {
			fOriginalCandidates.clear();
			fOriginalCandidates = null;
		}
		if(fChangedConfigurations != null) {
			fChangedConfigurations.clear();
			fChangedConfigurations = null;
		}
		super.dispose();
	}

	/**
	 * Returns the viewer displaying possible default configurations.
	 *
	 * @return viewer
	 */
	protected TableViewer getViewer() {
		return fViewer;
	}

	/**
	 * Returns the launch manager
	 * @return the launch manager
	 */
	protected LaunchManager getLaunchManager() {
		return (LaunchManager) DebugPlugin.getDefault().getLaunchManager();
	}

	/**
	 * Collects the applicable launch configuration types for the backing resource.
	 * Default implementation uses the launch shortcut evaluation expressions and leverages the
	 * mapping of launch shortcut to config type id to derive the applicable types.
	 * @return the listing of applicable launch configuration types for the backing resource
	 */
	protected ILaunchConfigurationType[] collectTypeCandidates() {
		if(fTypeCandidates == null) {
			String[] types = DebugUIPlugin.getDefault().getLaunchConfigurationManager().getApplicableConfigurationTypes(getResource());
			fTypeCandidates = new ArrayList<>(types.length);
			for(int i = 0; i < types.length; i++) {
				fTypeCandidates.add(DebugPlugin.getDefault().getLaunchManager().getLaunchConfigurationType(types[i]));
			}

			Collections.sort(fTypeCandidates, new Comparator<ILaunchConfigurationType>() {
				@Override
				public int compare(ILaunchConfigurationType o1, ILaunchConfigurationType o2) {
					ILaunchConfigurationType t1 = o1;
					ILaunchConfigurationType t2 = o2;
					return t1.getName().compareTo(t2.getName());
				}
			});
		}
		return fTypeCandidates.toArray(new ILaunchConfigurationType[fTypeCandidates.size()]);
	}

	/**
	 * Returns a set of potential default configurations candidates for the given
	 * resource. The configurations are working copies.
	 *
	 * @param resource resource
	 * @return list of default candidates
	 */
	protected Set<ILaunchConfiguration> collectConfigCandidates(IResource resource) {
		if(fOriginalCandidates == null) {
			fOriginalCandidates = new HashSet<>();
			try {
				ILaunchConfiguration[] configs = DebugUIPlugin.getDefault().getLaunchConfigurationManager().getApplicableLaunchConfigurations(null, resource);
				for(int i = 0; i < configs.length; i++) {
					fOriginalCandidates.add(configs[i].getWorkingCopy());
				}
			}
			catch(CoreException ce) {DebugUIPlugin.log(ce);}
		}
		return fOriginalCandidates;
	}



	/**
	 * Returns the resource this property page is open on.
	 *
	 * @return resource
	 */
	protected IResource getResource() {
		IAdaptable element = getElement();
		IResource resource = null;
		if (element instanceof IResource) {
			resource = (IResource) element;
		} else if (element != null) {
			resource = element.getAdapter(IResource.class);
		}
		return resource;
	}

	@Override
	public boolean performOk() {
	//delete
		for (ILaunchConfigurationWorkingCopy currentConfig : fDeletedConfigurations) {
			try{
				if (currentConfig.getOriginal() != null){
					currentConfig.getOriginal().delete();
				}
			} catch (CoreException e) {
				DebugPlugin.logMessage("Problem deleting configuration " + currentConfig.getName(), e); //$NON-NLS-1$
			}
		}
	//add
		for (ILaunchConfigurationWorkingCopy currentConfig : fChangedConfigurations) {
			try{
				currentConfig.doSave();
			} catch (CoreException e) {
				DebugPlugin.logMessage("Problem saving changes to configuration " + currentConfig.getName(), e); //$NON-NLS-1$
			}
		}

		return super.performOk();
	}

	@Override
	protected void performDefaults() {
		setErrorMessage(null);
		setValid(true);
		fOriginalCandidates.clear();
		fOriginalCandidates = null;
		getViewer().setInput(collectConfigCandidates(getResource()));
		fChangedConfigurations.clear();
		fDeletedConfigurations.clear();
		fViewer.refresh(true, true);
		super.performDefaults();
	}

	/**
	 * Returns the names of the launch configurations passed in as original input to the tree viewer
	 * @return the names of the original launch configurations
	 */
	private Set<String> getConfigurationNames() {
		Set<String> names = new HashSet<>();
		for(ILaunchConfiguration config : fOriginalCandidates) {
			names.add(config.getName());
		}

		for(ILaunchConfigurationWorkingCopy config : fChangedConfigurations) {
			names.add(config.getName());
		}
		return names;
	}

	/**
	 * Returns selected configurations.
	 *
	 * @return selected configurations
	 */
	private ILaunchConfigurationWorkingCopy[] getSelectedConfigurations() {
		IStructuredSelection ss = fViewer.getStructuredSelection();
		return (ILaunchConfigurationWorkingCopy[]) ss.toList().toArray(new ILaunchConfigurationWorkingCopy[ss.size()]);
	}

	/**
	 * Copy the selection
	 */
	private void handleCopy() {
		ILaunchConfigurationWorkingCopy configuration = getSelectedConfigurations()[0];
		try {
			ILaunchConfigurationWorkingCopy copy = configuration.copy(
					((LaunchManager)DebugPlugin.getDefault().getLaunchManager()).generateUniqueLaunchConfigurationNameFrom(configuration.getName(), getConfigurationNames()));
			copy.setAttributes(configuration.getAttributes());
			fChangedConfigurations.add(copy);
			fViewer.add(copy);
			fViewer.setSelection(new StructuredSelection(copy));
		} catch (CoreException e) {
			setErrorMessage(e.getMessage());
		}
	}

	/**
	 * Delete the selection
	 */
	private void handleDelete() {
		Table table = fViewer.getTable();
		int[] indices = table.getSelectionIndices();
		Arrays.sort(indices);
		ILaunchConfigurationWorkingCopy[] configurations = getSelectedConfigurations();
		for (int i = 0; i < configurations.length; i++) {
			fDeletedConfigurations.add(configurations[i]);
			fChangedConfigurations.remove(configurations[i]);
			fViewer.remove(configurations[i]);
		}
		if (indices[0] < table.getItemCount()) {
			fViewer.setSelection(new StructuredSelection(table.getItem(indices[0]).getData()));
		} else if (table.getItemCount() > 0) {
			fViewer.setSelection(new StructuredSelection(table.getItem(table.getItemCount() - 1).getData()));
		}
	}

	/**
	 * Edit the selection
	 */
	private void handleEdit() {
		ILaunchConfigurationWorkingCopy config = getSelectedConfigurations()[0];
		int ret = edit(config, false);
		if(ret == IDialogConstants.OK_ID) {
			fChangedConfigurations.add(config);
			fViewer.refresh(config, true, true);
		}
		else if(ret == IDialogConstants.ABORT_ID) {
			setErrorMessage(MessageFormat.format(DebugPreferencesMessages.RunDebugPropertiesPage_0, new Object[] { config.getName() }));
		}
	}

	/**
	 * Edits the given configuration as a nested working copy.
	 * Returns the code from the dialog used to edit the configuration.
	 *
	 * @param configuration the configuration working copy to editor
	 * @param setDefaults whether to set default values in the config
	 * @return dialog return code - OK or CANCEL
	 */
	private int edit(ILaunchConfigurationWorkingCopy configuration, boolean setDefaults) {
		try {
			LaunchConfigurationManager lcm = DebugUIPlugin.getDefault().getLaunchConfigurationManager();
			ILaunchGroup group = null;
			// bug 208034, we should try modes we know about first then guess
			ILaunchConfigurationType type = configuration.getType();
			if(type.supportsMode(ILaunchManager.RUN_MODE)) {
				group = lcm.getLaunchGroup(type, ILaunchManager.RUN_MODE);
			}
			else if(type.supportsMode(ILaunchManager.DEBUG_MODE)) {
				group = lcm.getLaunchGroup(type, ILaunchManager.DEBUG_MODE);
			}
			else if(type.supportsMode(ILaunchManager.PROFILE_MODE)) {
				group = lcm.getLaunchGroup(type, ILaunchManager.PROFILE_MODE);
			}
			else {
				for (Set<String> modes : type.getSupportedModeCombinations()) {
					group = DebugUIPlugin.getDefault().getLaunchConfigurationManager().getLaunchGroup(type, modes);
					if(group != null) {
						break;
					}
				}
			}
			if(group != null) {
				return DebugUIPlugin.openLaunchConfigurationPropertiesDialog(getShell(), configuration, group.getIdentifier(), getConfigurationNames(), null, setDefaults);
			}
		}
		catch(CoreException ce) {}
		return IDialogConstants.ABORT_ID;
	}

	/**
	 * Create a new configuration
	 */
	private void handleNew() {

		final ILaunchConfigurationType[] typeCandidates = collectTypeCandidates();

		SelectionDialog dialog = new AbstractDebugListSelectionDialog(getShell()){

			@Override
			protected String getDialogSettingsId() {
				return DebugUIPlugin.getUniqueIdentifier() + ".SELECT_CONFIGURATION_TYPE_DIALOG"; //$NON-NLS-1$
			}

			@Override
			protected Object getViewerInput() {
				return typeCandidates;
			}

			@Override
			protected String getHelpContextId() {
				return IDebugHelpContextIds.SELECT_CONFIGURATION_TYPE_DIALOG;
			}

			@Override
			protected String getViewerLabel() {
				return DebugPreferencesMessages.DefaultLaunchConfigurationsPropertiesPage_12;
			}

		};
		dialog.setTitle(DebugPreferencesMessages.DefaultLaunchConfigurationsPropertiesPage_11);

		if (dialog.open() == Window.OK) {
			Object[] result = dialog.getResult();
			if (result.length == 1) {
				ILaunchConfigurationType type = (ILaunchConfigurationType) result[0];
				try {
					ILaunchConfigurationWorkingCopy wc = type.newInstance(null,
							((LaunchManager)DebugPlugin.getDefault().getLaunchManager()).
							generateUniqueLaunchConfigurationNameFrom("New_configuration", getConfigurationNames())); //$NON-NLS-1$
					int ret = edit(wc, true);
					if (ret == Window.OK) {
						fChangedConfigurations.add(wc);
						fViewer.add(wc);
						fViewer.setSelection(new StructuredSelection(wc));
					}
					else if(ret == IDialogConstants.ABORT_ID) {
						setErrorMessage(MessageFormat.format(DebugPreferencesMessages.RunDebugPropertiesPage_0, new Object[] { wc.getName() }));
					}
				} catch (CoreException e) {
					setErrorMessage(e.getMessage());
				}
			}
		}
	}
}
