package de.dhbwstuttgart.bytecode.utilities;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.objectweb.asm.Type;

import de.dhbwstuttgart.bytecode.TPHExtractor;
import de.dhbwstuttgart.bytecode.constraint.EqualConstraint;
import de.dhbwstuttgart.bytecode.constraint.ExtendsConstraint;
import de.dhbwstuttgart.bytecode.constraint.TPHConstraint;
import de.dhbwstuttgart.bytecode.constraint.TPHConstraint.Relation;
import de.dhbwstuttgart.syntaxtree.type.TypePlaceholder;

public class Simplify {

	public static HashMap<TPHConstraint, HashSet<String>> simplifyConstraints(String name, TPHExtractor tphExtractor,
			ArrayList<String> tphsClass) {
		// 1. check if there are any simple cycles like L<R and R<L:
		// a) yes => set L=R and:
		// * remove both constraints
		// * substitute L with R in all constraint
		// b)no => go to next step
		// 2. check the result of step 1 if there are any equal-constraints like L=R,
		// M=R ..
		// a) yes => put all such TPhs in a map and define "key-Cons"
		// -- key-Cons = TPH < Object --
		// put this Constraint and the
		// b) no
		// 3. is

		// all constraints that will be simplified
//		ArrayList<TPHConstraint> allCons = tphExtractor.allCons;
		ArrayList<TPHConstraint> allCons = new ArrayList<>();
		
		for(TPHConstraint c : tphExtractor.allCons) {
			if(!containsConstraint(allCons,c)) {
				TPHConstraint nc = new TPHConstraint(c.getLeft(), c.getRight(), c.getRel());
				allCons.add(nc);
			}
		}
		ArrayList<TPHConstraint> consToRemove = new ArrayList<>();

		// get all tph of the method
		ArrayList<String> methodTphs = new ArrayList<>();
		ArrayList<String> localTphs = new ArrayList<>();
		for (MethodAndTPH m : tphExtractor.ListOfMethodsAndTph) {
			if (m.getName().equals(name)) {
				methodTphs = m.getTphs();
				localTphs = m.getLocalTphs();
				break;
			}
		}

		// step 1:
		for (TPHConstraint c : allCons) {

			String left = c.getLeft();
			String right = c.getRight();
			if (c.getRel() == Relation.EXTENDS) {
				TPHConstraint revCon = getReverseConstraint(allCons, left, right);
				if (revCon != null) {
					revCon.setRel(Relation.EQUAL);
					// the reverse constraint is removed because
					// otherwise there is the same constraint twice
					// (e.g. A<B and B<A => A=B and B=A)
					consToRemove.add(revCon);
					c.setRel(Relation.EQUAL);
					substituteTPH(allCons, left, right);
				}
			}
		}

		System.out.println();
		System.out.println("NEW ALL CONST: " + allCons.size());
		allCons.forEach(c -> System.out.println(c.toString()));
		System.out.println("----------------");
		allCons.removeAll(consToRemove);
		consToRemove = new ArrayList<>();

		int size = allCons.size();

		System.out.println("AFTER DELETE ALL CONST: " + allCons.size());
		allCons.forEach(c -> System.out.println(c.toString()));
		System.out.println("----------------");
		HashMap<TPHConstraint, HashSet<String>> result = new HashMap<>();

		// check if there is any long cycle (e.g. A<B<C<A)

		// to save all types that are in relation
		LinkedList<String> allTypes = new LinkedList<>();
		// we will put all constraints which are in the cycle, in this ArrayList.
		// Later these contraints will be converted to equal-constraints
		ArrayList<TPHConstraint> constraints = new ArrayList<>(size);
		int visited = 0;

		// contains all constraints
		HashMap<String, String> ss1 = new HashMap<>();

		for (TPHConstraint constr : allCons) {
			ss1.put(constr.getLeft(), constr.getRight());
		}

		for (TPHConstraint c : allCons) {

			if (visited >= size)
				break;
			// Only extends-constraints will be checked
			if (c.getRel() == Relation.EXTENDS) {
				++visited;

				String left = c.getLeft();
				String right = c.getRight();
				// put the types in linked list
				allTypes.add(left);
				allTypes.add(right);

				constraints.add(c);

				boolean isCycle = false;

				// iterate through the map to check if there is a cycle
				while (ss1.containsKey(right)) {
					++visited;
					String oldRight = right;
					right = ss1.get(right);

					TPHConstraint toAdd = getConstraint(oldRight, right, allCons);

					if (toAdd != null)
						constraints.add(toAdd);

					if (left.equals(right)) {
						isCycle = true;
						break;
					}

					allTypes.add(right);
				}

				if (isCycle) {
					// convert all constraints to equal constraints
					setAllEqual(constraints);
					// these constraints will be removed from allCons
					consToRemove.addAll(constraints);
					// all equal types will be substitute with one type
					substituteAllTPH(allCons, constraints, left);

					HashSet<String> eq = new HashSet<>();
					// put all equal types in a set
					for (String t : allTypes) {
						eq.add(t);
					}
					// generate a new constraint (left < Object)
					TPHConstraint constraint = new ExtendsConstraint(left, Type.getInternalName(Object.class),
							Relation.EXTENDS);
					// put the generated constraint and its equal set into result set
					result.put(constraint, eq);
					constraints.clear();
				}
				allTypes.clear();
			}
		}

		for (TPHConstraint ec : allCons) {
			
			if(ec.getRel() == Relation.EQUAL) {
				if(!localTphs.contains(ec.getRight())) {
					localTphs.add(ec.getRight());
				}
				if(!methodTphs.contains(ec.getRight())) {
					methodTphs.add(ec.getRight());
				}
				
			}
		}
		// build an equal set that contains all types
		// which are equal and for each equal constraint put left side and right side
		// in this set Then generate a constraint type < Object and put it
		// with the equal set into the result.
//		for(TPHConstraint c : allCons) {
//			if(c.getRel()==Relation.EQUAL) {
//				if(!isTPHInResEqual(result, c.getLeft())) {
//					HashSet<String> equalTPHs = getEqualsTPHs(result, c);
//					TPHConstraint constraint = getKeyConstraint(result,c);
//					equalTPHs.add(c.getLeft());
//					equalTPHs.add(c.getRight());
//					result.put(constraint, equalTPHs);
//				}
//				consToRemove.add(c);
//				size--;
//			}
//		}

		System.out.println("Step 2 Result: ");
		result.forEach((c, hs) -> {
			System.out.print(c.toString() + " -> ");
			hs.forEach(s -> {
				System.out.print(s + ", ");
			});
			System.out.println();
		});
		System.out.println("----------------");

		if (!allCons.isEmpty() && allCons.size() < 2) {
			TPHConstraint cons = allCons.get(0);
			if (!result.containsKey(cons)) {
				result.put(cons, null);
				if (!cons.getRight().equals(Type.getInternalName(Object.class)))
					result.put(new ExtendsConstraint(cons.getRight(), Type.getInternalName(Object.class),
							Relation.EXTENDS), null);
			}

			return result;
		}

		size += result.keySet().size();
		// all constraints which have Object on the right side will
		// be ignored, because they are simplified and can not be changed.
		for (TPHConstraint c : allCons) {
			if (c.getRight().equals(Type.getInternalName(Object.class)))
				size--;
		}

		// finder looks for constraints that have the same left hand side
		// and put them in a list
		ConstraintsFinder finder = new ConstraintsFinder(allCons);
		List<List<TPHConstraint>> foundCons = finder.findConstraints();

		ArrayList<TPHConstraint> eqCons = new ArrayList<>();

		for (List<TPHConstraint> list : foundCons) {
			// generate a new name and replace the right hand side for each constraint
			// in list with the new name
			NameReplacer replacer = new NameReplacer(list, allCons, methodTphs, localTphs);
			// new name -> [all old names]
			Map<String, List<String>> replRes = replacer.replaceNames();
			// create an equal constraint for each value in repres
			String key = replRes.keySet().iterator().next();
			for (String val : replRes.values().iterator().next()) {
				EqualConstraint ec = new EqualConstraint(val, key, Relation.EQUAL);
				eqCons.add(ec);
			}
			for (TPHConstraint c : allCons) {
				if (c.getRel() == Relation.EQUAL && key.equals(c.getRight())) {
					eqCons.add(c);
				}
			}
			updateEqualCons(replRes, eqCons);

			TPHConstraint c = list.get(0);
			allCons.removeAll(list);
			allCons.add(c);
		}

		// check if there are multiple constraint with the same left side.
		// if yes => check if the super type in the method, if not
		// then ignore it.
//		HashMap<String, String> subAndSuper = new HashMap<>();
//		ArrayList<TPHConstraint> eqCons = new ArrayList<>();
//		for(TPHConstraint c : allCons) {
//			if(subAndSuper.containsKey(c.getLeft())) {
//				LinkedList<String> all = new LinkedList<>();
//				all.add(c.getLeft());
//				String sup =c.getRight();
//				all.add(sup);
//				HashMap<String, String> ss = new HashMap<>();
//				for(TPHConstraint constr : allCons) {
//					ss.put(constr.getLeft(), constr.getRight());
//				}
//				while(ss.containsKey(sup)) {
//					sup = ss.get(sup);
//					all.add(sup);
//				}
//				if(!containTPH(methodTphs, all.getLast()))
//					continue;
//			}
//			if(subAndSuper.containsKey(c.getLeft())) {
//				System.out.println(c.getLeft());
//				String r = c.getRight();
//				String r2 = subAndSuper.get(c.getLeft());
//				EqualConstraint eq = new EqualConstraint(r2, r, Relation.EQUAL);
//				eqCons.add(eq);
//				substituteInMap(subAndSuper,eqCons,allCons,r,r2);
//			}
//			subAndSuper.put(c.getLeft(), c.getRight());
//		}
//		
//		System.out.println("SAME LEFT SIDE: ");
//		subAndSuper.forEach((c,hs)->{
//			if(c!=null) {
//				System.out.print(c+ " -> " + hs);
//				
//			}
//			
//			
//			System.out.println();
//		});
//		System.out.println("----------------");
		HashMap<String, String> subAndSuper = new HashMap<>();

		for (TPHConstraint c : allCons) {
			if (c.getRel() == Relation.EXTENDS)
				subAndSuper.put(c.getLeft(), c.getRight());
		}

		for (TPHConstraint ec : allCons) {
			if(ec.getRel() == Relation.EQUAL) {
				methodTphs.remove(ec.getLeft());
				localTphs.remove(ec.getLeft());
				if(!localTphs.contains(ec.getRight())) {
					localTphs.add(ec.getRight());
				}
				if(!methodTphs.contains(ec.getRight())) {
					methodTphs.add(ec.getRight());
				}
			}
			
		}

		int numOfVisitedPairs = 0;
		for (String sub : subAndSuper.keySet()) {
			if (isTPHInConstraint(result, sub))
				continue;

			if (!containTPH(methodTphs, sub))
				continue;

			if (numOfVisitedPairs >= size)
				break;
			LinkedList<String> tphInRel = new LinkedList<>();
			tphInRel.add(sub);
			String superT = subAndSuper.get(sub);
			tphInRel.add(superT);

			numOfVisitedPairs++;
			while (!localTphs.contains(superT) && subAndSuper.containsKey(superT)) {
				superT = subAndSuper.get(superT);
				if (tphInRel.contains(superT)) {
					break;
				}
				tphInRel.add(superT);
				numOfVisitedPairs++;
			}

			// Subtype
			String subTphRes = tphInRel.getFirst();
			// Die größte Supertype
			String superTphRes = tphInRel.getLast();

			// if there is any constraint X < subTph, then
			// add X at the beginning of the list.
			while (!localTphs.contains(subTphRes) && subAndSuper.containsValue(subTphRes)) {
				for (String tph : subAndSuper.keySet()) {
					if (containTPH(methodTphs, tph) && subAndSuper.get(tph).equals(subTphRes)) {
						subTphRes = tph;
						break;
					}
				}
				if (subTphRes.equals(tphInRel.getFirst())) {
					break;
				}

				if (isTPHInConstraint(result, subTphRes))
					break;

				tphInRel.addFirst(subTphRes);
				numOfVisitedPairs++;
			}

			subTphRes = tphInRel.getFirst();
			// if the last type in the list not a type in method-types
			// then find the last type in front of this type, which is
			// a type in method-types
			int i = 2;
			while (!containTPH(methodTphs, superTphRes) && (tphInRel.size() - i) > 0) {
				superTphRes = tphInRel.get(tphInRel.size() - i);
				i++;
			}

			if (!containTPH(methodTphs, superTphRes)) {
				HashSet<String> equals = getEqualsTphsFromEqualCons(eqCons, superTphRes);
				if (tphsClass.contains(superTphRes)/*classTPHSContainsTPH(tphsClass, superTphRes)*/) {
					result.put(new ExtendsConstraint(subTphRes, superTphRes, Relation.EXTENDS), equals);
				} else {
					result.put(new ExtendsConstraint(subTphRes, Type.getInternalName(Object.class), Relation.EXTENDS),
							equals);
				}

			} else {
				HashSet<String> equals = getEqualsTphsFromEqualCons(eqCons, subTphRes);
				result.put(new ExtendsConstraint(subTphRes, superTphRes, Relation.EXTENDS), equals);
				if (!isTPHInConstraint(result, superTphRes) && !isTphInEqualSet(result, superTphRes)) {
					HashSet<String> equals2 = getEqualsTphsFromEqualCons(eqCons, superTphRes);
					result.put(new ExtendsConstraint(superTphRes, Type.getInternalName(Object.class), Relation.EXTENDS),
							equals2);
				}
			}
		}

//		for(String tph : methodTphs) {
		for (String tph : localTphs) {
			if (!isTPHInConstraint(result, tph) && !isTphInEqualSet(result, tph)) {
				HashSet<String> equals = getEqualsTphsFromEqualCons(eqCons, tph);
				result.put(new ExtendsConstraint(tph, Type.getInternalName(Object.class), Relation.EXTENDS), equals);
			}
		}

		// build an equal set that contains all types
		// which are equal and for each equal constraint put left side and right side
		// in this set Then generate a constraint type < Object and put it
		// with the equal set into the result.
		for (TPHConstraint c : allCons) {
			if (c.getRel() == Relation.EQUAL) {
				if (!isTPHInResEqual(result, c.getLeft())) {
					HashSet<String> equalTPHs = getEqualsTPHs(result, c);
					TPHConstraint constraint = getKeyConstraint(result, c);
					equalTPHs.add(c.getLeft());
					equalTPHs.add(c.getRight());
					result.put(constraint, equalTPHs);
				}
				consToRemove.add(c);
				size--;
			}
		}

		// remove all equal-constraints
		allCons.removeAll(consToRemove);
		// add all generated constraints to allCons
		allCons.addAll(result.keySet());

		System.out.println("EndResult: ");
		result.forEach((c, hs) -> {
			if (c != null) {
				System.out.print(c.toString() + " -> ");
				if (hs == null) {
					System.out.print(" [] ");
				} else {
					hs.forEach(s -> {
						System.out.print(s + ", ");
					});
				}
			}

			System.out.println();
		});
		System.out.println("----------------");
		return result;
	}

