/*******************************************************************************
 * Copyright (c) 2009, 2011 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;

import java.util.EventObject;
import org.eclipse.equinox.internal.p2.core.helpers.Tracing;
import org.eclipse.equinox.internal.provisional.p2.core.eventbus.SynchronousProvisioningListener;
import org.eclipse.equinox.internal.provisional.p2.repository.RepositoryEvent;
import org.eclipse.equinox.p2.engine.IProfileEvent;
import org.eclipse.equinox.p2.repository.IRepository;

/**
 * ProvisioningListener which handles event batching and other
 * extensions to the provisioning event framework that are used by
 * the UI.
 *
 * @since 3.5
 */
public abstract class ProvUIProvisioningListener implements SynchronousProvisioningListener {

	public static final int PROV_EVENT_METADATA_REPOSITORY = 0x0001;
	public static final int PROV_EVENT_IU = 0x0002;
	public static final int PROV_EVENT_PROFILE = 0x0004;
	public static final int PROV_EVENT_ARTIFACT_REPOSITORY = 0x0008;

	int eventTypes = 0;
	String name;
	private ProvisioningOperationRunner runner;

	public ProvUIProvisioningListener(String name, int eventTypes, ProvisioningOperationRunner runner) {
		this.name = name;
		this.eventTypes = eventTypes;
		this.runner = runner;
	}

	@Override
	public void notify(EventObject o) {
		if (o instanceof RepositoryOperationBeginningEvent) {
			if (Tracing.DEBUG_EVENTS_CLIENT)
				Tracing.debug("Batch Eventing:  Ignore Following Events. " + getReceiverString()); //$NON-NLS-1$
		} else if (o instanceof RepositoryOperationEndingEvent) {
			RepositoryOperationEndingEvent event = (RepositoryOperationEndingEvent) o;

			if (Tracing.DEBUG_EVENTS_CLIENT)
				Tracing.debug("Batch Eventing:  Batch Ended. " + getReceiverString()); //$NON-NLS-1$
			// A batch operation completed.  Refresh.
			if (runner.eventBatchCount <= 0) {
				if (Tracing.DEBUG_EVENTS_CLIENT)
					Tracing.debug("Batch Eventing Complete." + getReceiverString()); //$NON-NLS-1$
				if (event.getEvent() == null && event.update()) {
					if (Tracing.DEBUG_EVENTS_CLIENT) {
						Tracing.debug("Refreshing After Batch." + getReceiverString()); //$NON-NLS-1$
					}
					refreshAll();
				} else if (event.update()) {
					if (Tracing.DEBUG_EVENTS_CLIENT)
						Tracing.debug("Dispatching Last Event in Batch." + getReceiverString()); //$NON-NLS-1$
					notify(event.getEvent());
				} else if (Tracing.DEBUG_EVENTS_CLIENT) {
					Tracing.debug("No Refresh on Batch Complete."); //$NON-NLS-1$
				}
			} else {
				// We are still in the middle of a batch operation, but we've been notified
				// about a nested batch that ended.  See if it ended with a specific event.
				// If it did, this means there was a user action involving a repository
				// (rather than side-effect events).  For example, the user might add a repo while a full
				// background load is running.  We want to honor that
				// event.  See https://bugs.eclipse.org/bugs/show_bug.cgi?id=305478
				RepositoryEvent innerEvent = event.getEvent();
				if (innerEvent != null) {
					handleRepositoryEvent(innerEvent);
				}
			}
		} else if (runner.eventBatchCount > 0) {
			// ignore raw events during a batch
			if (Tracing.DEBUG_EVENTS_CLIENT)
				Tracing.debug(name + " Ignoring: " + o.toString()); //$NON-NLS-1$
			return;
		} else if (o instanceof IProfileEvent && (((eventTypes & PROV_EVENT_IU) == PROV_EVENT_IU) || ((eventTypes & PROV_EVENT_PROFILE) == PROV_EVENT_PROFILE))) {
			if (Tracing.DEBUG_EVENTS_CLIENT)
				Tracing.debug(o.toString() + getReceiverString());
			IProfileEvent event = (IProfileEvent) o;
			if (event.getReason() == IProfileEvent.CHANGED) {
				profileChanged(event.getProfileId());
			} else if (event.getReason() == IProfileEvent.ADDED) {
				profileAdded(event.getProfileId());
			} else if (event.getReason() == IProfileEvent.REMOVED) {
				profileRemoved(event.getProfileId());
			}
		} else if (o instanceof RepositoryEvent) {
			if (Tracing.DEBUG_EVENTS_CLIENT)
				Tracing.debug(o.toString() + getReceiverString());
			handleRepositoryEvent((RepositoryEvent) o);
		}
	}

