/*******************************************************************************
 * Copyright (c) 2006, 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
 *******************************************************************************/

package org.eclipse.ui;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.window.IShellProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.internal.InternalSaveable;
import org.eclipse.ui.internal.PartSite;
import org.eclipse.ui.progress.IJobRunnable;

/**
 * A <code>Saveable</code> represents a unit of saveability, e.g. an editable
 * subset of the underlying domain model that may contain unsaved changes.
 * Different workbench parts (editors and views) may present the same saveables
 * in different ways. This interface allows the workbench to provide more
 * appropriate handling of operations such as saving and closing workbench
 * parts. For example, if two editors sharing the same saveable with unsaved
 * changes are closed simultaneously, the user is only prompted to save the
 * changes once for the shared saveable, rather than once for each editor.
 * <p>
 * Workbench parts that work in terms of saveables should implement
 * {@link ISaveablesSource}.
 * </p>
 *
 * @see ISaveablesSource
 * @since 3.2
 */
public abstract class Saveable extends InternalSaveable implements IAdaptable {

	/**
	 * Either {@code null} or the System's {@link SWT#CURSOR_WAIT} cursor
	 * instance. Should never be disposed.
	 */
	private Cursor waitCursor;
	private Cursor originalCursor;

	/**
	 * Attempts to show this saveable in the given page and returns
	 * <code>true</code> on success. The default implementation does nothing
	 * and returns <code>false</code>.
	 *
	 * @param page
	 *            the workbench page in which to show this saveable
	 * @return <code>true</code> if this saveable is now visible to the user
	 * @since 3.3
	 */
	public boolean show(IWorkbenchPage page) {
		if (page == null) {
			// I wish it was easier to avoid warnings about unused parameters
		}
		return false;
	}

	/**
	 * Returns the name of this saveable for display purposes.
	 *
	 * @return the model's name; never <code>null</code>.
	 */
	public abstract String getName();

	/**
	 * Returns the tool tip text for this saveable. This text is used to
	 * differentiate between two inputs with the same name. For instance,
	 * MyClass.java in folder X and MyClass.java in folder Y. The format of the
	 * text varies between input types.
	 *
	 * @return the tool tip text; never <code>null</code>
	 */
	public abstract String getToolTipText();

	/**
	 * Returns the image descriptor for this saveable.
	 *
	 * @return the image descriptor for this model; may be <code>null</code>
	 *         if there is no image
	 */
	public abstract ImageDescriptor getImageDescriptor();

	/**
	 * Saves the contents of this saveable.
	 * <p>
	 * If the save is cancelled through user action, or for any other reason,
	 * the part should invoke <code>setCancelled</code> on the
	 * <code>IProgressMonitor</code> to inform the caller.
	 * </p>
	 * <p>
	 * This method is long-running; progress and cancellation are provided by
	 * the given progress monitor.
	 * </p>
	 *
	 * @param monitor
	 *            the progress monitor
	 * @throws CoreException
	 *             if the save fails; it is the caller's responsibility to
	 *             report the failure to the user
	 */
	public abstract void doSave(IProgressMonitor monitor) throws CoreException;

	/**
	 * Returns whether the contents of this saveable have changed since the last
	 * save operation.
	 * <p>
	 * <b>Note:</b> this method is called frequently, for example by actions to
	 * determine their enabled status.
	 * </p>
	 *
	 * @return <code>true</code> if the contents have been modified and need
	 *         saving, and <code>false</code> if they have not changed since
	 *         the last save
	 */
	public abstract boolean isDirty();

	/**
	 * Clients must implement equals and hashCode as defined in
	 * {@link Object#equals(Object)} and {@link Object#hashCode()}. Two
	 * saveables should be equal if their dirty state is shared, and saving one
	 * will save the other. If two saveables are equal, their names, tooltips,
	 * and images should be the same because only one of them will be shown when
	 * prompting the user to save.
	 *
	 * @param object
	 * @return true if this Saveable is equal to the given object
	 */
	@Override
	public abstract boolean equals(Object object);

	/**
	 * Clients must implement equals and hashCode as defined in
	 * {@link Object#equals(Object)} and {@link Object#hashCode()}. Two
	 * saveables should be equal if their dirty state is shared, and saving one
	 * will save the other. If two saveables are equal, their hash codes MUST be
	 * the same, and their names, tooltips, and images should be the same
	 * because only one of them will be shown when prompting the user to save.
	 * <p>
	 * IMPORTANT: Implementers should ensure that the hashCode returned is
	 * sufficiently unique so as not to collide with hashCodes returned by other
	 * implementations. It is suggested that the defining plug-in's ID be used
	 * as part of the returned hashCode, as in the following example:
	 * </p>
	 *
	 * <pre>
	 *     int PRIME = 31;
	 *     int hash = ...; // compute the &quot;normal&quot; hash code, e.g. based on some identifier unique within the defining plug-in
	 *     return hash * PRIME + MY_PLUGIN_ID.hashCode();
	 * </pre>
	 *
	 * @return a hash code
	 */
	@Override
	public abstract int hashCode();