	private static boolean containsConstraint(ArrayList<TPHConstraint> allCons, TPHConstraint c) {
		for(TPHConstraint con:allCons) {
			if(c.getLeft().equals(con.getLeft()) && c.getRight().equals(c.getRight())) {
				return true;
			}
		}
		return false;
	}

	private static void updateEqualCons(Map<String, List<String>> replRes, ArrayList<TPHConstraint> eqCons) {
		List<String> oldNames = replRes.values().iterator().next();
		String newName = replRes.keySet().iterator().next();
		for (TPHConstraint c : eqCons) {
//			if(oldNames.contains(c.getLeft()))
//				c.setLeft(newName);
			if (oldNames.contains(c.getRight()))
				c.setRight(newName);
		}

	}

	public static HashMap<TPHConstraint, HashSet<String>> simplifyConstraintsClass(TPHExtractor tphExtractor,
			ArrayList<String> tphsClass) {
		// all constraints that will be simplified
		ArrayList<TPHConstraint> allCons = tphExtractor.allCons;
		ArrayList<TPHConstraint> consToRemove = new ArrayList<>();

		// step 1:
		for (TPHConstraint c : allCons) {

			String left = c.getLeft();
			String right = c.getRight();
			if (c.getRel() == Relation.EXTENDS) {
				TPHConstraint revCon = getReverseConstraint(allCons, left, right);
				if (revCon != null) {
					revCon.setRel(Relation.EQUAL);
					// the reverse constraint is removed because
					// otherwise there is the same constraint twice
					// (e.g. A<B and B<A => A=B and B=A)
					consToRemove.add(revCon);
					c.setRel(Relation.EQUAL);
					substituteTPH(allCons, left, right);
				}
			}
		}

		allCons.removeAll(consToRemove);
		consToRemove = new ArrayList<>();

		int size = allCons.size();

		HashMap<TPHConstraint, HashSet<String>> result = new HashMap<>();

		// check if there is any long cycle (e.g. A<B<C<A)

		// to save all types that are in relation
		LinkedList<String> allTypes = new LinkedList<>();
		// we will put all constraints which are in the cycle, in this ArrayList.
		// Later these contraints will be converted to equal-constraints
		ArrayList<TPHConstraint> constraints = new ArrayList<>(size);
		int visited = 0;

		// contains all constraints
		HashMap<String, String> ss1 = new HashMap<>();

		for (TPHConstraint constr : allCons) {
			ss1.put(constr.getLeft(), constr.getRight());
		}

		for (TPHConstraint c : allCons) {

			if (visited >= size)
				break;
			// Only extends-constraints will be checked
			if (c.getRel() == Relation.EXTENDS) {
				++visited;

				String left = c.getLeft();
				String right = c.getRight();
				// put the types in linked list
				allTypes.add(left);
				allTypes.add(right);

				constraints.add(c);

				boolean isCycle = false;

				// iterate through the map to check if there is a cycle
				while (ss1.containsKey(right)) {
					++visited;
					String oldRight = right;
					right = ss1.get(right);

					TPHConstraint toAdd = getConstraint(oldRight, right, allCons);

					if (toAdd != null)
						constraints.add(toAdd);

					if (left.equals(right)) {
						isCycle = true;
						break;
					}

					allTypes.add(right);
				}

				if (isCycle) {
					// convert all constraints to equal constraints
					setAllEqual(constraints);
					// these constraints will be removed from allCons
					consToRemove.addAll(constraints);
					// all equal types will be substitute with one type
					substituteAllTPH(allCons, constraints, left);

					HashSet<String> eq = new HashSet<>();
					// put all equal types in a set
					for (String t : allTypes) {
						eq.add(t);
					}
					// generate a new constraint (left < Object)
					TPHConstraint constraint = new ExtendsConstraint(left, Type.getInternalName(Object.class),
							Relation.EXTENDS);
					// put the generated constraint and its equal set into result set
					result.put(constraint, eq);
					constraints.clear();
				}
				allTypes.clear();
			}
		}
		// build an equal set that contains all types
		// which are equal and for each equal constraint put left side and right side
		// in this set Then generate a constraint type < Object and put it
		// with the equal set into the result.
		for (TPHConstraint c : allCons) {
			if (c.getRel() == Relation.EQUAL) {
				if (!isTPHInResEqual(result, c.getLeft())) {
					HashSet<String> equalTPHs = getEqualsTPHs(result, c);
					TPHConstraint constraint = getKeyConstraint(result, c);
					equalTPHs.add(c.getLeft());
					equalTPHs.add(c.getRight());
					result.put(constraint, equalTPHs);
				}
				consToRemove.add(c);
				size--;
			}
		}

		// remove all equal-constraints
		allCons.removeAll(consToRemove);
		// add all generated constraints to allCons
		allCons.addAll(result.keySet());

		if (!allCons.isEmpty() && allCons.size() < 2) {
			TPHConstraint cons = allCons.get(0);
			if (!result.containsKey(cons)) {
				result.put(cons, null);
				result.put(new ExtendsConstraint(cons.getRight(), Type.getInternalName(Object.class), Relation.EXTENDS),
						null);
			}

			return result;
		}

		size += result.keySet().size();
		// all constraints which have Object on the right side will
		// be ignored, because they are simplified and can not be changed.
		for (TPHConstraint c : allCons) {
			if (c.getRight().equals(Type.getInternalName(Object.class)))
				size--;
		}

		// check if there are multiple constraint with the same left side.
		// if yes => check if the super type in the method, if not
		// then ignore it.
		HashMap<String, String> subAndSuper = new HashMap<>();
		ArrayList<TPHConstraint> eqCons = new ArrayList<>();
		for (TPHConstraint c : allCons) {

			subAndSuper.put(c.getLeft(), c.getRight());
		}

		int numOfVisitedPairs = 0;
		for (String sub : subAndSuper.keySet()) {
			if (isTPHInConstraint(result, sub))
				continue;

//			if (!classTPHSContainsTPH(tphsClass, sub))
//				continue;
			if (!tphsClass.contains(sub))
				continue;

			if (numOfVisitedPairs >= size)
				break;
			LinkedList<String> tphInRel = new LinkedList<>();
			tphInRel.add(sub);
			String superT = subAndSuper.get(sub);
			tphInRel.add(superT);

			numOfVisitedPairs++;
			while (subAndSuper.containsKey(superT)) {
				superT = subAndSuper.get(superT);
				if (tphInRel.contains(superT)) {
					break;
				}
				tphInRel.add(superT);
				numOfVisitedPairs++;
			}

			// Subtype
			String subTphRes = tphInRel.getFirst();
			// Die größte Supertype
			String superTphRes = tphInRel.getLast();

			// if there is any constraint X < subTph, then
			// add X at the beginning of the list.
			while (subAndSuper.containsValue(subTphRes)) {
				for (String tph : subAndSuper.keySet()) {
					if (/*classTPHSContainsTPH(tphsClass, tph)*/tphsClass.contains(tph) && subAndSuper.get(tph).equals(subTphRes)) {
						subTphRes = tph;
						break;
					}
				}
				if (subTphRes.equals(tphInRel.getFirst())) {
					break;
				}

				if (isTPHInConstraint(result, subTphRes))
					break;

				tphInRel.addFirst(subTphRes);
				numOfVisitedPairs++;
			}

			subTphRes = tphInRel.getFirst();

			HashSet<String> equals = getEqualsTphsFromEqualCons(eqCons, superTphRes);
			result.put(new ExtendsConstraint(subTphRes, superTphRes, Relation.EXTENDS), equals);

		}

		System.out.println("EndResult: ");
		result.forEach((c, hs) -> {
			if (c != null) {
				System.out.print(c.toString() + " -> ");
				if (hs == null) {
					System.out.print(" [] ");
				} else {
					hs.forEach(s -> {
						System.out.print(s + ", ");
					});
				}
			}

			System.out.println();
		});
		System.out.println("----------------");
		return result;
	}

