package org.spoofax.jsglr2.cli;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import org.spoofax.jsglr2.parseforest.IParseForest;

import org.spoofax.jsglr2.parser.AbstractParseState;
import org.spoofax.jsglr2.stack.IStackNode;
import org.spoofax.jsglr2.stack.StackLink;

class StackDotVisualisationParserObserver
//@formatter:off
   <ParseForest extends IParseForest,
    StackNode   extends IStackNode,
    ParseState  extends AbstractParseState<ParseForest, StackNode>>
//@formatter:on
    extends DotVisualisationParserObserver<ParseForest, StackNode, ParseState> {

    private Map<StackNode, Integer> stackNodeRank;
    private int maxStackNodeRank;
    private int[] offsetMaxStackNodeRank;

    public StackDotVisualisationParserObserver(Consumer<String> outputConsumer) {
        super(outputConsumer);
    }

    @Override public void parseStart(ParseState parseState) {
        super.parseStart(parseState);
        stackNodeRank = new HashMap<>();
        maxStackNodeRank = 0;
        offsetMaxStackNodeRank = new int[parseState.inputLength + 1];
    }

    private void rankStackNode(StackNode stackNode, int rank) {
        int rankAtOffset;

        if(stackNode.position().offset > 0)
            rankAtOffset = Math.max(rank, offsetMaxStackNodeRank[stackNode.position().offset - 1]);
        else
            rankAtOffset = rank;

        stackNodeRank.put(stackNode, rankAtOffset);
        maxStackNodeRank = Math.max(maxStackNodeRank, rankAtOffset);
        offsetMaxStackNodeRank[stackNode.position().offset] = maxStackNodeRank;
    }

    @Override public void createStackNode(StackNode stack) {
        super.createStackNode(stack);

        if(id(stack) == 0)
            rankStackNode(stack, 0);

        dotStatement(idNode(stackNodeId(stack), id(stack), "" + stack.state().id()) + ";");
    }

    @Override public void createStackLink(StackLink<ParseForest, StackNode> link) {
        super.createStackLink(link);

        if(!stackNodeRank.containsKey(link.from))
            rankStackNode(link.from, stackNodeRank.get(link.to) + 1);

        dotStatement(stackNodeId(link.to) + ":p:e -> " + stackNodeId(link.from) + ":p:w [label=\""
            + id(link.parseForest) + ": " + escape(link.parseForest.descriptor()) + "\"];");
    }

    String stackNodeId(StackNode stack) {
        return stackNodeId(id(stack));
    }

    String stackNodeId(int id) {
        return "stack_" + id;
    }

    void output() {
        String prefix = "digraph {\nrankdir = LR;\nedge [dir=\"back\"];\n";

        for(int rank = 0; rank <= maxStackNodeRank; rank++) {
            Collection<StackNode> stackNodesForRank = new ArrayList<>();

            for(Map.Entry<StackNode, Integer> entry : stackNodeRank.entrySet()) {
                if(entry.getValue() == rank)
                    stackNodesForRank.add(entry.getKey());
            }

            dotStatement("{rank=same; "
                + stackNodesForRank.stream().map(id -> stackNodeId(id) + ";").collect(Collectors.joining()) + "}");
        }

        outputConsumer.accept(prefix + dotStatements + "}");
    }

}

@@ -30,186 +30,8 @@
 i;%0A%0A
-import java.util.ArrayList;%0Aimport java.util.Collection;%0Aimport java.util.HashMap;%0Aimport java.util.Map;%0Aimport java.util.function.Consumer;%0Aimport java.util.stream.Collectors;%0A%0A
 impo
@@ -78,17 +78,16 @@
 Forest;%0A
-%0A
 import o
@@ -219,16 +219,194 @@
 kLink;%0A%0A
+import java.util.ArrayList;%0Aimport java.util.Collection;%0Aimport java.util.HashMap;%0Aimport java.util.Map;%0Aimport java.util.function.Consumer;%0Aimport java.util.stream.Collectors;%0A%0A
 class St
@@ -773,32 +773,63 @@
 xStackNodeRank;%0A
+    private int currentOffset;%0A
     private int%5B
@@ -849,24 +849,77 @@
 ackNodeRank;
+%0A    private Map%3CStackNode, Integer%3E stackNodeOffset;
 %0A%0A    public
@@ -1169,16 +1169,43 @@
 ap%3C%3E();%0A
+        currentOffset = 0;%0A
         
@@ -1299,24 +1299,177 @@
  1%5D;%0A    %7D%0A%0A
+    @Override public void parseRound(ParseState parseState, Iterable%3CStackNode%3E activeStacks) %7B%0A        currentOffset = parseState.currentOffset;%0A    %7D%0A%0A
     private 
@@ -1567,34 +1567,37 @@
 tackNode
-.position().offset
+Offset.get(stackNode)
  %3E 0)%0A  
@@ -1668,34 +1668,37 @@
 tackNode
-.position().offset
+Offset.get(stackNode)
  - 1%5D);%0A
@@ -1909,26 +1909,29 @@
 Node
-.position().offset
+Offset.get(stackNode)
 %5D = 
@@ -2051,24 +2051,76 @@
 de(stack);%0A%0A
+        stackNodeOffset.put(stack, currentOffset);%0A%0A
         if(i

Maintain offsets of stack nodes in DOT visualisation of stacks, removes direct dependency on positions on stacks