package gr.uom.java.ast.association;

import gr.uom.java.ast.ClassObject;
import gr.uom.java.ast.FieldInstructionObject;
import gr.uom.java.ast.FieldObject;
import gr.uom.java.ast.MethodObject;
import gr.uom.java.ast.SystemObject;

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

import javax.swing.tree.DefaultMutableTreeNode;

public class AssociationDetection {
	private List<Association> associationList;
	private List<String> acceptableOriginClassNames;
	
	public AssociationDetection(SystemObject system) {
		this.associationList = new ArrayList<Association>();
		acceptableOriginClassNames = new ArrayList<String>();
		acceptableOriginClassNames.add("java.util.Collection");
		acceptableOriginClassNames.add("java.util.AbstractCollection");
		acceptableOriginClassNames.add("java.util.List");
		acceptableOriginClassNames.add("java.util.AbstractList");
		acceptableOriginClassNames.add("java.util.ArrayList");
		acceptableOriginClassNames.add("java.util.LinkedList");
		acceptableOriginClassNames.add("java.util.Set");
		acceptableOriginClassNames.add("java.util.AbstractSet");
		acceptableOriginClassNames.add("java.util.HashSet");
		acceptableOriginClassNames.add("java.util.LinkedHashSet");
		acceptableOriginClassNames.add("java.util.SortedSet");
		acceptableOriginClassNames.add("java.util.TreeSet");
		acceptableOriginClassNames.add("java.util.Vector");
		acceptableOriginClassNames.add("java.util.Stack");
		generateAssociations(system);
	}
	
	public boolean containsFieldObject(FieldObject field) {
		for(Association association : associationList)
			if(association.getFieldObject().equals(field))
				return true;
		return false;
	}
	
	public List<Association> getAssociationsOfClass(ClassObject classObject) {
		List<Association> associations = new ArrayList<Association>();
		for(Association association : associationList) {
			if(association.getFrom().equals(classObject.getName()))
				associations.add(association);
		}
		return associations;
	}
	
	private void generateAssociations(SystemObject systemObject) {
		ListIterator<ClassObject> classIt = systemObject.getClassListIterator();
        while(classIt.hasNext()) {
            ClassObject classObject = classIt.next();
            
            ListIterator<FieldObject> fieldIt = classObject.getFieldIterator();
            while(fieldIt.hasNext()) {
                FieldObject fieldObject = fieldIt.next();
                String type = fieldObject.getType().getClassType();
                //cover also other collections in the future
				if(acceptableOriginClassNames.contains(type)) {
					String genericType = fieldObject.getType().getGenericType();
					if(genericType != null) {
						for(String className : systemObject.getClassNames()) {
							if(genericType.contains(className)) {
								Association association = new Association(fieldObject, classObject.getName(), className);
								association.setContainer(true);
								if(!associationList.contains(association))
									associationList.add(association);
							}
						}
					}
					else {
						Association association = checkCollectionAttribute(systemObject,classObject, fieldObject);
						if(association != null && !associationList.contains(association))
							associationList.add(association);
					}
				} else if(systemObject.getClassObject(type) != null) {
					Association association = new Association(fieldObject, classObject.getName(), type);
					if(fieldObject.getType().getArrayDimension() > 0)
						association.setContainer(true);
					if (!associationList.contains(association))
						associationList.add(association);
				}
            }
        }
	}
	
	private Association checkCollectionAttribute(SystemObject systemObject, ClassObject classObject, FieldObject field) {
        ListIterator<MethodObject> methodIt = classObject.getMethodIterator();
        while(methodIt.hasNext()) {
            MethodObject method = methodIt.next();
            FieldInstructionObject fieldInstruction = method.isCollectionAdder();
            if(fieldInstruction != null && field.equals(fieldInstruction)) {
            	List<String> parameterList = method.getParameterList();
            	if(parameterList.size() == 1 && systemObject.getClassObject(parameterList.get(0)) != null) {
            		 Association association = new Association(field, classObject.getName(), parameterList.get(0));
                     association.setContainer(true);
                     return association;
            	}
            }
        }
        return null;
    }
	
	public boolean checkForContainerAssociation(String from, String to) {
        DefaultMutableTreeNode root = new DefaultMutableTreeNode(from);
        recurse(root);
        DefaultMutableTreeNode firstLeaf = root.getFirstLeaf();
        if(checkFullPathForContainerAssociation(firstLeaf,to))
            return true;
        DefaultMutableTreeNode leaf = firstLeaf.getNextLeaf();
        while(leaf != null) {
            if(checkFullPathForContainerAssociation(leaf,to))
                return true;
            leaf = leaf.getNextLeaf();
        }
        return false;
    }

    private boolean checkFullPathForContainerAssociation(DefaultMutableTreeNode leaf, String nodeName) {
        if(leaf.getUserObject().equals(nodeName))
            return checkPathForContainerAssociation(leaf);
        DefaultMutableTreeNode node = (DefaultMutableTreeNode)leaf.getParent();
        while(node != null) {
            if(node.getUserObject().equals(nodeName))
                return checkPathForContainerAssociation(node);
            node = (DefaultMutableTreeNode)node.getParent();
        }
        return false;
    }

    // node is the target class of move operation
    private boolean checkPathForContainerAssociation(DefaultMutableTreeNode node) {
        while(node.getParent() != null) {
            DefaultMutableTreeNode parent = (DefaultMutableTreeNode)node.getParent();
            Association association = getAssociation((String)node.getUserObject(), (String)parent.getUserObject());
            if(association.isContainer())
                return true;
            node = parent;
        }
        return false;
    }

    private void recurse(DefaultMutableTreeNode root) {
        List<Association> endingAssociations = getAssociationsEndingTo((String)root.getUserObject());
        for(Association association : endingAssociations) {
            Object[] pathFromRoot = root.getUserObjectPath();
            boolean cycle = false;
            for(Object node : pathFromRoot) {
                if(association.getFrom().equals(node)) {
                    cycle = true;
                    break;
                }
            }
            if(!cycle) {
                DefaultMutableTreeNode node = new DefaultMutableTreeNode(association.getFrom());
                root.add(node);
                recurse(node);
            }
        }
    }

    private List<Association> getAssociationsEndingTo(String to) {
        List<Association> list = new ArrayList<Association>();
        for(Association association : associationList) {
            if(association.getTo().equals(to))
                list.add(association);
        }
        return list;
    }

    public Association getAssociation(String from, String to) {
         for(Association association : associationList) {
            if(association.getFrom().equals(from) && association.getTo().equals(to))
                return association;
         }
        return null;
    }
}