	private static boolean classTPHSContainsTPH(ArrayList<TypePlaceholder> tphsClass, String superTphRes) {
		for (TypePlaceholder tph : tphsClass) {
			if (tph.getName().equals(superTphRes))
				return true;
		}
		return false;
	}

	private static boolean isTphInEqualSet(HashMap<TPHConstraint, HashSet<String>> result, String tph) {
		for (HashSet<String> hs : result.values()) {
			if (hs.contains(tph))
				return true;
		}
		return false;
	}

	private static HashSet<String> getEqualsTphsFromEqualCons(ArrayList<TPHConstraint> eqCons, String tph) {
		HashSet<String> ee = new HashSet<>();
		for (TPHConstraint c : eqCons) {
			if (c.getLeft().equals(tph))
				ee.add(c.getRight());
			if (c.getRight().equals(tph))
				ee.add(c.getLeft());
		}
		return ee;
	}

	private static void substituteInMap(HashMap<String, String> subAndSuper, ArrayList<TPHConstraint> allCons,
			ArrayList<TPHConstraint> eqCons, String toSubs, String tph) {
		substituteTPH(allCons, toSubs, tph);
		if (subAndSuper.containsKey(toSubs) && subAndSuper.containsKey(tph)) {
			toSubs = subAndSuper.remove(toSubs);
			EqualConstraint eq = new EqualConstraint(subAndSuper.get(tph), toSubs, Relation.EQUAL);
			eqCons.add(eq);
			substituteInMap(subAndSuper, allCons, eqCons, toSubs, subAndSuper.get(tph));
		} else if (subAndSuper.containsKey(toSubs) && !subAndSuper.containsKey(tph)) {
			String val = subAndSuper.remove(toSubs);
			subAndSuper.put(tph, val);
		} else {
			for (String k : subAndSuper.keySet()) {
				subAndSuper.replace(k, toSubs, tph);
			}
		}
	}

