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 reference type e.q. Integer or List<T>.
 * @author Florian Steurer
 *
 */
public class ReferenceType extends UnifyType {
	
	/**
	 * The buffered hashCode
	 */
	private final int hashCode;
	
	/**
	 * gibt an, ob der ReferenceType eine generische Typvariable ist
	 */
	private final boolean genericTypeVar;
	

	public <T> UnifyType accept(UnifyTypeVisitor<T> visitor, T ht) {
		return visitor.visit(this, ht);
	}
	
	public ReferenceType(String name, Boolean genericTypeVar) {
		super(name, new TypeParams());
		hashCode = 31 + 17 * typeName.hashCode() + 17 * typeParams.hashCode();
		this.genericTypeVar = genericTypeVar;
	}
	
	public ReferenceType(String name, UnifyType... params) {
		super(name, new TypeParams(params));
		hashCode = 31 + 17 * typeName.hashCode() + 17 * typeParams.hashCode();
		genericTypeVar = false;
	}
	
	public ReferenceType(String name, TypeParams params) {
		super(name, params);
		hashCode = 31 + 17 * typeName.hashCode() + 17 * typeParams.hashCode();
		genericTypeVar = false;
	}

	public boolean isGenTypeVar () { 
		return genericTypeVar;
	}
	
	@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) {
		TypeParams newParams = typeParams.apply(unif);
		
		if(newParams.hashCode() == typeParams.hashCode() && newParams.equals(typeParams))
			return this;
		
		return new ReferenceType(typeName, newParams);
	}
	
	@Override
	public UnifyType setTypeParams(TypeParams newTp) {
		if(newTp.hashCode() == typeParams.hashCode() && newTp.equals(typeParams))
			return this; // reduced the amount of objects created
		return new ReferenceType(typeName, newTp);
	}
	
	@Override
	public int hashCode() {
		return hashCode;
	}
	
	@Override
	public boolean equals(Object obj) {
		if(!(obj instanceof ReferenceType))
			return false;
		
		if(obj.hashCode() != this.hashCode())
			return false;
		
		ReferenceType other = (ReferenceType) obj;
		
		if(!other.getName().equals(typeName))
			return false;

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