package lapsePlus;

/*
* CallerFinder.java, version 2.8, 2010
*/

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.ReturnStatement;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.core.search.SearchParticipant;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.core.search.SearchRequestor;
import org.eclipse.jdt.internal.core.JavaProject;
import org.eclipse.jdt.internal.core.search.JavaSearchScope;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.ui.PlatformUI;

public class CallerFinder {
	    
    private static IJavaSearchScope fSearchScope;

    public static Collection/*<Util.ExprUn...>*/ findCallers(final String methodName, final JavaProject project, final boolean isConstructor) {
    	class FindingOp implements IRunnableWithProgress { 
    		Collection c = null;
    		public void run(IProgressMonitor monitor) {
    			c = findCallers(monitor, methodName, project, isConstructor);
    			if(c == null) {
    				JavaPlugin.logErrorMessage("Collection is null");
    			}
//    			notify();
    		}
			public Collection/*<MethodDeclarationUnitPair>*/ getCollection() {return c;}
    	}			
					
    	try {
    		FindingOp operation = new FindingOp();
			PlatformUI.getWorkbench().getProgressService().run(false, true, operation);
//			operation.wait();
			Collection c = operation.getCollection();
			if(c == null) {
				JavaPlugin.logErrorMessage("No collection is computed");
			}
			return c;
		} catch (InvocationTargetException e) {
			JavaPlugin.log(e);
		} catch (InterruptedException e) {
			// canceled
		}
		return null;
    }
    
    public static Collection/*<Util.ExprUn...>*/ findCallers(final IMethod method, final IJavaProject project) {
		// TODO Auto-generated method stub
		return null;
	}
    
    public static Collection/*<MethodDeclarationUnitPair>*/ findMethods(final SimpleName methodName, final JavaProject project, final boolean isConstructor) {
    	class FindingOp implements IRunnableWithProgress { 
    		Collection c = null;
    		public void run(IProgressMonitor monitor) {
    			c = findCallees(monitor, methodName.toString(), project, isConstructor);
    			if(c == null) {
    				JavaPlugin.logErrorMessage("Collection is null");
    			}
//    			notify();
    		}
			public Collection/*<MethodDeclarationUnitPair>*/ getCollection() {return c;}
    	}
    	try {
    		FindingOp operation = new FindingOp();
			PlatformUI.getWorkbench().getProgressService().run(false, true, operation);
//			operation.wait();
			Collection c = operation.getCollection();
			if(c == null) {
				JavaPlugin.logErrorMessage("No collection is computed");
			}
			return c;
		} catch (InvocationTargetException e) {
			JavaPlugin.log(e);
		} catch (InterruptedException e) {
			// canceled
		}
		return null;
    }
    
