package de.dhbwstuttgart.typeinference.unify.cartesianproduct;

import de.dhbwstuttgart.typeinference.constraints.Constraint;
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 abstract class VarianceCase {

  public static VarianceCase createFromVariance(int variance, boolean isOderConstraint, TypeUnifyTask typeUnifyTask, UnifyContext context) {
    return switch (variance) {
      case 0    -> new UnknownVarianceCase(isOderConstraint, typeUnifyTask, context);
      case 1    -> new ContravarianceCase(isOderConstraint, typeUnifyTask, context);
      case -1   -> new CovarianceCase(isOderConstraint, typeUnifyTask, context);
      case 2    -> new InvarianceOrConstraintCase(isOderConstraint, typeUnifyTask, context);
      default   -> throw new RuntimeException("Invalid variance: " + variance);
    };
  }

  protected final boolean isOderConstraint;
  protected final TypeUnifyTask typeUnifyTask;
  protected final UnifyContext context;

  /**
   * Aktueller Fall
   */
  public Set<UnifyPair> a;

  /**
   * Liste der Faelle für die parallele Verarbeitung
   * Enthaelt Elemente, die nicht in Relation zu aktuellem Fall in der
   * Variablen a stehen. Diese muesse auf alle Faelle bearbeitet werden,
   * Deshalb wird ihre Berechnung parallel angestossen.
   */
  public List<Set<UnifyPair>> nextSetasListRest = new ArrayList<>();

  /**
   * Liste der Faelle, bei dem Receiver jeweils "? extends" enthaelt bzw. nicht enthaelt
   * In der Regel ist dies genau ein Element
   * Dieses Element wird später aus nextSetasList geloescht, wenn das jeweils andere Element zum Erfolg
   * gefuehrt hat.
   */
  public List<Set<UnifyPair>> nextSetasListOderConstraints = new ArrayList<>();


  protected VarianceCase(boolean isOderConstraint, TypeUnifyTask typeUnifyTask, UnifyContext context) {
    this.isOderConstraint = isOderConstraint;
    this.typeUnifyTask = typeUnifyTask;
    this.context = context;
  }

  /**
   *  Selects values for the next iteration in the run method:
   *  - a : The element to ???
   *  - nextSetAsList: The list of cases that have no relation to the selected a and will have to be worked on
   *  - nextSetasListOderConstraints: The list of cases of which the receiver contains "? extends", typically one element
   */
  public abstract void selectNextData(
    TypeUnifyTask typeUnifyTask,
    List<Set<UnifyPair>> nextSetAsList,
    Optional<UnifyPair> optOrigPair
  );

  /**
   *
   */
  public abstract 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
  );

  /**
   *
   */
  public abstract void applyComputedResults(
    Set<Set<UnifyPair>> result,
    Set<Set<UnifyPair>> currentThreadResult,
    Set<UnifyPair> compResult,
    Set<UnifyPair> compRes
  );

  /**
   *
   * @return If the current iteration should be broken out of
   */
  public abstract boolean eraseInvalidSets(
    int rekTiefe,
    Set<Set<UnifyPair>> aParDef,
    List<Set<UnifyPair>> nextSetAsList
  );

  /**
   * Wrapper class for the parallel computation results
   */
  public static class ComputationResults {
    public Set<Set<UnifyPair>> mainResult;
    public Set<Set<Set<UnifyPair>>> forkResults;

    public ComputationResults() {
      this(new HashSet<>(), new HashSet<>());
    }

    public ComputationResults(Set<Set<UnifyPair>> mainResult) {
      this(mainResult, new HashSet<>());
    }

    public ComputationResults(Set<Set<UnifyPair>> mainResult, Set<Set<Set<UnifyPair>>> forkResults) {
      this.mainResult = mainResult;
      this.forkResults = forkResults;
    }

    void addForkResult(Set<Set<UnifyPair>> forkResult) {
      forkResults.add(forkResult);
    }
  }
}
