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.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;

public class InvarianceOrConstraintCase extends VarianceCase {

  // either for invariance or for oderConstraints
  protected final int variance = 2;

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

  @Override
  public void selectNextData(
    TypeUnifyTask typeUnifyTask,
    List<Set<UnifyPair>> nextSetAsList,
    Optional<UnifyPair> optOrigPair

  ) {
    a = nextSetAsList.removeFirst();
    //Fuer alle Elemente wird parallele Berechnung angestossen.
    nextSetasListRest = new ArrayList<>(nextSetAsList);
  }


  @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
  ) {
    context.logger().debug("var2einstieg");
    Set<TypeUnify2Task> forks = new HashSet<>();
    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, new HashSet<>(methodSignatureConstraint));
    typeUnifyTask.addChildTask(forkOrig);
    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 2");
      forkOrig.closeLogFile();
      return new ComputationResults(currentThreadResult);
    });

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

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

    //Fuer parallele Berechnung der Oder-Contraints wird methodSignature kopiert
    //und jeweils die methodSignature von a bzw. nSaL wieder gelöscht, wenn es keine Lösung ist.
    Set<UnifyPair> methodSignatureConstraintForParallel = new HashSet<>(methodSignatureConstraint);
    Set<UnifyPair> nSaL = a;

    while (!nextSetasListRest.isEmpty()) {
      methodSignatureConstraintForParallel.removeAll(((Constraint<UnifyPair>) nSaL).getmethodSignatureConstraint());
      nSaL = nextSetasListRest.removeFirst();
      nextSetAsList.remove(nSaL); //PL einkommentiert 20-02-03
      methodSignatureConstraintForParallel.addAll(((Constraint<UnifyPair>) nSaL).getmethodSignatureConstraint());
      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<>(methodSignatureConstraintForParallel));
      typeUnifyTask.addChildTask(fork);
      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;
        }
        prevResults.addForkResult(fork_res);
        fork.context.logger().debug("final 2");
        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
  ) {
    // Nothing
  }

  @Override
  public boolean eraseInvalidSets(
    int rekTiefe,
    Set<Set<UnifyPair>> aParDef,
    List<Set<UnifyPair>> nextSetAsList
  ) {
    // Nothing
    return false;
  }

}
