package de.dhbwstuttgart.typeinference.unify.model;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.BinaryOperator;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.google.common.collect.Ordering;

import de.dhbwstuttgart.typeinference.unify.TypeUnifyTask;
import de.dhbwstuttgart.typeinference.unify.interfaces.IFiniteClosure;



public class OrderingUnifyPair extends Ordering<Set<UnifyPair>> {
	
	protected IFiniteClosure fc;
	
	public OrderingUnifyPair(IFiniteClosure fc) {
		this.fc = fc;
	}
	
	/*
	 * 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);
		}
			else	 {
				return fc.compare(left.getRhsType(), right.getRhsType(), PairOperator.SMALLERDOT);
		}}
		catch (ClassCastException e) {
			try {
			((FiniteClosure)fc).logFile.write("ClassCastException: " + left.toString() +"\n\n");
			((FiniteClosure)fc).logFile.flush();
			}
			catch (IOException ie) {
			}
			return -99;
		}
	}
	
	/*
	public int compareEq (UnifyPair left, UnifyPair right) {
		if (left == null || right == null)
			System.out.println("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)))
				{
					System.out.println("");
				}
				if (((right instanceof SuperType) && (((SuperType)right).getSuperedType().getName().equals("java.lang.Object")))
		||((left instanceof SuperType) && (((SuperType)left).getSuperedType().getName().equals("java.lang.Object"))))
{
						System.out.println("");
					}
		}
			else	 {
				up = new UnifyPair(left, right, PairOperator.SMALLERDOT);
		}
		TypeUnifyTask unifyTask = new TypeUnifyTask();
		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)))
				{
					System.out.println("");
				}
				if (right instanceof SuperType) 
					{
						System.out.println("");
					}
			}
				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> left, Set<UnifyPair> right) {
		/*
		//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);
		*/
		
		if ((left.size() == 1) && right.size() == 1) { 
			if (left.iterator().next().getLhsType().getName().equals("AFS")) {
				System.out.println("");
			}
		if (((right.iterator().next().getRhsType() instanceof SuperType) && (((SuperType)right.iterator().next().getRhsType()).getSuperedType().getName().equals("java.lang.Object")))
				||((left.iterator().next().getRhsType() instanceof SuperType) && (((SuperType)left.iterator().next().getRhsType()).getSuperedType().getName().equals("java.lang.Object"))))
		{
								System.out.println("");
							}
				}
		Set<UnifyPair> lefteq = left.stream()
				.filter(x -> (x.getLhsType() instanceof PlaceholderType && x.getPairOp() == PairOperator.EQUALSDOT))
				.collect(Collectors.toCollection(HashSet::new));
		Set<UnifyPair> righteq = right.stream()
				.filter(x -> (x.getLhsType() instanceof PlaceholderType && x.getPairOp() == PairOperator.EQUALSDOT))
				.collect(Collectors.toCollection(HashSet::new));
		Set<UnifyPair> leftle = left.stream()
				.filter(x -> ((x.getLhsType() instanceof PlaceholderType || x.getRhsType() instanceof PlaceholderType) 
						      && x.getPairOp() == PairOperator.SMALLERDOT))
				.collect(Collectors.toCollection(HashSet::new));
		Set<UnifyPair> rightle = right.stream()
				.filter(x -> ((x.getLhsType() instanceof PlaceholderType || x.getRhsType() instanceof PlaceholderType) 
						      && x.getPairOp() == PairOperator.SMALLERDOT))
				.collect(Collectors.toCollection(HashSet::new));
		Set<UnifyPair> leftlewc = left.stream()
				.filter(x -> ((x.getLhsType() instanceof PlaceholderType || x.getRhsType() instanceof PlaceholderType) 
						      && x.getPairOp() == PairOperator.SMALLERDOTWC))
				.collect(Collectors.toCollection(HashSet::new));
		Set<UnifyPair> rightlewc = right.stream()
				.filter(x -> ((x.getLhsType() instanceof PlaceholderType || x.getRhsType() instanceof PlaceholderType) 
						      && x.getPairOp() == PairOperator.SMALLERDOTWC))
				.collect(Collectors.toCollection(HashSet::new));
		//System.out.println(left.toString());
		//Fall 2
		//if (lefteq.iterator().next().getLhsType().getName().equals("AJO")) {
		//	System.out.print("");
		//}
		if (lefteq.size() == 1 && lefteq.iterator().next().getRhsType() instanceof ExtendsType && leftle.size() == 1  && righteq.size() == 0 && rightle.size() == 1) {
			return 1;
		}
		//Fall 2
		if (lefteq.size() == 0 && 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.size() == 0 && rightle.size() == 1) {
			return -1;
		}
		//Fall 3
		if (lefteq.size() == 0 && leftle.size() == 1 && righteq.size() == 1 && righteq.iterator().next().getRhsType() instanceof SuperType && rightle.size() == 1) {
			return 1;
		}
		//Fall 5
		if (lefteq.size() == 1 && leftle.size() == 0 && righteq.size() == 1 && rightle.size() == 1) {
			return -1;
		}
		//Fall 5
		if (lefteq.size() == 1 && leftle.size() == 1 && righteq.size() == 1 && rightle.size() == 0) {
			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.size() == 0 && rightle.size() == 0 && leftlewc.size() == 0 && rightlewc.size() ==0) {
			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; } );
			if (!si.isPresent()) return 0;
			else return si.get();
		}
		//Fall 1 und 4
		if (lefteq.size() >= 1 && righteq.size() >= 1 && (leftlewc.size() > 0 || rightlewc.size() > 0)) {
			if (lefteq.iterator().next().getLhsType().getName().equals("D")) 
				System.out.print("");
			//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().get().forEach(x -> uni.add((PlaceholderType) x.getLhsType(), x.getRhsType()));
				if (!lseq.getRhsType().getName().equals(rseq.getRhsType().getName()) 
					|| leftlewc.size() == 0 || rightlewc.size() == 0) return int_Unifier.getKey();
				else {
					Set <UnifyPair> lsleuni = leftlewc.stream().map(x -> uni.apply(x)).collect(Collectors.toCollection(HashSet::new));
					Set <UnifyPair> rsleuni = rightlewc.stream().map(x -> uni.apply(x)).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)).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)).reduce((x,y)-> { if (x == y) return x; else return 0; } );
					}
					if (!si.isPresent()) return 0;
					else return si.get();
				}
			} else {
				if (leftlewc.size() > 0) {
					Set<UnifyPair> subst;
					if (leftlewc.iterator().next().getLhsType() instanceof PlaceholderType) {
						subst = leftlewc.stream().map(x -> new UnifyPair(x.getLhsType(), x.getRhsType(), PairOperator.EQUALSDOT)).collect(Collectors.toCollection(HashSet::new));
					}
					else {
						subst = leftlewc.stream().map(x -> 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()));
					lseq = uni.apply(lseq);
				}
				else {
					Set<UnifyPair> subst;
					if (rightlewc.iterator().next().getLhsType() instanceof PlaceholderType) {
						subst = rightlewc.stream().map(x -> new UnifyPair(x.getLhsType(), x.getRhsType(), PairOperator.EQUALSDOT)).collect(Collectors.toCollection(HashSet::new));
					}
					else {
						subst = rightlewc.stream().map(x -> 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;	
	}		
}

