package de.dhbwstuttgart.typeinference.unify.model;

import java.io.File;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;

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

/**
 * An unbounded placeholder type.
 * @author Florian Steurer
 */
public final class PlaceholderType extends UnifyType{
	
	/**
	 * Static list containing the names of all existing placeholders.
	 * Used for generating fresh placeholders.
	 */
	public static final ArrayList<String> EXISTING_PLACEHOLDERS = new ArrayList<String>();
	
	/**
	 * Prefix of auto-generated placeholder names.
	 */
	protected static String nextName = "gen_";
	
	/**
	 * Random number generator used to generate fresh placeholder name.
	 */
	protected static Random rnd = new Random(43558747548978L);
	
	/**
	 * True if this object was auto-generated, false if this object was user-generated.
	 */
	private final boolean IsGenerated;
	
	
	/**
	 * isWildcardable gibt an, ob ein Wildcardtyp dem PlaceholderType zugeordnet werden darf 
	 */
	private boolean wildcardable = true;  
	
	/**
	 * is innerType gibt an, ob der Type des PlaceholderType innerhalb eines Typkonstruktorsverwendet wird
	 */
	private boolean innerType = false;  
	
	/**
     * variance shows the variance of the pair
     * -1: contravariant
     * 1 covariant
     * 0 invariant
     * PL 2018-03-21
     */
    private int variance = 0;
	
	/**
	 * Creates a new placeholder type with the specified name.
	 */
	public PlaceholderType(String name) {
		super(name, new TypeParams());
		EXISTING_PLACEHOLDERS.add(name); // Add to list of existing placeholder names
		IsGenerated = false; // This type is user generated
	}
	
	/**
	 * Creates a new placeholdertype 
	 * @param isGenerated true if this placeholder is auto-generated, false if it is user-generated.
	 */
	protected PlaceholderType(String name, boolean isGenerated) {
		super(name, new TypeParams());
		EXISTING_PLACEHOLDERS.add(name); // Add to list of existing placeholder names
		IsGenerated = isGenerated;
	}
	
	public <T> UnifyType accept(UnifyTypeVisitor<T> visitor, T ht) {
		return visitor.visit(this, ht);
	}
	
	/**
	 * Creates a fresh placeholder type with a name that does so far not exist.
	 * A user could later instantiate a type using the same name that is equivalent to this type.
	 * @return A fresh placeholder type.
	 */
	public synchronized static PlaceholderType freshPlaceholder() {
		String name = nextName + (char) (rnd.nextInt(22) + 97); // Returns random char between 'a' and 'z'
		// Add random chars while the name is in use.
		while(EXISTING_PLACEHOLDERS.contains(name)) {
			name += (char) (rnd.nextInt(22) + 97); // Returns random char between 'a' and 'z'
		}
		return new PlaceholderType(name, true);
	}
	
	
	/**
	 * True if this placeholder is auto-generated, false if it is user-generated.
	 */
	public boolean isGenerated() {
		return IsGenerated;
	}

	public void setVariance(int v) {
		variance = v;
	}
	
	public int getVariance() {
		return variance;
	}
	
	public void reversVariance() {
		if (variance == 1) {
			setVariance(-1);
		} else {
	    if (variance == -1) {
			setVariance(1);
		}}
	}
	
	public Boolean isWildcardable() {
		return wildcardable;
	}
	public void disableWildcardtable() {
		wildcardable = false;
	}
	
	public void enableWildcardtable() {
		wildcardable = true;
	}
	
	public void setWildcardtable(Boolean wildcardable) {
		this.wildcardable = wildcardable;
	}
	
	public Boolean isInnerType() {
		return innerType;
	}
	
	public void setInnerType(Boolean innerType) {
		this.innerType = innerType;
	}
	
	@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
	public UnifyType setTypeParams(TypeParams newTp) {
		return this; // Placeholders never have params.
	}
	
	@Override
	public int hashCode() {
		return typeName.hashCode();
	}
	
	@Override
	UnifyType apply(Unifier unif) {
		if(unif.hasSubstitute(this)) {
			UnifyType ret = unif.getSubstitute(this);
			//PL 2018-05-17 Auskommentierung muesste korrekt sein,
			//bereits in JavaTXComplier Variancen gesetzt werden.
			//ret.accept(new distributeVariance(), this.getVariance());
			return ret;
		}
		return this;
	}
	
	@Override
	public boolean equals(Object obj) {
		if(!(obj instanceof PlaceholderType))
			return false;
		
		return ((PlaceholderType) obj).getName().equals(typeName);
	}
	

	@Override
	public Collection<PlaceholderType> getInvolvedPlaceholderTypes() {
		ArrayList<PlaceholderType> ret = new ArrayList<>();
		ret.add(this);
		return ret;
	}
}
