package org.spoofax.jsglr2.reducing;

import org.spoofax.jsglr2.JSGLR2Variants.ParseForestConstruction;
import org.spoofax.jsglr2.actions.IAction;
import org.spoofax.jsglr2.actions.IReduce;
import org.spoofax.jsglr2.parseforest.AbstractParseForest;
import org.spoofax.jsglr2.parseforest.ParseForestManager;
import org.spoofax.jsglr2.parser.Parse;
import org.spoofax.jsglr2.parsetable.IParseTable;
import org.spoofax.jsglr2.stack.AbstractStackNode;
import org.spoofax.jsglr2.stack.StackLink;
import org.spoofax.jsglr2.stack.StackManager;
import org.spoofax.jsglr2.stack.StackPath;
import org.spoofax.jsglr2.states.IState;

public class ReduceManager<StackNode extends AbstractStackNode<ParseForest>, ParseForest extends AbstractParseForest, ParseNode extends ParseForest, Derivation> {

    protected final IParseTable parseTable;
    protected final StackManager<StackNode, ParseForest> stackManager;
    protected final ParseForestManager<ParseForest, ParseNode, Derivation> parseForestManager;
    protected final Reducer<StackNode, ParseForest, ParseNode, Derivation> reducer;

    public ReduceManager(IParseTable parseTable, StackManager<StackNode, ParseForest> stackManager,
        ParseForestManager<ParseForest, ParseNode, Derivation> parseForestManager,
        ParseForestConstruction parseForestConstruction) {
        this.parseTable = parseTable;
        this.stackManager = stackManager;
        this.parseForestManager = parseForestManager;

        if(parseForestConstruction == ParseForestConstruction.Optimized)
            this.reducer = new ReducerSkipLayoutAndLexicalAndRejects<StackNode, ParseForest, ParseNode, Derivation>(
                stackManager, parseForestManager);
        else
            this.reducer = new Reducer<StackNode, ParseForest, ParseNode, Derivation>(stackManager, parseForestManager);
    }

    public void doReductions(Parse<StackNode, ParseForest> parse, StackNode stack, IReduce reduce) {
        if(reduce.production().isCompletionOrRecovery())
            return;

        parse.notify(observer -> observer.doReductions(parse, stack, reduce));

        doReductionsHelper(parse, stack, reduce, null);
    }

    private void doLimitedRedutions(Parse<StackNode, ParseForest> parse, StackNode stack, IReduce reduce,
        StackLink<StackNode, ParseForest> throughLink) {
        if(reduce.production().isCompletionOrRecovery())
            return;

        parse.notify(observer -> observer.doLimitedReductions(parse, stack, reduce, throughLink));

        doReductionsHelper(parse, stack, reduce, throughLink);
    }

    protected void doReductionsHelper(Parse<StackNode, ParseForest> parse, StackNode stack, IReduce reduce,
        StackLink<StackNode, ParseForest> throughLink) {
        for(StackPath<StackNode, ParseForest> path : stackManager.findAllPathsOfLength(stack, reduce.arity())) {
            if(throughLink == null || path.contains(throughLink)) {
                ParseForest[] parseNodes = stackManager.getParseForests(parseForestManager, path);
                StackNode pathBegin = path.head();

                reducer(parse, pathBegin, reduce, stackManager.getParseForests(parseForestManager, path));
            }
        }
    }

    /**
     * Perform a reduction for the given reduce action and parse forests. The reduce action contains which production
     * will be reduced and the parse forests represent the right hand side of this production. The reduced derivation
     * will end up on a stack link from the given stack to a stack with the goto state. The latter can already exist or
     * not and if such an active stack already exists, the link to it can also already exist. Based on the existence of
     * the stack with the goto state and the link to it, different actions are performed.
     */
    protected void reducer(Parse<StackNode, ParseForest> parse, StackNode stack, IReduce reduce,
        ParseForest[] parseForests) {
        int gotoId = stack.state.getGotoId(reduce.production().id());
        IState gotoState = parseTable.getState(gotoId);

        StackNode activeStackWithGotoState = parse.activeStacks.findWithState(gotoState);

        parse.notify(observer -> observer.reducer(stack, reduce, parseForests, activeStackWithGotoState));

        if(activeStackWithGotoState != null) {
            StackLink<StackNode, ParseForest> directLink = stackManager.findDirectLink(activeStackWithGotoState, stack);

            parse.notify(observer -> observer.directLinkFound(parse, directLink));

            if(directLink != null) {
                reducer.reducerExistingStackWithDirectLink(parse, reduce, directLink, parseForests);
            } else {
                StackLink<StackNode, ParseForest> link = reducer.reducerExistingStackWithoutDirectLink(parse, reduce,
                    activeStackWithGotoState, stack, parseForests);

                for(StackNode activeStack : parse.activeStacks.forLimitedReductions(parse.forActorStacks)) {
                    for(IAction action : activeStack.state.getReduceActions(parse))
                        doLimitedRedutions(parse, activeStack, (IReduce) action, link);
                }
            }
        } else {
            StackNode newStack = reducer.reducerNoExistingStack(parse, reduce, stack, gotoState, parseForests);

            parse.activeStacks.add(newStack);
            parse.forActorStacks.add(newStack);
        }
    }

}

@@ -101,51 +101,8 @@
 on;%0A
-import org.spoofax.jsglr2.actions.IAction;%0A
 impo
@@ -4938,16 +4938,22 @@
 or(I
-Action a
+Reduce reduceA
 ctio
@@ -5066,19 +5066,15 @@
 ck, 
-(IR
+r
 educe
-) a
+A
 ctio

Remove unnecessary cast