package xapi.ui.api;
import xapi.util.impl.ReverseIterable;
import xapi.fu.In1Out1;
import static xapi.util.X_Util.pushIfMissing;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* Created by James X. Nelson (james @wetheinter.net) on 6/27/16.
*/
public class PhaseMap <K extends Comparable<K>> {
private static final PhaseNode[] EMPTY = new PhaseNode[0];
public PhaseMap() {
instances = new LinkedHashMap<>();
}
public static <S, T extends Comparable<T>> PhaseMap<T> toMap(Iterable<S> source,
In1Out1<S, T> getId,
In1Out1<S, Integer> getPriority,
In1Out1<S, T> getPrerequisite,
In1Out1<S, T> getBlock) {
PhaseMap<T> map = new PhaseMap<>();
for (S s : source) {
T id = getId.io(s);
Integer priority = getPriority.io(s);
T prerequisite = getPrerequisite.io(s);
T block = getBlock.io(s);
map.addNode(id, priority, prerequisite, block);
}
return map;
}
protected PhaseNode<K> tail, head;
private final Map<K, PhaseNode<K>> instances;
public PhaseNode<K> addNode(K id, int priority, K prerequisite, K block) {
assert isWellStructured(id, prerequisite, block);
PhaseNode<K> node = getOrCreateNode(id, priority);
if (isAcceptable(prerequisite)) {
PhaseNode<K> prereq= getOrCreateNode(prerequisite, priority);
// make sure the thing that is our prerequisite knows to block us.
prereq.block = pushIfMissing(prereq.block, node);
node.prerequisites = pushIfMissing(node.prerequisites, prereq);
}
if (isAcceptable(block)) {
PhaseNode<K> blocked = getOrCreateNode(block, priority);
// make sure the thing that we block knows we are its prerequisite.
blocked.prerequisites = pushIfMissing(blocked.prerequisites, node);
node.block = pushIfMissing(node.block, blocked);
}
assert isWellStructured(id, prerequisite, block);
if (head == null) {
head = tail = node;
} else {
if (head.isBlockerOf(node)) {
node.next = head;
head = node;
assert !tail.isPrerequisiteOf(node);
} else if (tail.isPrerequisiteOf(node)) {
tail.next = node;
tail = node;
assert !tail.isBlockerOf(node);
} else {
// an insertion in the middle... lets walk the nodes to find our insertion point.
PhaseNode<K> n = head;
while (n != null) {
if (n.isBlockerOf(node)) {
node.next = n.next;
n.next = node;
break;
}
n = n.next;
}
assert n != null : "Unable to find insertion point of node " + node + " from head: " + head;
}
}
return node;
}
protected class Itr implements Iterator<PhaseNode<K>> {
PhaseNode<K> node = head;
@Override
public boolean hasNext() {
return node != null;
}
@Override
public PhaseNode<K> next() {
try {
return node;
} finally {
node = node.next;
}
}
}
public Iterable<PhaseNode<K>> forEachNode() {
return Itr::new;
}
public Iterable<PhaseNode<K>> forEachNodeReverse() {
return ReverseIterable.reverse(Itr::new);
}
private boolean isAcceptable(K prerequisite) {
return prerequisite != null && !"".equals(prerequisite);
}
protected PhaseNode<K> getOrCreateNode(K id, int priority) {
return instances.compute(id, (i, n)->{
if (n == null) {
n = new PhaseNode<>(id, priority);
} else {
if (priority < n.priority) {
n.priority = priority;
}
}
return n;
});
}
private boolean isWellStructured(K id, K prerequisite, K block) {
if (!isAcceptable(id)) {
if (isAcceptable(prerequisite) || isAcceptable(block)) {
assert false : "Cannot have a null id unless you are root (all null pointers)";
return false;
}
} else {
// with a non-null id, we must have either a before, or an after...
if (!isAcceptable(prerequisite)) {
if (!isAcceptable(block)) {
assert false : "If you have an id (" + id +") then you must have at least one preqrequisite or one block requirement";
return false;
}
} else {
// before != null; don't allow it to equal id
if (prerequisite.equals(id)) {
assert false : "It is not legal for a phase to have prerequisite key == id key; bad key: " + prerequisite +
" (block: " + block + ")";
return false;
}
}
if (isAcceptable(block)) {
if (block.equals(id)) {
assert false : "It is not legal for a phase to have block key == id key; bad key: " + block +
" (prerequisite: " + prerequisite + ")";
return false;
}
}
}
if (isEmpty()) {
return true;
}
return true;
}
public boolean isEmpty() {
return head == tail;
}
public static class PhaseNode <K extends Comparable<K>> implements Comparable<PhaseNode<K>> {
PhaseNode<K> next;
PhaseNode<K>[] prerequisites;
PhaseNode<K>[] block;
Map<K, Integer> relations;
K id;
int priority;
PhaseNode(K id, int priority) {
this.id = id;
this.priority = priority;
relations = createRelationMap();
prerequisites = block = EMPTY;
}
public PhaseNode<K>[] getPrerequisites() {
return prerequisites;
}
public PhaseNode<K>[] getBlock() {
return block;
}
public K getId() {
return id;
}
public int getPriority() {
return priority;
}
@Override
public int compareTo(PhaseNode<K> o) {
if (this.equals(o)) {
return 0;
}
return relations.computeIfAbsent(o.id, id -> {
int relation = 0;
boolean iBlockYou = isBlocking(o);
// if (prerequisite.get(o.id) != null) {
// if (o.prerequisite.get(id) != null) {
// throw new IllegalStateException("Phases " + id + " and "
// + o.id + " cannot both be prerequisite of each other");
// }
// o.block.getOrCreate(id, i -> this);
// prerequisite.putAll(o.prerequisite.entries());
// o.block.putAll(block.entries());
// relation = 1;
// }
// if (block.get(o.id) != null) {
// if (o.block.get(id) != null) {
// throw new IllegalStateException("Phases " + id + " and "
// + o.id + " cannot both block each other");
// }
// o.prerequisite.getOrCreate(id, i -> this);
// block.putAll(o.block.entries());
// o.prerequisite.putAll(prerequisite.entries());
// assert relation != 1;
// relation = -1;
// }
//
// if (o.prerequisite.get(id) != null) {
// block.getOrCreate(id, i -> o);
// o.prerequisite.putAll(prerequisite.entries());
// block.putAll(o.block.entries());
// assert relation != 1;
// relation = -1;
// }
// if (o.block.get(id) != null) {
// prerequisite.getOrCreate(id, i -> o);
// o.block.putAll(block.entries());
// prerequisite.putAll(o.prerequisite.entries());
// assert relation != -1;
// relation = 1;
// }
//
// if (relation == 0) {
// for (PhaseNode node : prerequisite.values()) {
// if (o.block.get(node.id) != null) {
// // o blocks our prerequisite.
// relation = 1;
// break;
// }
// }
// }
// if (relation == 0) {
// for (PhaseNode node : block.values()) {
// if (o.prerequisite.get(node.id) != null) {
// relation = -1;
// break;
// }
// }
// }
//
// if (relation == 0) {
// for (PhaseNode node : o.prerequisite.values()) {
// if (block.get(node.id) != null) {
// // we blocks one of o's prerequisite.
// relation = -1;
// break;
// }
// }
// }
// if (relation == 0) {
// for (PhaseNode node : o.block.values()) {
// if (prerequisite.get(node.id) != null) {
// // we require a node that o blocks.
// relation = 1;
// break;
// }
// }
// }
// if (relation == 0) {
// // these nodes are in the same phase...
// relation = Integer.compare(priority, o.priority);
// if (relation == 0) {
// relation = id.compareTo(o.id);
// }
// }
//
// assert relation != 0;
return relation;
});
}
protected boolean isBlocking(PhaseNode<K> o) {
PhaseNode<K>[] cur = block;
if (cur == EMPTY) {
return false;
}
Set<K> seen = new HashSet<>();
return deepContains(seen, o, PhaseNode::getBlock, cur);
}
protected boolean isPrerequisite(PhaseNode<K> o) {
PhaseNode<K>[] cur = prerequisites;
if (cur == EMPTY) {
return false;
}
Set<K> seen = new HashSet<>();
return deepContains(seen, o, PhaseNode::getPrerequisites, cur);
}
protected final boolean isBlockerOf(PhaseNode<K> o) {
return o.isBlocking(this);
}
protected final boolean isPrerequisiteOf(PhaseNode<K> o) {
return o.isPrerequisite(this);
}
public boolean deepContains(Set<K> seen, PhaseNode<K> o, In1Out1<PhaseNode<K>, PhaseNode<K>[]> target, PhaseNode<K>[] cur) {
if (cur == EMPTY || cur == null || cur.length == 0) {
return false;
}
for (PhaseNode<K> node : cur) {
// prevent recursion sickness
if (seen.add(node.id)) {
if (Objects.equals(o.id, node.id)) {
return true;
}
final PhaseNode<K>[] nexts = target.io(node);
if (node.deepContains(seen, o, target, nexts)) {
return true;
}
}
}
return false;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof PhaseNode))
return false;
final PhaseNode phaseNode = (PhaseNode) o;
return id != null ? id.equals(phaseNode.id) : phaseNode.id == null;
}
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
@Override
public String toString() {
return "PhaseNode{" +
"priority=" + priority +
", id=" + id +
", next=" + next +
'}';
}
protected Map<K, Integer> createRelationMap() {
return new LinkedHashMap<>();
}
}
}