	/**
	 * Saves this saveable, or prepares this saveable for a background save
	 * operation. Returns null if this saveable has been successfully saved, or
	 * a job runnable that needs to be run to complete the save in the
	 * background. This method is called in the UI thread. If this saveable
	 * supports saving in the background, it should do only minimal work.
	 * However, since the job runnable returned by this method (if any) will not
	 * run on the UI thread, this method should copy any state that can only be
	 * accessed from the UI thread so that the job runnable will be able to
	 * access it.
	 * <p>
	 * The supplied shell provider can be used from within this method and from
	 * within the job runnable for the purpose of parenting dialogs. Care should
	 * be taken not to open dialogs gratuitously and only if user input is
	 * required for cases where the save cannot otherwise proceed - note that in
	 * any given save operation, many saveable objects may be saved at the same
	 * time. In particular, errors should be signaled by throwing an exception,
	 * or if an error occurs while running the job runnable, an error status
	 * should be returned.
	 * </p>
	 * <p>
	 * If the foreground part of the save is cancelled through user action, or
	 * for any other reason, the part should invoke <code>setCancelled</code>
	 * on the <code>IProgressMonitor</code> to inform the caller. If the
	 * background part of the save is cancelled, the job should return a
	 * {@link IStatus#CANCEL} status.
	 * </p>
	 * <p>
	 * This method is long-running; progress and cancellation are provided by
	 * the given progress monitor.
	 * </p>
	 * <p>
	 * The default implementation of this method calls
	 * {@link #doSave(IProgressMonitor)} and returns <code>null</code>.
	 * </p>
	 *
	 * @param monitor
	 *            a progress monitor used for reporting progress and
	 *            cancellation
	 * @param shellProvider
	 *            an object that can provide a shell for parenting dialogs
	 * @return <code>null</code> if this saveable has been saved successfully,
	 *         or a job runnable that needs to be run to complete the save in
	 *         the background.
	 * @throws CoreException
	 *             if the save fails; it is the caller's responsibility to
	 *             report the failure to the user
	 * @since 3.3
	 */
	public IJobRunnable doSave(IProgressMonitor monitor,
			IShellProvider shellProvider) throws CoreException {
		doSave(monitor);
		return null;
	}

	/**
	 * Disables the UI of the given parts containing this saveable if necessary.
	 * This method is not intended to be called by clients. A corresponding call
	 * to
	 * <p>
	 * Saveables that can be saved in the background should ensure that the user
	 * cannot make changes to their data from the UI, for example by disabling
	 * controls, unless they are prepared to handle this case. This method is
	 * called on the UI thread after a job runnable has been returned from
	 * {@link #doSave(IProgressMonitor, IShellProvider)} and before
	 * spinning the event loop. The <code>closing</code> flag indicates that
	 * this saveable is currently being saved in response to closing a workbench
	 * part, in which case further changes to this saveable through the UI must
	 * be prevented.
	 * </p>
	 * <p>
	 * The default implementation calls setEnabled(false) on the given parts'
	 * composites.
	 * </p>
	 *
	 * @param parts
	 *            the workbench parts containing this saveable
	 * @param closing
	 *            a boolean flag indicating whether the save was triggered by a
	 *            request to close a workbench part, and all of the given parts
	 *            will be closed after the save operation finishes successfully.
	 *
	 * @since 3.3
	 */
	public void disableUI(IWorkbenchPart[] parts, boolean closing) {
		for (IWorkbenchPart workbenchPart : parts) {
			Composite paneComposite = (Composite) ((PartSite) workbenchPart.getSite()).getModel().getWidget();
			Control[] paneChildren = paneComposite.getChildren();
			Composite toDisable = ((Composite) paneChildren[0]);
			toDisable.setEnabled(false);
			if (waitCursor == null) {
				waitCursor = workbenchPart.getSite().getWorkbenchWindow().getShell().getDisplay()
						.getSystemCursor(SWT.CURSOR_WAIT);
			}
			if (waitCursor.equals(paneComposite.getCursor())) {
				originalCursor = paneComposite.getCursor();
				paneComposite.setCursor(waitCursor);
			}
		}
	}

	/**
	 * Enables the UI of the given parts containing this saveable after a
	 * background save operation has finished. This method is not intended to be
	 * called by clients.
	 * <p>
	 * The default implementation calls setEnabled(true) on the given parts'
	 * composites.
	 * </p>
	 *
	 * @param parts
	 *            the workbench parts containing this saveable
	 *
	 * @since 3.3
	 */
	public void enableUI(IWorkbenchPart[] parts) {
		for (IWorkbenchPart workbenchPart : parts) {
			Composite paneComposite = (Composite) ((PartSite) workbenchPart.getSite()).getModel().getWidget();
			Control[] paneChildren = paneComposite.getChildren();
			Composite toEnable = ((Composite) paneChildren[0]);
			paneComposite.setCursor(originalCursor);
			if (waitCursor != null) {
				/*
				 * waitCursor is always the System SWT.CURSOR_WAIT instance and
				 * should never be disposed
				 */
				waitCursor = null;
			}
			toEnable.setEnabled(true);
		}
	}

	/**
	 * This implementation of {@link IAdaptable#getAdapter(Class)} returns
	 * <code>null</code>. Subclasses may override. This allows two unrelated
	 * subclasses of Saveable to implement {@link #equals(Object)} and
	 * {@link #hashCode()} based on an underlying implementation class that is
	 * shared by both Saveable subclasses.
	 *
	 * @since 3.3
	 */
	@Override
	public <T> T getAdapter(Class<T> adapter) {
		return null;
	}
}