	public static Collection/*<MethodDeclarationUnitPair>*/ 
			findCallees(IProgressMonitor progressMonitor, String methodName, IJavaProject project, boolean isConstructor) 
	{
		try {
            MethodSearchRequestor.MethodDeclarationsSearchRequestor searchRequestor = 
            	new MethodSearchRequestor.MethodDeclarationsSearchRequestor();
            SearchEngine searchEngine = new SearchEngine();

            IProgressMonitor monitor = new SubProgressMonitor(
            		progressMonitor, 5, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL);
            monitor.beginTask("Searching for declaration of " + methodName +
            		(project != null ? " in " + project.getProject().getName() : ""), 100);
            IJavaSearchScope searchScope = getSearchScope(project);
            int matchType = !isConstructor ? IJavaSearchConstants.METHOD : IJavaSearchConstants.CONSTRUCTOR;
            SearchPattern pattern = SearchPattern.createPattern(
            		methodName, 
					matchType,
					IJavaSearchConstants.DECLARATIONS, 
					SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE );
            
            searchEngine.search(
            		pattern, 
					new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() },
                    searchScope, 
					searchRequestor, 
					monitor
					);
            monitor.done();

            return searchRequestor.getMethodUnitPairs();
        } catch (CoreException e) {
            JavaPlugin.log(e);

            return new LinkedList();
        }
	}
	
	/**
	 * Returns a collection of return statements withing @param methodDeclaration.
	 * */
	public static Collection<ReturnStatement> findReturns(IProgressMonitor progressMonitor, MethodDeclaration methodDeclaration, JavaProject project) {
		progressMonitor.setTaskName("Looking for returns in " + methodDeclaration.getName());
		final Collection<ReturnStatement> returns = new ArrayList<ReturnStatement>();
		ASTVisitor finder = new ASTVisitor() {			
			public boolean visit(ReturnStatement node) {
				return returns.add(node);
			}
		};
		
		methodDeclaration.accept(finder);
		return returns;
	}	
    
	public static String callersToString(IMethod method) {
		Collection/*<MethodUnitPair>*/ callers = findCallers(method, null);
		String result = "[ ";
		for (Iterator iter = callers.iterator(); iter.hasNext();) {
			Object element = (Object) iter.next();
			result += element + " ";			
		}
		return result + "]";
	}
	
	public static Collection<Utils.ExpressionUnitPair> getActualsForFormal(IMethod method, Name name, Expression onlyCall, IProgressMonitor monitor, IJavaProject project) {
		Collection<Utils.ExpressionUnitPair> result = new ArrayList<Utils.ExpressionUnitPair>();
		Collection/*<MethodUnitPair>*/ c = findCallers(monitor, method, project);
		if (c.isEmpty()) {
			log("No callers for " + method);
		} else {
			monitor.beginTask("Getting actuals for " + name + " in " + method.getElementName(), c.size());
			int i = 0;
			for (Iterator iter = c.iterator(); iter.hasNext();) {
				Utils.ExprUnitResource mi = (Utils.ExprUnitResource) iter.next();
				int pos = SlicingUtils.getFormalArgumentPos(method, name);
				if (pos == SlicingUtils.NO_FORMAL_ARGUMENT) {
					log("No parameter " + name + " found in " + method);
					continue;
				}
				if (mi.getExpression() == null) {
					logError("unexpected error: mi.getExpression() == null when looking at " + name + " and " + method);
					continue;
				}
				String s1 = mi.getExpression().toString();
				//String s2 = onlyCall.toString();
				if ( (onlyCall != null) && (!s1.equals(onlyCall.toString())) ) {
					log("Skipping " + mi.getExpression() + "while looking for " + onlyCall + " instead");
					continue;
				}
				Expression expr = SlicingUtils.mapFormal2Actual(mi.getExpression(), pos);
				if(expr != null) {
					result.add(new Utils.ExpressionUnitPair(expr, mi.getCompilationUnit(), mi.getResource()));	
				}			
				monitor.worked(++i);
			}
			monitor.done();
		}
		
		return result;
	}
	
	public static Collection/*<ExpressionUnitPair>*/ getActualsForFormal(final IMethod method, final Name name, final Expression onlyCall, final JavaProject project) {
		class FindingOp implements IRunnableWithProgress { 
			Collection c = null;
			public void run(IProgressMonitor monitor) {
				c = getActualsForFormal(method, name, onlyCall, monitor, project);
			}
			public Collection getCollection() {return c;}
		}
		try {
    		FindingOp operation = new FindingOp();
			PlatformUI.getWorkbench().getProgressService().run(false, true, operation);
			
			return operation.getCollection();
		} catch (InvocationTargetException e) {
			JavaPlugin.log(e);
		} catch (InterruptedException e) {
			// canceled
		}
		return null;		
	}

	public static Collection/*<MethodUnitPair>*/ findCallers(IProgressMonitor progressMonitor, IMethod member, IJavaProject project) {
        boolean isConstructor = false;
        try {
            isConstructor = member.isConstructor(); // interrogate the member itself
        } catch (JavaModelException e) {
            JavaPlugin.log(e);
        }
        String methodName = isConstructor ?
            member.getDeclaringType().getElementName() :
            member.getDeclaringType().getElementName() + "." + member.getElementName();
		return findCallers(progressMonitor, methodName, project, isConstructor);
    }
	    
	public static Collection/*<MethodUnitPair>*/ findCallers(IProgressMonitor progressMonitor, String methodName, IJavaProject project, boolean isConstructor) {
        
		try {
			
			MethodSearchRequestor.initializeParserMap();
			
        	SearchRequestor searchRequestor =  (SearchRequestor)new MethodSearchRequestor.MethodReferencesSearchRequestor();
			
            SearchEngine searchEngine = new SearchEngine();

            IProgressMonitor monitor = new SubProgressMonitor(progressMonitor, 5, SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK);
            
            monitor.beginTask("Searching for calls to " + methodName + (project != null ? " in " + project.getProject().getName() : ""), 100);
            
            IJavaSearchScope searchScope = getSearchScope(project);
            
            // This is kind of hacky: we need to make up a string name for the search to work right
            
            log("Looking for calls to " + methodName);
            
            int matchType = !isConstructor ? IJavaSearchConstants.METHOD : IJavaSearchConstants.CONSTRUCTOR;
            
            SearchPattern pattern = SearchPattern.createPattern(
            		methodName, 
					matchType,
					IJavaSearchConstants.REFERENCES,
					SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE);
            
            searchEngine.search(
            		pattern, 
					new SearchParticipant[] {SearchEngine.getDefaultSearchParticipant()},
                    searchScope, 
					searchRequestor, 
					monitor
			);

            if(searchRequestor instanceof MethodSearchRequestor.MethodDeclarationsSearchRequestor){                
                return ((MethodSearchRequestor.MethodDeclarationsSearchRequestor)searchRequestor).getMethodUnitPairs();
            }else{
                return ((MethodSearchRequestor.MethodReferencesSearchRequestor)searchRequestor).getMethodUnitPairs();
            }
            
        } catch (CoreException e) {
            JavaPlugin.log(e);

            return new LinkedList();
        }
    }
    
    public static Collection/*<MethodUnitPair>*/ findDeclarations(IProgressMonitor progressMonitor, String methodName, IJavaProject project, boolean isConstructor) {
        try {
            SearchRequestor searchRequestor = new MethodSearchRequestor.MethodDeclarationsSearchRequestor(); 
            SearchEngine searchEngine = new SearchEngine();

            IProgressMonitor monitor = new SubProgressMonitor(
                    progressMonitor, 5, SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK);
            monitor.beginTask("Searching for calls to " + 
                    methodName + (project != null ? " in " + project.getProject().getName() : ""), 100);            
            IJavaSearchScope searchScope = getSearchScope(project);
            // This is kind of hacky: we need to make up a string name for the search to work right
            log("Looking for " + methodName);
            int matchType = !isConstructor ? IJavaSearchConstants.METHOD : IJavaSearchConstants.CONSTRUCTOR;
            SearchPattern pattern = SearchPattern.createPattern(
                    methodName, 
                    matchType,
                    IJavaSearchConstants.DECLARATIONS,
                    SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE );
            
            searchEngine.search(
                    pattern, 
                    new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() },
                    searchScope, 
                    searchRequestor, 
                    monitor
                    );

            if(searchRequestor instanceof MethodSearchRequestor.MethodDeclarationsSearchRequestor){                
                return ((MethodSearchRequestor.MethodDeclarationsSearchRequestor)searchRequestor).getMethodUnitPairs();
            }else{
                return ((MethodSearchRequestor.MethodReferencesSearchRequestor)searchRequestor).getMethodUnitPairs();
            }
        } catch (CoreException e) {
            JavaPlugin.log(e);

            return new LinkedList();
        }
    }
    
    public static IJavaSearchScope getSearchScope(IJavaProject project) {
    	if (fSearchScope == null) {
            fSearchScope = SearchEngine.createWorkspaceScope();
        	//fSearchScope = SearchEngine.createJavaSearchScope(new IResource[] {method.getResource()});
        }
//    	return fSearchScope;

    	if(project == null) {	        
	        return fSearchScope;
    	} else {
    		JavaSearchScope js = new JavaSearchScope();
    		try {
    			int includeMask = 
    				JavaSearchScope.SOURCES | 
					JavaSearchScope.APPLICATION_LIBRARIES | 
					JavaSearchScope.SYSTEM_LIBRARIES ;
				js.add((JavaProject) project, includeMask, new HashSet());
			} catch (JavaModelException e) {
				log(e.getMessage(), e);
				return fSearchScope;
			}
    		return js;
    	} 
    }    	
        
    public static class SlicingUtils {
		public static final int NO_FORMAL_ARGUMENT = -1;

		public static Expression mapFormal2Actual(Expression mi, int pos){
			//if(mi == null) return null;
			List args = null;
			if (mi instanceof ClassInstanceCreation) {
				args = ((ClassInstanceCreation)mi).arguments();
			} else if(mi instanceof MethodInvocation) {
				args = ((MethodInvocation)mi).arguments();
			} else {
				JavaPlugin.logErrorMessage("Unexpected type in mapFormal2Actual for " + mi);
				return null;
			}
	    	
	    	return (Expression) args.get(pos);
		}
		
		public static SimpleName getVariable(Expression expr){
			if (expr instanceof SimpleName) {
				return (SimpleName) expr;
			} else {
				return null;
			}			
		}
		
		/**
		 * @return NO_FORMAL_ARGUMENT signals that there's no matching position in the method declaration.
		 * */
		
		public static int getFormalArgumentPos(IMethod method, Name arg){
			String[] names;
			try {
				names = method.getParameterNames();
			} catch (JavaModelException e) {
				return NO_FORMAL_ARGUMENT; 
			}
			for (int i = 0; i < names.length; i++) {
				if(names[i].equals(arg.toString())) {
					return i;
				}
			}
			return NO_FORMAL_ARGUMENT;
		}
    }

    private static void log(String message, Throwable e) {
        LapsePlugin.trace(LapsePlugin.SEARCH, "Call finder: " + message, e);
    }
    
    private static void logError(String message) {
        log(message, new Throwable());
    }
    
    private static void log(String message) {
        log(message, null);
    }
}