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.server.packet.dataContainers.serialized.SerialValue;
import de.dhbwstuttgart.typeinference.unify.UnifyContext;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;

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

/**
 * Represents a java type.
 * @author Florian Steurer
 */
public abstract class UnifyType implements ISerializableData {
	
	/**
	 * The name of the type e.q. "Integer", "? extends Integer" or "List" for (List<T>)
	 */
	protected final String typeName;
	
	/**
	 * The type parameters of the type.
	 */
	protected final TypeParams typeParams;
	
	/**
	 * Creates a new instance
	 * @param name Name of the type (e.q. List for List<T>, Integer or ? extends Integer)
	 * @param p Parameters of the type (e.q. <T> for List<T>)
	 */
	protected UnifyType(String name, TypeParams p) {
		typeName = name;
		typeParams = p;
	}
	

	abstract public <T> UnifyType accept(UnifyTypeVisitor<T> visitor, T ht); 
	
	/**
	 * Returns the name of the type.
	 * @return The name e.q. List for List<T>, Integer or ? extends Integer
	 */
	public String getName() {
		return typeName;
	}
	
	/**
	 * The parameters of the type.
	 * @return Parameters of the type, e.q. <T> for List<T>.
	 */
	public TypeParams getTypeParams() {
		return typeParams;
	}
	
	/**
	 * Returns a new type that equals this type except for the type parameters.
	 * @param newTp The type params of the new type.
	 * @return A new type object.
	 */
	public abstract UnifyType setTypeParams(TypeParams newTp);
	
	/**
	 * Implementation of the visitor-pattern. Returns the set of smArg
	 * by calling the most specific overload in the FC.
	 * @param fc The FC that is called.
	 * @return The set that is smArg(this)
	 */
	abstract Set<UnifyType> smArg(IFiniteClosure fc, Set<UnifyType> fBounded);
	
	/**
	 * Implementation of the visitor-pattern. Returns the set of grArg
	 * by calling the most specific overload in the FC.
	 * @param fc The FC that is called.
	 * @return The set that is grArg(this)
	 */
	abstract Set<UnifyType> grArg(IFiniteClosure fc, Set<UnifyType> fBounded);
	
	/**
	 * Applies a unifier to this object.
	 * @param unif The unifier
	 * @return A UnifyType, that may or may not be a new object, that has its subtypes substituted.
	 */
	abstract UnifyType apply(Unifier unif);
	
	@Override
	public String toString() {
		StringBuilder params = new StringBuilder();
		if(typeParams.size() != 0) {
			for(UnifyType param : typeParams)
				params.append(param.toString()).append(",");
			params = new StringBuilder("<" + params.substring(0, params.length() - 1) + ">");
		}
		
		return typeName + params;
	}

	public Collection<PlaceholderType> getInvolvedPlaceholderTypes() {
    return new ArrayList<>(typeParams.getInvolvedPlaceholderTypes());
	}
	
	public boolean wrongWildcard() {//default
		return false;
	}

	@Override
	public int hashCode() {
		return this.toString().hashCode();
	}

	@Override
	public boolean equals(Object obj) {
		if(obj == null) return false;
		return this.toString().equals(obj.toString());
	}

	public SerialMap toSerial(KeyStorage keyStorage) {
		SerialMap serialized = new SerialMap();
		String type = switch (this) {
			case FunInterfaceType _ -> "funi";
			case ReferenceType _ -> "ref";
      case ExtendsType _ -> "ext";
      case SuperType _ -> "sup";
      case FunNType _ -> "funn";
      case PlaceholderType _ -> "tph";
      default -> throw new RuntimeException("No type defined for UnifyType of class " + this.getClass().getName());
    };

    serialized.put("type", type);
		// we only insert null for the object and expect the child classes to call this and override the value with themselves
		serialized.put("object", SerialValue.NULL);
		return serialized;
	}

	public static UnifyType fromSerial(SerialMap data, UnifyContext context) {
		var type = data.getValue("type").getOf(String.class);
		var object = data.getMap("object");

		return switch (type) {
			case "funi" -> FunInterfaceType.fromSerial(object, context);
			case "ref" -> ReferenceType.fromSerial(object, context);
			case "ext" -> ExtendsType.fromSerial(object, context);
			case "sup" -> SuperType.fromSerial(object, context);
			case "funn" -> FunNType.fromSerial(object, context);
			case "tph" -> PlaceholderType.fromSerial(object, context);
			default -> throw new RuntimeException("Could not unserialize class of unhandled type " + type);
		};
	}
}
