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.IField;
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.parsingElements.ClassParser;
import antipatternsrecovery.parsingElements.MethodInvocationsVisitor;
import antipatternsrecovery.parsingElements.Parser;

public class MessageChains {
	private IJavaProject projectToAnalyze;
	private ClassParser classParser;
	private HashMap<IMethod, Vector<Integer>> methodChains;
	private HashMap<IMethod, Vector<Integer>> candidateMessageChains;
	private Vector<IMethod> analyzedMethod=new Vector<IMethod>();
	
	public MessageChains(IJavaProject pProject){
		this.setProjectToAnalyze(pProject);
		this.classParser=new ClassParser();
		this.methodChains=new HashMap<IMethod, Vector<Integer>>();
	}
	
	public HashMap<IMethod, Vector<Integer>> searchMessageChains() {
		candidateMessageChains=new HashMap<IMethod, Vector<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 m : t.getMethods()) {
									if((!m.getElementName().equals("execute")) && ! m.getElementName().equals("init")
											&& !m.getElementName().equals("printElementDecl") && (!m.getElementName().contains("Compile"))
											&& (!m.getElementName().contains("copyFilesToDestination")) && (!m.getElementName().equals("testSupportsCharacters"))) {
									if(!m.isConstructor())
										this.methodChains.put(m, this.counter(m));
									}
								}			
							}
						}	
					}
				}
			}
		} catch (JavaModelException e) {
			e.printStackTrace();
		}
		
		int max=this.max();
		if(max!=-1){
			double upperHinge=this.upperHinge();
			for(Entry<IMethod, Vector<Integer>> value: this.methodChains.entrySet()){
				//System.out.println(value.getKey().getElementName() + " " + value.getValue());
				if((value.getValue().get(0)>upperHinge) && (value.getValue().get(0)<max)) {
					if(value.getValue().get(0).intValue()>100)
						candidateMessageChains.put(value.getKey(), value.getValue());	
				}
			}
		}
		return candidateMessageChains;
	}
	
	public IJavaProject getProjectToAnalyze() {
		return this.projectToAnalyze;
	}

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

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

	public void setParser(ClassParser parser) {
		this.classParser = parser;
	}
	
	private Double median(ArrayList<Integer> values) {
		Collections.sort(values);	
		if (values.size()==0) return 0.0;
		else 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.methodChains.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.methodChains.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 boolean isObjectField(String pExpr, ICompilationUnit pClass){
		IType type=pClass.getType(pClass.getElementName().substring(0, pClass.getElementName().length()-5));
		try {
			for(IField field: type.getFields())
				if((field.getElementName().equals(pExpr)) && (field.getTypeSignature().length()>1)) return true;	
		} catch (JavaModelException e) {
			return false;
		}
		return false;
	}
	
	private String checkType(String pExpr, ICompilationUnit pClass) {
		IType type=pClass.getType(pClass.getElementName().substring(0, pClass.getElementName().length()-5));
		try {
			for(IField field: type.getFields()){
				if(field.getElementName().equals(pExpr)) {
					String source=field.getSource();
					if(source.contains("<")){
						Pattern pattern=Pattern.compile("<[A-Za-z0-9]*>");
						Matcher matcher=pattern.matcher(source);
						while(matcher.find()) {
							Pattern patternField=Pattern.compile("[A-Za-z0-9]+");
							Matcher matcherField=patternField.matcher(matcher.group());
							if(matcherField.find()){
								return matcherField.group();
							}
						}
					} else if (source.contains("[]")){
						Pattern pattern=Pattern.compile("[A-Za-z0-9]+[�]*\\[[ ]*\\]");
						Matcher matcher=pattern.matcher(source);
						while(matcher.find()){
							Pattern patternField=Pattern.compile("[A-Za-z0-9]+");
							Matcher matcherField=patternField.matcher(matcher.group());
							if(matcherField.find()){
								return matcherField.group();
							}	
						}
					} else {
						return field.getTypeSignature().substring(1, field.getTypeSignature().length()-1);
					}
				}
			}
		} catch (JavaModelException e) {
			e.printStackTrace();
		}
		return "";
	}
	
	private Vector<Integer> counter(IMethod pMethodToAnalyze) {
		Vector<Integer> toReturn=new Vector<Integer>();
		this.analyzedMethod=new Vector<IMethod>();
		int loc=0;
		toReturn.add(this.analyzeMethod(pMethodToAnalyze));
		for(IMethod method:this.analyzedMethod){
			loc=this.LOC(method);
		}
			toReturn.add(loc);
		
		return toReturn;
	}
	
	private int analyzeMethod(IMethod pMethodToAnalyze) {
			int counter=1;
			MethodInvocationsVisitor methodInvocationVisitor = new MethodInvocationsVisitor();
			Parser parser = new Parser();
			TypeDeclaration block;
			
			try {
				block = parser.createParser(pMethodToAnalyze.getSource(), ASTParser.K_CLASS_BODY_DECLARATIONS);
				block.accept(methodInvocationVisitor);
			} catch (Exception e) {
				return counter;
			}
			if (methodInvocationVisitor.getMethods().size() != 0) {
				if (methodInvocationVisitor.getMethods().size() < 6) {
					for (MethodInvocation mi : methodInvocationVisitor.getMethods()) {
						@SuppressWarnings("unused")
						String methodCall="";
						if((mi.getExpression()!=null)){
							String expr=mi.getExpression().toString();
							String anchor="";
							int index=expr.lastIndexOf(".");
							if(index!=-1) {
								for(int i=index+1;i<expr.length(); i++){
									anchor+=expr.charAt(i);
								}
								index=anchor.indexOf("(");
								if(index!=-1){
									for(int i=0;i<index; i++)
										methodCall+=anchor.charAt(i);
									counter++;
								}
							} else {
								if(expr.equals("this")) {
									IMethod newMethod=this.retrieveMethod(pMethodToAnalyze.getCompilationUnit(), mi.getName().getIdentifier(), pMethodToAnalyze.getElementName());
									if(newMethod!=null){
										this.analyzedMethod.add(newMethod);
										counter+=analyzeMethod(newMethod);
									}
								}
								else {
									boolean isObjectField=isObjectField(expr, pMethodToAnalyze.getCompilationUnit());
									if(isObjectField) {
										IMethod newMethod=this.retrieveMethod(this.checkType(expr, pMethodToAnalyze.getCompilationUnit()), mi.getName().getIdentifier(), pMethodToAnalyze.getElementName());
										if(newMethod!=null) {
											this.analyzedMethod.add(newMethod);
											counter+=analyzeMethod(newMethod);
										}
									}
								}
							}
						 
						}
					}
				}
			}	
			return counter;
	}
	
	private IMethod retrieveMethod(ICompilationUnit pBelongingClass, String pMethodName, String pOldMethod) {
		IType type=pBelongingClass.getType(pBelongingClass.getElementName().substring(0, pBelongingClass.getElementName().length()-5));

		try {
			for(IMethod method: type.getMethods()){
				if((method.getElementName().equals(pMethodName)) && (!method.getElementName().equals(pOldMethod))) 
					return method;
			}
		} catch (JavaModelException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	private IMethod retrieveMethod(String pClassName, String pMethodName, String pOldMethod){
		String cuName=pClassName+".java";
		try {
			for (IPackageFragment pack : this.projectToAnalyze.getPackageFragments()) {
				for (ICompilationUnit cu : pack.getCompilationUnits()) {
					if(cu.getElementName().equals(cuName)){
						for (IType t : cu.getTypes()) {
							for (IMethod m : t.getMethods()) {
								if((m.getElementName().equals(pMethodName)) && (!m.getElementName().equals(pOldMethod)))
									return m;
							}				
						}	
					}
				}
			}
		} catch (JavaModelException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	private int LOC(IMethod pMethod){
		int loc=0;
		try {
			String source=pMethod.getSource();
			String regex="[\n]";
			Pattern pattern=Pattern.compile(regex);
			Matcher matcher = pattern.matcher(source);
			while (matcher.find()) 
			      loc++;		    
		} catch (JavaModelException e) {
			return loc;
		}
		return loc+1;
	}
	
	public String toString() { 
		String toReturn="";
		for(Entry<IMethod, Vector<Integer>> value: candidateMessageChains.entrySet()){
			try {
				if((value.getKey().getCompilationUnit().getPackageDeclarations().length!=0) && (value.getKey().getCompilationUnit().getPackageDeclarations()[0]!=null))
					toReturn+=value.getKey().getElementName()+"; " +value.getKey().getCompilationUnit().getElementName()+"; "+ value.getKey().getCompilationUnit().getPackageDeclarations()[0].getElementName()+"; " + value.getValue().get(0).intValue()+ "; " + value.getValue().get(1).intValue() +";\n";
				else toReturn+=value.getKey().getElementName() +"; " +value.getKey().getCompilationUnit().getElementName()+"; "+ + value.getValue().get(0).intValue()+ "; " + value.getValue().get(1).intValue() + ";\n"; 
			} catch (JavaModelException e) {
				e.printStackTrace();
			}
		}
		return toReturn;
	}
}