package de.dhbwstuttgart.typeinference.unify.model;

import de.dhbwstuttgart.server.packet.dataContainers.KeyStorage;
import de.dhbwstuttgart.server.packet.dataContainers.serialized.SerialMap;
import de.dhbwstuttgart.typeinference.unify.PlaceholderRegistry;
import de.dhbwstuttgart.typeinference.unify.UnifyContext;
import de.dhbwstuttgart.typeinference.unify.interfaces.IFiniteClosure;
import de.dhbwstuttgart.typeinference.unify.interfaces.UnifyTypeVisitor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;

/**
 * An unbounded placeholder type.
 * @author Florian Steurer
 */
public final class PlaceholderType extends UnifyType{

	/**
	 * 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;

	/*
	 * Fuer Oder-Constraints:
	 * orCons = 1: Receiver
	 * orCons = 0: Argument oder kein Oder-Constraint
	 * orCons = -1: RetType
	 */
	private byte orCons = 0;

	/**
	 * Creates a new placeholder type with the specified name.
	 */
	public PlaceholderType(String name, PlaceholderRegistry placeholderRegistry) {
		super(name, new TypeParams());
		placeholderRegistry.addPlaceholder(name); // Add to list of existing placeholder names
		IsGenerated = false; // This type is user generated
	}


	public PlaceholderType(String name, int variance, PlaceholderRegistry placeholderRegistry) {
		super(name, new TypeParams());
		placeholderRegistry.addPlaceholder(name); // Add to list of existing placeholder names
		IsGenerated = false; // This type is user generated
		this.variance = variance;
	}

	/**
	 * 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, PlaceholderRegistry placeholderRegistry) {
		super(name, new TypeParams());
		placeholderRegistry.addPlaceholder(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 from the chars A-Z.
	 * A user could later instantiate a type using the same name that is equivalent to this type.
	 * @return A fresh placeholder type.
	 */
	public static PlaceholderType freshPlaceholder(PlaceholderRegistry placeholderRegistry) {
		var name = placeholderRegistry.generateFreshPlaceholderName();
		return new PlaceholderType(name, true, placeholderRegistry);
	}


	/**
	 * 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 void setOrCons(byte i) {
		orCons = i;
	}

	public byte getOrCons() {
		return orCons;
	}

	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;
	}


	@Override
	public SerialMap toSerial(KeyStorage keyStorage) {
		SerialMap serialized = new SerialMap();
		serialized.put("name", this.typeName);
		// Placeholders never make use of the typeParams
		serialized.put("isGenerated", IsGenerated);
		serialized.put("wildcardable", wildcardable);
		serialized.put("isInnerType", innerType);
		serialized.put("variance", variance);
		serialized.put("orCons", orCons);

		// create the wrapper and put this as the object
		var serializedWrapper = super.toSerial(keyStorage);
		serializedWrapper.put("object", serialized);
		return serializedWrapper;
	}

	public static PlaceholderType fromSerial(SerialMap data, UnifyContext context) {
		var name 				= data.getValue("name").getOf(String.class);
		var isGenerated = data.getValue("isGenerated").getOf(Boolean.class);
		var wildcardable = data.getValue("wildcardable").getOf(Boolean.class);
		var isInnerType = data.getValue("isInnerType").getOf(Boolean.class);
		var variance 		= data.getValue("variance").getOf(Integer.class);
		var orCons 			= data.getValue("orCons").getOf(Number.class).byteValue();

		var placeholderType = new PlaceholderType(name, isGenerated, context.placeholderRegistry());
		placeholderType.setWildcardtable(wildcardable);
		placeholderType.setInnerType(isInnerType);
		placeholderType.setVariance(variance);
		placeholderType.setOrCons(orCons);

		return placeholderType;
	}
}
