/*******************************************************************************
 * Copyright (c) 2010, 2014 Andrew Gvozdev 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:
 *     Andrew Gvozdev (Quoin Inc.) - initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.managedbuilder.internal.ui.commands;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
import org.eclipse.cdt.core.settings.model.ICProjectDescription;
import org.eclipse.cdt.managedbuilder.ui.properties.ManagedBuilderUIImages;
import org.eclipse.cdt.managedbuilder.ui.properties.ManagedBuilderUIPlugin;
import org.eclipse.cdt.ui.CUIPlugin;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
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.Shell;
import org.eclipse.ui.dialogs.ContainerCheckedTreeViewer;
import org.eclipse.ui.internal.ide.actions.BuildUtilities;
import org.eclipse.ui.model.WorkbenchLabelProvider;

/**
 * Dialog to let the user to clean and rebuild configurations of the selected projects.
 */
public class RebuildConfigurationsDialog extends MessageDialog {
	private static final String DIALOG_SETTINGS_SECTION = "RebuildConfigurationsDialogSettings"; //$NON-NLS-1$
	private static final String DIALOG_ORIGIN_X = "DIALOG_X_ORIGIN"; //$NON-NLS-1$
	private static final String DIALOG_ORIGIN_Y = "DIALOG_Y_ORIGIN"; //$NON-NLS-1$
	private static final String DIALOG_WIDTH = "DIALOG_WIDTH"; //$NON-NLS-1$
	private static final String DIALOG_HEIGHT = "DIALOG_HEIGHT"; //$NON-NLS-1$

	private Button cleanCheckbox;
	private Button buildCheckbox;

	private ContainerCheckedTreeViewer cfgCheckboxViewer;

	private IProject[] projects;
	private Object[] selected;
	private int cleanKind;
	private int buildKind;

	private static class ConfigurationLabelProvider implements ILabelProvider {
		WorkbenchLabelProvider workbenchLabelProvider = new WorkbenchLabelProvider();

		@Override
		public void addListener(ILabelProviderListener listener) {
			workbenchLabelProvider.addListener(listener);
		}

		@Override
		public void dispose() {
			workbenchLabelProvider.dispose();
		}

		@Override
		public boolean isLabelProperty(Object element, String property) {
			return workbenchLabelProvider.isLabelProperty(element, property);
		}

		@Override
		public void removeListener(ILabelProviderListener listener) {
			workbenchLabelProvider.removeListener(listener);
		}

		@Override
		public Image getImage(Object element) {
			if (element instanceof ICConfigurationDescription)
				return ManagedBuilderUIImages.get(ManagedBuilderUIImages.IMG_BUILD_CONFIG);
			return workbenchLabelProvider.getImage(element);
		}

		@Override
		public String getText(Object element) {
			if (element instanceof ICConfigurationDescription) {
				ICConfigurationDescription cfgDescription = (ICConfigurationDescription) element;
				String name = cfgDescription.getName();
				if (cfgDescription.isActive()) {
					return name + ' ' + Messages.RebuildConfigurationsDialog_Active;
				}
				return name;
			}

			return workbenchLabelProvider.getText(element);
		}
	}

	private class ConfigurationContentProvider implements ITreeContentProvider {
		@Override
		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
		}

		@Override
		public void dispose() {
		}

		@Override
		public Object[] getElements(Object inputElement) {
			return getChildren(inputElement);
		}

		@Override
		public boolean hasChildren(Object element) {
			if (element instanceof IProject[])
				return ((IProject[]) element).length > 0;

			if (element instanceof IProject) {
				IProject project = (IProject) element;
				ICProjectDescription prjd = CoreModel.getDefault().getProjectDescription(project, false);
				if (prjd == null)
					return false;

				ICConfigurationDescription[] cfgDescriptions = prjd.getConfigurations();
				return cfgDescriptions != null && cfgDescriptions.length > 0;
			}
			return false;
		}

		@Override
		public Object getParent(Object element) {
			if (element instanceof IProject) {
				return projects;
			}
			if (element instanceof ICConfigurationDescription) {
				ICConfigurationDescription cfgDescription = (ICConfigurationDescription) element;
				return cfgDescription.getProjectDescription().getProject();
			}
			return null;
		}

