package de.westnordost.streetcomplete.data.osm.tql;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.ListIterator;
/** A boolean expression of values that are connected by ANDs and ORs */
public class BooleanExpression<T extends BooleanExpressionValue>
{
public enum Type
{
AND, OR, ROOT, LEAF
}
// once set, these are final
private Type type;
private T value;
private BooleanExpression<T> parent;
private LinkedList<BooleanExpression<T>> children = new LinkedList<>();
public BooleanExpression(boolean asRoot)
{
if(asRoot) setType(Type.ROOT);
}
public BooleanExpression()
{
}
/** --------------------- Methods for extending the boolean expression ---------------------- */
public BooleanExpression<T> addAnd()
{
if(!isAnd())
{
BooleanExpression<T> newChild = createIntermediateChild();
newChild.setType(Type.AND);
return newChild;
}
return this;
}
public BooleanExpression<T> addOr()
{
BooleanExpression<T> node = this;
if(isAnd())
{
if (getParent().isRoot())
{
node = createIntermediateParent();
node.setType(Type.OR);
}
else
{
node = getParent();
}
}
if (!node.isOr())
{
BooleanExpression<T> newChild = node.createIntermediateChild();
newChild.setType(Type.OR);
return newChild;
}
return node;
}
public void addValue(T t)
{
BooleanExpression<T> child = createChild();
child.setType(Type.LEAF);
child.value = t;
}
public BooleanExpression<T> addOpenBracket()
{
return createChild();
}
private BooleanExpression<T> createChild()
{
BooleanExpression<T> child = new BooleanExpression<>();
addChild(child);
return child;
}
private BooleanExpression<T> createIntermediateParent()
{
BooleanExpression<T> newParent = new BooleanExpression<>();
BooleanExpression<T> parent = getParent();
parent.removeChild(this);
newParent.addChild(this);
parent.addChild(newParent);
return newParent;
}
private BooleanExpression<T> createIntermediateChild()
{
BooleanExpression<T> lastChild = removeLastChild();
BooleanExpression<T> newNode = createChild();
if(lastChild != null) newNode.addChild(lastChild);
return newNode;
}
private BooleanExpression<T> removeLastChild()
{
if(children.isEmpty()) return null;
return children.removeLast();
}
private void addChild(BooleanExpression<T> child)
{
child.setParent(this);
children.add(child);
}
private void removeChild(BooleanExpression<T> child)
{
children.remove(child);
child.setParent(null);
}
/** --------------------- Methods for accessing the boolean expression ---------------------- */
public boolean matches(Object element)
{
switch(type)
{
case LEAF:
return value.matches(element);
case OR:
for(BooleanExpression<T> child : children)
{
if(child.matches(element)) return true;
}
return false;
case AND:
for(BooleanExpression<T> child : children)
{
if(!child.matches(element)) return false;
}
return true;
case ROOT:
return children.getFirst().matches(element);
}
return false;
}
public BooleanExpression<T> getFirstChild()
{
return children.isEmpty() ? null : children.getFirst();
}
public Iterable<BooleanExpression<T>> getChildren()
{
return Collections.unmodifiableList(children);
}
public T getValue() { return value; }
public BooleanExpression<T> getParent() { return parent; }
private void setParent(BooleanExpression<T> newParent) { parent = newParent; }
public boolean isOr() { return type == Type.OR; }
public boolean isAnd() { return type == Type.AND; }
public boolean isValue() { return type == Type.LEAF; }
public boolean isRoot() { return type == Type.ROOT; }
private void setType(Type op)
{
if(type != null) throw new IllegalStateException();
type = op;
}
/** Removes unnecessary depth in the expression tree */
public void flatten()
{
removeEmptyNodes();
mergeNodesWithSameOperator();
}
/** remove nodes from superfluous brackets */
private void removeEmptyNodes()
{
ListIterator<BooleanExpression<T>> it = children.listIterator();
while ( it.hasNext() )
{
BooleanExpression<T> child = it.next();
if(child.type == null && child.children.size() == 1)
{
replaceChildAt(it, child.children.getFirst());
it.previous(); // = the just replaced node will be checked again
}
else
{
child.removeEmptyNodes();
}
}
}
/** merge children recursively which do have the same operator set (and, or) */
private void mergeNodesWithSameOperator()
{
if(isValue()) return;
ListIterator<BooleanExpression<T>> it = children.listIterator();
while (it.hasNext())
{
BooleanExpression<T> child = it.next();
child.mergeNodesWithSameOperator();
// merge two successive nodes of same type
if (child.type == type)
{
replaceChildAt(it, child.children);
}
}
}
private void replaceChildAt(ListIterator<BooleanExpression<T>> at,
BooleanExpression<T> with)
{
replaceChildAt(at, Collections.singletonList(with));
}
private void replaceChildAt(ListIterator<BooleanExpression<T>> at,
Collection<BooleanExpression<T>> with)
{
at.remove();
for(BooleanExpression<T> withEle : with)
{
at.add(withEle);
withEle.setParent(this);
}
}
/** Expand the expression so that all ANDs have only leaves */
public void expand()
{
moveDownAnds();
mergeNodesWithSameOperator();
}
private void moveDownAnds()
{
if(isValue()) return;
if(isAnd())
{
BooleanExpression<T> rest = removeFirstOr();
if(rest != null)
{
// the first OR moves into our place, we are now an orphan
getParent().replaceChild(this, rest);
addCopiesOfMyselfInBetweenChildrenOf(rest);
rest.mergeNodesWithSameOperator();
rest.moveDownAnds();
return;
}
}
for(BooleanExpression<T> child : new LinkedList<>(children)) child.moveDownAnds();
}
private void replaceChild(BooleanExpression<T> replace, BooleanExpression<T> with)
{
ListIterator<BooleanExpression<T>> it = children.listIterator();
while ( it.hasNext() )
{
BooleanExpression<T> child = it.next();
if(child == replace)
{
replaceChildAt(it, with);
return;
}
}
}
public BooleanExpression<T> copy()
{
BooleanExpression<T> result = new BooleanExpression<>();
result.type = type;
result.value = value; // <- this is a a reference! value should be immutable
result.parent = null; // parent is set on parent.addChild, see for loop
result.children = new LinkedList<>();
for(BooleanExpression<T> child : children)
{
result.addChild(child.copy());
}
return result;
}
/** Adds deep copies of this as children to other, each taking one original child as its own */
private void addCopiesOfMyselfInBetweenChildrenOf(BooleanExpression<T> other)
{
ListIterator<BooleanExpression<T>> it = other.children.listIterator();
while (it.hasNext())
{
BooleanExpression<T> child = it.next();
BooleanExpression<T> clone = copy();
clone.replacePlaceholder(child);
other.replaceChildAt(it, clone);
}
}
private void replacePlaceholder(BooleanExpression<T> with)
{
ListIterator<BooleanExpression<T>> it = children.listIterator();
while (it.hasNext())
{
BooleanExpression<T> child = it.next();
if(child.type == null)
{
replaceChildAt(it, with);
return;
}
}
}
/** Find first OR child and remove it from my children */
private BooleanExpression<T> removeFirstOr()
{
ListIterator<BooleanExpression<T>> it = children.listIterator();
while ( it.hasNext() )
{
BooleanExpression<T> child = it.next();
if (child.isOr())
{
it.remove();
BooleanExpression<T> placeholder = new BooleanExpression<>();
it.add(placeholder);
return child;
}
}
return null;
}
@Override
public String toString()
{
if(type == Type.LEAF) return value.toString();
StringBuilder builder = new StringBuilder();
if(isOr() && !getParent().isRoot()) builder.append('(');
boolean first = true;
for(BooleanExpression<T> child : children)
{
if(first) first = false;
else
{
builder.append(' ');
builder.append(type.toString().toLowerCase());
builder.append(' ');
}
builder.append(child.toString());
}
if(isOr() && !getParent().isRoot()) builder.append(')');
return builder.toString();
}
}