package it.micegroup.voila2.cartridge.library.javaservice;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.transaction.RunnableWithResult;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.emf.transaction.util.TransactionUtil;
import org.eclipse.uml2.uml.Association;
import org.eclipse.uml2.uml.Class;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.EnumerationLiteral;
import org.eclipse.uml2.uml.Property;
import org.eclipse.uml2.uml.Stereotype;
import org.eclipse.uml2.uml.Type;

public class AssociationUtils {

	/**
	 * Return true if the association is a OneToOneRelation
	 * 
	 * @author Jessica Pascarella <jessica.pascarella@micegroup.it>
	 *
	 * @param association
	 * @return true if the association is a OneToOneRelation
	 */
	public static boolean isOneToOneRelation(Association association) {
		for (Property p : association.getMemberEnds()) {
			if (p.getUpper() != 1)
				return false;
		}
		return true;
	}

	/**
	 * Return true if the association is a ManyToOneRelation
	 * 
	 * @author Antonio Di Raffaele <antonio.diraffaele@micegroup.it>
	 *
	 * @param association
	 * @return true if the association is a OneToOneRelation
	 */
	public static boolean isManyToOneRelation(Association association) {
		for (Property p : association.getMemberEnds()) {
			if (p.getUpper() == -1)
				return true;
		}
		return false;
	}

	public static enum RelationType {
		OneToOne, ManyToOne
	}

	/**
	 * Return true if the association is a OneToOneRelation or a ManyToOneRelation
	 * 
	 * @author Antonio Di Raffaele <antonio.diraffaele@micegroup.it>
	 *
	 * @param association
	 * @param relationType
	 * @return true if the association is a OneToOneRelation or a ManyToOneRelation
	 */
	public static boolean isAssociationInRelationType(Association association, RelationType relationType) {
		if (relationType == RelationType.OneToOne) {
			return isOneToOneRelation(association);
		} else if (relationType == RelationType.ManyToOne) {
			return isManyToOneRelation(association);
		}
		return false;
	}

	/**
	 * Return true if the association is a SelfRelation
	 * 
	 * @author Jessica Pascarella <jessica.pascarella@micegroup.it>
	 *
	 * @param association
	 * @return true if the association is a SelfRelation
	 */
	public static boolean isSelfRelation(Association association) {
		Set<Type> memberEnds = new HashSet<>(association.getEndTypes());
		if (memberEnds.size() > 1)
			return false;
		return true;
	}

	/**
	 * Return true if both members of the relation are optional
	 * 
	 * @author Jessica Pascarella <jessica.pascarella@micegroup.it>
	 * @param association
	 * @return true if both members of the relation are optional
	 */
	public static boolean isBothOptional(Association association) {
		EList<Property> properties = null;
		properties = association.getMemberEnds();
		if (properties.get(0).getLower() == 0 && properties.get(1).getLower() == 0) {
			return true;
		}
		return false;
	}

	/**
	 * Return the optional member, if present, of a OneToOneRelation
	 * 
	 * @author Jessica Pascarella <jessica.pascarella@micegroup.it>
	 * 
	 * @param association
	 * @return the optional member, if present, of a OneToOneRelation
	 * 
	 **/
	public static Class getOptionalMember(Association association) {
		EList<Property> properties = null;
		Class optionalMember = null;
		properties = association.getMemberEnds();
		if (properties.get(0).getLower() == 0) {
			optionalMember = (Class) properties.get(0).getType();
		} else if (properties.get(1).getLower() == 0) {
			optionalMember = (Class) properties.get(1).getType();
		}
		return optionalMember;
	}

	/**
	 * Return the optional member, if present, of a SelfRelation
	 * 
	 * @author Jessica Pascarella <jessica.pascarella@micegroup.it>
	 * 
	 * @param association
	 * @return the optional member, if present, of a SelfRelation
	 * 
	 **/
	public static Property getSelfOptionalMember(Association association) {
		EList<Property> properties = null;
		Property optionalMember = null;
		properties = association.getMemberEnds();
		if (properties.get(0).getLower() == 0) {
			optionalMember = properties.get(0);
		} else if (properties.get(1).getLower() == 0) {
			optionalMember = properties.get(1);
		}
		return optionalMember;
	}

