package gr.uom.java.ast.decomposition.cfg.mapping;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import gr.uom.java.ast.decomposition.cfg.PDGControlDependence;
import gr.uom.java.ast.decomposition.cfg.PDGNode;

public class ControlDependenceTreeNode {
	private ControlDependenceTreeNode parent;
	private PDGNode node;
	private int level;
	private List<ControlDependenceTreeNode> children;
	private ControlDependenceTreeNode ifParent;
	private ControlDependenceTreeNode elseIfChild;
	private boolean isElseNode = false;
	private boolean isTernary = false;

	private ControlDependenceTreeNode(ControlDependenceTreeNode treeNode) {
		this.parent = treeNode.parent;
		this.node = treeNode.node;
		this.level = treeNode.level;
		this.children = new ArrayList<ControlDependenceTreeNode>();
		this.children.addAll(treeNode.children);
	}

	public ControlDependenceTreeNode(ControlDependenceTreeNode parent, PDGNode node) {
		this.parent = parent;
		if(parent == null) {
			level = 0;
		}
		else {
			level = parent.level + 1;
		}
		this.node = node;
		this.children = new ArrayList<ControlDependenceTreeNode>();
		if(parent != null)
			parent.children.add(this);
	}

	public ControlDependenceTreeNode(ControlDependenceTreeNode parent, ControlDependenceTreeNode previousSibling, PDGNode node) {
		this.parent = parent;
		if(parent == null) {
			level = 0;
		}
		else {
			level = parent.level + 1;
		}
		this.node = node;
		this.children = new ArrayList<ControlDependenceTreeNode>();
		if(parent != null) {
			int indexOfPreviousSibling = parent.children.indexOf(previousSibling);
			parent.children.add(indexOfPreviousSibling+1, this);
		}
	}

	public boolean parentChildRelationship(double parentId, double childId) {
		ControlDependenceTreeNode root = getRoot();
		ControlDependenceTreeNode treeParent = root.getNode(parentId);
		if(treeParent != null) {
			for(ControlDependenceTreeNode treeChild : treeParent.children) {
				if(treeChild.getId() == childId)
					return true;
			}
		}
		return false;
	}

	public boolean ifElseRelationship(double ifParentId, double elseChildId) {
		ControlDependenceTreeNode root = getRoot();
		ControlDependenceTreeNode ifParent = root.getNode(ifParentId);
		ControlDependenceTreeNode elseChild = root.getNode(elseChildId);
		if(ifParent != null && elseChild != null) {
			if(elseChild.isElseNode && elseChild.ifParent.getId() == ifParent.getId())
				return true;
		}
		return false;
	}

	public ControlDependenceTreeNode getNode(double id) {
		if(this.getId() == id) {
			return this;
		}
		else if(this.isLeaf()) {
			return null;
		}
		else {
			for(ControlDependenceTreeNode child : this.children) {
				ControlDependenceTreeNode treeNode = child.getNode(id);
				if(treeNode != null)
					return treeNode;
			}
		}
		return null;
	}

	public ControlDependenceTreeNode getNode(PDGNode node) {
		if(this.node != null && this.node.equals(node)) {
			return this;
		}
		else if(this.isLeaf()) {
			return null;
		}
		else {
			for(ControlDependenceTreeNode child : this.children) {
				ControlDependenceTreeNode treeNode = child.getNode(node);
				if(treeNode != null)
					return treeNode;
			}
		}
		return null;
	}

	public ControlDependenceTreeNode getElseNode(PDGNode ifParent) {
		if(this.isElseNode && this.getIfParent().getNode().equals(ifParent)) {
			return this;
		}
		else if(this.isLeaf()) {
			return null;
		}
		else {
			for(ControlDependenceTreeNode child : this.children) {
				ControlDependenceTreeNode treeNode = child.getElseNode(ifParent);
				if(treeNode != null)
					return treeNode;
			}
		}
		return null;
	}

	public Set<PDGNode> getControlPredicateNodesInLevel(int level) {
		ControlDependenceTreeNode root = getRoot();
		List<ControlDependenceTreeNode> levelNodes = root.getControlDependenceTreeNodesInLevel(level);
		Set<PDGNode> predicateNodes = new LinkedHashSet<PDGNode>();
		for(ControlDependenceTreeNode levelNode : levelNodes) {
			PDGNode pdgNode = levelNode.node;
			if(levelNode.isElseNode)
				pdgNode = levelNode.ifParent.node;
			if(pdgNode != null)
				predicateNodes.add(pdgNode);
		}
		return predicateNodes;
	}

