package antipatternsrecovery.antipatterns;

import java.util.HashMap;
import java.util.Map.Entry;

import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.MethodInvocation;

import antipatternsrecovery.beans.BeanConverter;
import antipatternsrecovery.beans.ClassBean;
import antipatternsrecovery.beans.MethodBean;
import antipatternsrecovery.parsingElements.ClassParser;
import antipatternsrecovery.parsingElements.MethodInvocationsVisitor;
import antipatternsrecovery.parsingElements.Parser;

public class FeatureEnvy {
	private IJavaProject projectToAnalyze;
	private ClassParser parser;
	private HashMap<MethodBean, HashMap<String, Integer>> candidateFeatureEnvies;
	private HashMap<MethodBean, HashMap<String, Integer>> app;
	int resultSize=0;


	public FeatureEnvy(IJavaProject pProject){
		this.setProjectToAnalyze(pProject);
		this.parser=new ClassParser();
	}

	public HashMap<MethodBean, HashMap<String, Integer>> searchCandidateFeatureEnvies() {
		candidateFeatureEnvies=new HashMap<MethodBean, HashMap<String, Integer>>();
		app=new HashMap<MethodBean, HashMap<String, Integer>>();
		try {
			for(IPackageFragment pack: this.projectToAnalyze.getPackageFragments()) {
				for(ICompilationUnit cu: pack.getCompilationUnits()) {
					if(cu.getElementName().contains(".java")) {
						for(IType t: cu.getTypes()) {
							if(! t.isInterface()) {
								for(IMethod method:t.getMethods()) {										
									HashMap<String, Integer> coupling=new HashMap<String, Integer>();
									@SuppressWarnings("unused")
									int counterPro=0;

									MethodInvocationsVisitor methodInvocationVisitor=new MethodInvocationsVisitor();
									try {
										Parser parser=new Parser();
										String methodToParse = "public class A { " + method.getSource() + "}";
										CompilationUnit block;
										block = parser.createParser(methodToParse, true);
										block.accept(methodInvocationVisitor);		
										if(methodInvocationVisitor.getMethods().size()!=0) {
											for(MethodInvocation mi: methodInvocationVisitor.getMethods()){
												if(this.isContained(mi, method.getCompilationUnit())) counterPro++;
												else if(this.isContainedInProject(mi, method.getCompilationUnit().getJavaProject())) {
													ClassBean classSearched=BeanConverter.castCUToClassBean(this.searchClass(mi, method.getCompilationUnit().getJavaProject()));
													if(coupling.containsKey(classSearched.getName())) {
														coupling.put(classSearched.getName(), coupling.get(classSearched.getName())+1);
													}
													else {
														coupling.put(classSearched.getName(), new Integer(1));
													}
													this.app.put(BeanConverter.castIMToMethodBean(method), coupling);
												}

											}
										}
									} catch (Exception e) {
										continue;
									}

									for(Entry<MethodBean, HashMap<String, Integer>> value: this.app.entrySet()) {			
										for(Entry<String, Integer> ints: value.getValue().entrySet()){
											if(ints.getValue().intValue()>10) {
												HashMap<String, Integer> featureEnvy=new HashMap<String, Integer>();
												featureEnvy.put(ints.getKey(), ints.getValue());
												if(!this.isIn(value.getKey()))
													this.candidateFeatureEnvies.put(value.getKey(), featureEnvy);
											}
										}
									}										
								}
							}
						}
					}
				}
			}
		} catch (JavaModelException e) {
			e.printStackTrace();
		}

		return candidateFeatureEnvies;
	}

	public IJavaProject getProjectToAnalyze() {
		return this.projectToAnalyze;
	}

	public void setProjectToAnalyze(IJavaProject pProjectToAnalyze) {
		this.projectToAnalyze = pProjectToAnalyze;
	}

	public ClassParser getParser() {
		return this.parser;
	}

	public void setParser(ClassParser parser) {
		this.parser = parser;
	}

	private boolean isContained(MethodInvocation pMethod, ICompilationUnit pClass) {
		try {
			for(IType type: pClass.getTypes()){
				for(IMethod method: type.getMethods()){
					if(method.getElementName().equals(pMethod.getName().getIdentifier())) {
						//	System.out.println("Invocation: " + pMethod.getName().getIdentifier() + " in class " + pClass.getElementName() + " TRUE;");
						return true;
					}

				}
			}
		} catch (JavaModelException e) {
			e.printStackTrace();
		}
		//System.out.println("Invocation: " + pMethod.getName().getIdentifier() + " in class " + pClass.getElementName() + " FALSE;");
		return false;
	}

	private boolean isContainedInProject(MethodInvocation pMethod, IJavaProject pJavaProject) {
		try {
			for(IPackageFragment pack: pJavaProject.getPackageFragments()){
				for(ICompilationUnit cu: pack.getCompilationUnits()){
					for(IType type: cu.getTypes()){
						for(IMethod method: type.getMethods()) {
							if(method.getElementName().equals(pMethod.getName().getIdentifier())) {
								return true;
							}
						}
					}
				}
			}
		} catch (JavaModelException e) {
			e.printStackTrace();
		}
		return false;
	}

	private ICompilationUnit searchClass(MethodInvocation pMethod, IJavaProject pJavaProject) {
		try {
			for(IPackageFragment pack: pJavaProject.getPackageFragments()){
				for(ICompilationUnit cu: pack.getCompilationUnits()){
					for(IType type: cu.getTypes()){
						for(IMethod method: type.getMethods()) {
							if(method.getElementName().equals(pMethod.getName().getIdentifier())){
								return method.getCompilationUnit();
							}
						}
					}
				}
			}
		} catch (JavaModelException e) {
			e.printStackTrace();
		}
		return null;
	}

	private boolean isIn(MethodBean pMethodBean){
		for(Entry<MethodBean, HashMap<String, Integer>> fe: this.candidateFeatureEnvies.entrySet()) {
			if(pMethodBean.equals(fe.getKey()))
				return true;
		}
		return false;
	}

	public String toString() { 
		String toReturn="";
		for(Entry<MethodBean, HashMap<String, Integer>> value: this.candidateFeatureEnvies.entrySet()) {		
			this.resultSize++;
			if(resultSize<30) {
				for(Entry<String, Integer> ints: value.getValue().entrySet()) {
					toReturn+=value.getKey().getBelongingClass().getBelongingPackage().getName()+"."+value.getKey().getBelongingClass().getName()+"."+value.getKey().getName()+";" + ints.getKey() + "; " + ints.getValue().intValue() + "; \n";
				}
			}
		}	
		return toReturn;
	}
}