/*******************************************************************************
 * 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
 *******************************************************************************/
package org.eclipse.ltk.core.refactoring.participants;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;

import org.eclipse.core.resources.IFile;

import org.eclipse.ltk.core.refactoring.IRefactoringCoreStatusCodes;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.internal.core.refactoring.Messages;
import org.eclipse.ltk.internal.core.refactoring.RefactoringCoreMessages;
import org.eclipse.ltk.internal.core.refactoring.RefactoringCorePlugin;

/**
 * A context that is shared between the refactoring processor and all its
 * associated participants during condition checking.
 * <p>
 * The context manages a set of {@link IConditionChecker}objects to collect
 * condition checks that should be perform across all participants and the
 * processor. For example validating if a file can be changed (see
 * {@link org.eclipse.core.resources.IWorkspace#validateEdit(org.eclipse.core.resources.IFile[], java.lang.Object)}
 * should only be called once for all files modified by the processor and all
 * participants.
 * </p>
 * <p>
 * Note: this class is not intended to be extended by clients.
 * </p>
 *
 * @since 3.0
 *
 * @noextend This class is not intended to be subclassed by clients.
 */
public class CheckConditionsContext {

	private Map<Class<? extends IConditionChecker>, IConditionChecker> fCheckers= new HashMap<>();

	/**
	 * Returns the condition checker of the given type.
	 *
	 * @param clazz the type of the condition checker
	 *
	 * @return the condition checker or <code>null</code> if
	 *  no checker is registered for the given type
	 */
	public <T extends IConditionChecker> T getChecker(Class<T> clazz) {
		return clazz.cast(fCheckers.get(clazz));
	}

	/**
	 * Adds the given condition checker. An exception will be
	 * thrown if a checker of the same type already exists in
	 * this context.
	 *
	 * @param checker the checker to add
	 * @throws CoreException if a checker of the same type already
	 *  exists
	 */
	public void add(IConditionChecker checker) throws CoreException {
		IConditionChecker old= fCheckers.put(checker.getClass(), checker);
		if (old != null) {
			fCheckers.put(checker.getClass(), old);
			throw new CoreException(new Status(IStatus.ERROR, RefactoringCorePlugin.getPluginId(),
				IRefactoringCoreStatusCodes.CHECKER_ALREADY_EXISTS_IN_CONTEXT,
				Messages.format(RefactoringCoreMessages.CheckConditionContext_error_checker_exists, checker.getClass().toString()),
				null));
		}
	}

	/**
	 * Checks the condition of all registered condition checkers and returns a
	 * merge status result.
	 *
	 * @param pm a progress monitor or <code>null</code> if no progress
	 *  reporting is desired
	 *
	 * @return the combined status result
	 *
	 * @throws CoreException if an error occurs during condition checking
	 */
	public RefactoringStatus check(IProgressMonitor pm) throws CoreException {
		if (pm == null)
			pm= new NullProgressMonitor();
		RefactoringStatus result= new RefactoringStatus();
		mergeResourceOperationAndValidateEdit();
		List<IConditionChecker> values= new ArrayList<>(fCheckers.values());
		Collections.sort(values, (o1, o2) -> {
			// Note there can only be one ResourceOperationChecker. So it
			// is save to not test the case that both objects are
			// ResourceOperationChecker
			if (o1 instanceof ResourceChangeChecker)
				return -1;
			if (o2 instanceof ResourceChangeChecker)
				return 1;
			return 0;
		});
		pm.beginTask("", values.size()); //$NON-NLS-1$
		for (IConditionChecker checker : values) {
			result.merge(checker.check(new SubProgressMonitor(pm, 1)));
			if (pm.isCanceled())
				throw new OperationCanceledException();
		}
		return result;
	}

	private void mergeResourceOperationAndValidateEdit() throws CoreException {
		ValidateEditChecker validateEditChecker= getChecker(ValidateEditChecker.class);
		if (validateEditChecker == null)
			return;
		ResourceChangeChecker resourceChangeChecker= getChecker(ResourceChangeChecker.class);
		if (resourceChangeChecker == null)
			return;

		IFile[] changedFiles= resourceChangeChecker.getChangedFiles();
		validateEditChecker.addFiles(changedFiles);
	}
}