	public Set<ControlDependenceTreeNode> getElseNodesInLevel(int level) {
		ControlDependenceTreeNode root = getRoot();
		List<ControlDependenceTreeNode> levelNodes = root.getControlDependenceTreeNodesInLevel(level);
		Set<ControlDependenceTreeNode> elseNodes = new LinkedHashSet<ControlDependenceTreeNode>();
		for(ControlDependenceTreeNode levelNode : levelNodes) {
			if(levelNode.isElseNode) {
				elseNodes.add(levelNode);
			}
		}
		return elseNodes;
	}

	public int getMaxLevel() {
		ControlDependenceTreeNode root = getRoot();
		List<ControlDependenceTreeNode> leaves = root.getLeaves();
		int max = 0;
		for(ControlDependenceTreeNode leaf : leaves) {
			if(leaf.level > max) {
				max = leaf.level;
			}
		}
		return max;
	}

	private ControlDependenceTreeNode getRoot() {
		if(parent == null)
			return this;
		else 
			return parent.getRoot();
	}
	
	private boolean isLeaf() {
		return this.children.isEmpty();
	}

	public List<ControlDependenceTreeNode> getLeaves() {
		List<ControlDependenceTreeNode> leaves = new ArrayList<ControlDependenceTreeNode>();
		if(this.isLeaf()) {
			leaves.add(this);
		}
		else {
			for(ControlDependenceTreeNode child : this.children) {
				leaves.addAll(child.getLeaves());
			}
		}
		return leaves;
	}
	
	public boolean ifStatementInsideElseIfChain() {
		return ifParent != null || elseIfChild != null;
	}

	public int getNumberOfIfParents() {
		if(ifParent == null)
			return 0;
		else 
			return ifParent.getNumberOfIfParents() + 1;
	}
	
	public int getNumberOfElseIfChildren() {
		if(elseIfChild == null)
			return 0;
		else 
			return elseIfChild.getNumberOfElseIfChildren() + 1;
	}
	
	public int getLengthOfElseIfChain() {
		return getNumberOfIfParents() + getNumberOfElseIfChildren();
	}

	public List<ControlDependenceTreeNode> getIfParents() {
		List<ControlDependenceTreeNode> ifParents = new ArrayList<ControlDependenceTreeNode>();
		if(ifParent != null) {
			ifParents.addAll(ifParent.getIfParents());
			ifParents.add(ifParent);
		}
		return ifParents;
	}

	public List<ControlDependenceTreeNode> getElseIfChildren() {
		List<ControlDependenceTreeNode> elseIfChildren = new ArrayList<ControlDependenceTreeNode>();
		if(elseIfChild != null) {
			elseIfChildren.add(elseIfChild);
			elseIfChildren.addAll(elseIfChild.getElseIfChildren());
		}
		return elseIfChildren;
	}

	public boolean isElseNode() {
		return isElseNode;
	}

	public void setElseNode(boolean isElseNode) {
		this.isElseNode = isElseNode;
	}

	public boolean isTernary() {
		return isTernary;
	}

	public void setTernary(boolean isTernary) {
		this.isTernary = isTernary;
	}

	public List<ControlDependenceTreeNode> getSiblings() {
		List<ControlDependenceTreeNode> siblings = new ArrayList<ControlDependenceTreeNode>();
		if(this.parent != null) {
			for(ControlDependenceTreeNode sibling : parent.children) {
				if(!sibling.equals(this))
					siblings.add(sibling);
			}
		}
		return siblings;
	}

	public ControlDependenceTreeNode getPreviousSibling() {
		List<ControlDependenceTreeNode> siblings = this.getSiblings();
		ControlDependenceTreeNode previousSibling = null;
		for(ControlDependenceTreeNode sibling : siblings) {
			if(sibling.getId() < this.getId()) {
				previousSibling = sibling;
			}
		}
		return previousSibling;
	}

	public boolean areAllSiblingsLeaves() {
		if(this.isLeaf()) {
			ControlDependenceTreeNode parent = this.getParent();
			if(parent != null) {
				for(ControlDependenceTreeNode sibling : parent.children) {
					if(!sibling.isLeaf())
						return false;
				}
				return true;
			}
		}
		return false;
	}

