package de.dhbwstuttgart.typeinference.constraints;

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.io.Serializable;
import java.util.HashMap;
import java.util.Map;

import de.dhbwstuttgart.parser.SourceLoc;
import de.dhbwstuttgart.syntaxtree.type.RefTypeOrTPHOrWildcardOrGeneric;
import de.dhbwstuttgart.syntaxtree.type.TypePlaceholder;
import de.dhbwstuttgart.typeinference.unify.model.PairOperator;


public class Pair implements Serializable, IConstraintElement, ISerializableData {
  public final RefTypeOrTPHOrWildcardOrGeneric TA1;
  public final RefTypeOrTPHOrWildcardOrGeneric TA2;

  private SourceLoc location;

  private PairOperator eOperator = PairOperator.SMALLER;
  private boolean noUnification = false;


  private Pair(RefTypeOrTPHOrWildcardOrGeneric TA1, RefTypeOrTPHOrWildcardOrGeneric TA2) {
    this.TA1 = TA1;
    this.TA2 = TA2;
    if (TA1 == null || TA2 == null)
      throw new NullPointerException();
    eOperator = PairOperator.SMALLER;
  }

  public Pair(RefTypeOrTPHOrWildcardOrGeneric TA1, RefTypeOrTPHOrWildcardOrGeneric TA2, PairOperator eOp) {
    // Konstruktor
    this(TA1, TA2);
    this.eOperator = eOp;
  }

  public Pair(RefTypeOrTPHOrWildcardOrGeneric TA1, RefTypeOrTPHOrWildcardOrGeneric TA2, PairOperator e0p, SourceLoc location) {
    this(TA1, TA2, e0p);
    this.location = location;
  }

  public Pair(RefTypeOrTPHOrWildcardOrGeneric TA1, RefTypeOrTPHOrWildcardOrGeneric TA2, PairOperator eOp, boolean noUnification) {
    // Konstruktor
    this(TA1, TA2);
    this.eOperator = eOp;
    this.noUnification = noUnification;
  }

  public SourceLoc getLocation() {
    return this.location;
  }

  public String toString() {
    // otth: Gibt ein Paar als String aus --> zum Debuggen und Vergleichen
    String strElement1 = "NULL";
    String strElement2 = "NULL";
    String Operator = "<.";

    if (TA1 != null)
      strElement1 = TA1.toString();

    if (TA2 != null)
      strElement2 = TA2.toString();
        
       /* PL ausskommentiert 2018-05-24
        if(OperatorEqual())
        	Operator = "=";
        if(OperatorSmaller())
        	Operator = "<.";
        if(OperatorSmallerExtends())
        	Operator = "<?";
        	*/

    return "\n(P: " + strElement1 + " " + eOperator.toString() + " " + strElement2 + ")";

    /*- Equals: " + bEqual*/
  }

  /**
   * <br/>Author: JÃ¯Â¿Â½rg BÃ¯Â¿Â½uerle
   *
   * @param obj
   * @return
   */
  public boolean equals(Object obj) {
    return (
      (obj instanceof Pair pairObj) &&
        pairObj.TA1.equals(this.TA1) &&
        pairObj.TA2.equals(this.TA2)
    );
  }

  /**
   * Author: Arne LÃ¼dtke<br/>
   * Abfrage, ob Operator vom Typ Equal ist.
   */
  public boolean OperatorEqual() {
    return eOperator == PairOperator.EQUALSDOT;
  }

  /**
   * Author: Arne LÃ¼dtke<br/>
   * Abfrage, ob Operator vom Typ Smaller ist.
   */
  public boolean OperatorSmaller() {
    return eOperator == PairOperator.SMALLER;
  }

  /**
   * Author: Arne LÃ¼dtke<br/>
   * Abfrage, ob Operator vom Typ SmallerExtends ist.
   */
  public boolean OperatorSmallerExtends() {
    return eOperator == PairOperator.SMALLERDOTWC;
  }

  /**
   * Author: Arne LÃ¼dtke<br/>
   * Gibt den Operator zurÃ¼ck.
   */
  public PairOperator GetOperator() {
    return eOperator;
  }

  public boolean OperatorSmallerDot() {
    return eOperator == PairOperator.SMALLERDOT;
  }


  static public Map<String, TypePlaceholder> generateTPHMap(ConstraintSet<Pair> constraints) {
    HashMap<String, TypePlaceholder> ret = new HashMap<>();
    constraints.map((Pair p) -> {
      if (p.TA1 instanceof TypePlaceholder) {
        ret.put(((TypePlaceholder) p.TA1).getName(), (TypePlaceholder) p.TA1);
      }
      if (p.TA2 instanceof TypePlaceholder) {
        ret.put(((TypePlaceholder) p.TA2).getName(), (TypePlaceholder) p.TA2);
      }
      return null;
    });
    return ret;
  }

  @Override
  public SerialMap toSerial(KeyStorage keyStorage) {
    // because toString() will output TA1 and TA2 recursively, we can ignore potential infinite recursion here too
    SerialMap serialized = new SerialMap();
    serialized.put("ta1", this.TA1.toSerial(keyStorage));
    serialized.put("ta2", this.TA2.toSerial(keyStorage));
    serialized.put("op", this.eOperator.toString());
    serialized.put("noUnification", this.noUnification ? 1 : 0);
    serialized.put("location", this.location == null ? null : this.location.toSerial(keyStorage));
    return serialized;
  }

  public static Pair fromSerial(SerialMap data, UnifyContext context) {
    String op = data.getValue("op").getOf(String.class);
    SerialMap ta1 = data.getMap("ta1");
    SerialMap ta2 = data.getMap("ta2");
    boolean noUnification = data.getValue("noUnification").getOf(Integer.class) == 1;
    SerialMap location = data.getMapOrNull("location");

    var pair = new Pair(
      RefTypeOrTPHOrWildcardOrGeneric.fromSerial(ta1, context),
      RefTypeOrTPHOrWildcardOrGeneric.fromSerial(ta2, context),
      PairOperator.fromString(op),
      noUnification
    );
    if (location != null) pair.location = SourceLoc.fromSerial(location);
    return pair;
  }
}
// ino.end
