package de.dhbwstuttgart.syntaxtree.type;
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.PlaceholderRegistry;
import de.dhbwstuttgart.typeinference.unify.UnifyContext;

import de.dhbwstuttgart.parser.NullToken;
import de.dhbwstuttgart.syntaxtree.ASTVisitor;
import de.dhbwstuttgart.syntaxtree.factory.NameGenerator;
import de.dhbwstuttgart.typeinference.result.ResultSetVisitor;
import org.antlr.v4.runtime.Token;

/**
 * ReprÃ¯Â¿Â½sentiert einen Typparameter fÃ¯Â¿Â½r einen vom Programmierer nicht angegeben    
 * Typ. Jede TypePlaceholder besitzt einen eindeutigen Namen aus einem Namenspool  
 * und
 * ist in einer zentralen Registry, d.h. einer <code>Hashtable</code> abgelegt.
 * @author JÃ¯Â¿Â½rg BÃ¯Â¿Â½uerle
 * @version $Date: 2013/06/19 12:45:37 $
 */
public class TypePlaceholder extends RefTypeOrTPHOrWildcardOrGeneric implements ISerializableData
{
	private final String name;
	
	/**
     * variance shows the variance of the pair
     * 1: contravariant
     * -1 covariant
     * 0 invariant
     */
	private int variance = 0;
	
	/**
	 * isWildcardable gibt an, ob ein Wildcardtyp dem PlaceholderType zugeordnet werden darf 
	 */
	private boolean wildcardable = true;
	
	/*
     * Fuer Oder-Constraints:
     * orCons = 1: Receiver
     * orCons = 0: Argument oder kein Oder-Constraint 
     * orCons = -1: RetType
     */
    private byte orCons = 0;


    /**
     * Privater Konstruktor - Eine TypePlaceholder-Variable wird Ã¯Â¿Â½ber die
     * Factory-Methode <code>fresh()</code> erzeugt.
     * <br>Author: JÃ¯Â¿Â½rg BÃ¯Â¿Â½uerle
     */
    private TypePlaceholder(String name, Token offset, int variance, boolean wildcardable)
    {
        super(offset);
        this.name = name;
        this.variance = variance;
        this.wildcardable = wildcardable;
    }
    
    
    /**
     * @author Andreas Stadelmeier, a10023
     * Ruft die TypePlaceholder.fresh()-Methode auf.
     * Fügt zusÃ¤tzlich einen Replacementlistener hinzu.
     * @return
     */
    public static TypePlaceholder fresh(Token position){
		return new TypePlaceholder(NameGenerator.makeNewName(), position, 0, true);
    }

  public static TypePlaceholder fresh(Token position, PlaceholderRegistry placeholderRegistry){
    String newName = placeholderRegistry.generateFreshPlaceholderName();
    return new TypePlaceholder(newName, position, 0, true);
  }

    public static TypePlaceholder fresh(Token position, int variance, boolean wildcardable){
		return new TypePlaceholder(NameGenerator.makeNewName(), position, variance, wildcardable);
    }

    public static RefTypeOrTPHOrWildcardOrGeneric of(String name) {
        return new TypePlaceholder(name, new NullToken(),0, true);
    }


    /**
     * Author: JÃ¯Â¿Â½rg BÃ¯Â¿Â½uerle<br/>
     * @return
     */
    public boolean equals(Object obj)
    {
        if(obj instanceof TypePlaceholder){
            return this.toString().equals(((TypePlaceholder)obj).toString());
        	//return super.equals(obj);
        }
        else{
            return false;
        }
    }

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

    public String toString()
    {
        return "TPH " + this.name;
    }

    public String getName() {
        return name;
    }

    public void setVariance(int variance) {
    	this.variance= variance;
    }
    
    public int getVariance() {
    	return this.variance;
    }
    
    @Override
    public void accept(ASTVisitor visitor) {
        visitor.visit(this);
    }

    @Override
    public <A> A acceptTV(TypeVisitor<A> visitor) {
        return visitor.visit(this);
    }

    @Override
    public void accept(ResultSetVisitor visitor) {
        visitor.visit(this);
    }

    public void setOrCons(byte i) {
		orCons = i;
	}

	public byte getOrCons() {
		return orCons;
	}


	public Boolean getWildcardtable() {
		return wildcardable;
	}

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

    // 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 TypePlaceholder fromSerial(SerialMap data, UnifyContext context) {
    return new TypePlaceholder(
      data.getValue("name").getOf(String.class),
      new NullToken(),
      data.getValue("variance").getOf(Integer.class),
      data.getValue("wildcardable").getOf(Boolean.class)
    );
  }
}