	private List<ControlDependenceTreeNode> getControlDependenceTreeNodesInLevel(int level) {
		List<ControlDependenceTreeNode> levelNodes = new ArrayList<ControlDependenceTreeNode>();
		if(this.level == level) {
			levelNodes.add(this);
		}
		else {
			for(ControlDependenceTreeNode child : this.children) {
				levelNodes.addAll(child.getControlDependenceTreeNodesInLevel(level));
			}
		}
		return levelNodes;
	}

	public ControlDependenceTreeNode shallowCopy() {
		return new ControlDependenceTreeNode(this);
	}

	public ControlDependenceTreeNode getParent() {
		return parent;
	}

	public PDGNode getNode() {
		return node;
	}

	public int getLevel() {
		return level;
	}

	public List<ControlDependenceTreeNode> getChildren() {
		return children;
	}

	public Set<ControlDependenceTreeNode> getDescendants() {
		Set<ControlDependenceTreeNode> descendants = new LinkedHashSet<ControlDependenceTreeNode>();
		descendants.addAll(children);
		for(ControlDependenceTreeNode child : this.children) {
			descendants.addAll(child.getDescendants());
		}
		return descendants;
	}

	public int getNodeCount() {
		// count 1 for "this" node
		int count = 1;
		for(ControlDependenceTreeNode child : children) {
			count += child.getNodeCount();
		}
		return count;
	}

	public List<ControlDependenceTreeNode> getNodesInBreadthFirstOrder() {
		List<ControlDependenceTreeNode> nodes = new ArrayList<ControlDependenceTreeNode>();
		List<ControlDependenceTreeNode> queue = new LinkedList<ControlDependenceTreeNode>();
		nodes.add(this);
		queue.add(this);
		while(!queue.isEmpty()) {
			ControlDependenceTreeNode node = queue.remove(0);
			nodes.addAll(node.children);
			queue.addAll(node.children);
		}
		return nodes;
	}

	public ControlDependenceTreeNode getIfParent() {
		return ifParent;
	}

	public void setIfParent(ControlDependenceTreeNode ifParent) {
		this.ifParent = ifParent;
	}

	public ControlDependenceTreeNode getElseIfChild() {
		return elseIfChild;
	}

	public void setElseIfChild(ControlDependenceTreeNode elseIfChild) {
		this.elseIfChild = elseIfChild;
	}

	public void setIfParentAndElseIfChild(ControlDependenceTreeNode ifParent) {
		this.ifParent = ifParent;
		ifParent.elseIfChild = this;
	}

	public int hashCode() {
		if(this.getNode() != null) {
			return this.getNode().hashCode();
		}
		else {
			//else node
			int result = 17;
			result = 37*result + (isElseNode ? 1 : 0);
			result = 37*result + ifParent.getNode().hashCode();
			return result;
		}
	}

	public boolean equals(Object o) {
		if(this == o)
			return true;
		if(o instanceof ControlDependenceTreeNode) {
			ControlDependenceTreeNode node = (ControlDependenceTreeNode)o;
			if(this.getNode() != null && node.getNode() != null) {
				return this.getNode().equals(node.getNode());
			}
			if(this.getNode() == null && node.getNode() == null) {
				return this.isElseNode == node.isElseNode && this.getIfParent().equals(node.getIfParent());
			}
		}
		return false;
	}

	public String toString() {
		if(node != null)
			return node.toString();
		else
			return getId() + "\telse\n";
	}

	public double getId() {
		if(isElseNode) {
			Set<PDGNode> controlDependentNodes = ifParent.getNode().getControlDependentNodes();
			int id = 0;
			for(PDGNode controlDependentNode : controlDependentNodes) {
				PDGControlDependence controlDependence = controlDependentNode.getIncomingControlDependence();
				if(controlDependence.isFalseControlDependence()) {
					id = controlDependentNode.getId();
					break;
				}
			}
			//special handling if a try block follows after else clause
			for(ControlDependenceTreeNode child : children) {
				if(child.getId() < id)
					id = (int) child.getId();
			}
			return id - 0.5;
		}
		else {
			return node.getId();
		}
	}
	
	public ControlDependenceTreeNode getElseChild() {
		for(ControlDependenceTreeNode cdtNode : getSiblings()) {
			if(cdtNode.isElseNode && this.equals(cdtNode.getIfParent())) {
				return cdtNode;
			}
		}
		return null;
	}
}
