/*******************************************************************************
 * Copyright (c) 2000, 2015 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
 *     Anton Leherbauer (Wind River) -  [296800] UI build actions should not lock the workspace
 *******************************************************************************/
package org.eclipse.ui.actions;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchCommandConstants;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.internal.ide.IDEInternalWorkbenchImages;
import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
import org.eclipse.ui.internal.ide.IIDEHelpContextIds;
import org.eclipse.ui.internal.ide.actions.BuildUtilities;
import org.eclipse.ui.progress.IProgressConstants2;
import org.eclipse.ui.statushandlers.StatusManager;

/**
 * Standard action for full and incremental builds of all projects within the
 * workspace.
 * <p>
 * This class may be instantiated; it is not intended to be subclassed.
 * </p>
 * @noextend This class is not intended to be subclassed by clients.
 */
public class GlobalBuildAction extends Action implements
		ActionFactory.IWorkbenchAction {
	/**
	 * The type of build performed by this action. Can be either
	 * <code>IncrementalProjectBuilder.INCREMENTAL_BUILD</code> or
	 * <code>IncrementalProjectBuilder.FULL_BUILD</code>.
	 */
	private int buildType;

	/**
	 * The workbench window; or <code>null</code> if this action has been
	 * <code>dispose</code>d.
	 */
	private IWorkbenchWindow workbenchWindow;

	/**
	 * Creates a new action of the appropriate type. The action id is
	 * <code>IWorkbenchActionConstants.BUILD</code> for incremental builds and
	 * <code>IWorkbenchActionConstants.REBUILD_ALL</code> for full builds.
	 *
	 * @param workbench
	 *            the active workbench
	 * @param shell
	 *            the shell for any dialogs
	 * @param type
	 *            the type of build; one of
	 *            <code>IncrementalProjectBuilder.INCREMENTAL_BUILD</code> or
	 *            <code>IncrementalProjectBuilder.FULL_BUILD</code>
	 *
	 * @deprecated use GlobalBuildAction(IWorkbenchWindow, type) instead
	 */
	@Deprecated
	public GlobalBuildAction(IWorkbench workbench, Shell shell, int type) {
		// always use active window; ignore shell
		this(workbench.getActiveWorkbenchWindow(), type);
		Assert.isNotNull(shell);
	}

	/**
	 * Creates a new action of the appropriate type. The action id is
	 * <code>IWorkbenchActionConstants.BUILD</code> for incremental builds and
	 * <code>IWorkbenchActionConstants.REBUILD_ALL</code> for full builds.
	 *
	 * @param window
	 *            the window in which this action appears
	 * @param type
	 *            the type of build; one of
	 *            <code>IncrementalProjectBuilder.INCREMENTAL_BUILD</code> or
	 *            <code>IncrementalProjectBuilder.FULL_BUILD</code>
	 */
	public GlobalBuildAction(IWorkbenchWindow window, int type) {
		Assert.isNotNull(window);
		this.workbenchWindow = window;
		setBuildType(type);
	}

	/**
	 * Sets the build type.
	 *
	 * @param type
	 *            the type of build; one of
	 *            <code>IncrementalProjectBuilder.INCREMENTAL_BUILD</code> or
	 *            <code>IncrementalProjectBuilder.FULL_BUILD</code>
	 */
	private void setBuildType(int type) {
		// allow AUTO_BUILD as well for backwards compatibility, but treat it
		// the same as INCREMENTAL_BUILD
		switch (type) {
		case IncrementalProjectBuilder.INCREMENTAL_BUILD:
		case IncrementalProjectBuilder.AUTO_BUILD:
			setText(IDEWorkbenchMessages.GlobalBuildAction_text);
			setToolTipText(IDEWorkbenchMessages.GlobalBuildAction_toolTip);
			setId("build"); //$NON-NLS-1$
			workbenchWindow.getWorkbench().getHelpSystem().setHelp(this,
					IIDEHelpContextIds.GLOBAL_INCREMENTAL_BUILD_ACTION);
			setImageDescriptor(IDEInternalWorkbenchImages
					.getImageDescriptor(IDEInternalWorkbenchImages.IMG_ETOOL_BUILD_EXEC));
			setDisabledImageDescriptor(IDEInternalWorkbenchImages
					.getImageDescriptor(IDEInternalWorkbenchImages.IMG_ETOOL_BUILD_EXEC_DISABLED));
			setActionDefinitionId(IWorkbenchCommandConstants.PROJECT_BUILD_ALL);
			break;
		case IncrementalProjectBuilder.FULL_BUILD:
			setText(IDEWorkbenchMessages.GlobalBuildAction_rebuildText);
			setToolTipText(IDEWorkbenchMessages.GlobalBuildAction_rebuildToolTip);
			setId("rebuildAll"); //$NON-NLS-1$
			workbenchWindow.getWorkbench().getHelpSystem().setHelp(this,
					IIDEHelpContextIds.GLOBAL_FULL_BUILD_ACTION);
			setActionDefinitionId("org.eclipse.ui.project.rebuildAll"); //$NON-NLS-1$
			break;
		default:
			throw new IllegalArgumentException("Invalid build type"); //$NON-NLS-1$
		}
		this.buildType = type;
	}

	/**
	 * Returns the shell to use.
	 */
	private Shell getShell() {
		return workbenchWindow.getShell();
	}

	/**
	 * Returns the operation name to use
	 */
	private String getOperationMessage() {
		if (buildType == IncrementalProjectBuilder.INCREMENTAL_BUILD) {
			return IDEWorkbenchMessages.GlobalBuildAction_buildOperationTitle;
		}
		return IDEWorkbenchMessages.GlobalBuildAction_rebuildAllOperationTitle;
	}

	/**
	 * Builds all projects within the workspace. Does not save any open editors.
	 */
	public void doBuild() {
		doBuildOperation();
	}

	/**
	 * Invokes a build on all projects within the workspace. Reports any errors
	 * with the build to the user.
	 */
	/* package */void doBuildOperation() {
		Job buildJob = new Job(IDEWorkbenchMessages.GlobalBuildAction_jobTitle) {
			@Override
			protected IStatus run(IProgressMonitor monitor) {
				SubMonitor subMonitor = SubMonitor.convert(monitor, getOperationMessage(), 100);
				try {
					ResourcesPlugin.getWorkspace().build(buildType, subMonitor.split(100));
				} catch (CoreException e) {
					return e.getStatus();
				}
				return Status.OK_STATUS;
			}

			@Override
			public boolean belongsTo(Object family) {
				return ResourcesPlugin.FAMILY_MANUAL_BUILD == family;
			}
		};
		buildJob.setUser(true);
		buildJob.setProperty(IProgressConstants2.SHOW_IN_TASKBAR_ICON_PROPERTY, Boolean.TRUE);
		buildJob.schedule();
	}

	/**
	 * Returns an array of all projects in the workspace
	 */
	/* package */IProject[] getWorkspaceProjects() {
		return ResourcesPlugin.getWorkspace().getRoot().getProjects();
	}

	@Override
	public void run() {
		if (workbenchWindow == null) {
			// action has been disposed
			return;
		}
		// Do nothing if there are no projects...
		IProject[] roots = getWorkspaceProjects();
		if (roots.length < 1) {
			return;
		}
		// Verify that there are builders registered on at
		// least one project
		if (!verifyBuildersAvailable(roots)) {
			return;
		}
		if (!verifyNoManualRunning()) {
			return;
		}
		// Save all resources prior to doing build
		BuildUtilities.saveEditors(null);
		// Perform the build on all the projects
		doBuildOperation();
	}

	/**
	 * Checks that there is at least one project with a builder registered on
	 * it.
	 */
	/* package */boolean verifyBuildersAvailable(IProject[] roots) {
		try {
			for (IProject root : roots) {
				if (root.isAccessible()) {
					if (root.getDescription().getBuildSpec().length > 0) {
						return true;
					}
				}
			}
		} catch (CoreException e) {
			StatusManager.getManager().handle(e, IDEWorkbenchPlugin.IDE_WORKBENCH);
			ErrorDialog
					.openError(
							getShell(),
							IDEWorkbenchMessages.GlobalBuildAction_buildProblems,
							NLS.bind(IDEWorkbenchMessages.GlobalBuildAction_internalError, e.getMessage()),
							e.getStatus());
			return false;
		}
		return false;
	}

	@Override
	public void dispose() {
		if (workbenchWindow == null) {
			// action has already been disposed
			return;
		}
		workbenchWindow = null;
	}

	/**
	 * Verify that no manual build is running. If it is then give the use the
	 * option to cancel. If they cancel, cancel the jobs and return true,
	 * otherwise return false.
	 *
	 * @return whether or not there is a manual build job running.
	 */
	private boolean verifyNoManualRunning() {
		Job[] buildJobs = Job.getJobManager().find(
				ResourcesPlugin.FAMILY_MANUAL_BUILD);
		if (buildJobs.length == 0) {
			return true;
		}
		boolean cancel = MessageDialog.openQuestion(getShell(),
				IDEWorkbenchMessages.GlobalBuildAction_BuildRunningTitle,
				IDEWorkbenchMessages.GlobalBuildAction_BuildRunningMessage);
		if (cancel) {
			for (Job job : buildJobs) {
				job.cancel();
			}
		}
		//If they cancelled get them to do it again.
		return false;
	}
}