	private String getReceiverString() {
		return " --  <" + name + "> "; //$NON-NLS-1$//$NON-NLS-2$
	}

	private void handleRepositoryEvent(RepositoryEvent event) {
		// Do not handle unless this is the type of repo that we are interested in
		if ((event.getRepositoryType() == IRepository.TYPE_METADATA && (eventTypes & PROV_EVENT_METADATA_REPOSITORY) == PROV_EVENT_METADATA_REPOSITORY) || (event.getRepositoryType() == IRepository.TYPE_ARTIFACT && (eventTypes & PROV_EVENT_ARTIFACT_REPOSITORY) == PROV_EVENT_ARTIFACT_REPOSITORY)) {
			if (event.getKind() == RepositoryEvent.ADDED && event.isRepositoryEnabled()) {
				repositoryAdded(event);
			} else if (event.getKind() == RepositoryEvent.REMOVED && event.isRepositoryEnabled()) {
				repositoryRemoved(event);
			} else if (event.getKind() == RepositoryEvent.DISCOVERED) {
				repositoryDiscovered(event);
			} else if (event.getKind() == RepositoryEvent.CHANGED) {
				repositoryChanged(event);
			} else if (event.getKind() == RepositoryEvent.ENABLEMENT) {
				repositoryEnablement(event);
			}
		}
	}

	/**
	 * A repository has been added.  Subclasses may override.  May be called
	 * from a non-UI thread.
	 *
	 * @param event the RepositoryEvent describing the details
	 */
	protected void repositoryAdded(RepositoryEvent event) {
		// Do nothing.  This method is not abstract because subclasses
		// may not be interested in repository events at all and should
		// not have to implement it.
	}

	/**
	 * A repository has been removed.  Subclasses may override.  May be called
	 * from a non-UI thread.
	 *
	 * @param event the RepositoryEvent describing the details
	 */
	protected void repositoryRemoved(RepositoryEvent event) {
		// Do nothing.  This method is not abstract because subclasses
		// may not be interested in repository events at all and should
		// not have to implement it.
	}

	/**
	 * A repository has been discovered.  Subclasses may override.  May be called
	 * from a non-UI thread.
	 *
	 * @param event the RepositoryEvent describing the details
	 */
	protected void repositoryDiscovered(RepositoryEvent event) {
		// Do nothing.  This method is not abstract because subclasses
		// may not be interested in repository events at all and should
		// not have to implement it.
	}

	/**
	 * A repository has changed.  Subclasses may override.  May be called
	 * from a non-UI thread.
	 *
	 * @param event the RepositoryEvent describing the details
	 */
	protected void repositoryChanged(RepositoryEvent event) {
		// Do nothing.  This method is not abstract because subclasses
		// may not be interested in repository events at all and should
		// not have to implement it.
	}

	/**
	 * A repository's enablement state has changed.  This is treated
	 * as repository addition or removal by default.  Subclasses may
	 * override.  May be called from a non-UI thread.
	 * @param event
	 */
	protected void repositoryEnablement(RepositoryEvent event) {
		// We treat enablement of a repository as if one were added.
		if (event.isRepositoryEnabled())
			repositoryAdded(event);
		else
			repositoryRemoved(event);
	}

	/**
	 * The specified profile has changed.   Subclasses may override.  May be called
	 * from a non-UI thread.
	 *
	 * @param profileId the id of the profile that changed.
	 */
	protected void profileChanged(final String profileId) {
		// Do nothing.  This method is not abstract because subclasses
		// may not be interested in profile events at all and should
		// not have to implement it.
	}

	/**
	 * The specified profile has been added.  Subclasses may override.  May be called
	 * from a non-UI thread.
	 *
	 * @param profileId the id of the profile that has been added.
	 */
	protected void profileAdded(final String profileId) {
		// Do nothing.  This method is not abstract because subclasses
		// may not be interested in profile events at all and should
		// not have to implement it.
	}

	/**
	 * The specified profile has been removed.  Subclasses may override.  May be called
	 * from a non-UI thread.
	 *
	 * @param profileId the id of the profile that has been removed.
	 */
	protected void profileRemoved(final String profileId) {
		// Do nothing.  This method is not abstract because subclasses
		// may not be interested in profile events at all and should
		// not have to implement it.
	}

	/**
	 * An event requiring a complete refresh of the listener's state has
	 * been received.  This is used, for example, when a batch change has
	 * completed.  Subclasses may override.  May be called from a non-UI
	 * thread.
	 */
	protected void refreshAll() {
		// Do nothing by default.
	}

	public int getEventTypes() {
		return eventTypes;
	}
}
