/*******************************************************************************
 *  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.equinox.internal.p2.ui.viewers;

import org.eclipse.equinox.internal.p2.ui.ProvUIProvisioningListener;
import org.eclipse.equinox.internal.p2.ui.ProvisioningOperationRunner;
import org.eclipse.equinox.internal.p2.ui.model.ProfileElement;
import org.eclipse.equinox.internal.provisional.p2.repository.RepositoryEvent;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.PlatformUI;

/**
 * ProvisioningListener which updates a structured viewer based on
 * provisioning changes.  Provides default behavior which refreshes particular
 * model elements or the entire viewer based on the nature of the change and the
 * changes that the client is interested in.  Subclasses typically only need
 * override when there is additional, specialized behavior required.
 *
 * @since 3.4
 */
public class StructuredViewerProvisioningListener extends ProvUIProvisioningListener {

	StructuredViewer viewer;
	Display display;

	public StructuredViewerProvisioningListener(String name, StructuredViewer viewer, int eventTypes, ProvisioningOperationRunner runner) {
		super(name, eventTypes, runner);
		this.viewer = viewer;
		this.display = viewer.getControl().getDisplay();
	}

	/**
	 * A repository has been added.  The default behavior is to
	 * refresh the viewer.  Subclasses may override.  May be called
	 * from a non-UI thread.
	 *
	 * @param event the RepositoryEvent describing the details
	 */
	@Override
	protected void repositoryAdded(RepositoryEvent event) {
		safeRefresh();
	}

	/**
	 * A repository has been removed.  The default behavior is to
	 * refresh the viewer.  Subclasses may override.  May be called
	 * from a non-UI thread.
	 *
	 * @param event the RepositoryEvent describing the details
	 */
	@Override
	protected void repositoryRemoved(RepositoryEvent event) {
		safeRefresh();
	}

	/**
	 * A repository has been discovered.  Subclasses may override.  May be called
	 * from a non-UI thread.
	 *
	 * @param event the RepositoryEvent describing the details
	 */
	@Override
	protected void repositoryDiscovered(RepositoryEvent event) {
		// Do nothing for now
	}

	/**
	 * A repository has changed.  Subclasses may override.  May be called
	 * from a non-UI thread.
	 *
	 * @param event the RepositoryEvent describing the details
	 */
	@Override
	protected void repositoryChanged(RepositoryEvent event) {
		// Do nothing for now.  When this event is actually used in
		// the core, we may want to refresh particular elements the way
		// we currently refresh a profile element.
	}

	/**
	 * The specified profile has changed.  The default behavior is to refresh the viewer
	 * with a profile element of the matching id.  Subclasses may override.  May be called
	 * from a non-UI thread.
	 *
	 * @param profileId the id of the profile that changed.
	 */
	@Override
	protected void profileChanged(final String profileId) {
		display.asyncExec(() -> {
			if (isClosing())
				return;
			// We want to refresh the affected profile, so we
			// construct a profile element on this profile.
			ProfileElement element = new ProfileElement(null, profileId);
			viewer.refresh(element);
		});
	}

	/**
	 * The specified profile has been added.  The default behavior is to fully
	 * refresh the associated viewer. Subclasses may override.  May be called
	 * from a non-UI thread.
	 *
	 * @param profileId the id of the profile that has been added.
	 */
	@Override
	protected void profileAdded(final String profileId) {
		safeRefresh();
	}

	/**
	 * The specified profile has been removed.  The default behavior is to fully
	 * refresh the associated viewer. Subclasses may override.  May be called
	 * from a non-UI thread.
	 *
	 * @param profileId the id of the profile that has been removed.
	 */
	@Override
	protected void profileRemoved(final String profileId) {
		safeRefresh();
	}

	protected void safeRefresh() {
		if (Display.getCurrent() != null) {
			refreshViewer();
			return;
		}

		display.asyncExec(() -> {
			if (isClosing())
				return;
			refreshViewer();
		});
	}

	@Override
	protected void refreshAll() {
		safeRefresh();
	}

	/**
	 * Refresh the entire structure of the viewer.  Subclasses may
	 * override to ensure that any caching done in content providers or
	 * model elements is refreshed before the viewer is refreshed.  This will
	 * always be called from the UI thread.
	 */
	protected void refreshViewer() {
		viewer.refresh();
	}

	/**
	 * Return whether the viewer is closing or shutting down.
	 * This method should be used in async execs to ensure that
	 * the viewer is still alive.
	 * @return a boolean indicating whether the viewer is closing
	 */
	protected boolean isClosing() {
		IWorkbench workbench = PlatformUI.getWorkbench();
		if (workbench.isClosing())
			return true;

		if (viewer.getControl().isDisposed())
			return true;

		return false;
	}
}
