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.SerialList;
import de.dhbwstuttgart.server.packet.dataContainers.serialized.SerialMap;
import de.dhbwstuttgart.typeinference.unify.GuavaSetOperations;
import de.dhbwstuttgart.typeinference.unify.UnifyContext;

import de.dhbwstuttgart.typeinference.unify.model.FiniteClosure;
import java.util.*;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

public class ConstraintSet<A extends IConstraintElement> implements ISerializableData {
  Constraint<A> undConstraints = new Constraint<>();
  List<Set<Constraint<A>>> oderConstraints = new ArrayList<>();

  public void addUndConstraint(A p) {
    undConstraints.add(p);
  }

  public void addOderConstraint(Set<Constraint<A>> methodConstraints) {
    oderConstraints.add(methodConstraints);
  }

  public void addAllUndConstraint(Constraint<A> allUndConstraints) {
    undConstraints.addAll(allUndConstraints);
  }

  public void addAllOderConstraint(List<Set<Constraint<A>>> allOderConstraints) {
    this.oderConstraints.addAll(allOderConstraints);
  }

  public void addAll(ConstraintSet constraints) {
    this.addAllUndConstraint(constraints.undConstraints);
    this.addAllOderConstraint(constraints.oderConstraints);
  }

  @Override
  public String toString() {
    BinaryOperator<String> b = (x, y) -> x + y;
    return "\nUND:\n" + this.undConstraints.toString() +
      "ODER:" + this.oderConstraints.stream().reduce("", (x, y) -> x + "\n\t" + y, b) +
      "\n";
    //cartesianProduct().toString();
  }

  @Override
  public boolean equals(Object obj) {
    if (!(obj instanceof ConstraintSet<?> other)) return false;
    return Objects.equals(undConstraints, other.undConstraints)
      && Objects.equals(oderConstraints, other.oderConstraints);
  }

  @Override
  public int hashCode() {
    return Objects.hash(undConstraints, oderConstraints);
  }

  public Set<List<Constraint<A>>> cartesianProduct() {
    Set<Constraint<A>> toAdd = new HashSet<>();
    toAdd.add(undConstraints);
    List<Set<Constraint<A>>> allConstraints = new ArrayList<>();
    allConstraints.add(toAdd);
    allConstraints.addAll(oderConstraints);
    return new GuavaSetOperations().cartesianProduct(allConstraints);
  }

  public <B extends IConstraintElement> ConstraintSet<B> map(Function<? super A, ? extends B> o) {
    Hashtable<Constraint<A>, Constraint<B>> CSA2CSB = new Hashtable<>();
    ConstraintSet<B> ret = new ConstraintSet<>();
    ret.undConstraints = undConstraints.stream().map(o).collect(Collectors.toCollection(Constraint<B>::new));
    List<Set<Constraint<B>>> newOder = new ArrayList<>();
        /*
        for(Set<Constraint<A>> oderConstraint : oderConstraints){
        	oderConstraint.forEach(as -> {
            	Constraint<B> newConst = as.stream()
            			.map(o)
            			.collect(Collectors.toCollection(
            					() -> new Constraint<B> (as.isInherited())));                    							
            	CSA2CSB.put(as, newConst);} );
        }
        */

    for (Set<Constraint<A>> oderConstraint : oderConstraints) {
      newOder.add(
        oderConstraint.stream().map((Constraint<A> as) -> {

          Constraint<B> newConst = as.stream()
            .map(o)
            .collect(Collectors.toCollection((
              () -> new Constraint<B>(as.isInherited(),
                as.isImplemented(),
                (as.getExtendConstraint() != null)
                  ? as.getExtendConstraint().stream().map(o).collect(Collectors.toCollection(Constraint::new))
                  : null,
                as.getmethodSignatureConstraint().stream().map(o).collect(Collectors.toCollection(HashSet::new))))
            ));

          //CSA2CSB.put(as, newConst);

          return newConst;
                    	
                    	/*
                    	Constraint<B> bs = CSA2CSB.get(as);
                    	if (as.getExtendConstraint() != null) {
                    		bs.setExtendConstraint(CSA2CSB.get(as.getExtendConstraint()));
                    	}
                    	return bs;
                    	*/
        }).collect(Collectors.toSet())
      );
    }

    ret.oderConstraints = newOder;
    return ret;
  }

  public void forEach(Consumer<? super A> c) {
    undConstraints.stream().forEach(c);
    for (Set<Constraint<A>> oderConstraint : oderConstraints) {
      oderConstraint.parallelStream().forEach((Constraint<A> as) ->
        as.stream().forEach(c));
    }
  }

  public Set<A> getAll() {
    Set<A> ret = new HashSet<>(undConstraints);
    for (Set<Constraint<A>> oderConstraint : oderConstraints) {
      oderConstraint.parallelStream().forEach(ret::addAll);
    }
    return ret;
  }

  public List<Set<Constraint<A>>> getOderConstraints() {
    return oderConstraints;
  }

  public Set<A> getUndConstraints() {
    return undConstraints;
  }

  @Override
  public SerialMap toSerial(KeyStorage keyStorage) {
    SerialMap serialized = new SerialMap();
    serialized.put("undConstraints", undConstraints.toSerial(keyStorage));
    serialized.put("oderConstraints", SerialList.fromMapped(oderConstraints, oderConstraintSet ->
      SerialList.fromMapped(oderConstraintSet, oderConstraint ->
        oderConstraint.toSerial(keyStorage))
    ));
    return serialized;
  }

  public static <T extends IConstraintElement> ConstraintSet<T> fromSerial(SerialMap data, UnifyContext context, Class<T> target, KeyStorage keyStorage) {
    ConstraintSet<T> constraintSet = new ConstraintSet<>();

    constraintSet.undConstraints = Constraint.fromSerial(data.getUUID("undConstraints"), context, target, keyStorage);
    constraintSet.oderConstraints = data.getList("oderConstraints").assertListOfLists().stream()
      .map(oderConstraintSetData -> oderConstraintSetData.assertListOfUUIDs().stream()
        .map(oderConstraintData -> Constraint.fromSerial(oderConstraintData, context, target, keyStorage))
        .collect(Collectors.toSet())
      ).toList();

    return constraintSet;
  }

}
