package jalse.entities;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Objects;
import java.util.Queue;
import jalse.entities.EntityVisitor.EntityVisitResult;
class EntityTreeWalker {
private final EntityContainer container;
private final int maxDepth;
private final EntityVisitor visitor;
private final Queue<EntityTreeWalker> walkers;
private final Iterator<Entity> iterator;
private boolean ignoreSiblings;
private boolean exited;
EntityTreeWalker(final EntityContainer container, final int maxDepth, final EntityVisitor visitor) {
this.container = Objects.requireNonNull(container);
this.visitor = Objects.requireNonNull(visitor);
if (maxDepth <= 0) {
throw new IllegalArgumentException();
}
this.maxDepth = maxDepth;
walkers = new LinkedList<>();
iterator = container.streamEntities().iterator(); // Lazy
exited = false;
}
private boolean canAddChild() {
return !ignoreSiblings && iterator.hasNext();
}
private boolean canWalkChild() {
return !walkers.isEmpty();
}
public EntityContainer getContainer() {
return container;
}
public int getMaxDepth() {
return maxDepth;
}
public EntityVisitor getVisitor() {
return visitor;
}
private boolean hasExited() {
return exited;
}
public boolean isWalking() {
return !hasExited() && (canAddChild() || canWalkChild());
}
public Entity walk() {
if (!isWalking()) {
throw new IllegalStateException();
}
/*
* Visit direct children.
*/
if (canAddChild()) {
final Entity e = iterator.next();
final EntityVisitResult result = visitor.visit(e);
if (result == EntityVisitResult.EXIT) {
exited = true;
return e;
}
/*
* Remove other children.
*/
if (result == EntityVisitResult.IGNORE_SIBLINGS) {
ignoreSiblings = true;
walkers.clear();
}
/*
* Do not walk through child descendants.
*/
if (maxDepth > 1 && result != EntityVisitResult.IGNORE_CHILDREN) {
final EntityTreeWalker child = new EntityTreeWalker(e, maxDepth - 1, visitor);
if (child.isWalking()) {
walkers.add(child);
}
}
return e;
}
/*
* Walk already visited child.
*/
final EntityTreeWalker child = walkers.element();
final Entity e = child.walk();
/*
* Child has been fully traversed.
*/
if (!child.isWalking()) {
walkers.remove();
}
return e;
}
}