/*******************************************************************************
 * Copyright (c) 2006, 2010 Wind River Systems, 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:
 *     Markus Schorn - initial API and implementation
 *******************************************************************************/ 
package org.eclipse.cdt.internal.ui.callhierarchy;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.dom.ILinkage;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPMethod;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPSpecialization;
import org.eclipse.cdt.core.index.IIndex;
import org.eclipse.cdt.core.index.IIndexBinding;
import org.eclipse.cdt.core.index.IIndexName;
import org.eclipse.cdt.core.model.ICElement;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.model.ISourceReference;
import org.eclipse.cdt.core.model.ITranslationUnit;

import org.eclipse.cdt.internal.core.dom.parser.cpp.ClassTypeHelper;
import org.eclipse.cdt.internal.core.model.ext.ICElementHandle;

import org.eclipse.cdt.internal.ui.viewsupport.IndexUI;

/**
 * Access to high level queries in the index.
 * @since 4.0
 */
public class CHQueries {
	private static final CHNode[] EMPTY_NODES= new CHNode[0];
	
    private CHQueries() {}
    
	/**
	 * Searches for functions and methods that call a given element.
	 */
	public static CHNode[] findCalledBy(CHContentProvider cp, CHNode node, IIndex index, IProgressMonitor pm) 
			throws CoreException {
		CalledByResult result= new CalledByResult();
		ICElement callee= node.getRepresentedDeclaration();
		if (!(callee instanceof ISourceReference)) {
			return EMPTY_NODES;
		}
		boolean done= false;
		int linkageID= node.getLinkageID();
		if (linkageID == -1) {
			final ITranslationUnit tu = ((ISourceReference) callee).getTranslationUnit();
			if (tu == null)
				return EMPTY_NODES;
			
			final String ct = tu.getContentTypeId();
			if (ct.equals(CCorePlugin.CONTENT_TYPE_CXXHEADER)) {
				// bug 260262: in a header file we need to consider c and c++
				findCalledBy(callee, ILinkage.C_LINKAGE_ID, index, result);
				findCalledBy(callee, ILinkage.CPP_LINKAGE_ID, index, result);
				done= true;
			}
		}
		if (!done) {
			findCalledBy(callee, linkageID, index, result);
		}
		return cp.createNodes(node, result);
	}

	private static void findCalledBy(ICElement callee, int linkageID, IIndex index, CalledByResult result) 
			throws CoreException {
		final ICProject project = callee.getCProject();
		IIndexBinding calleeBinding= IndexUI.elementToBinding(index, callee, linkageID);
		if (calleeBinding != null) {
			findCalledBy1(index, calleeBinding, true, project, result);
			if (calleeBinding instanceof ICPPMethod) {
				IBinding[] overriddenBindings= ClassTypeHelper.findOverridden((ICPPMethod) calleeBinding);
				for (IBinding overriddenBinding : overriddenBindings) {
					findCalledBy1(index, overriddenBinding, false, project, result);
				}
			}
		}
	}

	private static void findCalledBy1(IIndex index, IBinding callee, boolean includeOrdinaryCalls,
			ICProject project, CalledByResult result) throws CoreException {
		findCalledBy2(index, callee, includeOrdinaryCalls, project, result);
		List<? extends IBinding> specializations = IndexUI.findSpecializations(index, callee);
		for (IBinding spec : specializations) {
			findCalledBy2(index, spec, includeOrdinaryCalls, project, result);
		}
	}

	private static void findCalledBy2(IIndex index, IBinding callee, boolean includeOrdinaryCalls,
			ICProject project, CalledByResult result) throws CoreException {
		IIndexName[] names= index.findNames(callee, IIndex.FIND_REFERENCES | IIndex.SEARCH_ACROSS_LANGUAGE_BOUNDARIES);
		for (IIndexName rname : names) {
			if (includeOrdinaryCalls || rname.couldBePolymorphicMethodCall()) {
				IIndexName caller= rname.getEnclosingDefinition();
				if (caller != null) {
					ICElement elem= IndexUI.getCElementForName(project, index, caller);
					if (elem != null) {
						result.add(elem, rname);
					} 
				}
			}
		}
	}

	/**
	 * Searches for all calls that are made within a given range.
	 */
	public static CHNode[] findCalls(CHContentProvider cp, CHNode node, IIndex index, IProgressMonitor pm) 
			throws CoreException {
		ICElement caller= node.getRepresentedDeclaration();
		CallsToResult result= new CallsToResult();
		IIndexName callerName= IndexUI.elementToName(index, caller);
		if (callerName != null) {
			IIndexName[] refs= callerName.getEnclosedNames();
			for (IIndexName name : refs) {
				IBinding binding= index.findBinding(name);
				if (CallHierarchyUI.isRelevantForCallHierarchy(binding)) {
					while (true) {
						ICElement[] defs= null;
						if (binding instanceof ICPPMethod && name.couldBePolymorphicMethodCall()) {
							defs = findOverriders(index, (ICPPMethod) binding);
						}
						if (defs == null) {
							defs= IndexUI.findRepresentative(index, binding);
						}
						if (defs != null && defs.length > 0) {
							result.add(defs, name);
						} else if (binding instanceof ICPPSpecialization) {
							binding= ((ICPPSpecialization) binding).getSpecializedBinding();
							if (binding != null)
								continue;
						}
						break;
					}
				}
			}
		}
		return cp.createNodes(node, result);
	}

	/**
	 * Searches for overriders of method and converts them to ICElement, returns null,
	 * if there are none.
	 */
	static ICElement[] findOverriders(IIndex index, ICPPMethod binding)	throws CoreException {
		IBinding[] virtualOverriders= ClassTypeHelper.findOverriders(index, binding);
		if (virtualOverriders.length > 0) {
			ArrayList<ICElementHandle> list= new ArrayList<ICElementHandle>();
			list.addAll(Arrays.asList(IndexUI.findRepresentative(index, binding)));
			for (IBinding overrider : virtualOverriders) {
				list.addAll(Arrays.asList(IndexUI.findRepresentative(index, overrider)));
			}
			return list.toArray(new ICElement[list.size()]);
		}
		return null;
	}
}
