/*******************************************************************************
 * Copyright (c) 2006, 2017 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 * IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.team.ui.synchronize;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.mapping.*;
import org.eclipse.core.runtime.*;
import org.eclipse.team.core.RepositoryProviderType;
import org.eclipse.team.core.diff.IDiff;
import org.eclipse.team.core.subscribers.*;
import org.eclipse.team.internal.ui.Policy;
import org.eclipse.team.internal.ui.Utils;
import org.eclipse.team.ui.mapping.*;

/**
 * A team state provider that makes use of a {@link Subscriber} to determine the synchronization
 * state. Repository provider types that have a subscriber will get one of these free through the adaptable mechanism.
 * If a repository provider type does not have a subscriber, or it a repository provider type wishes to se a custom
 * provider, they must adapt their {@link RepositoryProviderType} class to an appropriate {@link ITeamStateProvider}.
 * <p>
 * Clients may subclass this class.
 *
 * @since 3.2
 */
public class SubscriberTeamStateProvider extends TeamStateProvider implements ISubscriberChangeListener {

	private Subscriber subscriber;

	/**
	 * Create a provider that determines the synchronization state
	 * from the subscriber. This method registers this provider as a listener
	 * on the subscriber in order to know when to fire state change events.
	 * @param subscriber the subscriber for this provider
	 */
	public SubscriberTeamStateProvider(Subscriber subscriber) {
		this.subscriber = subscriber;
		subscriber.addListener(this);
	}

	@Override
	public boolean hasDecoratedState(Object element) throws CoreException {
		ResourceMapping mapping = Utils.getResourceMapping(element);
		if (mapping != null) {
			ResourceTraversal[] traversals = mapping.getTraversals(
					ResourceMappingContext.LOCAL_CONTEXT, null);
			for (int i = 0; i < traversals.length; i++) {
				ResourceTraversal traversal = traversals[i];
				IResource[] resources = traversal.getResources();
				for (int j = 0; j < resources.length; j++) {
					IResource resource = resources[j];
					if (getSubscriber().isSupervised(resource))
						return true;
				}
			}
		}
		return false;
	}

	/**
	 * Obtain the synchronization state of the element. If the model
	 * provider for the element adapts to an
	 * ISynchronizationCompareAdapter, then the adapter is used to determine the
	 * synchronization state. Others, the state is obtained from the subscriber
	 * using {@link Subscriber#getState(ResourceMapping, int, IProgressMonitor)}
	 *
	 * @param element the element
	 * @param stateMask the state mask that indicates which state flags are desired
	 * @param monitor a progress monitor
	 * @return the synchronization state of the element
	 * @throws CoreException
	 */
	protected final int getSynchronizationState(Object element, int stateMask,
			IProgressMonitor monitor) throws CoreException {
		ResourceMapping mapping = Utils.getResourceMapping(element);
		if (mapping != null) {
			return getSynchronizationState(mapping, stateMask, monitor);
		}
		return IDiff.NO_CHANGE;
	}

	private int getSynchronizationState(ResourceMapping mapping, int stateMask, IProgressMonitor monitor) throws CoreException {
		ISynchronizationCompareAdapter compareAdapter = Adapters.adapt(mapping.getModelProvider(), ISynchronizationCompareAdapter.class);
		try {
			if (compareAdapter != null) {
				int state = compareAdapter.getSynchronizationState(this, mapping, stateMask, monitor);
				if (state != -1)
					return state;
			}
			return getSubscriber().getState(mapping, stateMask, monitor);
		} catch (CoreException e) {
			IProject[] projects = mapping.getProjects();
			for (int i = 0; i < projects.length; i++) {
				IProject project = projects[i];
				// Only through the exception if the project for the mapping
				// is accessible
				if (project.isAccessible()) {
					throw e;
				}
			}
		}
		return IDiff.NO_CHANGE;
	}

	@Override
	public ITeamStateDescription getStateDescription(Object element, int stateMask,
			String[] properties, IProgressMonitor monitor) throws CoreException {
		monitor = Policy.monitorFor(monitor);
		if (stateMask == USE_DECORATED_STATE_MASK)
			stateMask = getDecoratedStateMask(element);
		return new TeamStateDescription(getSynchronizationState(element, stateMask, monitor));
	}

	@Override
	public ResourceMappingContext getResourceMappingContext(Object element) {
		return new SubscriberResourceMappingContext(subscriber, false);
	}

	/**
	 * Return the subscriber associated with this tester.
	 *
	 * @return the subscriber associated with this tester.
	 */
	protected final Subscriber getSubscriber() {
		return subscriber;
	}

	/**
	 * Called when the provider is no longer needed. This method stops listening
	 * to the subscriber. Subclasses may extend this method but must call this
	 * method if they do.
	 */
	public void dispose() {
		subscriber.removeListener(this);
	}

	@Override
	public void subscriberResourceChanged(ISubscriberChangeEvent[] deltas) {
		fireStateChangeEvent(new TeamStateChangeEvent(deltas));
	}

	@Override
	public String[] getDecoratedProperties(Object element) {
		return new String[0];
	}
}
