/*******************************************************************************
 * Copyright (c) 2014 Google Inc 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:
 *     John Glassmyer <jogl@google.com> - import group sorting is broken - https://bugs.eclipse.org/430303
 *******************************************************************************/
package org.aspectj.org.eclipse.jdt.internal.core.dom.rewrite.imports;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import org.eclipse.core.runtime.IProgressMonitor;
import org.aspectj.org.eclipse.jdt.core.JavaModelException;

public class ConflictIdentifier {
	/**
	 * Encapsulates those simple names (of type imports and of static imports) which would be
	 * imported from multiple on-demand or implicit import containers.
	 */
	static final class Conflicts {
		final Set<String> typeConflicts;
		final Set<String> staticConflicts;

		Conflicts(Set<String> typeConflicts, Set<String> staticConflicts) {
			this.typeConflicts = Collections.unmodifiableSet(new HashSet<String>(typeConflicts));
			this.staticConflicts = Collections.unmodifiableSet(new HashSet<String>(staticConflicts));
		}

		@Override
		public String toString() {
			return String.format(
					"Conflicts(type: %s; static: %s)", this.typeConflicts, this.staticConflicts); //$NON-NLS-1$
		}
	}

	private final OnDemandComputer onDemandComputer;
	private final TypeConflictingSimpleNameFinder typeConflictFinder;
	private final StaticConflictingSimpleNameFinder staticConflictFinder;
	private final Set<String> implicitImportContainers;

	ConflictIdentifier(
			OnDemandComputer onDemandComputer,
			TypeConflictingSimpleNameFinder typeConflictFinder,
			StaticConflictingSimpleNameFinder staticConflictFinder,
			Set<String> implicitImportContainers) {
		this.onDemandComputer = onDemandComputer;
		this.typeConflictFinder = typeConflictFinder;
		this.staticConflictFinder = staticConflictFinder;
		this.implicitImportContainers = implicitImportContainers;
	}

	/**
	 * Identifies the simple names (of the elements of {@code imports}) which would be imported from
	 * multiple on-demand or implicit import containers.
	 *
	 * @param imports
	 *            imports whose simple names are to be considered for conflicts
	 * @param addedImports
	 *            imports which have been added as part of the rewrite (and could therefore trigger
	 *            on-demand reductions; a subset of {@code imports}
	 * @param typeExplicitSimpleNames
	 *            simple names of types which are already known to require explicit imports
	 * @param staticExplicitSimpleNames
	 *            simple names of statics which are already known to require explicit imports
	 * @param progressMonitor
	 *            a progress monitor used to track time spent searching for conflicts
	 * @return a {@link Conflicts} object encapsulating the found conflicting type and static names
	 * @throws JavaModelException if an error occurs while searching for declarations
	 */
	Conflicts identifyConflicts(
			Set<ImportName> imports,
			Set<ImportName> addedImports,
			Set<String> typeExplicitSimpleNames,
			Set<String> staticExplicitSimpleNames,
			IProgressMonitor progressMonitor) throws JavaModelException {
		Collection<OnDemandReduction> onDemandCandidates = this.onDemandComputer.identifyPossibleReductions(
				imports, addedImports, typeExplicitSimpleNames, staticExplicitSimpleNames);

		Set<String> typeOnDemandContainers = new HashSet<String>(extractContainerNames(onDemandCandidates, false));
		Set<String> staticOnDemandContainers = new HashSet<String>(extractContainerNames(onDemandCandidates, true));

		if (!typeOnDemandContainers.isEmpty()) {
			// Existing on-demands might conflict with new or existing on-demands.
			typeOnDemandContainers.addAll(extractOnDemandContainerNames(imports, false));

			// Implicitly imported types might conflict with type on-demands.
			typeOnDemandContainers.addAll(this.implicitImportContainers);

			// Member types imported by static on-demands might conflict with type on-demands.
			typeOnDemandContainers.addAll(staticOnDemandContainers);
		}

		if (!staticOnDemandContainers.isEmpty()) {
			// Existing on-demands might conflict with new or existing on-demands.
			staticOnDemandContainers.addAll(extractOnDemandContainerNames(imports, true));
		}

		Set<String> typeConflicts = findConflictingSimpleNames(
				this.typeConflictFinder, imports, false, typeOnDemandContainers, progressMonitor);

		Set<String> staticConflicts = findConflictingSimpleNames(
				this.staticConflictFinder, imports, true, staticOnDemandContainers, progressMonitor);

		return new Conflicts(typeConflicts, staticConflicts);
	}

	private Collection<String> extractContainerNames(
			Collection<OnDemandReduction> onDemandCandidates, boolean isStatic) {
		Collection<String> containerNames = new ArrayList<String>(onDemandCandidates.size());
		for (OnDemandReduction onDemandCandidate : onDemandCandidates) {
			ImportName containerOnDemand = onDemandCandidate.containerOnDemand;
			if (containerOnDemand.isStatic == isStatic) {
				containerNames.add(containerOnDemand.containerName);
			}
		}

		return containerNames;
	}

	private Collection<String> extractOnDemandContainerNames(
			Collection<ImportName> imports, boolean isStatic) {
		Collection<String> onDemandContainerNames = new ArrayList<String>(imports.size());
		for (ImportName importName : imports) {
			if (importName.isOnDemand() && importName.isStatic == isStatic) {
				onDemandContainerNames.add(importName.containerName);
			}
		}

		return onDemandContainerNames;
	}

	private Set<String> findConflictingSimpleNames(
			ConflictingSimpleNameFinder conflictFinder,
			Set<ImportName> imports,
			boolean isStatic,
			Set<String> onDemandImportedContainers,
			IProgressMonitor monitor) throws JavaModelException {
		if (onDemandImportedContainers.isEmpty() || imports.isEmpty()) {
			return Collections.emptySet();
		}

		Set<String> simpleNames = new HashSet<String>();
		for (ImportName currentImport : imports) {
			if (currentImport.isStatic == isStatic) {
				simpleNames.add(currentImport.simpleName);
			}
		}

		return conflictFinder.findConflictingSimpleNames(simpleNames, onDemandImportedContainers, monitor);
	}
}
