package de.dhbwstuttgart.typeinference.unify.model;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;


/**
 * A pair which contains two types and an operator, e.q. (Integer <. a).
 * @author Florian Steurer
 */
public class UnifyPair {
	
	/**
	 * The type on the left hand side of the pair.
	 */
	private final UnifyType lhs;
	
	/**
	 * The type on the right hand side of the pair.
	 */
	private final UnifyType rhs;
	
	/**
	 * The operator that determines the relation between the left and right hand side type.
	 */
	private PairOperator pairOp;
	
	/** wieder loesecn wird nicht mehr benoetigt PL 2018-03-31
     * variance shows the variance of the pair
     * -1: contravariant
     * 1 covariant
     * 0 invariant
     * PL 2018-03-21
     */
    private byte variance = 0;
    
    private boolean undefinedPair = false;
    
    /**
     * Unifier/substitute that generated this pair
     * PL 2018-03-15
     */
    private Set<UnifyPair> substitution;
    
    /**
     * Base on which the the unifier is applied
     * PL 2018-03-15
     */
    private UnifyPair basePair;
    
    /**
     * For pairs a <. Theta generated in the rule reduceTphSup
     * to store the f-Bouned Elements to avoid endless recursion 
     * PL 2018-03-15
     */
    private Set<UnifyType> fBounded = new HashSet<>();
	
	private final int hashCode;
	
	/**
	 * Creates a new instance of the pair.
	 * @param lhs The type on the left hand side of the pair.
	 * @param rhs The type on the right hand side of the pair.
	 * @param op The operator that determines the relation between the left and right hand side type.
	 */
	public UnifyPair(UnifyType lhs, UnifyType rhs, PairOperator op) {
		this.lhs = lhs;
		this.rhs = rhs;
		pairOp = op;
		substitution = new HashSet<>();
		
		// Caching hashcode
		hashCode = 17 + 31 * lhs.hashCode() + 31 * rhs.hashCode() + 31 * pairOp.hashCode();
	}	
	
	public UnifyPair(UnifyType lhs, UnifyType rhs, PairOperator op, Set<UnifyPair> uni, UnifyPair base) {
		this.lhs = lhs;
		this.rhs = rhs;
		pairOp = op;
		substitution = uni;
		basePair = base;
		this.variance = variance;
		
		
		// Caching hashcode
		hashCode = 17 + 31 * lhs.hashCode() + 31 * rhs.hashCode() + 31 * pairOp.hashCode();
	}	
	
	public UnifyPair(UnifyType lhs, UnifyType rhs, PairOperator op, Set<UnifyPair> uni, UnifyPair base, Set<UnifyType> fBounded) {
		this(lhs, rhs, op, uni, base);
		
		this.fBounded = fBounded;
	}
		
	/**
	 * Returns the type on the left hand side of the pair.
	 */
	public UnifyType getLhsType() {
		return lhs;
	}
	
	/**
	 * Returns the type on the right hand side of the pair.
	 */
	public UnifyType getRhsType() {
		return rhs;
	}
	
	/**
	 * Returns the operator that determines the relation between the left and right hand side type.
	 */
	public PairOperator getPairOp() {
		return pairOp;
	}
	
	public void setPairOp(PairOperator po) {
		pairOp = po;
	}
	
	public void addSubstitutions(Set<UnifyPair> sup) {
		substitution.addAll(sup);
	}
	
	public byte getVariance() {
		return variance;
	}
	
	public void setVariance(byte v) {
		variance = v;
	}
	
	public void setUndefinedPair() {
		undefinedPair = true;
	}
	public Set<UnifyPair> getSubstitution() {
		return new HashSet<>(substitution);
	}
	
	public UnifyPair getBasePair() {
		return basePair;
	}
	public boolean isUndefinedPair() {
		return undefinedPair;
	}
	
	public Set<UnifyPair> getAllSubstitutions () {
		Set<UnifyPair> ret = new HashSet<>();
		ret.addAll(new ArrayList<>(getSubstitution()));
		if (basePair != null) {
			ret.addAll(new ArrayList<>(basePair.getAllSubstitutions()));
		}
		return ret;
	}
	
	public Set<UnifyPair> getAllBases () {
		Set<UnifyPair> ret = new HashSet<>();
		if (basePair != null) {
			ret.add(getBasePair());
			ret.addAll(basePair.getAllBases());
		}
		return ret;
	}
	
	public UnifyPair getGroundBasePair () {
		if (basePair == null) {
			return this;
		}
		if (basePair.getBasePair() == null) {
			return basePair;
		}
		else {
			return basePair.getGroundBasePair();
		}
	}
	
	/**
	 * wenn in einem  Paar bestehend aus 2 Typvariablen eine nicht wildcardtable ist,
	 * so beide auf nicht wildcardtable setzen
	 */
	public void disableCondWildcards() {
		if (lhs instanceof PlaceholderType && rhs instanceof PlaceholderType
			&& (!((PlaceholderType)lhs).isWildcardable() || !((PlaceholderType)rhs).isWildcardable()))
		{
			((PlaceholderType)lhs).disableWildcardtable();
			((PlaceholderType)rhs).disableWildcardtable();
		}
			
	}
	
	public Boolean wrongWildcard() {
		return lhs.wrongWildcard() || rhs.wrongWildcard();
	}
	
	public Set<UnifyType> getfBounded() {
		return this.fBounded;
	}
	
	@Override
	public boolean equals(Object obj) {
		if(!(obj instanceof UnifyPair))
			return false;
		
		if(obj.hashCode() != this.hashCode())
			return false;

		UnifyPair other = (UnifyPair) obj;
		
		if (isUndefinedPair()) {
			if (other.getBasePair() != basePair || (other.getBasePair() == null && basePair == null)) { 
				return false;
			}
			if (!other.getBasePair().equals(basePair) ||
				!other.getAllSubstitutions().equals(getAllSubstitutions())) {
				return false;
			}
		}
		
		return other.getPairOp() == pairOp 
				&& other.getLhsType().equals(lhs) 
				&& other.getRhsType().equals(rhs);
	}

	@Override
	public int hashCode() {
		return hashCode;
	}
	
	@Override
	public String toString() {
		String ret = "";
		if (lhs instanceof PlaceholderType) {
			ret = new Integer(((PlaceholderType)lhs).getVariance()).toString() + " " + ((PlaceholderType)lhs).isInnerType() 
					+ " " + ((PlaceholderType)lhs).isWildcardable();
		}
		if (rhs instanceof PlaceholderType) {
			ret = ret + ", " + new Integer(((PlaceholderType)rhs).getVariance()).toString() + " " + ((PlaceholderType)rhs).isInnerType()
					+ " " + ((PlaceholderType)rhs).isWildcardable();
		}
		return "(" + lhs + " " + pairOp + " " + rhs + ", " + ret + ")"; //+ ", [" + getfBounded().toString()+ "])";
	}

	/*
	public List<? extends PlaceholderType> getInvolvedPlaceholderTypes() {
		ArrayList<PlaceholderType> ret = new ArrayList<>();
		ret.addAll(lhs.getInvolvedPlaceholderTypes());
		ret.addAll(rhs.getInvolvedPlaceholderTypes());
		return ret;
	}
	*/
}


