package gr.uom.java.ast;

import gr.uom.java.ast.decomposition.MethodBodyObject;
import gr.uom.java.ast.decomposition.cfg.AbstractVariable;
import gr.uom.java.ast.decomposition.cfg.PlainVariable;

import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ArrayList;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;

import org.eclipse.jdt.core.dom.MethodDeclaration;

public class ConstructorObject implements AbstractMethodDeclaration {

    protected String name;
	protected List<ParameterObject> parameterList;
	protected List<CommentObject> commentList;
    protected Access access;
    protected String className;
    protected MethodBodyObject methodBody;
    protected Set<String> exceptionsInJavaDocThrows;
    //protected MethodDeclaration methodDeclaration;
    protected ASTInformation methodDeclaration;
    private volatile int hashCode = 0;

    public ConstructorObject() {
		this.parameterList = new ArrayList<ParameterObject>();
		this.commentList = new ArrayList<CommentObject>();
		this.exceptionsInJavaDocThrows = new LinkedHashSet<String>();
        this.access = Access.NONE;
    }

    public void setMethodDeclaration(MethodDeclaration methodDeclaration) {
    	//this.methodDeclaration = methodDeclaration;
    	this.methodDeclaration = ASTInformationGenerator.generateASTInformation(methodDeclaration);
    }

    public MethodDeclaration getMethodDeclaration() {
    	//return this.methodDeclaration;
    	return (MethodDeclaration)this.methodDeclaration.recoverASTNode();
    }

    public void setMethodBody(MethodBodyObject methodBody) {
    	this.methodBody = methodBody;
    }

    public MethodBodyObject getMethodBody() {
    	return this.methodBody;
    }

    public void addExceptionInJavaDocThrows(String exception) {
    	this.exceptionsInJavaDocThrows.add(exception);
    }

    public Set<String> getExceptionsInJavaDocThrows() {
		return exceptionsInJavaDocThrows;
	}

    public void setAccess(Access access) {
        this.access = access;
    }

    public Access getAccess() {
        return access;
    }

    public void setName(String name) {
		this.name = name;
	}

	public String getName() {
		return name;
	}

    public void setClassName(String className) {
        this.className = className;
    }

    public String getClassName() {
        return this.className;
    }

	public boolean addComment(CommentObject comment) {
		return commentList.add(comment);
	}

	public ListIterator<CommentObject> getCommentListIterator() {
		return commentList.listIterator();
	}

	public boolean addParameter(ParameterObject parameter) {
		return parameterList.add(parameter);
	}

    public ListIterator<ParameterObject> getParameterListIterator() {
		return parameterList.listIterator();
	}

    public ParameterObject getParameter(int position) {
    	if(position >= 0 && position < parameterList.size())
    		return parameterList.get(position);
    	else if(position >= parameterList.size()) {
    		ParameterObject param = parameterList.get(parameterList.size()-1);
    		if(param.isVarargs())
    			return param;
    		else
    			return null;
    	}
    	else
    		return null;
    }

	public List<MethodInvocationObject> getMethodInvocations() {
		if(methodBody != null)
			return methodBody.getMethodInvocations();
		else
			return new ArrayList<MethodInvocationObject>();
	}

	public List<SuperMethodInvocationObject> getSuperMethodInvocations() {
		if(methodBody != null)
			return methodBody.getSuperMethodInvocations();
		else
			return new ArrayList<SuperMethodInvocationObject>();
	}

	public List<ConstructorInvocationObject> getConstructorInvocations() {
		if(methodBody != null)
			return methodBody.getConstructorInvocations();
		else
			return new ArrayList<ConstructorInvocationObject>();
	}

    public List<FieldInstructionObject> getFieldInstructions() {
    	if(methodBody != null)
    		return methodBody.getFieldInstructions();
    	else
    		return new ArrayList<FieldInstructionObject>();
    }

    public List<SuperFieldInstructionObject> getSuperFieldInstructions() {
    	if(methodBody != null)
    		return methodBody.getSuperFieldInstructions();
    	else
    		return new ArrayList<SuperFieldInstructionObject>();
    }

    public List<LocalVariableDeclarationObject> getLocalVariableDeclarations() {
    	if(methodBody != null)
    		return methodBody.getLocalVariableDeclarations();
    	else
    		return new ArrayList<LocalVariableDeclarationObject>();
    }

