package antipatternsrecovery.antipatterns;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

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 antipatternsrecovery.parsingElements.ClassParser;

public class InappropriateIntimacy {
	private IJavaProject projectToAnalyze;
	private ClassParser parser;
	private Vector<IType> types;
	// Salvo la coppia analizzata e il loro fan in!
	private HashMap<Vector<IType>, Vector<Integer>> classCoupling;
	private HashMap<Vector<ICompilationUnit>, Vector<Integer>> candidateInappropriateIntimacies;
	int i=1;
	int counterSecondInFirst=0;
	int counterFirstInSecond=0;
	int resultSize=0;

	public InappropriateIntimacy(IJavaProject pProject){
		this.setProjectToAnalyze(pProject);
		this.classCoupling=new HashMap<Vector<IType>, Vector<Integer>>();
		this.types=new Vector<IType>();
	}

	public HashMap<Vector<ICompilationUnit>, Vector<Integer>> searchInappropriateIntimacies() {		
		candidateInappropriateIntimacies=new HashMap<Vector<ICompilationUnit>, Vector<Integer>>();
		Vector<IType> analyzedClasses;
		try {
			for(IPackageFragment p: this.projectToAnalyze.getPackageFragments()) {
				for(ICompilationUnit cu: p.getCompilationUnits()) {
					if(cu.getElementName().contains(".java")) {
						for(IType t: cu.getTypes()) {
							types.add(t);
						}
					}
				}
			}
		} catch (JavaModelException e) {
			e.printStackTrace();
		}

		for(IType type: this.types) {
			for(IType secondType: this.types) {
				analyzedClasses=new Vector<IType>();
				analyzedClasses.add(type);
				analyzedClasses.add(secondType);
				Vector<IType> test=new Vector<IType>();
				test.add(secondType);
				test.add(type);
				if((!this.classCoupling.containsKey(analyzedClasses)) && (!this.classCoupling.containsKey(test) && (type!=secondType))) {
					Vector<Integer> fanIn=this.fanIn(type, secondType);
					this.classCoupling.put(analyzedClasses, fanIn);
				}
			}
		}

		int maxFanIn=this.max();
		if(maxFanIn!=-1){
			double upperHinge=this.upperHinge();
			
			for(Entry<Vector<IType>, Vector<Integer>> value: this.classCoupling.entrySet()) {
				if((value.getValue().get(0)>upperHinge) && (value.getValue().get(0)<maxFanIn)) {
					Vector<ICompilationUnit> couple=new Vector<ICompilationUnit>();
					for(IType t: value.getKey())
						couple.add(t.getCompilationUnit());

					if(value.getValue().get(0).intValue()>150)
						candidateInappropriateIntimacies.put(couple, value.getValue());
					i++;
				}
			}
		}
		return candidateInappropriateIntimacies;
	}

	public IJavaProject getProjectToAnalyze() {
		return projectToAnalyze;
	}
	public void setProjectToAnalyze(IJavaProject projectToAnalyze) {
		this.projectToAnalyze = projectToAnalyze;
	}
	public ClassParser getParser() {
		return parser;
	}
	public void setParser(ClassParser parser) {
		this.parser = parser;
	}

	public Vector<IType> getTypes() {
		return types;
	}

	public void setTypes(Vector<IType> types) {
		this.types = types;
	}

	private Double median(ArrayList<Integer> values) {
		Collections.sort(values);	

		if((values.size()%2==0))
			return (double) ((values.get((values.size()/2)-1) + (values.get(values.size()/2))))/2;
		else return (double) (values.get(values.size()/2));
	}

	private int max() {
		Vector<Integer> valuesForAnalysis=new Vector<Integer>();
		ArrayList<Integer> values=new ArrayList<Integer>();

		for(Vector<Integer> v: this.classCoupling.values()){
			valuesForAnalysis.add(v.get(0).intValue());
		}

		for(Integer i: valuesForAnalysis)
			values.add(i);

		if(values.size()>0)
			return Collections.max(values);
		else return -1;
	}

	private Double upperHinge() {
		ArrayList<Integer> values=new ArrayList<Integer>();
		ArrayList<Integer> valuesForUpperHinge=new ArrayList<Integer>();

		Vector<Integer> valuesForAnalysis=new Vector<Integer>();

		for(Vector<Integer> v: this.classCoupling.values()){
			valuesForAnalysis.add(v.get(0).intValue());
		}

		for(Integer i: valuesForAnalysis)
			values.add(i);

		int max=this.max();
		double median=this.median(values);

		for(Integer value: valuesForAnalysis)
			if((value>median) && (value<max)) valuesForUpperHinge.add(value);

		return this.median(valuesForUpperHinge);
	}

	private Vector<Integer> fanIn(IType pType, IType pSecondType) {
		Vector<Integer> results=new Vector<Integer>();
		i++;
		Matcher matcher;
		String codeSecondType="";
		String codeFirstType="";
		if(pType!=pSecondType) {
			// Cerco una dichiarazione di pType in pSecondType
			Pattern pattern=Pattern.compile("[^A-Za-z0-9]"+pType.getElementName()+"[^A-Za-z0-9]");
			try {
				codeSecondType=pSecondType.getSource();
				matcher=pattern.matcher(codeSecondType);
				while(matcher.find()) {
					counterFirstInSecond++;
				}
			} catch (JavaModelException e) {
				return results;
			}


			// Cerco una dichiarazione di pSecondType in pType
			Pattern searchSecondType=Pattern.compile("[^A-Za-z0-9]"+pSecondType.getElementName()+"[^A-Za-z0-9]");
			try {
				codeFirstType=pType.getSource();
				matcher=searchSecondType.matcher(codeFirstType);
				while(matcher.find()) {
					counterSecondInFirst++;
				}
			} catch (JavaModelException e) {
				return results;
			}

			if(counterSecondInFirst>0 || counterFirstInSecond>0){
				try {
					codeFirstType=pType.getSource();
					for(IMethod method: pSecondType.getMethods()){
						Pattern methodCall=Pattern.compile(method.getElementName());
						Matcher matcherCall=methodCall.matcher(codeFirstType);
						while(matcherCall.find()) {
							counterSecondInFirst++;
						}
					}
				} catch (JavaModelException e) {
					return results;
				}

				try {

					for(IMethod method: pType.getMethods()) {
						Pattern methodCall=Pattern.compile(method.getElementName());
						Matcher matcherCall=methodCall.matcher(codeSecondType);
						while(matcherCall.find()) {
							counterFirstInSecond++;
						}
					}
				} catch (JavaModelException e) {
					return results;
				}
			}
		}

		results.add(counterFirstInSecond+counterSecondInFirst);
		results.add(counterFirstInSecond);
		results.add(counterSecondInFirst);
		counterFirstInSecond=0;
		counterSecondInFirst=0;
		return results;
	}

	public String toString() {
		String toReturn="";
		for(Entry<Vector<ICompilationUnit>, Vector<Integer>> value: this.candidateInappropriateIntimacies.entrySet()) {
			this.resultSize++;
			if(resultSize<50) {
				for(ICompilationUnit cu: value.getKey())
					try {
						if((cu.getPackageDeclarations().length!=0) && (cu.getPackageDeclarations()[0]!=null))
							toReturn+=cu.getElementName()+"; " + cu.getPackageDeclarations()[0].getElementName() + "; ";
						else toReturn+=cu.getElementName() + "; ";
					} catch (JavaModelException e) {
						e.printStackTrace();
					}
				toReturn+="1->2; " + value.getValue().get(1) + ";  2->1; " + value.getValue().get(2) + ";\n";
			}
		}
		return toReturn;
	}
}