	private static TPHConstraint getConstraint(String oldRight, String right, ArrayList<TPHConstraint> allCons) {
		for (TPHConstraint c : allCons) {
			if (c.getLeft().equals(oldRight) && c.getRight().equals(right))
				return c;
		}
		return null;
	}

	private static boolean isTPHInResEqual(HashMap<TPHConstraint, HashSet<String>> result, String left) {
		for (HashSet<String> eq : result.values()) {
			if (eq.contains(left)) {
				return true;
			}
		}
		return false;
	}

	private static void substituteAllTPH(ArrayList<TPHConstraint> allCons, ArrayList<TPHConstraint> constraints,
			String first) {
		for (TPHConstraint c : allCons) {
			for (TPHConstraint c2 : constraints) {
				if (c.getRel() == Relation.EXTENDS) {
					if (c2.getLeft().equals(c.getLeft()) || c2.getRight().equals(c.getLeft()))
						c.setLeft(first);
					if (c2.getLeft().equals(c.getRight()) || c2.getRight().equals(c.getRight()))
						c.setRight(first);
				}

			}
		}
	}

	private static void setAllEqual(ArrayList<TPHConstraint> constraints) {
		for (TPHConstraint c : constraints) {
			c.setRel(Relation.EQUAL);
		}
	}

