package de.dhbwstuttgart.typeinference.unify.model;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Set;

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

/**
 * A real function type in java.
 * @author Florian Steurer
 */
public class FunNType extends UnifyType {

	public <T> UnifyType accept(UnifyTypeVisitor<T> visitor, T ht) {
		return visitor.visit(this, ht);
	}
	
	/**
	 * Creates a FunN-Type with the specified TypeParameters.
	 */
	protected FunNType(TypeParams p) {
		super("Fun"+(p.size()-1)+"$$", p);
	}

	/**
	 * Creates a new FunNType.
	 * @param tp The parameters of the type.
	 * @return A FunNType.
	 * @throws IllegalArgumentException is thrown when there are to few type parameters or there are wildcard-types.
	 */
	public static FunNType getFunNType(TypeParams tp) throws IllegalArgumentException {
		if(tp.size() == 0)
			throw new IllegalArgumentException("FunNTypes need at least one type parameter");
		for(UnifyType t : tp)
			if(t instanceof WildcardType)
				throw new IllegalArgumentException("Invalid TypeParams for a FunNType: " + tp);
		return new FunNType(tp);
	}
	
	/**
	 * Returns the degree of the function type, e.g. 2 for FunN<Integer, Integer, Integer>.
	 */
	public int getN() {
		return typeParams.size()-1;
	}
	
	@Override
	public UnifyType setTypeParams(TypeParams newTp) {
		if(newTp.hashCode() == typeParams.hashCode() && newTp.equals(typeParams))
			return this;
		return getFunNType(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) {
		// TODO this bypasses the validation of the type parameters.
		// Wildcard types can be unified into FunNTypes.
		TypeParams newParams = typeParams.apply(unif);
		if(newParams.hashCode() == typeParams.hashCode() && newParams.equals(typeParams))
			return this;
		
		return new FunNType(newParams);
	}

	@Override
	public Boolean wrongWildcard() {
		return (new ArrayList<UnifyType>(Arrays.asList(getTypeParams()
				.get())).stream().filter(x -> (x instanceof WildcardType)).findFirst().isPresent());
	}
	
	@Override
	public int hashCode() {
		return 181 + typeParams.hashCode();
	}
	
	
	@Override
	public boolean equals(Object obj) {
		if(!(obj instanceof FunNType))
			return false;
		
		if(obj.hashCode() != this.hashCode())
			return false;
		
		FunNType other = (FunNType) obj;

		return other.getTypeParams().equals(typeParams);
	}
	
}
