/*******************************************************************************
 * Copyright (c) 2010 Red Hat, Inc.
 * Distributed under license by Red Hat, Inc. All rights reserved.
 * This program is 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:
 *     Red Hat, Inc. - initial API and implementation
 ******************************************************************************/
package org.jboss.tools.cdi.ui.search;

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

import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.ILocalVariable;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.ui.search.ElementQuerySpecification;
import org.eclipse.jdt.ui.search.IMatchPresentation;
import org.eclipse.jdt.ui.search.IQueryParticipant;
import org.eclipse.jdt.ui.search.ISearchRequestor;
import org.eclipse.jdt.ui.search.QuerySpecification;
import org.jboss.tools.cdi.core.CDICoreNature;
import org.jboss.tools.cdi.core.CDICorePlugin;
import org.jboss.tools.cdi.core.CDIUtil;
import org.jboss.tools.cdi.core.IBean;
import org.jboss.tools.cdi.core.ICDIElement;
import org.jboss.tools.cdi.core.ICDIProject;
import org.jboss.tools.cdi.core.IClassBean;
import org.jboss.tools.cdi.core.IInjectionPoint;
import org.jboss.tools.cdi.core.IObserverMethod;
import org.jboss.tools.cdi.core.IParameter;

public class InjectionPointQueryParticipant implements IQueryParticipant{
	ArrayList<String> objects = new ArrayList<String>();
	
	public int estimateTicks(QuerySpecification specification) {
		return 10;
	}

	public IMatchPresentation getUIParticipant() {
		return new InjectionPointMatchPresentation();
	}
	
	private static boolean containsInSearchScope(QuerySpecification querySpecification, IPath projectPath){
		IJavaSearchScope searchScope = querySpecification.getScope();
		if(searchScope == null)
			return true;
		IPath[] paths = searchScope.enclosingProjectsAndJars();
		for(IPath path : paths){
			if(path.equals(projectPath))
				return true;
		}
		return false;
	}
	
	public static boolean containsInSearchScope(QuerySpecification querySpecification, ICDIElement element){
		return containsInSearchScope(querySpecification, element.getResource().getProject().getFullPath());
	}

	public void search(ISearchRequestor requestor,
			QuerySpecification querySpecification, IProgressMonitor monitor)
			throws CoreException {
		objects.clear();
		
		if(querySpecification instanceof ElementQuerySpecification){
			if (!isSearchForReferences(querySpecification.getLimitTo()))
				return;
			
			ElementQuerySpecification qs = (ElementQuerySpecification)querySpecification;
			IJavaElement element = qs.getElement();
			if(element instanceof IMethod || element instanceof IField || element instanceof ILocalVariable){
				IProject project = element.getJavaProject().getProject();
				if(project == null)
					return;
				
				CDICoreNature cdiNature = CDICorePlugin.getCDI(project, true);
				
				if(cdiNature == null)
					return;
				
				ICDIProject cdiProject = cdiNature.getDelegate();
				
				if(cdiProject == null)
					return;
				
				Collection<IBean> beans = cdiProject.getBeans(element.getPath());
				
				IInjectionPoint injectionPoint = CDIUtil.findInjectionPoint(beans, element, 0);
				if(injectionPoint != null){
					List<IBean> resultBeanList = CDIUtil.sortBeans(cdiProject.getBeans(false, injectionPoint));
					for(IBean bean : resultBeanList){
						if(bean != null && containsInSearchScope(querySpecification, bean)){
							CDIMatch match = new CDIMatch(bean);
							if(!objects.contains(match.getPath())){
								requestor.reportMatch(match);
								objects.add(match.getPath());
							}
						}
					}
					resolveObserverMethods(cdiProject, injectionPoint, requestor, querySpecification);
				}
				if(element instanceof IMethod){
					IParameter param = findObserverParameter(beans, (IMethod)element);
					if(param != null){
						findObservedEvents(cdiProject, param, requestor, querySpecification);
					}
				}
			}
		}
	}
	
	private void resolveObserverMethods(ICDIProject cdiProject, IInjectionPoint injectionPoint, ISearchRequestor requestor,
			QuerySpecification querySpecification){
		for(IObserverMethod observerMethod : cdiProject.resolveObserverMethods(injectionPoint)){
			if(containsInSearchScope(querySpecification, observerMethod)){
				// match observer method
				CDIMatch match = new CDIMatch(observerMethod);
				if(!objects.contains(match.getPath())){
					requestor.reportMatch(match);
					objects.add(match.getPath());
				}
			}
		}
	}
	
	private void findObservedEvents(ICDIProject cdiProject, IParameter param, ISearchRequestor requestor,
			QuerySpecification querySpecification){
		for(IInjectionPoint event : cdiProject.findObservedEvents(param)){
			if(containsInSearchScope(querySpecification, event)){
				// match event
				CDIMatch match = new CDIMatch(event);
				if(!objects.contains(match.getPath())){
					requestor.reportMatch(match);
					objects.add(match.getPath());
				}
			}
		}
	}
	
	private IParameter findObserverParameter(Collection<IBean> beans, IMethod method) throws JavaModelException {
		for (IBean bean: beans) {
			if(bean instanceof IClassBean) {
				for (IObserverMethod bm: ((IClassBean)bean).getObserverMethods()) {
					if(bm.getMethod().equals(method)){
						IObserverMethod obs = (IObserverMethod)bm;
						Collection<IParameter> ps = obs.getObservedParameters();
						if(!ps.isEmpty()) {
							return ps.iterator().next();
						}
					}
				}
			}
		}
		
		return null;
	}

	
	public boolean isSearchForReferences(int limitTo) {
    	int maskedLimitTo = limitTo & ~(IJavaSearchConstants.IGNORE_DECLARING_TYPE+IJavaSearchConstants.IGNORE_RETURN_TYPE);
    	if (maskedLimitTo == IJavaSearchConstants.REFERENCES || maskedLimitTo == IJavaSearchConstants.ALL_OCCURRENCES) {
    		return true;
    	}
    
    	return false;
    }
	
}