	public static HashMap<TPHConstraint, HashSet<String>> simplifyContraints(
			HashMap<TPHConstraint, HashSet<String>> allConstraints) {
		// 1. check if there are any cycles like L<R and R<L:
		// a) yes => set L=R and:
		// * remove both constraints
		// * substitute L with R in all constraint
		// b)no => go to next step
		// 2. check the result of step 1 if there are any equal-constraints like L=R,
		// M=R ..
		// a) yes => put all such TPhs in a map and define "key-Cons"
		// -- key-Cons = TPH < Object --
		// put this Constraint and the
		// b) no
		// 3. is
		ArrayList<TPHConstraint> allCons = new ArrayList<>();
		for (TPHConstraint tphCons : allConstraints.keySet()) {
			allCons.add(tphCons);
		}
		ArrayList<TPHConstraint> consToRemove = new ArrayList<>();
		// step 1:
		for (TPHConstraint c : allCons) {

			String left = c.getLeft();
			String right = c.getRight();
			if (c.getRel() == Relation.EXTENDS) {
				TPHConstraint revCon = getReverseConstraint(allCons, left, right);
				if (revCon != null) {
					revCon.setRel(Relation.EQUAL);
					consToRemove.add(revCon);
					c.setRel(Relation.EQUAL);
					substituteTPH(allCons, left, right);
				}
			}
		}
		System.out.println();
		System.out.println("NEW ALL CONST: " + allCons.size());
		allCons.forEach(c -> System.out.println(c.toString()));
		System.out.println("----------------");
		allCons.removeAll(consToRemove);
		consToRemove = new ArrayList<>();

//		int size = allCons.size();

		System.out.println("AFTER DELETE ALL CONST: " + allCons.size());
		allCons.forEach(c -> System.out.println(c.toString()));
		System.out.println("----------------");
		HashMap<TPHConstraint, HashSet<String>> result = new HashMap<>();

		for (TPHConstraint c : allCons) {
			if (c.getRel() == Relation.EQUAL) {
				HashSet<String> equalTPHs = getEqualsTPHs(result, c);
				TPHConstraint constraint = getKeyConstraint(result, c);
				equalTPHs.add(c.getLeft());
				equalTPHs.add(c.getRight());
				result.put(constraint, equalTPHs);
				consToRemove.add(c);
//				size--;
			}
		}
		System.out.println("Step 2 Result: ");
		result.forEach((c, hs) -> {
			System.out.print(c.toString() + " -> ");
			hs.forEach(s -> {
				System.out.print(s + ", ");
			});
			System.out.println();
		});
		System.out.println("----------------");
		allCons.removeAll(consToRemove);
		allCons.addAll(result.keySet());

		if (!allCons.isEmpty() && allCons.size() < 2) {

			if (!result.containsKey(allCons.get(0)))
				result.put(allCons.get(0), null);

			return result;
		}

//		size += result.keySet().size();

//		for(TPHConstraint c : allCons) {
//			if(c.getRight().equals(Type.getInternalName(Object.class)))
//				size--;
//		}

		HashMap<String, String> subAndSuper = new HashMap<>();

		for (TPHConstraint c : allCons) {
			subAndSuper.put(c.getLeft(), c.getRight());
		}

//		int numOfVisitedPairs = 0;
		for (String sub : subAndSuper.keySet()) {
			if (isTPHInConstraint(result, sub))
				continue;

//			if(numOfVisitedPairs>=size)
//				break;

			LinkedList<String> tphInRel = new LinkedList<>();
			tphInRel.add(sub);
			String superT = subAndSuper.get(sub);
			tphInRel.add(superT);

//			numOfVisitedPairs++;
			while (subAndSuper.containsKey(superT)) {
				superT = subAndSuper.get(superT);
				if (tphInRel.contains(superT)) {
					break;
				}
				tphInRel.add(superT);
//				numOfVisitedPairs++;
			}

			// Subtype
			String subTphRes = tphInRel.getFirst();
			// Die größte Supertype
			String superTphRes = tphInRel.getLast();

			while (subAndSuper.containsValue(subTphRes)) {
				for (String tph : subAndSuper.keySet()) {
					if (subAndSuper.get(tph).equals(subTphRes)) {
						subTphRes = tph;
						break;
					}
				}
				if (subTphRes.equals(tphInRel.getFirst())) {
					break;
				}
				tphInRel.addFirst(subTphRes);
//				numOfVisitedPairs++;
			}

			subTphRes = tphInRel.getFirst();

			int i = 2;
			while (superTphRes.equals(Type.getInternalName(Object.class)) && (tphInRel.size() - i) > 0) {
				superTphRes = tphInRel.get(tphInRel.size() - i);
				i++;
			}
			if (superTphRes.equals(Type.getInternalName(Object.class))) {
				result.put(new ExtendsConstraint(subTphRes, Type.getInternalName(Object.class), Relation.EXTENDS),
						null);
			} else {
				result.put(new ExtendsConstraint(subTphRes, superTphRes, Relation.EXTENDS), null);
				result.put(new ExtendsConstraint(superTphRes, Type.getInternalName(Object.class), Relation.EXTENDS),
						null);
			}

		}
		System.out.println("EndResult: ");
		result.forEach((c, hs) -> {
			if (c != null) {
				System.out.print(c.toString() + " -> ");
				if (hs == null) {
					System.out.print(" [] ");
				} else {
					hs.forEach(s -> {
						System.out.print(s + ", ");
					});
				}
			}

			System.out.println();
		});
		System.out.println("----------------");
		return result;
	}

