package antipatternsrecovery.antipatterns;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Vector;
import java.util.Map.Entry;
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 org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.TypeDeclaration;

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

public class LazyClass {
	private IJavaProject projectToAnalyze;
	private Vector<IType> types;
	private HashMap<IType, Integer> classFanOut;
	private HashMap<IType, Integer> classFanIn;
	private Vector<ClassBean> candidateLazyClasses;
	int volta=1;

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

	public Vector<ClassBean> searchLazyClasses() {		
		candidateLazyClasses=new Vector<ClassBean>();
		Vector<ClassBean> candidatefanOut=new Vector<ClassBean>();
		Vector<ClassBean> candidatefanIn=new Vector<ClassBean>();
		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) {
			if(! type.getElementName().contains("Stemmer")){
				int fanOut=this.fanOut(type);
				this.classFanOut.put(type, fanOut);
				int fanIn=this.fanIn(type, this.types);
				this.classFanIn.put(type, fanIn);
			}
		}

		int minFanOut=this.min(classFanOut);
		int minFanIn=this.min(classFanIn);
		if((minFanIn!=-1) && (minFanOut!=-1)){
			double lowerHingeFanOut=this.lowerHinge(classFanOut);
			double lowerHingeFanIn=this.lowerHinge(classFanIn);

			for(Entry<IType, Integer> valueOut: classFanOut.entrySet()) {
				if((valueOut.getValue()<lowerHingeFanOut) && (valueOut.getValue()>minFanOut))
					try {
						candidatefanOut.add(BeanConverter.softCastCUToClassBean(valueOut.getKey().getCompilationUnit()));
					} catch (JavaModelException e) {
						e.printStackTrace();
					}
			}

			for(Entry<IType, Integer> valueIn: classFanIn.entrySet()){
				if((valueIn.getValue()<lowerHingeFanIn) && (valueIn.getValue()>minFanIn))
					try {
						candidatefanIn.add(BeanConverter.softCastCUToClassBean(valueIn.getKey().getCompilationUnit()));
					} catch (JavaModelException e) {
						e.printStackTrace();
					}
			}

			for(ClassBean cb: candidatefanOut) { 
				for(ClassBean cb2: candidatefanIn)
					if(cb2.getName().equals(cb.getName())) candidateLazyClasses.add(cb);
			}
		}
		return candidateLazyClasses;
	}

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

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

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

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

	private Double median(ArrayList<Integer> values) {
		Collections.sort(values);
		if((values.size()%2==0)){
			try{
				return (double) ((values.get((values.size()/2)-1) + (values.get(values.size()/2))))/2;
			}catch(Exception e){
				return 0.0;
			}
		} else return (double) (values.get(values.size()/2));
	}

	private int min(HashMap<IType, Integer> pMap) { 
		if(pMap.values().size()>0)
			return Collections.min(pMap.values());
		else return -1;
	}

	private Double lowerHinge(HashMap<IType, Integer> pMap) {
		ArrayList<Integer> values=new ArrayList<Integer>();
		ArrayList<Integer> valuesForLowerHinge=new ArrayList<Integer>();

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

		int min=this.min(pMap);
		double median=this.median(values);

		for(Entry<IType, Integer> value: pMap.entrySet())
			if((value.getValue()<median) && (value.getValue()>min)) valuesForLowerHinge.add(value.getValue());

		return this.median(valuesForLowerHinge);
	}

	private int fanOut(IType pType) {
		MethodInvocationsVisitor methodInvocationVisitor=new MethodInvocationsVisitor();	
		int counter=0;
		try {
			for(IMethod m: pType.getMethods()){
				if(! m.getElementName().equals("cloneDate")){
					Parser parser=new Parser();
					//	System.out.println(m.getElementName() + " " + m.getCompilationUnit().getElementName() + " " + m.getCompilationUnit().getPackageDeclarations()[0].getElementName());
					TypeDeclaration block=parser.createParser(m.getSource(), ASTParser.K_CLASS_BODY_DECLARATIONS);
					block.accept(methodInvocationVisitor);
					if(methodInvocationVisitor.getMethods().size()!=0) {
						for(@SuppressWarnings("unused") MethodInvocation mi: methodInvocationVisitor.getMethods()){
							counter++;
						}
					}	
				}
			}
		} catch (Exception e) {
			return counter;
		}
		return counter;
	}

	private int fanIn(IType pType, Vector<IType> pOthers) {
		int counter=0;
		Matcher matcher;
		for(IType type: pOthers){
			String code="";
			if(type!=pType){
				Pattern pattern=Pattern.compile(pType.getElementName());
				try {
					code=type.getSource();
					matcher=pattern.matcher(code);
					while(matcher.find()) {
						counter++;
					}
				} catch (Exception e) {
					return counter;
				}			
			}
		}

		return counter;
	}

	public String toString() { 
		String toReturn="";
		for(ClassBean classBean: this.candidateLazyClasses){
			if(classBean.getBelongingPackage()!=null)
				toReturn+=classBean.getName()+"; " +classBean.getBelongingPackage().getName()+";\n";
			else 
				toReturn+=classBean.getName()+";\n";
		}
		return toReturn;
	}
}