package de.dhbwstuttgart.typeinference.unify.model;

import java.util.*;


/**
 * The generic or non-generic parameters of a type e.g. <T> for List<T>
 * @author Florian Steurer
 */
public final class TypeParams implements Iterable<UnifyType>{
	/**
	 * The array which backs the type parameters.
	 */
	private final UnifyType[] typeParams;

	/**
	 * Hashcode calculation is expensive and must be cached.
	 */
	private final int hashCode;
	
	/**
	 * Creates a new set of type parameters.
	 * @param types The type parameters.
	 */
	public TypeParams(List<UnifyType> types){
		typeParams = new UnifyType[types.size()];
		for(int i=0;i<types.size();i++){
			typeParams[i] = types.get(i);
			if(types.get(i)==null)throw new NullPointerException();
		}
		
		// Hashcode calculation is expensive and must be cached.
		hashCode = Arrays.deepHashCode(typeParams);
	}
	
	/**
	 * Creates a new set of type parameters.
	 * @param types The type parameters.
	 */
	public TypeParams(UnifyType... types) {
		typeParams = types;
	
		// Hashcode calculation is expensive and must be cached.
		hashCode = Arrays.deepHashCode(typeParams);
	}
	
	/**
	 * True if every parameter in this object is a placeholder type, false otherwise.
	 */
	public boolean arePlaceholders() {
		return Arrays.stream(typeParams).allMatch(x -> x instanceof PlaceholderType);
	}
	
	/**
	 * Returns the number of the type parameters in this object.
	 * @return number of type parameters, always positive (including 0).
	 */
	public int size() {
		return typeParams.length;
	}
	
	/**
	 * Returns true if this object has size() of zero, false otherwise.
	 */
	public boolean empty() {
		return typeParams.length == 0;
	}
	
	/**
	 * Applies a unifier to every parameter in this object.
	 * @param unif The applied unifier.
	 * @return A new type parameter object, where the unifier was applied to every parameter.
	 */
	public TypeParams apply(Unifier unif) {
		UnifyType[] newParams = new UnifyType[typeParams.length];
		
		// If true, a type was modified and a new typeparams object has to be created.
		// Otherwise it is enough to return this object, since it is immutable
		// This reduced the needed TypeParams-Instances for the lambda14-Test from
		// 130.000 to 30.000 without a decrease in speed.
		boolean isNew = false; 
		
		for(int i = 0; i < typeParams.length; i++) {
			UnifyType newType = typeParams[i].apply(unif);
			newParams[i] = newType;
			if(!isNew && (newType.hashCode() != typeParams[i].hashCode() || !newType.equals(typeParams[i])))
				isNew = true;
		}
		
		if(!isNew)
			return this;
		return new TypeParams(newParams);
	}
	
	/**
	 * True if the PlaceholderType t is a parameter of this object, or if any parameter
	 * contains t (arbitrary depth of recursion), false otherwise.
	 */
	public boolean occurs(PlaceholderType t) {
		for(UnifyType p : typeParams)
			if(p instanceof PlaceholderType) {//PL 2018-01-31 dangeling else Problem { ... } eingefuegt.
				if(p.equals(t))
					return true;
			}
			else {
				if(p.getTypeParams().occurs(t))
					return true; }
		return false;
	}
	
	/**
	 * Returns the i-th parameter of this object.
	 */
	public UnifyType get(int i) {
		return typeParams[i];
	}
	
	/**
	 * Returns the parameters of this object.
	 * PL 2018-03-17
	 */
	public UnifyType[] get() {
		return typeParams;
	}
	
	/**
	 * Sets the the type t as the i-th parameter and returns a new object
	 * that equals this object, except for the i-th type.
	 */
	public TypeParams set(UnifyType t, int idx) {
		// Reduce the creation of new objects for less memory 
		// Reduced the needed instances of TypeParams in the lambda14-Test from
		// 150.000 to 130.000
		if(t.hashCode() == typeParams[idx].hashCode() && t.equals(typeParams[idx]))
			return this;
		UnifyType[] newparams = Arrays.copyOf(typeParams, typeParams.length);
		newparams[idx] = t; 
		return new TypeParams(newparams);
	}

	@Override
	public Iterator<UnifyType> iterator() {
		return Arrays.stream(typeParams).iterator();
	}
	
	@Override
	public int hashCode() {
		return hashCode;
	}
	
	@Override
	public boolean equals(Object obj) {
		if(!(obj instanceof TypeParams))
			return false;
		
		if(obj.hashCode() != this.hashCode())
			return false;
		
		TypeParams other = (TypeParams) obj;
		
		if(other.size() != this.size())
			return false;

		for(int i = 0; i < this.size(); i++){
			//if(this.get(i) == null)
				//System.out.print("s");
		}
		for(int i = 0; i < this.size(); i++)
			if(!(this.get(i).equals(other.get(i))))
				return false;
		
		return true;
	}
	
	@Override
	public String toString() {
		StringBuilder res = new StringBuilder();
		for(UnifyType t : typeParams)
			res.append(t).append(",");
		return "<" + res.substring(0, res.length()-1) + ">";
	}

	public Collection<? extends PlaceholderType> getInvolvedPlaceholderTypes() {
		ArrayList<PlaceholderType> ret = new ArrayList<>();
		for(UnifyType t : typeParams){
			ret.addAll(t.getInvolvedPlaceholderTypes());
		}
		return ret;
	}
}