    public List<LocalVariableInstructionObject> getLocalVariableInstructions() {
    	if(methodBody != null)
    		return methodBody.getLocalVariableInstructions();
    	else
    		return new ArrayList<LocalVariableInstructionObject>();
    }

	public List<CreationObject> getCreations() {
		if(methodBody != null)
			return methodBody.getCreations();
		else
			return new ArrayList<CreationObject>();
	}

	public List<LiteralObject> getLiterals() {
		if(methodBody != null)
			return methodBody.getLiterals();
		else
			return new ArrayList<LiteralObject>();
	}

	public List<AnonymousClassDeclarationObject> getAnonymousClassDeclarations() {
		if(methodBody != null)
			return methodBody.getAnonymousClassDeclarations();
		else
			return new ArrayList<AnonymousClassDeclarationObject>();
	}

    public Set<String> getExceptionsInThrowStatements() {
    	if(methodBody != null)
			return methodBody.getExceptionsInThrowStatements();
		else
			return new LinkedHashSet<String>();
    }

    public boolean containsMethodInvocation(MethodInvocationObject methodInvocation) {
    	if(methodBody != null)
    		return methodBody.containsMethodInvocation(methodInvocation);
    	else
    		return false;
    }

    public boolean containsFieldInstruction(FieldInstructionObject fieldInstruction) {
    	if(methodBody != null)
    		return methodBody.containsFieldInstruction(fieldInstruction);
    	else
    		return false;
    }

    public boolean containsSuperMethodInvocation(SuperMethodInvocationObject superMethodInvocation) {
    	if(methodBody != null)
    		return methodBody.containsSuperMethodInvocation(superMethodInvocation);
    	else
    		return false;
    }

	public Map<AbstractVariable, LinkedHashSet<MethodInvocationObject>> getInvokedMethodsThroughFields() {
		if(methodBody != null)
			return methodBody.getInvokedMethodsThroughFields();
		else
			return new LinkedHashMap<AbstractVariable, LinkedHashSet<MethodInvocationObject>>();
	}

	public Map<AbstractVariable, ArrayList<MethodInvocationObject>> getNonDistinctInvokedMethodsThroughFields() {
		if(methodBody != null)
			return methodBody.getNonDistinctInvokedMethodsThroughFields();
		else
			return new LinkedHashMap<AbstractVariable, ArrayList<MethodInvocationObject>>();
	}

	public Map<AbstractVariable, LinkedHashSet<MethodInvocationObject>> getInvokedMethodsThroughParameters() {
		if(methodBody != null)
			return methodBody.getInvokedMethodsThroughParameters();
		else
			return new LinkedHashMap<AbstractVariable, LinkedHashSet<MethodInvocationObject>>();
	}

	public Map<AbstractVariable, ArrayList<MethodInvocationObject>> getNonDistinctInvokedMethodsThroughParameters() {
		if(methodBody != null)
			return methodBody.getNonDistinctInvokedMethodsThroughParameters();
		else
			return new LinkedHashMap<AbstractVariable, ArrayList<MethodInvocationObject>>();
	}

	public Map<AbstractVariable, LinkedHashSet<MethodInvocationObject>> getInvokedMethodsThroughLocalVariables() {
		if(methodBody != null)
			return methodBody.getInvokedMethodsThroughLocalVariables();
		else
			return new LinkedHashMap<AbstractVariable, LinkedHashSet<MethodInvocationObject>>();
	}

	public Set<MethodInvocationObject> getInvokedMethodsThroughThisReference() {
		if(methodBody != null)
			return methodBody.getInvokedMethodsThroughThisReference();
		else
			return new LinkedHashSet<MethodInvocationObject>();
	}

	public List<MethodInvocationObject> getNonDistinctInvokedMethodsThroughThisReference() {
		if(methodBody != null)
			return methodBody.getNonDistinctInvokedMethodsThroughThisReference();
		else
			return new ArrayList<MethodInvocationObject>();
	}

	public Set<MethodInvocationObject> getInvokedStaticMethods() {
		if(methodBody != null)
			return methodBody.getInvokedStaticMethods();
		else
			return new LinkedHashSet<MethodInvocationObject>();
	}

