package de.dhbwstuttgart.typeinference.unify;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * An intermediate class for the recursive steps of the TypeUnifyTask:
 * This allows canceling parts of the recursion tree, instead of only  the whole execution as before. But in
 * order for that to work, all cancellable child tasks must be added when they are created
 *
 * @param <T>
 */
public abstract class CancellableTask<T> extends RecursiveTask<T> {

  private final AtomicBoolean executionCancelled = new AtomicBoolean(false);
  private final List<CancellableTask<?>> childTasks = new LinkedList<>();
  private CancellableTask<?> parentTask = null;

  /**
   * Set the execution for this task and all its (recursive) children to be canceled
   */
  protected void cancelExecution() {
    // is this branch already canceled? Then do nothing
    if (this.executionCancelled.getAndSet(true)) return;
    this.cancelChildExecution();
  }

  private void cancelChildExecution() {
    synchronized (this.childTasks) {
      for (var childTask : childTasks) {
        // no need to cancel a branch that is already finished
        if (!childTask.isDone()) {
          childTask.cancelExecution();
        }
      }
    }
  }

  private void cancelChildExecutionAfter(CancellableTask<?> checkpointTask) {
    boolean reachedCheckpoint = false;
    int i = 0;
    for (var childTask : childTasks) {
      if (!reachedCheckpoint) {
        reachedCheckpoint = childTask == checkpointTask;
      }
      else {
        // no need to cancel a branch that is already finished
        if (!childTask.isDone()) {
          childTask.cancelExecution();
        }
        i++;
      }
    }
    System.out.println("Skipped " + i + " younger siblings");
  }

  protected void cancelSiblingTasks() {
    if (this.parentTask != null) {
      boolean thisWasCancelledBefore = this.executionCancelled.get();
      this.parentTask.cancelChildExecution();
      this.executionCancelled.set(thisWasCancelledBefore);
    }
  }

  public void cancelYoungerSiblingTasks() {
    if (this.parentTask != null) {
      this.parentTask.cancelChildExecutionAfter(this);
    }
  }

  public Boolean isExecutionCancelled() {
    return executionCancelled.get();
  }

  public void addChildTask(CancellableTask<?> childTask) {
    this.childTasks.add(childTask);
    childTask.setParentTask(this);
    if (this.executionCancelled.get()) {
      childTask.executionCancelled.set(true);
    }
  }

  private void setParentTask(CancellableTask<?> parentTask) {
    this.parentTask = parentTask;
  }

}