	/**
	 * Return true if the association has an optional member
	 * 
	 * @author Jessica Pascarella <jessica.pascarella@micegroup.it>
	 *
	 * @param association
	 * @return true if the association has an optional member
	 */
	public static boolean hasOptionalMember(Association association) {
		for (Property p : association.getMemberEnds()) {
			if (p.getLower() == 0) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Return true if the input class is the first processed by the generator during
	 * the association valutation
	 * 
	 * @author Jessica Pascarella <jessica.pascarella@micegroup.it>
	 *
	 * @param classe
	 * @param association
	 * @param property
	 * @return true if the input class is the first processed by the generator
	 *         during the association valutation
	 */
	public static boolean isFirstMapped(Class classe, Association association, Property property) {
		if (!isSelfRelation(association) && classe == getFirstMapped(association))
			return true;
		else if (isSelfRelation(association) && property != null && property.getName() != null
				&& property.getName().equals(getSelfFirstMapped(association)))
			return true;
		return false;
	}

	/**
	 * Return the first processed class by the generator during the association
	 * valutation
	 * 
	 * @author Jessica Pascarella <jessica.pascarella@micegroup.it>
	 *
	 * @param classe
	 * @param association
	 * @param property
	 * @return
	 */
	public static boolean isOptionalMember(Class classe, Association association, Property property) {
		if (!isSelfRelation(association) && getOptionalMember(association) == classe)
			return true;
		else if (isSelfRelation(association) && property != null && property.getName() != null
				&& property.getName().equals(getSelfOptionalMember(association).getName()))
			return true;
		return false;
	}

	/**
	 * Return the first processed class by the generator during the association
	 * valutation
	 * 
	 * @author Jessica Pascarella <jessica.pascarella@micegroup.it>
	 *
	 * @param classe
	 * @return the first processed class by the generator during the association
	 *         valutation
	 */
	public static Class getFirstMapped(Association association) {
		EList<Property> properties = null;
		properties = association.getMemberEnds();
		return (Class) properties.get(0).getType();
	}

	/**
	 * Return the first processed member for a self-relation
	 * 
	 * @author Jessica Pascarella <jessica.pascarella@micegroup.it>
	 *
	 * @param classe
	 * @return the first processed member for a self-relation
	 * 
	 */
	public static String getSelfFirstMapped(Association association) {
		EList<Property> properties = null;
		properties = association.getMemberEnds();
		return properties.get(0).getName();
	}

	/**
	 * Return true if the input class has a parent class referring to the input
	 * association
	 * 
	 * @author Jessica Pascarella <jessica.pascarella@micegroup.it>
	 *
	 * @param classe
	 * @param association
	 * @return true if the input class has a parent class referring to the input
	 *         association
	 */
	public static boolean hasParentOneToOne(Class classe, Association association, Property property) {
		return (isOneToOneRelation(association)) && ((!hasOptionalMember(association)
				&& !isFirstMapped(classe, association, property))
				|| (hasOptionalMember(association)
						&& ((!isBothOptional(association) && isOptionalMember(classe, association, property))
								|| (isBothOptional(association) && !isFirstMapped(classe, association, property)))));

	}

	/**
	 * Return true if the input class has a child class referring to the input
	 * association
	 * 
	 * @author Jessica Pascarella <jessica.pascarella@micegroup.it>
	 *
	 * @param classe
	 * @param association
	 * @return true if the input class has a child class referring to the input
	 *         association
	 */
	public static boolean hasChildOneToOne(Class classe, Association association, Property property) {
		return (isOneToOneRelation(association)) && ((!hasOptionalMember(association)
				&& isFirstMapped(classe, association, property))
				|| (hasOptionalMember(association)
						&& ((!isBothOptional(association) && !isOptionalMember(classe, association, property))
								|| (isBothOptional(association) && isFirstMapped(classe, association, property)))));
	}

	/**
	 * Return true if the input property is a child for the input ownerClass
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 * @author Jessica Pascarella <jessica.pascarella@micegroup.it>,
	 * 
	 * @param ownerClass
	 * @param a
	 * @param p
	 * @return true if the input property is a child for the input ownerClass
	 */
	public static boolean isClassChild(Class ownerClass, Association a, Property p) {
//		if (hasParentOneToOne((Class) p.getType(), a, p) && (p.getType() != ownerClass || isSelfRelation(a)))
//			return true;
//		else 
//		if (isOneToOneRelation(a) && (p.getType() != ownerClass || isSelfRelation(a)))
//			return true;
//		else 
		if ((!isOneToOneRelation(a)) && (p.getType() != ownerClass || isSelfRelation(a)) && (p.getUpper() == -1)) {
			return true;
		}
		return false;
	}

	/**
	 * Return true if the input property is a parent for the input ownerClass
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 * @author Jessica Pascarella <jessica.pascarella@micegroup.it>
	 * @param ownerClass
	 * @param association
	 * @param property
	 * @return
	 */
	public static boolean isClassParent(Class ownerClass, Association association, Property property) {
		if (isOneToOneRelation(association) && (property.getType() != ownerClass || isSelfRelation(association)))
			return true;
		else if ((!isOneToOneRelation(association)) && (property.getType() != ownerClass || isSelfRelation(association))
				&& upper01(property)) {
			return true;
		}
		return false;
	}

	private static boolean upper01(Property property) {
		return property.getUpper() == 1 || property.getUpper() == 0;
	}

	/**
	 * Return the set of parentEnd for the given class from all the associations
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 * @author Jessica Pascarella <jessica.pascarella@micegroup.it>
	 * @param classe
	 **/
	public Collection<Property> getParents(Class classe) {
		Collection<Property> parents = new ArrayList<>();
		for (Association a : classe.getAssociations()) {
			for (Property p : a.getMemberEnds()) {
				if (hasChildOneToOne((Class) p.getType(), a, p) && (!p.getType().getName().equals(classe.getName())))
					parents.add(p);
				else if ((!isOneToOneRelation(a)) && !p.getType().getName().equals(classe.getName())
						&& PropertyUtils.isZeroOneMultiplicity(p))
					parents.add(p);
			}

		}
		return parents;
	}

	/**
	 * Return the set of childEnd for the given class from all associations
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 * @author Jessica Pascarella <jessica.pascarella@micegroup.it>
	 * @param classe
	 **/
	public Collection<Property> getChilds(Class classe) {
		Collection<Property> childs = new ArrayList<>();
		for (Association a : classe.getAssociations()) {
			for (Property p : a.getMemberEnds()) {
				if (hasParentOneToOne((Class) p.getType(), a, p) && !p.getType().getName().equals(classe.getName()))
					childs.add(p);
				else if (!isOneToOneRelation(a) && !p.getType().getName().equals(classe.getName())
						&& (p.getUpper() == -1)) {
					childs.add(p);
				}
			}
		}
		return childs;
	}

	/**
	 * Return the set of child classes for the given class
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 * @author Jessica Pascarella <jessica.pascarella@micegroup.it>
	 * @param classe
	 **/
	public static Collection<Class> getChildClasses(Class classe) {
		Collection<Class> childs = new ArrayList<>();
		for (Association a : classe.getAssociations()) {
			for (Property p : a.getMemberEnds()) {
				if (isClassChild(classe, a, p)) {
					if (belongToSameModel(p.getType(), classe)) {
						childs.add((Class) p.getType());
					}
				}
			}
		}
		return childs;
	}

	/**
	 * Return the set of child classes for the given class without duplicates
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 * @param classe
	 **/
	public static Collection<Class> getChildClassesNoDup(Class classe) {
		Collection<Class> childClasses = new ArrayList<>();
		Collection<String> childNames = new ArrayList<>();
		for (Association a : classe.getAssociations()) {
			for (Property p : a.getMemberEnds()) {
				if (isClassChild(classe, a, p)) {
					if (belongToSameModel(p.getType(), classe)) {
						if (!childNames.contains(p.getType().getName())) {
							childClasses.add((Class) p.getType());
							childNames.add(p.getType().getName());
						}
					}
				}
			}
		}
		return childClasses;
	}

	/**
	 * Return the set of parent classes for the given class
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 * @author Jessica Pascarella <jessica.pascarella@micegroup.it>
	 * @param classe
	 **/
	public static Collection<Class> getParentClasses(Class classe) {
		Collection<Class> parents = new ArrayList<>();
		for (Association a : classe.getAssociations()) {
			for (Property p : a.getMemberEnds()) {
				if (isClassParent(classe, a, p))
					// Controllo se il padre appartiene al modello della classe in input
					if (belongToSameModel(p.getType(), classe)) {
						parents.add((Class) p.getType());
					}
			}
		}
		return parents;
	}

	/**
	 * Return the set of parent classes for the given class
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 * @author Jessica Pascarella <jessica.pascarella@micegroup.it>
	 * @param classe
	 **/
	public static Collection<Class> getParentClassesNoDup(Class classe) {
		Collection<Class> parents = new ArrayList<>();
		Collection<String> parentNames = new ArrayList<>();
		for (Association a : classe.getAssociations()) {
			for (Property p : a.getMemberEnds()) {
				if (isClassParent(classe, a, p)) {
					// Controllo se il padre appartiene al modello della classe in input
					if (belongToSameModel(p.getType(), classe)) {
						if (!parentNames.contains(p.getType().getName())) {
							parents.add((Class) p.getType());
							parentNames.add(p.getType().getName());
						}
					}
				}
			}
		}
		return parents;
	}

	/**
	 * Return a set of ID property for the given class
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 * @author Jessica Pascarella <jessica.pascarella@micegroup.it>
	 * @param classe
	 **/
	public Collection<Property> getParentIds(Class classe) {
		Collection<Property> parents = new ArrayList<>();
		for (Association a : classe.getAssociations()) {
			for (Property p : a.getMemberEnds()) {
				if (belongToSameModel(p.getType(), classe)) {
					if (isClassParent(classe, a, p)) {
						p.getType();

//					for(Property parentId : EntityUtils.getClassIDs((Class) p.getType())) {
//						Property parentIdnew 
//						parentId.setName(PropertyUtils.getAttributeName(parentId, classe, a, true));
//						parents.add(parentId);
//					}

						parents.addAll(EntityUtils.getClassIDs((Class) p.getType()));
					}
//					if (hasChildOneToOne((Class) p.getType(), a, p)
//							&& !p.getType().getName().equals(classe.getName())) {
//						p.getType();
//						parents.addAll(EntityUtils.getClassIDs((Class) p.getType()));
//					} else if ((!isOneToOneRelation(a)) && (!p.getType().getName().equals(classe.getName()))
//							&& PropertyUtils.isZeroOneMultiplicity(p)) {
//						p.getType();
//						parents.addAll(EntityUtils.getClassIDs((Class) p.getType()));
//					}
				}
			}
		}
		return parents;
	}

	/**
	 * Return true if the class has at least one child
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 * @param classe
	 * @return
	 */
	public boolean hasChilds(Class classe) {
		if (getChildClasses(classe).size() > 0)
			return true;
		return false;
	}

	/**
	 * Return true if the class has at least one parent
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 * @param classe
	 * @return
	 */
	public boolean hasParents(Class classe) {
		if (getParentAssociations(classe).size() > 0)
			return true;
		return false;
	}

	/**
	 * Return the child Class of a a parentClass from the given association
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 * @param parentClass
	 * @param association
	 * @return
	 */
	public Class getChildClass(Class parentClass, Association association) {
		for (Property p : association.getMemberEnds()) {
			if (isClassChild(parentClass, association, p))
				return (Class) p.getType();
		}
		return null;
	}

	/**
	 * Return the parent Class of a childClass from the given association
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 * @param childClass
	 * @param association
	 * @return
	 */
	public static Class getParentClass(Class childClass, Association association) {
		for (Property p : association.getMemberEnds()) {
			if (isClassParent(childClass, association, p))
				return (Class) p.getType();
		}
		return null;
	}

	/**
	 * Return the childEnd of a parentClass from the given association
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 * @param parentClass
	 * @param association
	 * @return
	 */
	public static Property getChildEnd(Class parentClass, Association association) {
		for (Property p : association.getMemberEnds()) {
			if (isClassChild(parentClass, association, p))
				return p;
		}
		return null;
	}

	/**
	 * Return the parentEnd of a childClass from the given association
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 * @param childClass
	 * @param association
	 * @return
	 */
	public static Property getParentEnd(Class childClass, Association association) {
		for (Property p : association.getMemberEnds()) {
			if (isClassParent(childClass, association, p))
				return p;
		}
		return null;
	}

	/**
	 * Return a Set of Association that link to child classes
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 * @param classe
	 * @return
	 */
	public static Collection<Association> getChildAssociations(Class classe) {
		Collection<Association> childAssociations = new ArrayList<>();
		for (Association a : classe.getAssociations()) {
			for (Property p : a.getMemberEnds()) {
				if (isClassChild(classe, a, p)) {
					if (belongToSameModel(p.getType(), classe)) {
						childAssociations.add(a);
					}
				}
			}
		}
		return childAssociations;
	}

	/**
	 * Return a Set of Association that link to Parent classes
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 * @param classe
	 * @return
	 */
	public static Collection<Association> getParentAssociations(Class classe) {
		return getParentAssociationsExcluding(classe, null);
	}

	/**
	 * Return a Set of Association that link to Parent classes
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 * @param classe
	 * @return
	 */
	public static Collection<Association> getParentAssociationsExcluding(Class classe, Association daEscludere) {
		Collection<Association> parentAssociations = new ArrayList<>();
		for (Association a : classe.getAssociations()) {
			if (daEscludere == null || a != daEscludere) {
				for (Property p : a.getMemberEnds()) {
					if (isClassParent(classe, a, p)) {
						// Controllo se il padre appartiene al modello della classe in input
						if (belongToSameModel(p.getType(), classe))
							parentAssociations.add(a);
					}
				}
			}
		}
		return parentAssociations;
	}

	public static Collection<Association> getParentAssociationsNoDup(Class classe) {
		Collection<Association> parentAssociations = new ArrayList<>();
		Collection<String> parentsName = new ArrayList<>();
		for (Association a : classe.getAssociations()) {

			for (Property p : a.getMemberEnds()) {
				if (isClassParent(classe, a, p)) {
					// Controllo se il padre appartiene al modello della classe in input
					if (belongToSameModel(p.getType(), classe))
						if (!parentsName.contains(p.getType().getName())) {
							parentsName.add(p.getType().getName());
							parentAssociations.add(a);
						}
				}
			}

		}
		return parentAssociations;
	}

	/**
	 * 
	 * Return MappedName Stereotype's property
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 * @param association
	 * @param property
	 * @return
	 */
	public String getMappedName(Association association, Property property) {
		Map<String, String> nameMappingMap = new HashMap<>();
		String mappedName = "";

		Stereotype stereotype = null;

		if (isOneToOneRelation(association)) {
			stereotype = association.getAppliedStereotype(StereotypeName.ONE_TO_ONE_RELATION);
		}

		else {
			stereotype = association.getAppliedStereotype(StereotypeName.MANY_TO_ONE_RELATION);
		}

		String nameMapping = (String) association.getValue(stereotype, StereotypePropertyName.NAME_MAPPING);

		if (nameMapping != null && !("").equals(nameMapping)) {
			nameMapping = nameMapping.trim();
			String[] parts = nameMapping.split("=");
			for (int i = 0; i < parts.length; i = i + 2) {
				nameMappingMap.put(parts[i], parts[i + 1]);
			}
			mappedName = nameMappingMap.get(property.getName());
		}
		if (mappedName == null || mappedName == "") {
			mappedName = property.getName();
		}
		return mappedName;
	}

	@Deprecated // VN Questo metodo non funziona per selfRelation
	public static Class getOtherEnd(Class ownerClass, Association association) {
		for (Property end : association.getMemberEnds()) {
			if (!end.getType().getName().equals(ownerClass.getName())) {
				return (Class) end.getType();
			}
		}
		return null;
	}

	/**
	 * Return true if the association has identifying stereotype and it is set to
	 * true
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>, Jessica
	 *         Pascarella <jessica.pascarella@micegroup.it>
	 * 
	 * @param association
	 * @return true if the association has identifying stereotype and it is set to
	 *         true
	 */
	public static boolean isIdentifying(Association association) {
		Stereotype stereotype = null;
		if (isOneToOneRelation(association)) {
			stereotype = association.getAppliedStereotype(StereotypeName.ONE_TO_ONE_RELATION);
			if (association.hasValue(stereotype, StereotypePropertyName.IDENTIFYING)
					&& true == (Boolean) association.getValue(stereotype, StereotypePropertyName.IDENTIFYING))
				return true;
		} else {
			stereotype = association.getAppliedStereotype(StereotypeName.MANY_TO_ONE_RELATION);
			if (association.hasValue(stereotype, StereotypePropertyName.IDENTIFYING)
					&& true == (Boolean) association.getValue(stereotype, StereotypePropertyName.IDENTIFYING))
				return true;
		}
		return false;
	}

	/**
	 * Return the first child from the collection
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 * @param parentClass
	 * @return
	 */
	public static Class getFirstChild(Class parentClass) {
		Collection<Class> childs = getChildClasses(parentClass);
		if (childs.size() > 0) {
			Iterator<Class> iterator = childs.iterator();
			if (iterator != null)
				return iterator.next();
		}
		return null;
	}

	/**
	 * Return true if the parent is required (multiplicity > 0)
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 *
	 * @param association
	 * @return
	 */
	public static boolean isParentRequired(Association association) {
		if (isIdentifying(association)) {
			return true;
		}
		for (Property end : association.getMemberEnds()) {
			if (end.getLower() >= 1 && end.getUpper() == 1) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Ritorna il metodo selezionato per la gestione dei figli per un'entità
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 *
	 * @param association
	 * @return
	 */
	public static String getChildManagement(Association association) {
		return StereotypeUtils.<EnumerationLiteral, Association>getStereotypeValue(association,
				StereotypeName.MANY_TO_ONE_RELATION, StereotypePropertyName.CHILD_MANAGEMENT).getName();
	}

	/**
	 * Ritorna true se la gestione dei figli nel frontEnd per un'entità è fatta
	 * tramite tab
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 *
	 * @param association
	 * @return
	 */
	public static boolean isTabbedChild(Association association) {
		return getChildManagement(association).equals(ChildManagement.tabbed.toString());
	}

	public static String getParentAssName(Association association, Class ownerClass) {
		Property parentEnd = getParentEnd(ownerClass, association);
		if (parentEnd != null && parentEnd.getName() != null && !parentEnd.getName().equalsIgnoreCase("")) {
			return parentEnd.getName();
		}
		return "";
	}

	public static String getChildAssName(Association association, Class ownerClass) {
		Property childEnd = getChildEnd(ownerClass, association);
		if (childEnd != null && childEnd.getName() != null && !childEnd.getName().equalsIgnoreCase("")) {
			return childEnd.getName();
		}
		return "";
	}

	/**
	 * Controlla se l'associazione è readOnly evitando la possibilità di modificare
	 * le entità associate.
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 *
	 * @param association
	 * @return
	 */
	public static boolean isReadOnly(Association association) {
		boolean readOnly = StereotypeUtils.<Boolean, Association>getStereotypeValue(association,
				StereotypeName.MANY_TO_ONE_RELATION, StereotypePropertyName.IS_READONLY);
		return readOnly != false;
	}

	public static Collection<Class> getParentClassesExcluding(Class classe, Class classeDaEscludere) {
		Collection<Class> parents = new ArrayList<>();
		for (Association a : classe.getAssociations()) {
			for (Property p : a.getMemberEnds()) {
				if (classeDaEscludere == null || (Class) p.getType() != classeDaEscludere) {
					if (isClassParent(classe, a, p))
						parents.add((Class) p.getType());
				}
			}
		}
		return parents;
	}

	/**
	 * Return a collection of parent association from IMPORTED models. Usually other
	 * MS.
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 *
	 * @param classe
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static Collection<Association> getParentAssFromImportedModels(Class classe) {

//		ConsoleUtils.consoleMessage(SeverityLevels.ERROR, "Parent from imported models");

		Collection<Association> importedParents = new ArrayList<>();

		TransactionalEditingDomain domain = TransactionUtil.getEditingDomain(classe.getModel());

		// --
		for (Association a : classe.getAssociations()) {
			for (Property p : a.getMemberEnds()) {
				if (isClassParent(classe, a, p)) {
					// Controllo se il padre appartiene al modello della classe in input
					if (!belongToSameModel(p.getType(), classe))
						importedParents.add(a);
				}
			}
		}
		// --

//		 VN Per la lettura degli elementi importati nel modello è stato necessario
//		 introdurre questa transazione di lettura. USare il metodo doEdit per
//		 attivarne una di scrittura.
//		try {
//			importedParents = (Collection<Association>) domain.runExclusive(new RunnableWithResult.Impl() {
//
//				public void run() {
//					Collection<Association> parents = new ArrayList<>();
//					for (Association a : classe.getAssociations()) {
//						for (Property p : a.getMemberEnds()) {
//							if (isClassParent(classe, a, p)) {
//								// Controllo se il padre appartiene al modello della classe in input
//								if (!belongToSameModel(p.getType(), classe))
//									parents.add(a);
//							}
//						}
//					}
//					setResult(parents);
//				}
//			});
//		} catch (InterruptedException e) {
//			e.printStackTrace();
//		}
		return importedParents;
	}

	/**
	 * Return a collection of parent association from IMPORTED models. Usually other
	 * MS.
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 *
	 * @param classe
	 * @return
	 */
	public static Collection<Association> getChildAssFromImportedModels(Class classe) {

//		ConsoleUtils.consoleMessage(SeverityLevels.ERROR, "Childs from imported models");

		Collection<Association> importedChilds = new ArrayList<>();

		for (Association a : classe.getAssociations()) {
			for (Property p : a.getMemberEnds()) {
				if (isClassChild(classe, a, p)) {
					// Controllo se il padre appartiene al modello della classe in input
					if (!belongToSameModel(p.getType(), classe))
						importedChilds.add(a);
				}
			}
		}

		return importedChilds;
	}

	/**
	 * Return true if input elements have same parent model. Comparison is made on
	 * model name
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 *
	 * @param a
	 * @param b
	 * @return
	 */
	public static boolean belongToSameModel(Element a, Element b) {
		return a.getModel().getName().equals(b.getModel().getName());
	}

	/**
	 * Returns a collection of all parent for an entity. Actually they are the union
	 * of parents belonging to Class's microservice and importend parents.
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 *
	 * @param classe
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static Collection<Association> getParentAssFromAllModels(Class classe) {
		Collection<Association> result = new ArrayList<>();
		TransactionalEditingDomain domain = TransactionUtil.getEditingDomain(classe.getModel());
		try {
			result = (Collection<Association>) domain.runExclusive(new RunnableWithResult.Impl() {
				public void run() {
					Collection<Association> innerResult = getParentAssociations(classe);
//					TODO Sistemare gli imported parents innerResult.addAll(getParentAssFromImportedModels(classe));
					setResult(innerResult);
				}
			});
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return result;

	}

	/**
	 * Ritorna una stringa che indica se i parent sono selezionabili tramite
	 * dropdown o popup
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 *
	 * @param association
	 * @return
	 */
	public static String getParentSelection(Association association) {
		EnumerationLiteral parentSelection = null;
		if (isOneToOneRelation(association)) {
			parentSelection = StereotypeUtils.<EnumerationLiteral, Association>getStereotypeValue(association,
					StereotypeName.ONE_TO_ONE_RELATION, StereotypePropertyName.PARENT_SELECTION);
		} else {
			parentSelection = StereotypeUtils.<EnumerationLiteral, Association>getStereotypeValue(association,
					StereotypeName.MANY_TO_ONE_RELATION, StereotypePropertyName.PARENT_SELECTION);
		}
		if (parentSelection != null) {
			return parentSelection.getName();
		}
		return "";
	}

	/**
	 * Ritorna true se la selezione del parent nell'interfaccia avviente tramite
	 * dropdown
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 *
	 * @param association
	 * @return
	 */
	public static boolean hasDropdownParentSelection(Association association) {
		return ParentSelection.dropdown.toString().equals(getParentSelection(association));
	}

	/**
	 * Ritorna true se la selezione del parent nell'interfaccia avviene tramite
	 * popup
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 *
	 * @param association
	 * @return
	 */
	public static boolean hasPopupParentSelection(Association association) {
		return ParentSelection.popup.toString().equals(getParentSelection(association));
	}

	/**
	 * Check if has at least one dropdown selection association.
	 * 
	 * @author Federico Gambardella<federico.gambardella@micegroup.it>
	 * @param classe
	 * @return
	 */
	public static boolean hasAtLeastOneDrpodownSelection(Class classe) {
		for (Association association : getParentAssFromAllModels(classe)) {
			if (hasDropdownParentSelection(association)) {

				return true;
			}
		}
		return false;
	}

	public static boolean hasNavigableParents(Class classe) {
		Collection<Association> result = getParentAssFromAllModels(classe).stream()
				.filter(a -> getParentEnd(classe, a).isNavigable()).collect(Collectors.toCollection(ArrayList::new));
		if (result == null || result.isEmpty()) {
			return false;
		}
		return true;
	}

	/**
	 * Con questo metodo si ottiene il nome della colonna associato al nome della
	 * proprietà per una specifica associazione
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 *
	 * @param association
	 * @param property
	 * @return mapped name for property on the association
	 */
	public String getPhysicalMappedName(Association association, Property property) {
		Map<String, String> nameMappingMap = new HashMap<>();
		String mappedName = "";

		Stereotype stereotype = null;

		if (isOneToOneRelation(association)) {
			stereotype = association.getAppliedStereotype(StereotypeName.ONE_TO_ONE_RELATION);
		}

		else {
			stereotype = association.getAppliedStereotype(StereotypeName.MANY_TO_ONE_RELATION);
		}

		String nameMapping = (String) association.getValue(stereotype, StereotypePropertyName.PHYSICAL_NAME_MAPPING);

		if (nameMapping != null && !("").equals(nameMapping)) {
			nameMapping = nameMapping.trim();
			String[] parts = nameMapping.split("=");
			for (int i = 0; i < parts.length; i = i + 2) {
				nameMappingMap.put(parts[i], parts[i + 1]);
			}
			mappedName = nameMappingMap.get(property.getName());
		}
		// Associazione con nome
		if ((mappedName == null || mappedName == "") && !AssociationUtils.isSelfRelation(association)) {
			for (Property p : association.getMemberEnds()) {
				if (upper01(p) && p.getName() != null) {
					mappedName = StringUtils.camelToUnderscore(property.getName()) + "_"
							+ StringUtils.camelToUnderscore(p.getName());
				}
			}
		}
		if ((mappedName == null || mappedName == "") && AssociationUtils.isSelfRelation(association)) {
			mappedName = StringUtils.camelToUnderscore(AssociationUtils
					.getParentEnd(((Class) association.getMemberEnds().iterator().next().getType()), association)
					.getName());
		} else if (mappedName == null || mappedName == "") {
			mappedName = StringUtils.camelToUnderscore(property.getName());
		}
		return mappedName;
	}

	/**
	 * Ritorna true se esiste almeno una parentAssociation che non è identifying
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 *
	 * @param classe
	 * @return
	 */
	public static boolean hasNoIdentifyingParent(Class classe) {
		for (Association parentAss : AssociationUtils.getParentAssociations(classe)) {
			if (!isIdentifying(parentAss)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Ritorna una lista dei padri con id composito per la classe in input
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 *
	 * @param classe
	 * @return
	 */
	public Collection<Association> getParentAssociationHavingCK(Class classe) {
		Collection<Association> parents = new ArrayList<>();
		for (Association a : getParentAssociations(classe)) {
			Class parent = getParentClass(classe, a);
			if (isIdentifying(a) && EntityUtils.hasCompositeKey(parent)) {
				parents.add(a);
			}
		}
		return parents;
	}

	public static Collection<Association> getChildAssociationsNoDup(Class classe) {
		Collection<Association> childAssociations = new ArrayList<>();
		Collection<String> childNames = new ArrayList<>();
		for (Association a : classe.getAssociations()) {
			for (Property p : a.getMemberEnds()) {
				if (isClassChild(classe, a, p)) {
					if (belongToSameModel(p.getType(), classe)) {
						if (!childNames.contains(p.getType().getName())) {
							childAssociations.add(a);
							childNames.add(p.getType().getName());
						}
					}
				}
			}
		}
		return childAssociations;
	}

}