		@Override
		public Object[] getChildren(Object parentElement) {
			if (parentElement instanceof IProject[])
				return (IProject[]) parentElement;

			if (parentElement instanceof IProject) {
				IProject project = (IProject) parentElement;
				ICProjectDescription prjd = CoreModel.getDefault().getProjectDescription(project, false);
				if (prjd != null) {
					ICConfigurationDescription[] cfgDescriptions = prjd.getConfigurations();
					return cfgDescriptions;
				}
			}
			return new Object[0];
		}
	}

	/**
	 * Creates a new Clean and Build dialog.
	 *
	 * @param projects - the currently selected projects
	 */
	public RebuildConfigurationsDialog(IProject[] projects) {
		super(CUIPlugin.getActiveWorkbenchShell(), Messages.RebuildConfigurationsDialog_RebuildConfigurations, null,
				Messages.RebuildConfigurationsDialog_SelectConfigurations, NONE,
				new String[] { IDialogConstants.OK_LABEL, IDialogConstants.CANCEL_LABEL }, 0);
		this.projects = projects;
	}

	@Override
	protected void buttonPressed(int buttonId) {
		super.buttonPressed(buttonId);
		if (buttonId != IDialogConstants.OK_ID) {
			return;
		}

		// Save all dirty editors.
		BuildUtilities.saveEditors(null);

		if (selected != null) {
			List<ICConfigurationDescription> cfgDescriptions = new ArrayList<>();
			for (Object sel : selected) {
				if (sel instanceof ICConfigurationDescription) {
					cfgDescriptions.add((ICConfigurationDescription) sel);
				}
			}

			if (cleanKind != 0 || buildKind != 0) {
				ICConfigurationDescription[] cfgdArray = cfgDescriptions
						.toArray(new ICConfigurationDescription[cfgDescriptions.size()]);
				Job buildJob = new BuildConfigurationsJob(cfgdArray, cleanKind, buildKind);
				buildJob.schedule();
			}
		}
	}

	@Override
	protected Control createCustomArea(Composite parent) {
		Composite area = new Composite(parent, SWT.NONE);
		GridLayout layout = new GridLayout();
		layout.marginWidth = layout.marginHeight = 0;
		layout.numColumns = 2;
		layout.makeColumnsEqualWidth = true;
		area.setLayout(layout);
		area.setLayoutData(new GridData(GridData.FILL_BOTH));
		SelectionListener updateEnablement = new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				updateEnablement();
			}
		};

		// Second row.
		createProjectSelectionTable(area);

		// Third row.
		cleanCheckbox = new Button(parent, SWT.CHECK);
		cleanCheckbox.setText(Messages.RebuildConfigurationsDialog_CleanConfigurations);
		cleanCheckbox.setSelection(true);
		cleanCheckbox.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING));
		cleanCheckbox.addSelectionListener(updateEnablement);
		cleanKind = cleanCheckbox.getSelection() ? IncrementalProjectBuilder.CLEAN_BUILD : 0;

		buildCheckbox = new Button(parent, SWT.CHECK);
		buildCheckbox.setText(Messages.RebuildConfigurationsDialog_BuildConfigurations);
		buildCheckbox.setSelection(true);
		buildCheckbox.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING));
		buildCheckbox.addSelectionListener(updateEnablement);
		buildKind = buildCheckbox.getSelection() ? IncrementalProjectBuilder.INCREMENTAL_BUILD : 0;

		return area;
	}

	private void createProjectSelectionTable(Composite area) {
		cfgCheckboxViewer = new ContainerCheckedTreeViewer(area, SWT.BORDER);
		cfgCheckboxViewer.setContentProvider(new ConfigurationContentProvider());
		cfgCheckboxViewer.setLabelProvider(new ConfigurationLabelProvider());
		cfgCheckboxViewer.setInput(projects);
		GridData data = new GridData(GridData.FILL_BOTH);
		data.horizontalSpan = 2;
		data.widthHint = IDialogConstants.ENTRY_FIELD_WIDTH;
		data.heightHint = IDialogConstants.ENTRY_FIELD_WIDTH;
		cfgCheckboxViewer.getControl().setLayoutData(data);

		List<ICConfigurationDescription> initialSelection = new ArrayList<>(projects.length);
		for (IProject prj : projects) {
			ICProjectDescription prjd = CoreModel.getDefault().getProjectDescription(prj, false);
			if (prjd == null)
				continue;
			cfgCheckboxViewer.setChecked(prjd.getActiveConfiguration(), true);
			initialSelection.add(prjd.getActiveConfiguration());
		}
		cfgCheckboxViewer.expandAll();
		cfgCheckboxViewer.addCheckStateListener(new ICheckStateListener() {
			@Override
			public void checkStateChanged(CheckStateChangedEvent event) {
				updateEnablement();
				selected = cfgCheckboxViewer.getCheckedElements();
			}
		});
		selected = cfgCheckboxViewer.getCheckedElements();
		selected = initialSelection.toArray(new ICConfigurationDescription[initialSelection.size()]);
	}

	/**
	 * Updates the enablement of the dialog's OK button based on the current choices in the dialog.
	 */
	protected void updateEnablement() {
		cleanKind = cleanCheckbox.getSelection() ? IncrementalProjectBuilder.CLEAN_BUILD : 0;
		buildKind = buildCheckbox.getSelection() ? IncrementalProjectBuilder.INCREMENTAL_BUILD : 0;
		boolean enabled = cfgCheckboxViewer.getCheckedElements().length > 0 && (cleanKind != 0 || buildKind != 0);
		getButton(OK).setEnabled(enabled);
	}

	@Override
	public boolean close() {
		persistDialogSettings(getShell(), DIALOG_SETTINGS_SECTION);
		return super.close();
	}

	@Override
	protected Point getInitialLocation(Point initialSize) {
		Point p = getInitialLocation(DIALOG_SETTINGS_SECTION);
		return p != null ? p : super.getInitialLocation(initialSize);
	}

	@Override
	protected Point getInitialSize() {
		Point p = super.getInitialSize();
		return getInitialSize(DIALOG_SETTINGS_SECTION, p);
	}

	/**
	 * Returns the initial location which is persisted in the IDE Plugin dialog settings under
	 * the provided dialog settings section name. If location is not persisted in the settings,
	 * the {@code null} is returned.
	 *
	 * @param dialogSettingsSectionName - The name of the dialog settings section
	 * @return The initial location or {@code null}
	 */
	private Point getInitialLocation(String dialogSettingsSectionName) {
		IDialogSettings settings = getDialogSettings(dialogSettingsSectionName);
		try {
			int x = settings.getInt(DIALOG_ORIGIN_X);
			int y = settings.getInt(DIALOG_ORIGIN_Y);
			return new Point(x, y);
		} catch (NumberFormatException e) {
		}
		return null;
	}

	private IDialogSettings getDialogSettings(String dialogSettingsSectionName) {
		IDialogSettings settings = ManagedBuilderUIPlugin.getDefault().getDialogSettings();
		IDialogSettings section = settings.getSection(dialogSettingsSectionName);
		if (section == null) {
			section = settings.addNewSection(dialogSettingsSectionName);
		}
		return section;
	}

	/**
	 * Persists the location and dimensions of the shell and other user settings in the plugin's
	 * dialog settings under the provided dialog settings section name.
	 *
	 * @param shell - The shell whose geometry is to be stored
	 * @param dialogSettingsSectionName - The name of the dialog settings section
	 */
	private void persistDialogSettings(Shell shell, String dialogSettingsSectionName) {
		Point shellLocation = shell.getLocation();
		Point shellSize = shell.getSize();
		IDialogSettings settings = getDialogSettings(dialogSettingsSectionName);
		settings.put(DIALOG_ORIGIN_X, shellLocation.x);
		settings.put(DIALOG_ORIGIN_Y, shellLocation.y);
		settings.put(DIALOG_WIDTH, shellSize.x);
		settings.put(DIALOG_HEIGHT, shellSize.y);
	}

	/**
	 * Returns the initial size which is the larger of the {@code initialSize} or the size
	 * persisted in the IDE UI Plugin dialog settings under the provided dialog settings section
	 * name. If no size is persisted in the settings, the {@code initialSize} is returned.
	 *
	 * @param initialSize - The initialSize to compare against
	 * @param dialogSettingsSectionName - The name of the dialog settings section
	 * @return the initial size
	 */
	private Point getInitialSize(String dialogSettingsSectionName, Point initialSize) {
		IDialogSettings settings = getDialogSettings(dialogSettingsSectionName);
		try {
			int x, y;
			x = settings.getInt(DIALOG_WIDTH);
			y = settings.getInt(DIALOG_HEIGHT);
			return new Point(Math.max(x, initialSize.x), Math.max(y, initialSize.y));
		} catch (NumberFormatException e) {
		}
		return initialSize;
	}

	@Override
	protected boolean isResizable() {
		return true;
	}
}
