package de.dhbwstuttgart.typeinference.unify.cartesianproduct;

import de.dhbwstuttgart.typeinference.constraints.Constraint;
import de.dhbwstuttgart.typeinference.unify.TypeUnify2Task;
import de.dhbwstuttgart.typeinference.unify.TypeUnifyTask;
import de.dhbwstuttgart.typeinference.unify.UnifyContext;
import de.dhbwstuttgart.typeinference.unify.interfaces.IFiniteClosure;
import de.dhbwstuttgart.typeinference.unify.model.UnifyPair;
import de.dhbwstuttgart.util.Tuple;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

public class CovarianceCase extends VarianceCase {

  protected final int variance = -1;

  protected CovarianceCase(boolean isOderConstraint, TypeUnifyTask typeUnifyTask, UnifyContext context) {
    super(isOderConstraint, typeUnifyTask, context);
  }

  @Override
  public void selectNextData(
    TypeUnifyTask typeUnifyTask,
    List<Set<UnifyPair>> nextSetAsList,
    Optional<UnifyPair> optOrigPair
  ) {
    a = typeUnifyTask.oup.min(nextSetAsList.iterator());
    context.logger().debug(() -> "Min: a in " + variance + " " + a);
    if (this.isOderConstraint) {
      nextSetasListOderConstraints.add(((Constraint<UnifyPair>) a).getExtendConstraint());
    }
    context.logger().debug(() -> "nextSetasListOderConstraints -1: " + nextSetasListOderConstraints);
    nextSetAsList.remove(a);

    //Alle minimalen Elemente in nextSetasListRest bestimmen
    //nur für diese wird parallele Berechnung angestossen.
    Set<UnifyPair> finalA = a;
    nextSetasListRest = typeUnifyTask.oup.minElements(
      nextSetAsList.stream().filter(a_next -> typeUnifyTask.oup.compare(finalA, a_next) != -1).toList()
    );
  }


  @Override
  public CompletableFuture<ComputationResults> computeParallel(
    Set<Set<UnifyPair>> elems,
    Set<UnifyPair> eq,
    List<Set<Constraint<UnifyPair>>> oderConstraints,
    IFiniteClosure fc,
    int rekTiefe,
    Set<UnifyPair> methodSignatureConstraint,
    List<Set<UnifyPair>> nextSetAsList,
    Set<UnifyPair> sameEqSet,
    Set<Set<UnifyPair>> result,
    Set<Set<UnifyPair>> aParDef
  ) {
    Set<UnifyPair> newEqOrig = new HashSet<>(eq);
    Set<Set<UnifyPair>> newElemsOrig = new HashSet<>(elems);
    List<Set<Constraint<UnifyPair>>> newOderConstraintsOrig = new ArrayList<>(oderConstraints);
    newElemsOrig.add(a);

    /* FORK ANFANG */
    TypeUnify2Task forkOrig = new TypeUnify2Task(newElemsOrig, newEqOrig, newOderConstraintsOrig, a, fc, context, rekTiefe, methodSignatureConstraint);
    typeUnifyTask.addChildTask(forkOrig);
    // schedule compute() on another thread
    CompletableFuture<Set<Set<UnifyPair>>> forkOrigFuture = CompletableFuture.supplyAsync(forkOrig::compute, context.executor()).thenCompose(f -> f);
    CompletableFuture<ComputationResults> resultValues = forkOrigFuture.thenApply(
      (currentThreadResult) -> {
        forkOrig.context.logger().debug("final Orig -1");
        forkOrig.closeLogFile();
        return new ComputationResults(currentThreadResult);
      });

    //forks.add(forkOrig);
    if (typeUnifyTask.isExecutionCancelled()) {
      return resultValues;
    }
    /* FORK ENDE */

    context.logger().debug(() -> "a in " + variance + " " + a);
    context.logger().debug(() -> "nextSetasListRest: " + nextSetasListRest.toString());

    while (!nextSetasListRest.isEmpty()) {
      Set<UnifyPair> nSaL = nextSetasListRest.removeFirst();
      nextSetAsList.remove(nSaL);
      context.logger().debug(() -> "-1 RM" + nSaL.toString());

      if (!this.isOderConstraint) {
        //ueberpruefung ob zu a =. ty \in nSaL in sameEqSet ein Widerspruch besteht
        if (!sameEqSet.isEmpty() && !typeUnifyTask.checkNoContradiction(nSaL, sameEqSet, result)) {
          TypeUnifyTask.noShortendElements++;
          continue;
        }
      } else {
        nextSetasListOderConstraints.add(((Constraint<UnifyPair>) nSaL).getExtendConstraint());
      }
      Set<UnifyPair> newEq = new HashSet<>(eq);
      Set<Set<UnifyPair>> newElems = new HashSet<>(elems);
      List<Set<Constraint<UnifyPair>>> newOderConstraints = new ArrayList<>(oderConstraints);
      newElems.add(nSaL);
      TypeUnify2Task fork = new TypeUnify2Task(newElems, newEq, newOderConstraints, nSaL, fc, context, rekTiefe, new HashSet<>(methodSignatureConstraint));
      typeUnifyTask.addChildTask(fork);
      // schedule compute() on another thread
      CompletableFuture<Set<Set<UnifyPair>>> forkFuture = CompletableFuture.supplyAsync(fork::compute, context.executor()).thenCompose(f -> f);
      resultValues = resultValues.thenCombine(forkFuture,
        (prevResults, fork_res) -> {
          if (typeUnifyTask.isExecutionCancelled()) {
            return prevResults;
          }
          context.logger().debug(() -> "fork_res: " + fork_res.toString());
          context.logger().debug(() -> Boolean.valueOf((typeUnifyTask.isUndefinedPairSetSet(fork_res))).toString());
          prevResults.addForkResult(fork_res);
          if (!typeUnifyTask.isUndefinedPairSetSet(fork_res)) {
            aParDef.add(fork.getNextSetElement());
          }
          fork.context.logger().debug("final -1");
          fork.closeLogFile();
        return prevResults;
        }
      );

      if (typeUnifyTask.isExecutionCancelled()) {
        return resultValues;
      }
    }

    return resultValues;
  }


