package de.dhbwstuttgart.typeinference.unify.model;

import java.util.HashMap;
import java.util.Set;

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

/**
 * A super wildcard type e.g. ? super Integer.
 * @author Florian Steurer
 */
public final class SuperType extends WildcardType {
	
	public <T> UnifyType accept(UnifyTypeVisitor<T> visitor, T ht) {
		return visitor.visit(this, ht);
	}
	
	/**
	 * Creates a new instance "? extends superedType"
	 * @param superedType The type that is supered e.g. Integer in "? super Integer"
	 */
	public SuperType(UnifyType superedType) {
		super("? super " + superedType.getName(), superedType);
	}
	
	/**
	 * The type that is supered e.g. Integer in "? super Integer"
	 */
	public UnifyType getSuperedType() {
		return wildcardedType;
	}
	
	@Override
	public String toString() {
		return "? super " + wildcardedType;
	}
	
	/**
	 * Sets the type parameters of the wildcarded type and returns a new supertype that supers that type.
	 */
	@Override
	public UnifyType setTypeParams(TypeParams newTp) {
		UnifyType newType = wildcardedType.setTypeParams(newTp);
		if(newType == wildcardedType)
			return this; // Reduced the amount of objects created
		return new SuperType(wildcardedType.setTypeParams(newTp));
	}
	
	@Override
	Set<UnifyType> smArg(IFiniteClosure fc, Set<UnifyType> fBounded) {
		return fc.smArg(this, fBounded);
	}

	@Override
	Set<UnifyType> grArg(IFiniteClosure fc, Set<UnifyType> fBounded) {
		return fc.grArg(this, fBounded);
	}
	
	@Override
	UnifyType apply(Unifier unif) {
		UnifyType newType = wildcardedType.apply(unif);
		if(newType.hashCode() == wildcardedType.hashCode() && newType.equals(wildcardedType))
			return this; // Reduced the amount of objects created
		return new SuperType(newType);
	}
	
	@Override
	public int hashCode() {
		/*
		 * It is important that the prime that is added is different to the prime added in hashCode() of ExtendsType.
		 * Otherwise ? extends T and ? super T have the same hashCode() for every Type T.
		 */
		return wildcardedType.hashCode() + 3917;
	}
	
	@Override
	public boolean equals(Object obj) {
		if(!(obj instanceof SuperType))
			return false;
		
		if(obj.hashCode() != this.hashCode())
			return false;
		
		SuperType other = (SuperType) obj;
		return other.getSuperedType().equals(wildcardedType);
	}
}
