package de.dhbwstuttgart.typeinference.unify.model;

import de.dhbwstuttgart.typeinference.unify.UnifyContext;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.google.common.collect.Ordering;

import de.dhbwstuttgart.typeinference.unify.Match;
import de.dhbwstuttgart.typeinference.unify.TypeUnifyTask;
import de.dhbwstuttgart.typeinference.unify.interfaces.IFiniteClosure;
import de.dhbwstuttgart.util.Pair;



public class OrderingUnifyPair extends OrderingExtend<Set<UnifyPair>> {
	
	protected IFiniteClosure fc;
	protected UnifyContext context;

	public OrderingUnifyPair(IFiniteClosure fc, UnifyContext context) {
		this.fc = fc;
		this.context = context;
	}
	
	/*
	 * vergleicht Paare (a =. Theta) und (a =. Theta')
	 * in dem compare(Theta, Theta') aufgerufen wird.
	 */
	public int compareEq (UnifyPair left, UnifyPair right) {
		try {
		//if (left.getRhsType() instanceof WildcardType || right.getRhsType() instanceof WildcardType) {//PL 2019-01-12 ausgetauscht
		if (((PlaceholderType)left.getLhsType()).isInnerType() && ((PlaceholderType)right.getLhsType()).isInnerType()) {
			return fc.compare(left.getRhsType(), right.getRhsType(), PairOperator.SMALLERDOTWC, context);
		}
			else	 {
				return fc.compare(left.getRhsType(), right.getRhsType(), PairOperator.SMALLERDOT, context);
		}}
		catch (ClassCastException e) {
			((FiniteClosure)fc).logger.debug(() -> "ClassCastException: " + left.toString() + " " + left.getGroundBasePair() + "\n\n");
			return -99;
		}
	}
	
	/*
	public int compareEq (UnifyPair left, UnifyPair right) {
		if (left == null || right == null)
			context.logger().info("Fehler");
		if (left.getLhsType() instanceof PlaceholderType) {
			return fc.compare(left.getRhsType(), right.getRhsType(), left.getPairOp());
		}
		else {
			return fc.compare(left.getLhsType(), right.getLhsType(), left.getPairOp());
		}
	}
	*/
	
	public Pair<Integer,Set<UnifyPair>> compare (UnifyType left, UnifyType right) {
		UnifyPair up;
		if (left instanceof WildcardType || right instanceof WildcardType) {
			up = new UnifyPair(left, right, PairOperator.SMALLERDOTWC);
			if (((left instanceof ExtendsType) 
					&& (((ExtendsType)left).getExtendedType().getName().equals("java.util.Vector"))
					&& (((ReferenceType)((ExtendsType)left).getExtendedType()).getTypeParams().iterator().next() instanceof ExtendsType)) ||
				((right instanceof ExtendsType) 
						&& (((ExtendsType)right).getExtendedType().getName().equals("java.util.Vector"))
						&& (((ReferenceType)((ExtendsType)right).getExtendedType()).getTypeParams().iterator().next() instanceof ExtendsType)))
				{
					// context.logger().info("");
				}
				if (((right instanceof SuperType) && (((SuperType)right).getSuperedType().getName().equals("java.lang.Object")))
		||((left instanceof SuperType) && (((SuperType)left).getSuperedType().getName().equals("java.lang.Object"))))
{
						// context.logger().info("");
					}
		}
			else	 {
				up = new UnifyPair(left, right, PairOperator.SMALLERDOT);
		}
		TypeUnifyTask unifyTask = new TypeUnifyTask(context);
		HashSet<UnifyPair> hs = new HashSet<>();
		hs.add(up);
		Set<UnifyPair> smallerRes = unifyTask.applyTypeUnificationRules(hs, fc);
		long smallerLen = smallerRes.stream().filter(x -> !(x.getLhsType() instanceof PlaceholderType && x.getRhsType() instanceof PlaceholderType)).count();
		if (smallerLen == 0) return new Pair<>(-1, smallerRes);
		else {
			if (left instanceof WildcardType || right instanceof WildcardType) {
				up = new UnifyPair(right, left, PairOperator.SMALLERDOTWC);
				if (((left instanceof ExtendsType) 
					&& (((ExtendsType)left).getExtendedType().getName().equals("java.util.Vector"))
					&& (((ReferenceType)((ExtendsType)left).getExtendedType()).getTypeParams().iterator().next() instanceof ExtendsType)) ||
				((right instanceof ExtendsType) 
						&& (((ExtendsType)right).getExtendedType().getName().equals("java.util.Vector"))
						&& (((ReferenceType)((ExtendsType)right).getExtendedType()).getTypeParams().iterator().next() instanceof ExtendsType)))
				{
					// context.logger().info("");
				}
				if (right instanceof SuperType) 
					{
						// context.logger().info("");
					}
			}
				else	 {
					up = new UnifyPair(right, left, PairOperator.SMALLERDOT);
			}
			//TypeUnifyTask unifyTask = new TypeUnifyTask();
			hs = new HashSet<>();
			hs.add(up);
			Set<UnifyPair> greaterRes = unifyTask.applyTypeUnificationRules(hs, fc);
			long greaterLen = greaterRes.stream().filter(x -> !(x.getLhsType() instanceof PlaceholderType && x.getRhsType() instanceof PlaceholderType)).count();
			if (greaterLen == 0) return new Pair<>(1, greaterRes);
			else return new Pair<>(0, new HashSet<>());
		}	
	}
	
