package gr.uom.java.ast;

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

import javax.swing.tree.DefaultMutableTreeNode;

import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.InfixExpression;
import org.eclipse.jdt.core.dom.InstanceofExpression;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.Type;

public class IfStatementExpressionAnalyzer {
	//parent nodes are CONDITIONAL_AND (&&), CONDITIONAL_OR (||) infix operators, while leaf nodes are expressions
	private DefaultMutableTreeNode root;
	private Expression completeExpression;
	//contains the expressions corresponding to each candidate type variable
	private Map<SimpleName, Expression> typeVariableExpressionMap;
	//contains the static fields corresponding to each candidate type variable
	private Map<SimpleName, ArrayList<SimpleName>> typeVariableStaticFieldMap;
	//contains the subclass types corresponding to each candidate type variable
	private Map<SimpleName, ArrayList<Type>> typeVariableSubclassMap;
	
	//contains the expressions corresponding to each candidate type method invocation
	private Map<MethodInvocation, Expression> typeMethodInvocationExpressionMap;
	//contains the static fields corresponding to each candidate type method invocation
	private Map<MethodInvocation, ArrayList<SimpleName>> typeMethodInvocationStaticFieldMap;
	//contains the subclass types corresponding to each candidate type method invocation
	private Map<MethodInvocation, ArrayList<Type>> typeMethodInvocationSubclassMap;
	
	public IfStatementExpressionAnalyzer(Expression completeExpression) {
		this.root = new DefaultMutableTreeNode();
		this.completeExpression = completeExpression;
		this.typeVariableExpressionMap = new LinkedHashMap<SimpleName, Expression>();
		this.typeVariableStaticFieldMap = new LinkedHashMap<SimpleName, ArrayList<SimpleName>>();
		this.typeVariableSubclassMap = new LinkedHashMap<SimpleName, ArrayList<Type>>();
		this.typeMethodInvocationExpressionMap = new LinkedHashMap<MethodInvocation, Expression>();
		this.typeMethodInvocationStaticFieldMap = new LinkedHashMap<MethodInvocation, ArrayList<SimpleName>>();
		this.typeMethodInvocationSubclassMap = new LinkedHashMap<MethodInvocation, ArrayList<Type>>();
		processExpression(root, completeExpression);
	}
	
	public void putTypeVariableExpression(SimpleName typeVariable, Expression expression) {
		typeVariableExpressionMap.put(typeVariable, expression);
	}
	
	public Set<SimpleName> getTargetVariables() {
		Set<SimpleName> targetVariables = new LinkedHashSet<SimpleName>();
		for(SimpleName targetVariable : typeVariableExpressionMap.keySet()) {
			if(typeVariableStaticFieldMap.keySet().contains(targetVariable) ||
					typeVariableSubclassMap.keySet().contains(targetVariable))
				targetVariables.add(targetVariable);
		}
		return targetVariables;
	}
	
	public Expression getTypeVariableExpression(SimpleName typeVariable) {
		return typeVariableExpressionMap.get(typeVariable);
	}
	
	public void putTypeVariableStaticField(SimpleName typeVariable, SimpleName staticField) {
		for(SimpleName keySimpleName : typeVariableStaticFieldMap.keySet()) {
			if(keySimpleName.resolveBinding().isEqualTo(typeVariable.resolveBinding())) {
				ArrayList<SimpleName> staticFields = typeVariableStaticFieldMap.get(keySimpleName);
				staticFields.add(staticField);
				return;
			}
		}
		ArrayList<SimpleName> staticFields = new ArrayList<SimpleName>();
		staticFields.add(staticField);
		typeVariableStaticFieldMap.put(typeVariable, staticFields);
	}
	
	public List<SimpleName> getTypeVariableStaticField(SimpleName typeVariable) {
		return typeVariableStaticFieldMap.get(typeVariable);
	}
	
	public void putTypeVariableSubclass(SimpleName typeVariable, Type subclass) {
		for(SimpleName keySimpleName : typeVariableSubclassMap.keySet()) {
			if(keySimpleName.resolveBinding().isEqualTo(typeVariable.resolveBinding())) {
				ArrayList<Type> subclasses = typeVariableSubclassMap.get(keySimpleName);
				subclasses.add(subclass);
				return;
			}
		}
		ArrayList<Type> subclasses = new ArrayList<Type>();
		subclasses.add(subclass);
		typeVariableSubclassMap.put(typeVariable, subclasses);
	}
	
	public List<Type> getTypeVariableSubclass(SimpleName typeVariable) {
		return typeVariableSubclassMap.get(typeVariable);
	}
	
