package de.dhbwstuttgart.syntaxtree.type;

import de.dhbwstuttgart.parser.NullToken;
import de.dhbwstuttgart.parser.scope.JavaClassName;
import de.dhbwstuttgart.server.packet.dataContainers.ISerializableData;
import de.dhbwstuttgart.server.packet.dataContainers.KeyStorage;
import de.dhbwstuttgart.server.packet.dataContainers.serialized.ISerialNode;
import de.dhbwstuttgart.server.packet.dataContainers.serialized.SerialList;
import de.dhbwstuttgart.server.packet.dataContainers.serialized.SerialMap;
import de.dhbwstuttgart.syntaxtree.ASTVisitor;
import de.dhbwstuttgart.typeinference.result.ResultSetVisitor;
import de.dhbwstuttgart.typeinference.unify.UnifyContext;
import org.antlr.v4.runtime.Token;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;


public class RefType extends RefTypeOrTPHOrWildcardOrGeneric implements ISerializableData {
  protected final JavaClassName name;
  protected final List<RefTypeOrTPHOrWildcardOrGeneric> parameter;
  /**
   * Ist primitiveFlag auf true, muss beim Codegen dieser Reftype durch
   * den primitiven Datentyp ersetzt werden
   * <p>
   * Bsp: java.lang.Integer mit Flag wird dann zu [int]
   */
  boolean primitiveFlag = false; // TODO Should be final

  public RefType(JavaClassName fullyQualifiedName, Token offset) {
    this(fullyQualifiedName, new ArrayList<>(), offset);
  }

  public boolean isPrimitive() {
    return primitiveFlag;
  }

  @Override
  public String toString() {
    String params = "";
    if (parameter.size() > 0) {
      params += "<";
      Iterator<RefTypeOrTPHOrWildcardOrGeneric> it = parameter.iterator();
      while (it.hasNext()) {
        RefTypeOrTPHOrWildcardOrGeneric param = it.next();
        params += param.toString();
        if (it.hasNext()) params += ", ";
      }
      params += ">";
    }
    return this.name.toString() + params;
  }

  @Override
  public int hashCode() {
    return this.name.hashCode();//Nur den Name hashen. Sorgt fÃ¼r langsame, aber funktionierende HashMaps
  }

  public RefType(JavaClassName fullyQualifiedName, List<RefTypeOrTPHOrWildcardOrGeneric> parameter, Token offset) {
    this(fullyQualifiedName, parameter, offset, false);
  }

  public RefType(JavaClassName fullyQualifiedName, List<RefTypeOrTPHOrWildcardOrGeneric> parameter, Token offset, boolean primitiveFlag) {
    super(offset);
    this.name = (fullyQualifiedName);
    this.parameter = parameter;
    this.primitiveFlag = primitiveFlag;
  }

  public JavaClassName getName() {
    return name;
  }

  public List<RefTypeOrTPHOrWildcardOrGeneric> getParaList() {
    if (this.parameter == null) return new ArrayList<>();
    return this.parameter;
  }

  /**
   * Author: Jrg Buerle<br/>
   *
   * @return
   */
  public boolean equals(Object obj) {
    if (!(obj instanceof RefType refObj)) {
      return false;
    }

    if (!Objects.equals(this.name, refObj.name)) return false;
    boolean ret = true;

    //if(!(super.equals(obj))) PL 2020-03-12 muss vll. einkommentiert werden
    //	return false;

    if (parameter == null || parameter.size() == 0) {
      ret &= (refObj.getParaList() == null || refObj.getParaList().isEmpty());
    } else {
      if (refObj.getParaList() == null) {
        ret = false;
      } else if (parameter.size() != refObj.getParaList().size()) {
        ret = false;
      } else {
        for (int i = 0; i < parameter.size(); i++) {
          ret &= parameter.get(i).equals(refObj.getParaList().get(i));
        }
      }
    }
    return ret;

  }


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

  @Override
  public ISerialNode toSerial(KeyStorage keyStorage) {
    SerialMap serialized = new SerialMap();
    serialized.put("isPrimitive", this.primitiveFlag);
    serialized.put("name", this.name.toString());
    serialized.put("parameters", SerialList.fromMapped(this.parameter, param -> param.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 RefType fromSerial(SerialMap data, UnifyContext context) {
    return new RefType(
      new JavaClassName(data.getValue("name").getOf(String.class)),
      data.getList("parameters").assertListOfMaps().stream()
        .map(param -> RefTypeOrTPHOrWildcardOrGeneric.fromSerial(param, context))
        .toList(),
      new NullToken(),
      data.getValue("isPrimitive").getOf(Boolean.class)
    );
  }
}