	public Set<AbstractVariable> getDefinedFieldsThroughFields() {
		if(methodBody != null)
			return methodBody.getDefinedFieldsThroughFields();
		else
			return new LinkedHashSet<AbstractVariable>();
	}

	public Set<AbstractVariable> getUsedFieldsThroughFields() {
		if(methodBody != null)
			return methodBody.getUsedFieldsThroughFields();
		else
			return new LinkedHashSet<AbstractVariable>();
	}

	public List<AbstractVariable> getNonDistinctDefinedFieldsThroughFields() {
		if(methodBody != null)
			return methodBody.getNonDistinctDefinedFieldsThroughFields();
		else
			return new ArrayList<AbstractVariable>();
	}

	public List<AbstractVariable> getNonDistinctUsedFieldsThroughFields() {
		if(methodBody != null)
			return methodBody.getNonDistinctUsedFieldsThroughFields();
		else
			return new ArrayList<AbstractVariable>();
	}

	public Set<AbstractVariable> getDefinedFieldsThroughParameters() {
		if(methodBody != null)
			return methodBody.getDefinedFieldsThroughParameters();
		else
			return new LinkedHashSet<AbstractVariable>();
	}

	public Set<AbstractVariable> getUsedFieldsThroughParameters() {
		if(methodBody != null)
			return methodBody.getUsedFieldsThroughParameters();
		else
			return new LinkedHashSet<AbstractVariable>();
	}

	public List<AbstractVariable> getNonDistinctDefinedFieldsThroughParameters() {
		if(methodBody != null)
			return methodBody.getNonDistinctDefinedFieldsThroughParameters();
		else
			return new ArrayList<AbstractVariable>();
	}

	public List<AbstractVariable> getNonDistinctUsedFieldsThroughParameters() {
		if(methodBody != null)
			return methodBody.getNonDistinctUsedFieldsThroughParameters();
		else
			return new ArrayList<AbstractVariable>();
	}

	public Set<AbstractVariable> getDefinedFieldsThroughLocalVariables() {
		if(methodBody != null)
			return methodBody.getDefinedFieldsThroughLocalVariables();
		else
			return new LinkedHashSet<AbstractVariable>();
	}

	public Set<AbstractVariable> getUsedFieldsThroughLocalVariables() {
		if(methodBody != null)
			return methodBody.getUsedFieldsThroughLocalVariables();
		else
			return new LinkedHashSet<AbstractVariable>();
	}

	public Set<PlainVariable> getDefinedFieldsThroughThisReference() {
		if(methodBody != null)
			return methodBody.getDefinedFieldsThroughThisReference();
		else
			return new LinkedHashSet<PlainVariable>();
	}

	public List<PlainVariable> getNonDistinctDefinedFieldsThroughThisReference() {
		if(methodBody != null)
			return methodBody.getNonDistinctDefinedFieldsThroughThisReference();
		else
			return new ArrayList<PlainVariable>();
	}

	public Set<PlainVariable> getUsedFieldsThroughThisReference() {
		if(methodBody != null)
			return methodBody.getUsedFieldsThroughThisReference();
		else
			return new LinkedHashSet<PlainVariable>();
	}

	public List<PlainVariable> getNonDistinctUsedFieldsThroughThisReference() {
		if(methodBody != null)
			return methodBody.getNonDistinctUsedFieldsThroughThisReference();
		else
			return new ArrayList<PlainVariable>();
	}

	public Set<PlainVariable> getDeclaredLocalVariables() {
		if(methodBody != null)
			return methodBody.getDeclaredLocalVariables();
		else
			return new LinkedHashSet<PlainVariable>();
	}

	public Set<PlainVariable> getDefinedLocalVariables() {
		if(methodBody != null)
			return methodBody.getDefinedLocalVariables();
		else
			return new LinkedHashSet<PlainVariable>();
	}

	public Set<PlainVariable> getUsedLocalVariables() {
		if(methodBody != null)
			return methodBody.getUsedLocalVariables();
		else
			return new LinkedHashSet<PlainVariable>();
	}

	public Map<PlainVariable, LinkedHashSet<MethodInvocationObject>> getParametersPassedAsArgumentsInMethodInvocations() {
		if(methodBody != null)
			return methodBody.getParametersPassedAsArgumentsInMethodInvocations();
		else
			return new LinkedHashMap<PlainVariable, LinkedHashSet<MethodInvocationObject>>();
	}

