package de.dhbwstuttgart.typeinference.unify.model;

import de.dhbwstuttgart.server.packet.dataContainers.ISerializableData;
import de.dhbwstuttgart.server.packet.dataContainers.KeyStorage;
import de.dhbwstuttgart.server.packet.dataContainers.serialized.SerialMap;
import de.dhbwstuttgart.typeinference.unify.UnifyContext;
import java.util.Set;

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

/**
 * An extends wildcard type "? extends T". 
 */
public final class ExtendsType extends WildcardType implements ISerializableData  {
	
	public <T> UnifyType accept(UnifyTypeVisitor<T> visitor, T ht) {
		return visitor.visit(this, ht);
	}
	
	/**
	 * Creates a new extends wildcard type.
	 * @param extendedType The extended type e.g. Integer in "? extends Integer"
	 */
	public ExtendsType(UnifyType extendedType) {
		super("? extends " + extendedType.getName(), extendedType);
	}

	/**
	 * The extended type e.g. Integer in "? extends Integer"
	 */
	public UnifyType getExtendedType() {
		return wildcardedType;
	}
	
	/**
	 * Sets the type parameters of the wildcarded type and returns a new extendstype that extends that type.
	 */
	@Override
	public UnifyType setTypeParams(TypeParams newTp) {
		UnifyType newType = wildcardedType.setTypeParams(newTp);
		if(newType == wildcardedType)
			return this; // Reduced the amount of objects created
		return new ExtendsType(wildcardedType.setTypeParams(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) {
		UnifyType newType = wildcardedType.apply(unif);
		if(newType.hashCode() == wildcardedType.hashCode() && newType.equals(wildcardedType))
			return this; // Reduced the amount of objects created
		return new ExtendsType(newType);
	}
	
	@Override
	public int hashCode() {
		/*
		 * It is important that the prime that is added is different to the prime added in hashCode() of SuperType.
		 * Otherwise ? extends T and ? super T have the same hashCode() for every Type T.
		 */
		return wildcardedType.hashCode() + 229; 
	}
	
	@Override
	public boolean equals(Object obj) {
		if(!(obj instanceof ExtendsType))
			return false;
		
		if(obj.hashCode() != this.hashCode())
			return false;
	
		ExtendsType other = (ExtendsType) obj;
		
	
		return other.getWildcardedType().equals(wildcardedType);
	}
	
	@Override
	public String toString() {
		return "? extends " + wildcardedType;
	}

	@Override
	public SerialMap toSerial(KeyStorage keyStorage) {
		SerialMap serialized = new SerialMap();
		serialized.put("wildcardedType", this.wildcardedType.toSerial(keyStorage));

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

	public static ExtendsType fromSerial(SerialMap data, UnifyContext context) {
		return new ExtendsType(
			UnifyType.fromSerial(data.getMap("wildcardedType"), context)
		);
	}
	
}