	private static void substituteTPH(ArrayList<TPHConstraint> allCons, String left, String right) {
		allCons.forEach(c -> {
			if (c.getRel() == Relation.EXTENDS) {
				if (c.getLeft().equals(left))
					c.setLeft(right);
				if (c.getRight().equals(left))
					c.setRight(right);
			}
		});
	}

	private static TPHConstraint getReverseConstraint(ArrayList<TPHConstraint> allCons, String left, String right) {
		for (TPHConstraint c : allCons) {
			if (c.getLeft().equals(right) && c.getRight().equals(left)) {
				return c;
			}
		}
		return null;
	}

	private static boolean isTPHInConstraint(HashMap<TPHConstraint, HashSet<String>> result, String sub) {
		for (TPHConstraint c : result.keySet()) {
			if (c.getLeft().equals(sub))
				return true;
		}
		return false;
	}

	private static boolean containTPH(ArrayList<String> methodTphs, String sub) {
		for (String tph : methodTphs) {
			if (tph.equals(sub))
				return true;
		}
		return false;
	}

	private static TPHConstraint getKeyConstraint(HashMap<TPHConstraint, HashSet<String>> result, TPHConstraint toFind,
			ArrayList<TPHConstraint> allCons) {

		for (TPHConstraint c : result.keySet()) {
			if (c.containTPH(toFind.getLeft()))
				return c;
		}
		for (TPHConstraint c : allCons) {
			if (toFind.getRight().equals(c.getLeft()))
				return c;
		}

		return new ExtendsConstraint(toFind.getRight(), Type.getInternalName(Object.class), Relation.EXTENDS);
	}

	private static TPHConstraint getKeyConstraint(HashMap<TPHConstraint, HashSet<String>> result,
			TPHConstraint toFind) {

		for (TPHConstraint c : result.keySet()) {
			if (c.containTPH(toFind.getLeft()) || c.containTPH(toFind.getRight()))
				return c;
		}

		return new ExtendsConstraint(toFind.getRight(), Type.getInternalName(Object.class), Relation.EXTENDS);
	}

	private static HashSet<String> getEqualsTPHs(HashMap<TPHConstraint, HashSet<String>> result, TPHConstraint toFind) {
		for (TPHConstraint c : result.keySet()) {
			if (c.containTPH(toFind.getLeft()) || c.containTPH(toFind.getRight()))
				return result.get(c);
		}
		return new HashSet<>();
	}
}