	public Map<PlainVariable, LinkedHashSet<SuperMethodInvocationObject>> getParametersPassedAsArgumentsInSuperMethodInvocations() {
		if(methodBody != null)
			return methodBody.getParametersPassedAsArgumentsInSuperMethodInvocations();
		else
			return new LinkedHashMap<PlainVariable, LinkedHashSet<SuperMethodInvocationObject>>();
	}

	public Map<PlainVariable, LinkedHashSet<ConstructorInvocationObject>> getParametersPassedAsArgumentsInConstructorInvocations() {
		if(methodBody != null)
			return methodBody.getParametersPassedAsArgumentsInConstructorInvocations();
		else
			return new LinkedHashMap<PlainVariable, LinkedHashSet<ConstructorInvocationObject>>();
	}

    public boolean containsSuperMethodInvocation() {
    	if(methodBody != null)
    		return methodBody.containsSuperMethodInvocation();
    	else
    		return false;
    }

    public boolean containsSuperFieldAccess() {
    	if(methodBody != null)
    		return methodBody.containsSuperFieldAccess();
    	else
    		return false;
    }

    public List<TypeObject> getParameterTypeList() {
    	List<TypeObject> list = new ArrayList<TypeObject>();
    	for(ParameterObject parameterObject : parameterList)
    		list.add(parameterObject.getType());
    	return list;
    }

    public List<String> getParameterList() {
    	List<String> list = new ArrayList<String>();
    	for(ParameterObject parameterObject : parameterList)
    		list.add(parameterObject.getType().toString());
    	return list;
    }

    public boolean equals(ClassInstanceCreationObject creationObject) {
    	return this.className.equals(creationObject.getType().getClassType()) &&
    			equalParameterTypes(this.getParameterTypeList(), creationObject.getParameterTypeList());
    }

    public boolean equals(ConstructorInvocationObject constructorInvocationObject) {
    	return this.className.equals(constructorInvocationObject.getOriginClassType().getClassType()) &&
    			equalParameterTypes(this.getParameterTypeList(), constructorInvocationObject.getParameterTypeList());
    }

    private boolean equalParameterTypes(List<TypeObject> list1, List<TypeObject> list2) {
    	if(list1.size() != list2.size())
    		return false;
    	for(int i=0; i<list1.size(); i++) {
    		TypeObject type1 = list1.get(i);
    		TypeObject type2 = list2.get(i);
    		if(!type1.equalsClassType(type2))
    			return false;
    		if(type1.getArrayDimension() != type2.getArrayDimension())
    			return false;
    	}
    	return true;
    }

    public boolean equals(Object o) {
        if(this == o) {
			return true;
		}

		if (o instanceof ConstructorObject) {
			ConstructorObject constructorObject = (ConstructorObject)o;

			return this.className.equals(constructorObject.className) && this.name.equals(constructorObject.name) &&
				this.parameterList.equals(constructorObject.parameterList);
		}
		return false;
    }

    public int hashCode() {
    	if(hashCode == 0) {
    		int result = 17;
    		result = 37*result + className.hashCode();
    		result = 37*result + name.hashCode();
    		result = 37*result + parameterList.hashCode();
    		hashCode = result;
    	}
    	return hashCode;
    }

    public String toString() {
        StringBuilder sb = new StringBuilder();
        if(!access.equals(Access.NONE))
            sb.append(access.toString()).append(" ");
        sb.append(name);
        sb.append("(");
        if(!parameterList.isEmpty()) {
            for(int i=0; i<parameterList.size()-1; i++)
                sb.append(parameterList.get(i).toString()).append(", ");
            sb.append(parameterList.get(parameterList.size()-1).toString());
        }
        sb.append(")");
        /*if(methodBody != null)
        	sb.append("\n").append(methodBody.toString());*/
        return sb.toString();
    }

	public String getSignature() {
		StringBuilder sb = new StringBuilder();
		sb.append(this.name);
		sb.append("(");
		if (!this.parameterList.isEmpty()) {
			for (int i = 0; i < this.parameterList.size() - 1; i++)
				sb.append(this.parameterList.get(i).getType()).append(", ");
			sb.append(this.parameterList.get(this.parameterList.size() - 1).getType());
		}
		sb.append(")");
		return sb.toString();
	}

	public boolean isAbstract() {
		return false;
	}
}