/*******************************************************************************
 * Copyright (c) 2010 itemis AG (http://www.itemis.eu) 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
 *******************************************************************************/
package org.eclipse.xtext.scoping.impl;

import static com.google.common.collect.Iterables.*;
import static com.google.common.collect.Lists.*;
import static com.google.common.collect.Sets.*;
import static java.util.Collections.*;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.naming.QualifiedName;
import org.eclipse.xtext.resource.IEObjectDescription;
import org.eclipse.xtext.resource.ISelectable;
import org.eclipse.xtext.resource.impl.AliasedEObjectDescription;
import org.eclipse.xtext.scoping.IScope;

import com.google.common.base.Predicate;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;

/**
 * @author Sven Efftinge - Initial contribution and API
 * @author Sebastian Zarnekow
 */
public class ImportScope extends AbstractScope {

	private final List<ImportNormalizer> normalizers;

	private final ISelectable importFrom;

	private final EClass type;

	public ImportScope(List<ImportNormalizer> namespaceResolvers, IScope parent, ISelectable importFrom, EClass type,
			boolean ignoreCase) {
		super(parent, ignoreCase);
		this.type = type;
		this.normalizers = removeDuplicates(namespaceResolvers);
		this.importFrom = importFrom;
	}

	protected List<ImportNormalizer> removeDuplicates(List<ImportNormalizer> namespaceResolvers) {
		ArrayList<ImportNormalizer> list = newArrayList();
		for (ImportNormalizer importNormalizer : namespaceResolvers) {
			if (!list.contains(importNormalizer))
				list.add(importNormalizer);
		}
		return list;
	}

	@Override
	public String toString() {
		return getClass().getSimpleName() + normalizers + " imports from " + importFrom + " for type "+type.getName();
	}

	@Override
	public Iterable<IEObjectDescription> getAllElements() {
		final Iterable<IEObjectDescription> globalElements = getParent().getAllElements();
		Iterable<IEObjectDescription> aliased = getAllLocalElements();
		final Set<QualifiedName> elements = newHashSet();
		for (IEObjectDescription from : aliased) {
			QualifiedName qn = getIgnoreCaseAwareQualifiedName(from);
			elements.add(qn);
		}
		return concat(aliased, filter(globalElements, new Predicate<IEObjectDescription>() {
			public boolean apply(IEObjectDescription input) {
				return !elements.contains(getIgnoreCaseAwareQualifiedName(input));
			}
		}));
	}

	protected QualifiedName getIgnoreCaseAwareQualifiedName(IEObjectDescription from) {
		return isIgnoreCase() ? from.getName().toLowerCase() : from.getName();
	}

	@Override
	protected Iterable<IEObjectDescription> getAllLocalElements() {
		final Iterable<IEObjectDescription> exportedObjects = getImportFrom().getExportedObjectsByType(type);
		return getAliasedElements(exportedObjects);
	}

	@Override
	protected Iterable<IEObjectDescription> getLocalElementsByEObject(final EObject object, final URI uri) {
		Iterable<IEObjectDescription> candidates = getImportFrom().getExportedObjectsByObject(object);
		final Iterable<IEObjectDescription> aliasedElements = getAliasedElements(candidates);
		// make sure that the element is returned when asked by name.
		return filter(aliasedElements, new Predicate<IEObjectDescription>() {
			public boolean apply(IEObjectDescription input) {
				IEObjectDescription description = getSingleLocalElementByName(input.getName());
				if (description==null)
					return false;
				if (description.getEObjectOrProxy()==input.getEObjectOrProxy())
					return true;
				if (input.getEObjectURI().equals(description.getEObjectURI()))
					return true;
				return false;
			}
		});
	}

	protected Iterable<IEObjectDescription> getAliasedElements(Iterable<IEObjectDescription> candidates) {
		Multimap<QualifiedName, IEObjectDescription> keyToDescription = LinkedHashMultimap.create();
		Multimap<QualifiedName, ImportNormalizer> keyToNormalizer = HashMultimap.create();

		for (IEObjectDescription imported : candidates) {
			QualifiedName fullyQualifiedName = imported.getName();
			for (ImportNormalizer normalizer : normalizers) {
				QualifiedName alias = normalizer.deresolve(fullyQualifiedName);
				if (alias != null) {
					QualifiedName key = alias;
					if (isIgnoreCase()) {
						key = key.toLowerCase();
					}
					keyToDescription.put(key, new AliasedEObjectDescription(alias, imported));
					keyToNormalizer.put(key, normalizer);
				}
			}
		}
		for (QualifiedName name : keyToNormalizer.keySet()) {
			if (keyToNormalizer.get(name).size() > 1)
				keyToDescription.removeAll(name);
		}
		return keyToDescription.values();
	}

	@Override
	protected IEObjectDescription getSingleLocalElementByName(QualifiedName name) {
		Iterator<IEObjectDescription> iterator = getLocalElementsByName(name).iterator();
		return iterator.hasNext() ? iterator.next() : null;
	}

	@Override
	protected Iterable<IEObjectDescription> getLocalElementsByName(QualifiedName name) {
		List<IEObjectDescription> result = newArrayList();
		QualifiedName resolvedQualifiedName = null;
		ISelectable importFrom = getImportFrom();
		for (ImportNormalizer normalizer : normalizers) {
			final QualifiedName resolvedName = normalizer.resolve(name);
			if (resolvedName != null) {
				Iterable<IEObjectDescription> resolvedElements = importFrom.getExportedObjects(type, resolvedName,
						isIgnoreCase());
				for (IEObjectDescription resolvedElement : resolvedElements) {
					if (resolvedQualifiedName == null)
						resolvedQualifiedName = resolvedName;
					else if (!resolvedQualifiedName.equals(resolvedName))
						return emptyList();
					QualifiedName alias = normalizer.deresolve(resolvedElement.getName());
					if (alias == null)
						throw new IllegalStateException("Couldn't deresolve " + resolvedElement.getName()
								+ " with import " + normalizer);
					final AliasedEObjectDescription aliasedEObjectDescription = new AliasedEObjectDescription(alias,
							resolvedElement);
					result.add(aliasedEObjectDescription);
				}
			}
		}
		return result;
	}

	protected ISelectable getImportFrom() {
		ISelectable importFrom = this.importFrom;
		if (importFrom == null) {
			importFrom = new ScopeBasedSelectable(getParent());
		}
		return importFrom;
	}

}

@@ -6133,17 +6133,106 @@
 edName))
+ %7B
 %0A
+%09%09%09%09%09%09if (result.get(0).getEObjectOrProxy() != resolvedElement.getEObjectOrProxy()) %7B%0A%09
 %09%09%09%09%09%09re
@@ -6245,24 +6245,39 @@
 mptyList();%0A
+%09%09%09%09%09%09%7D%0A%09%09%09%09%09%7D%0A
 %09%09%09%09%09Qualifi

Allow '.' as separator for nested types