package com.sag.baf.tools;

import java.util.Collection;
import java.util.Stack;

/**
 * This helper class is easy to override, and helps create a recursive function local to a method (nested functions don't exist in Java)
 *
 * @param <T> the type of object that we recurse on
 */
public abstract class RecursionHelper<T> {
  private boolean depthFirst = false;
  private Stack<T> currentRecursionStack;
  private T startElement;

  /**
   * sets the recursionHelper to go down the depth of the tree first, before doing any action on the nodes
   *
   * @return
   */
  public RecursionHelper<T> setDepthFirst() {
    this.depthFirst = true;
    return this;
  }

  /**
   * gets the next level of recursion
   *
   * @param currentElement is never null
   * @return the list of elements that are contained in currentElement
   */
  public abstract Collection<T> getRecursedElements(T currentElement);

  /**
   * do the desired action on each recursed elements
   *
   * @param currentElement is never null
   */
  public abstract void doActionOnElement(T currentElement);

  public RecursionHelper(T startElement) {
    this.startElement = startElement;
  }

  public int getCurrentRecursionLevel() {
    if (currentRecursionStack == null)
      return 0;
    return currentRecursionStack.size();
  }

  public Stack<T> getCurrentRecursionStack() {
    return currentRecursionStack;
  }

  public RecursionHelper<T> start() {
    currentRecursionStack = new Stack<T>();
    recurseOn(startElement);
    return this;
  }

  void recurseOn(T currentElement) {
    if (currentElement == null)
      return;
    if (currentRecursionStack.contains(currentElement))
      throw new RuntimeException("Circular recursion !");
    currentRecursionStack.push(currentElement);
    if (!depthFirst)
      doActionOnElement(currentElement);
    Collection<T> nextLevelElements = getRecursedElements(currentElement);
    if (nextLevelElements != null) {
      for (T nextLevelElement : getRecursedElements(currentElement)) {
        recurseOn(nextLevelElement);
      }
    }
    if (depthFirst)
      doActionOnElement(currentElement);
    currentRecursionStack.pop();
  }

}
