/*******************************************************************************
 * 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;
    }
}