	public void putTypeMethodInvocationExpression(MethodInvocation typeMethodInvocation, Expression expression) {
		typeMethodInvocationExpressionMap.put(typeMethodInvocation, expression);
	}
	
	public Set<MethodInvocation> getTargetMethodInvocations() {
		Set<MethodInvocation> targetMethodInvocations = new LinkedHashSet<MethodInvocation>();
		for(MethodInvocation targetMethodInvocation : typeMethodInvocationExpressionMap.keySet()) {
			if(typeMethodInvocationStaticFieldMap.keySet().contains(targetMethodInvocation) ||
					typeMethodInvocationSubclassMap.keySet().contains(targetMethodInvocation))
				targetMethodInvocations.add(targetMethodInvocation);
		}
		return targetMethodInvocations;
	}
	
	public Expression getTypeMethodInvocationExpression(MethodInvocation typeMethodInvocation) {
		return typeMethodInvocationExpressionMap.get(typeMethodInvocation);
	}
	
	public void putTypeMethodInvocationStaticField(MethodInvocation typeMethodInvocation, SimpleName staticField) {
		for(MethodInvocation keyMethodInvocation : typeMethodInvocationStaticFieldMap.keySet()) {
			if(keyMethodInvocation.resolveMethodBinding().isEqualTo(typeMethodInvocation.resolveMethodBinding())) {
				ArrayList<SimpleName> staticFields = typeMethodInvocationStaticFieldMap.get(keyMethodInvocation);
				staticFields.add(staticField);
				return;
			}
		}
		ArrayList<SimpleName> staticFields = new ArrayList<SimpleName>();
		staticFields.add(staticField);
		typeMethodInvocationStaticFieldMap.put(typeMethodInvocation, staticFields);
	}
	
	public List<SimpleName> getTypeMethodInvocationStaticField(MethodInvocation typeMethodInvocation) {
		return typeMethodInvocationStaticFieldMap.get(typeMethodInvocation);
	}
	
	public void putTypeMethodInvocationSubclass(MethodInvocation typeMethodInvocation, Type subclass) {
		for(MethodInvocation keyMethodInvocation : typeMethodInvocationSubclassMap.keySet()) {
			if(keyMethodInvocation.resolveMethodBinding().isEqualTo(typeMethodInvocation.resolveMethodBinding())) {
				ArrayList<Type> subclasses = typeMethodInvocationSubclassMap.get(keyMethodInvocation);
				subclasses.add(subclass);
				return;
			}
		}
		ArrayList<Type> subclasses = new ArrayList<Type>();
		subclasses.add(subclass);
		typeMethodInvocationSubclassMap.put(typeMethodInvocation, subclasses);
	}
	
	public List<Type> getTypeMethodInvocationSubclass(MethodInvocation typeMethodInvocation) {
		return typeMethodInvocationSubclassMap.get(typeMethodInvocation);
	}
	
	private void processExpression(DefaultMutableTreeNode parent, Expression expression) {
		if(expression instanceof InfixExpression) {
			InfixExpression infixExpression = (InfixExpression)expression;
			InfixExpression.Operator operator = infixExpression.getOperator();
			if(operator.equals(InfixExpression.Operator.CONDITIONAL_AND) || operator.equals(InfixExpression.Operator.CONDITIONAL_OR)) {
				parent.setUserObject(operator);
				DefaultMutableTreeNode leftOperandNode = new DefaultMutableTreeNode();
				DefaultMutableTreeNode rightOperandNode = new DefaultMutableTreeNode();
				parent.add(leftOperandNode);
				parent.add(rightOperandNode);
				processExpression(leftOperandNode, infixExpression.getLeftOperand());
				processExpression(rightOperandNode, infixExpression.getRightOperand());
				if(infixExpression.hasExtendedOperands()) {
					DefaultMutableTreeNode grandParent = (DefaultMutableTreeNode)parent.getParent();
					int parentIndex = -1;
					if(grandParent != null)
						parentIndex = grandParent.getIndex(parent);
					DefaultMutableTreeNode newParent = processExtendedOperands(parent, infixExpression.extendedOperands());
					if(grandParent != null)
						grandParent.insert(newParent, parentIndex);
					else
						root = newParent;
				}
			}
			else {
				parent.setUserObject(infixExpression);
			}
		}
		else {
			parent.setUserObject(expression);
		}
	}
	