	/* TODO muss noch verifiziert werden PL 2018-03-21
	 * (non-Javadoc)
	 * fuehrt zu Fehlern bei Arrays.sort (contract nicht erfuellt)
	 * @see com.google.common.collect.Ordering#compare(java.lang.Object, java.lang.Object)
	 */
	public int compare (Set<UnifyPair> leftpara, Set<UnifyPair> rightpara) {
		
		Set<UnifyPair> left = new HashSet<>(leftpara);
		Set<UnifyPair> right = new HashSet<>(rightpara);
		
		/*
		//pairop = PairOperator.SMALLERDOTWC;
		List<UnifyType> al = new ArrayList<>();
		PlaceholderType xx = PlaceholderType.freshPlaceholder();
		al.add(xx);
		UnifyType t1 = new ExtendsType(new ReferenceType("Vector", new TypeParams(al)));
		
		//PlaceholderType yy =new PlaceholderType("yy");
		List<UnifyType> alr = new ArrayList<>();
		UnifyType exx = new ExtendsType(xx);
		alr.add(exx);
		UnifyType t2 = new ExtendsType(new ReferenceType("Vector", new TypeParams(alr)));
		
		PlaceholderType a = PlaceholderType.freshPlaceholder();
		a.setInnerType(true);
		UnifyPair p1 = new UnifyPair(a, t1, PairOperator.SMALLERDOTWC); 
		PlaceholderType b = PlaceholderType.freshPlaceholder();
		b.setInnerType(true);
		UnifyPair p2 = new UnifyPair(b, t2, PairOperator.SMALLERDOTWC);
		
		List<UnifyType> al3 = new ArrayList<>();
		al3.add(a);
		
		List<UnifyType> al4 = new ArrayList<>();
		al4.add(b);
		
		UnifyPair p3 = new UnifyPair(new PlaceholderType("c"), new ReferenceType("Vector", new TypeParams(al3)), PairOperator.EQUALSDOT);
		UnifyPair p4 = new UnifyPair(new PlaceholderType("c"), new ReferenceType("Vector", new TypeParams(al4)), PairOperator.EQUALSDOT);
		
		right = new HashSet<>();
		right.add(p1);
		right.add(p3);
		left = new HashSet<>();
		left.add(p2);
		left.add(p4);
		*/

		Set<UnifyPair> lefteq = new HashSet<>();
		Set<UnifyPair> leftle = new HashSet<>();
		Set<UnifyPair> leftlewc = new HashSet<>();
		Set<UnifyPair> lefteqOder = new HashSet<>();
		Set<UnifyPair> lefteqRet = new HashSet<>();
		Set<UnifyPair> leftleOder = new HashSet<>();
		for (var x : left) {
			boolean isLeftPlaceholder = x.getLhsType() instanceof PlaceholderType;
			boolean isRightPlaceholder = x.getRhsType() instanceof PlaceholderType;
			boolean hasPlaceholder = isLeftPlaceholder || isRightPlaceholder;

			if (isLeftPlaceholder && x.getPairOp() == PairOperator.EQUALSDOT) lefteq.add(x);
			if (hasPlaceholder && x.getPairOp() == PairOperator.SMALLERDOT) leftle.add(x);
			if (hasPlaceholder && x.getPairOp() == PairOperator.SMALLERDOTWC) leftlewc.add(x);

			UnifyPair y = x.getGroundBasePair();
			boolean isBasePairLeftPlaceholder = y.getLhsType() instanceof PlaceholderType;
			boolean isBasePairRightPlaceholder = y.getRhsType() instanceof PlaceholderType;
			if (isBasePairLeftPlaceholder && !isBasePairRightPlaceholder && x.getPairOp() == PairOperator.EQUALSDOT) {
				lefteqOder.add(x);
			}
			else if (isBasePairRightPlaceholder && ((PlaceholderType)y.getRhsType()).getOrCons() == (byte)-1) {
				lefteqRet.add(x);
			}
			else if (x.getPairOp() == PairOperator.SMALLERDOT) {
				leftleOder.add(x);
			}
		}


		Set<UnifyPair> righteq = new HashSet<>();
		Set<UnifyPair> rightle = new HashSet<>();
		Set<UnifyPair> rightlewc = new HashSet<>();
		Set<UnifyPair> righteqOder = new HashSet<>();
		Set<UnifyPair> righteqRet = new HashSet<>();
		Set<UnifyPair> rightleOder = new HashSet<>();
		for (var x : right) {
			boolean isLeftPlaceholder = x.getLhsType() instanceof PlaceholderType;
			boolean isRightPlaceholder = x.getRhsType() instanceof PlaceholderType;
			boolean hasPlaceholder = isLeftPlaceholder || isRightPlaceholder;

			if (isLeftPlaceholder && x.getPairOp() == PairOperator.EQUALSDOT) righteq.add(x);
			if (hasPlaceholder && x.getPairOp() == PairOperator.SMALLERDOT) 	rightle.add(x);
			if (hasPlaceholder && x.getPairOp() == PairOperator.SMALLERDOTWC) rightlewc.add(x);

			UnifyPair y = x.getGroundBasePair();
			boolean isBasePairLeftPlaceholder = y.getLhsType() instanceof PlaceholderType;
			boolean isBasePairRightPlaceholder = y.getRhsType() instanceof PlaceholderType;
			if (isBasePairLeftPlaceholder && !isBasePairRightPlaceholder && x.getPairOp() == PairOperator.EQUALSDOT) {
				righteqOder.add(x);
			}
			else if (isBasePairRightPlaceholder && ((PlaceholderType)y.getRhsType()).getOrCons() == (byte)-1) {
				righteqRet.add(x);
			}
			else if (x.getPairOp() == PairOperator.SMALLERDOT) {
				rightleOder.add(x);
			}
		}

		//context.logger().info(left.toString());
		//Fall 2
		//if (lefteq.iterator().next().getLhsType().getName().equals("AJO")) {
		//	System.out.print("");
		//}
		
		//ODER-CONSTRAINT
		// Set<UnifyPair> leftBase = left.stream().map(x -> x.getGroundBasePair()).collect(Collectors.toCollection(HashSet::new));
		// Set<UnifyPair> rightBase = right.stream().map(x -> x.getGroundBasePair()).collect(Collectors.toCollection(HashSet::new));

		left.removeAll(lefteqOder);
		left.removeAll(lefteqRet);
		right.removeAll(righteqOder);
		right.removeAll(righteqRet);

		/*
		synchronized(this) {
					try {
			((FiniteClosure)fc).logFile.write("leftBase: " + leftBase.toString() +"\n");
			((FiniteClosure)fc).logFile.write("rightBase: " + rightBase.toString() +"\n\n");
			((FiniteClosure)fc).logFile.write("left: " + left.toString() +"\n");
			((FiniteClosure)fc).logFile.write("right: " + right.toString() +"\n\n");
			((FiniteClosure)fc).logFile.write("lefteqOder: " + lefteqOder.toString() +"\n");
			((FiniteClosure)fc).logFile.write("righteqOder: " + righteqOder.toString() +"\n\n");
			((FiniteClosure)fc).logFile.write("lefteqRet: " + lefteqRet.toString() +"\n");
			((FiniteClosure)fc).logFile.write("righteqRet: " + righteqRet.toString() +"\n\n");
			((FiniteClosure)fc).logFile.write("leftleOder: " + leftleOder.toString() +"\n");
			((FiniteClosure)fc).logFile.write("rightleOder: " + rightleOder.toString() +"\n\n");
			((FiniteClosure)fc).logFile.flush();
			}
			catch (IOException ie) {
			}
		}
		*/
		
		
		int compareEq;
		if (lefteqOder.size() == 1 && righteqOder.size() == 1 && lefteqRet.size() == 1 && righteqRet.size() == 1) {
			Match m = new Match();
			compareEq = compareEq(lefteqOder.iterator().next().getGroundBasePair(), righteqOder.iterator().next().getGroundBasePair());

			if (compareEq == -1) {
				ArrayList<UnifyPair> matchList =
						rightleOder.stream().map(x -> {
								UnifyPair leftElem = leftleOder.stream()
														     .filter(y -> y.getGroundBasePair().getLhsType().equals(x.getGroundBasePair().getLhsType()))
														     .findAny().orElseThrow();
								return new UnifyPair(x.getRhsType(), leftElem.getRhsType(), PairOperator.EQUALSDOT);}
							)
							.collect(Collectors.toCollection(ArrayList::new));

				return (m.match(matchList).isPresent()) ? -1 : 0;
			} else if (compareEq == 1) {
				ArrayList<UnifyPair> matchList =
						leftleOder.stream().map(x -> {
							UnifyPair rightElem = rightleOder.stream()
								.filter(y -> y.getGroundBasePair().getLhsType().equals(x.getGroundBasePair().getLhsType()))
								.findAny().orElseThrow();
							return new UnifyPair(x.getRhsType(), rightElem.getRhsType(), PairOperator.EQUALSDOT);}
						)
						.collect(Collectors.toCollection(ArrayList::new));
				return (m.match(matchList).isPresent()) ? 1 : 0;
			} else {
				/*
					((FiniteClosure)fc).logFile.write("leftBase: " + leftBase.toString() +"\n");
					((FiniteClosure)fc).logFile.write("rightBase: " + rightBase.toString() +"\n\n");
					((FiniteClosure)fc).logFile.write("left: " + left.toString() +"\n");
					((FiniteClosure)fc).logFile.write("right: " + right.toString() +"\n\n");
					((FiniteClosure)fc).logFile.write("lefteqOder: " + lefteqOder.toString() +"\n");
					((FiniteClosure)fc).logFile.write("righteqOder: " + righteqOder.toString() +"\n\n");
					((FiniteClosure)fc).logFile.write("lefteqRet: " + lefteqRet.toString() +"\n");
					((FiniteClosure)fc).logFile.write("righteqRet: " + righteqRet.toString() +"\n\n");
					((FiniteClosure)fc).logFile.write("leftleOder: " + leftleOder.toString() +"\n");
					((FiniteClosure)fc).logFile.write("rightleOder: " + rightleOder.toString() +"\n\n");
					((FiniteClosure)fc).logFile.write("result3: 0 \n\n");
					((FiniteClosure)fc).logFile.flush();
				*/
				return 0;
			}
		}
		
		
		if (lefteq.size() == 1 && lefteq.iterator().next().getRhsType() instanceof ExtendsType && leftle.size() == 1  && righteq.isEmpty() && rightle.size() == 1) {
			return 1;
		}
		//Fall 2
		if (lefteq.isEmpty() && leftle.size() == 1 && righteq.size() == 1 && righteq.iterator().next().getRhsType() instanceof ExtendsType && rightle.size() == 1) {
			return -1;
		}
		//Fall 3
		if (lefteq.size() == 1 && lefteq.iterator().next().getRhsType() instanceof SuperType && leftle.size() == 1  && righteq.isEmpty() && rightle.size() == 1) {
			return -1;
		}
		//Fall 3
		if (lefteq.isEmpty() && leftle.size() == 1 && righteq.size() == 1 && righteq.iterator().next().getRhsType() instanceof SuperType && rightle.size() == 1) {
			return 1;
		}
		//Fall 5
		if (lefteq.size() == 1 && leftle.isEmpty() && righteq.size() == 1 && rightle.size() == 1) {
			return -1;
		}
		//Fall 5
		if (lefteq.size() == 1 && leftle.size() == 1 && righteq.size() == 1 && rightle.isEmpty()) {
			return 1;
		}
		//Fall 5
		if (lefteq.size() == 1 && leftle.size() == 1 && righteq.size() == 1 && rightle.size() == 1) {
			return 0;
		}
		// Nur Paare a =. Theta
		if (leftle.isEmpty() && rightle.isEmpty() && leftlewc.isEmpty() && rightlewc.isEmpty()) {
			Stream<UnifyPair> lseq = lefteq.stream(); //left.filter(x -> (x.getLhsType() instanceof PlaceholderType && x.getPairOp() == PairOperator.EQUALSDOT));
			Stream<UnifyPair> rseq = righteq.stream(); //right.filter(x -> (x.getLhsType() instanceof PlaceholderType && x.getPairOp() == PairOperator.EQUALSDOT));
			BinaryOperator<HashMap<UnifyType,UnifyPair>> combiner = (x,y) -> { x.putAll(y); return x;};
			HashMap<UnifyType,UnifyPair> hm = rseq.reduce(new HashMap<UnifyType,UnifyPair>(), (x, y)-> { x.put(y.getLhsType(),y); return x; }, combiner);
			lseq = lseq.filter(x -> !(hm.get(x.getLhsType()) == null));//NOCHMALS UEBERPRUEFEN!!!!
			lseq = lseq.filter(x -> !x.equals(hm.get(x.getLhsType()))); //Elemente die gleich sind muessen nicht verglichen werden
			Optional<Integer> si = lseq.map(x -> compareEq(x, hm.get(x.getLhsType()))).reduce((x,y)-> { if (x == y) return x; else return 0; } );
			return si.orElse(0);
		}
		//Fall 1 und 4
		if (!lefteq.isEmpty() && !righteq.isEmpty() && (!leftlewc.isEmpty() || !rightlewc.isEmpty())) {
			//Set<PlaceholderType> varsleft = lefteq.stream().map(x -> (PlaceholderType)x.getLhsType()).collect(Collectors.toCollection(HashSet::new));
			//Set<PlaceholderType> varsright = righteq.stream().map(x -> (PlaceholderType)x.getLhsType()).collect(Collectors.toCollection(HashSet::new));
			//filtern des Paares a = Theta, das durch a <. Thata' generiert wurde (nur im Fall 1 relevant)  andere Substitutioen werden rausgefiltert
			lefteq.removeIf(x -> (x.getBasePair()!=null) && !(x.getLhsType().getName().equals(x.getBasePair().getLhsType().getName())
					              ||x.getLhsType().getName().equals(x.getBasePair().getRhsType().getName())));//removeIf(x -> !varsright.contains(x.getLhsType()));
			righteq.removeIf(x -> (x.getBasePair()!=null) && !(x.getLhsType().getName().equals(x.getBasePair().getLhsType().getName())
		              	           ||x.getLhsType().getName().equals(x.getBasePair().getRhsType().getName())));//.removeIf(x -> !varsleft.contains(x.getLhsType()));
			UnifyPair lseq = lefteq.iterator().next();
			UnifyPair rseq = righteq.iterator().next();
			if (lseq.getRhsType().getName().equals("Object")) {
				if (rseq.getRhsType().getName().equals("Object")) return 0;
				else return 1;
			}
			else {
				if (rseq.getRhsType().getName().equals("Object")) return -1;
			}
			if (leftlewc.size() == rightlewc.size()) {
				//TODO: Hier wird bei Wildcards nicht das richtige compare aufgerufen PL 18-04-20
				Pair<Integer, Set<UnifyPair>> int_Unifier = compare(lseq.getRhsType(), rseq.getRhsType());
				Unifier uni = new Unifier();
				int_Unifier.getValue().orElseThrow().forEach(x -> uni.add((PlaceholderType) x.getLhsType(), x.getRhsType()));
				if (!lseq.getRhsType().getName().equals(rseq.getRhsType().getName())
					|| leftlewc.isEmpty() || rightlewc.isEmpty()) return int_Unifier.getKey();
				else {
					Set <UnifyPair> lsleuni = leftlewc.stream().map(uni::apply).collect(Collectors.toCollection(HashSet::new));
					Set <UnifyPair> rsleuni = rightlewc.stream().map(uni::apply).collect(Collectors.toCollection(HashSet::new));
					BinaryOperator<HashMap<UnifyType,UnifyPair>> combiner = (x,y) -> { x.putAll(y); return x;};
				
					HashMap<UnifyType,UnifyPair> hm;
					Optional<Integer> si;
					//1. Fall
					if (leftlewc.iterator().next().getLhsType() instanceof PlaceholderType) {
						hm = rsleuni.stream().reduce(new HashMap<UnifyType,UnifyPair>(), (x, y)-> { x.put(y.getLhsType(),y); return x; }, combiner);
						Stream<UnifyPair> lslewcstr = lsleuni.stream().filter(x -> !(hm.get(x.getLhsType()) == null));
						si = lslewcstr.map(x -> fc.compare(x.getRhsType(), hm.get(x.getLhsType()).getRhsType(), PairOperator.SMALLERDOTWC, context)).reduce((x,y)-> { if (x == y) return x; else return 0; } );
					}
					//4. Fall
					else {
						hm = rsleuni.stream().reduce(new HashMap<UnifyType,UnifyPair>(), (x, y)-> { x.put(y.getRhsType(),y); return x; }, combiner);
						Stream<UnifyPair> lslewcstr = lsleuni.stream().filter(x -> !(hm.get(x.getRhsType()) == null));
						si = lslewcstr.map(x -> fc.compare(x.getLhsType(), hm.get(x.getRhsType()).getLhsType(), PairOperator.SMALLERDOTWC, context)).reduce((x,y)-> { if (x == y) return x; else return 0; } );
					}
					return si.orElse(0);
				}
			} else {
				if (!leftlewc.isEmpty()) {
					/*
					Set<UnifyPair> subst;
					subst = leftlewc.stream().map(x -> { 
						if (x.getLhsType() instanceof PlaceholderType) {
							return new UnifyPair(x.getLhsType(), x.getRhsType(), PairOperator.EQUALSDOT);
						}
						else {
							return new UnifyPair(x.getRhsType(), x.getLhsType(), PairOperator.EQUALSDOT);
						}}).collect(Collectors.toCollection(HashSet::new));
					 */
					Unifier uni = new Unifier();
					lseq = uni.apply(lseq);
				}
				else {
					Set<UnifyPair> subst;
					subst = rightlewc.stream().map(x -> { 
						if (x.getLhsType() instanceof PlaceholderType) {
							return new UnifyPair(x.getLhsType(), x.getRhsType(), PairOperator.EQUALSDOT);
						}
						else {
							return new UnifyPair(x.getRhsType(), x.getLhsType(), PairOperator.EQUALSDOT);
						}}).collect(Collectors.toCollection(HashSet::new));
					Unifier uni = new Unifier();
					subst.stream().forEach(x -> uni.add((PlaceholderType) x.getLhsType(), x.getRhsType()));
					rseq = uni.apply(rseq);
				}
				return compareEq(lseq, rseq);
			}
		}
		return 0;	
	}		
}