  @Override
  public void applyComputedResults(
    Set<Set<UnifyPair>> result,
    Set<Set<UnifyPair>> currentThreadResult,
    Set<UnifyPair> compResult,
    Set<UnifyPair> compRes
  ) {
    int resOfCompare = typeUnifyTask.oup.compare(compResult, compRes);
    if (resOfCompare == 1) {
      context.logger().debug(() -> "Geloescht result: " + result);
      result.clear();
      result.addAll(currentThreadResult);
    } else if (resOfCompare == 0) {
      result.addAll(currentThreadResult);
    } else if (resOfCompare == -1) {
      context.logger().debug(() -> "Geloescht currentThreadResult: " + currentThreadResult);
      //result = result;
    }
  }

  @Override
  public boolean eraseInvalidSets(
    int rekTiefe,
    Set<Set<UnifyPair>> aParDef,
    List<Set<UnifyPair>> nextSetAsList
  ) {

    // context.logger().info("");
    context.logger().debug(() -> "a: " + rekTiefe + " variance: " + variance + a.toString());
    context.logger().debug(() -> "aParDef: " + aParDef.toString());
    aParDef.add(a);
    Iterator<Set<UnifyPair>> aParDefIt = aParDef.iterator();
    if (this.isOderConstraint) {
      nextSetAsList.removeAll(nextSetasListOderConstraints);
      context.logger().debug(() -> "Removed: " + nextSetasListOderConstraints);
      nextSetasListOderConstraints = new ArrayList<>();
      while (aParDefIt.hasNext()) {
        Set<UnifyPair> a_new = aParDefIt.next();
        List<Set<UnifyPair>> greaterSetasList = typeUnifyTask.oup.greaterThan(a_new, nextSetAsList);

        //a_new muss hingefuegt werden, wenn es nicht vererbt ist, dann wird es spaeter wieder geloescht
        if (!((Constraint<UnifyPair>) a_new).isInherited()) {
          greaterSetasList.add(a_new);
        }
        List<Set<UnifyPair>> notInherited = greaterSetasList.stream()
          .filter(x -> !((Constraint<UnifyPair>) x).isInherited())
          .collect(Collectors.toCollection(ArrayList::new));
        List<Set<UnifyPair>> notErased = new ArrayList<>();

        //Wenn x nicht vererbt ist, beginnt beim naechstgroesseren Element die naechste Ueberladung
        notInherited.forEach(x -> {
          notErased.addAll(typeUnifyTask.oup.greaterEqThan(x, greaterSetasList));
        });

        //das kleineste Element ist das Element von dem a_new geerbt hat
        //muss deshalb geloescht werden
        Iterator<Set<UnifyPair>> notErasedIt = notErased.iterator();
        if (notErasedIt.hasNext()) {
          Set<UnifyPair> min = typeUnifyTask.oup.min(notErasedIt);
          notErased.remove(min);
          notErased.remove(((Constraint<UnifyPair>) min).getExtendConstraint());
        }

        List<Set<UnifyPair>> erased = new ArrayList<>(greaterSetasList);
        erased.removeAll(notErased);
        nextSetAsList.removeAll(erased);

        context.logger().debug(() -> "Removed: " + erased);

        context.logger().debug(() -> "Not Removed: " + nextSetAsList);

      }
    } else {
      while (aParDefIt.hasNext()) {
        //nextSetasListIt = nextSetasList.iterator(); Sollte eingefuegt werden PL 2020-04-28
        Set<UnifyPair> a_new = aParDefIt.next();
        List<Set<UnifyPair>> erased = typeUnifyTask.oup.greaterEqThan(a_new, nextSetAsList);

        nextSetAsList.removeAll(erased);

        context.logger().debug(() -> "Removed: " + erased);

        context.logger().debug(() -> "Not Removed: " + nextSetAsList);
      }
    }

    return false;
  }

}