	private DefaultMutableTreeNode processExtendedOperands(DefaultMutableTreeNode parent, List<Expression> extendedOperands) {
		InfixExpression.Operator operator = (InfixExpression.Operator)parent.getUserObject();
		DefaultMutableTreeNode newParent = null;
		DefaultMutableTreeNode oldParent = parent;
		for(Expression extendedOperand : extendedOperands) {
			newParent = new DefaultMutableTreeNode();
			newParent.setUserObject(operator);
			newParent.insert(oldParent, 0);
			DefaultMutableTreeNode rightOperandNode = new DefaultMutableTreeNode();
			rightOperandNode.setUserObject(extendedOperand);
			newParent.insert(rightOperandNode, 1);
			oldParent = newParent;
		}
		return newParent;
	}
	
	public List<InstanceofExpression> getInstanceofExpressions() {
		List<InstanceofExpression> expressionList = new ArrayList<InstanceofExpression>();
		DefaultMutableTreeNode leaf = root.getFirstLeaf();
		while(leaf != null) {
			Expression expression = (Expression)leaf.getUserObject();
			if(expression instanceof InstanceofExpression) {
				InstanceofExpression instanceofExpression = (InstanceofExpression)expression;
				expressionList.add(instanceofExpression);
			}
			leaf = leaf.getNextLeaf();
		}
		return expressionList;
	}
	
	public List<InfixExpression> getInfixExpressionsWithEqualsOperator() {
		List<InfixExpression> expressionList = new ArrayList<InfixExpression>();
		DefaultMutableTreeNode leaf = root.getFirstLeaf();
		while(leaf != null) {
			Expression expression = (Expression)leaf.getUserObject();
			if(expression instanceof InfixExpression) {
				InfixExpression infixExpression = (InfixExpression)expression;
				InfixExpression.Operator operator = infixExpression.getOperator();
				if(operator.equals(InfixExpression.Operator.EQUALS))
					expressionList.add(infixExpression);
			}
			leaf = leaf.getNextLeaf();
		}
		return expressionList;
	}
	
	public DefaultMutableTreeNode getRemainingExpression(Expression expressionToBeRemoved) {
		DefaultMutableTreeNode newRoot = new DefaultMutableTreeNode();
		processExpression(newRoot, completeExpression);
		DefaultMutableTreeNode leaf = newRoot.getFirstLeaf();
		while(leaf != null) {
			Expression expression = (Expression)leaf.getUserObject();
			if(expression.equals(expressionToBeRemoved)) {
				DefaultMutableTreeNode parent = (DefaultMutableTreeNode)leaf.getParent();
				if(parent != null) {
					DefaultMutableTreeNode grandParent = (DefaultMutableTreeNode)parent.getParent();
					DefaultMutableTreeNode sibling = null;
					if(leaf.getNextSibling() != null) {
						sibling = leaf.getNextSibling();
					}
					else if(leaf.getPreviousSibling() != null) {
						sibling = leaf.getPreviousSibling();
					}
					if(grandParent != null) {
						int parentIndex = grandParent.getIndex(parent);
						grandParent.remove(parent);
						grandParent.insert(sibling, parentIndex);
					}
					else {
						newRoot = sibling;
					}
					break;
				}
				else {
					newRoot = null;
					break;
				}
			}
			leaf = leaf.getNextLeaf();
		}
		return newRoot;
	}
	
	public boolean allParentNodesAreConditionalAndOperators() {
		Enumeration<DefaultMutableTreeNode> enumeration = root.breadthFirstEnumeration();
		while(enumeration.hasMoreElements()) {
			DefaultMutableTreeNode node = enumeration.nextElement();
			if(!node.isLeaf()) {
				InfixExpression.Operator operator = (InfixExpression.Operator)node.getUserObject();
				if(!operator.equals(InfixExpression.Operator.CONDITIONAL_AND))
					return false;
			}
		}
		return true;
	}

	public boolean allParentNodesAreConditionalOrOperators() {
		Enumeration<DefaultMutableTreeNode> enumeration = root.breadthFirstEnumeration();
		while(enumeration.hasMoreElements()) {
			DefaultMutableTreeNode node = enumeration.nextElement();
			if(!node.isLeaf()) {
				InfixExpression.Operator operator = (InfixExpression.Operator)node.getUserObject();
				if(!operator.equals(InfixExpression.Operator.CONDITIONAL_OR))
					return false;
			}
		}
		return true;
	}
	
	public int getNumberOfConditionalOperatorNodes() {
		int counter = 0;
		Enumeration<DefaultMutableTreeNode> enumeration = root.breadthFirstEnumeration();
		while(enumeration.hasMoreElements()) {
			DefaultMutableTreeNode node = enumeration.nextElement();
			if(!node.isLeaf()) {
				counter++;
			}
		}
		return counter;
	}
	
	public Expression getCompleteExpression() {
		return completeExpression;
	}

	public String toString() {
		return completeExpression.toString();
	}
}
