package cucumber.runtime.formatter;

import gherkin.formatter.Formatter;
import gherkin.formatter.NiceAppendable;
import gherkin.formatter.Reporter;
import gherkin.formatter.model.Background;
import gherkin.formatter.model.Examples;
import gherkin.formatter.model.Feature;
import gherkin.formatter.model.Match;
import gherkin.formatter.model.Result;
import gherkin.formatter.model.Scenario;
import gherkin.formatter.model.ScenarioOutline;
import gherkin.formatter.model.Step;

import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;


/**
 * Formatter for reporting all failed features and print their locations
 * Failed means: (failed, undefined, pending) test result
 */
class RerunFormatter implements Formatter, Reporter {
    private final NiceAppendable out;
    private String featureLocation;
    private Scenario scenario;
    private ScenarioState state = ScenarioState.None;
    private boolean isTestFailed = false;
    private Map<String, LinkedHashSet<Integer>> featureAndFailedLinesMapping = new HashMap<String, LinkedHashSet<Integer>>();

    enum ScenarioState {
        None, In_Background_Or_Before, In_Scenario
    }

    public RerunFormatter(Appendable out) {
        this.out = new NiceAppendable(out);
    }

    @Override
    public void uri(String uri) {
        this.featureLocation = uri;
    }

    @Override
    public void feature(Feature feature) {
    }

    @Override
    public void background(Background background) {
        recordPreviousScenarioIfFailed();
        state = ScenarioState.In_Background_Or_Before;
    }

    @Override
    public void scenario(Scenario scenario) {
        recordPreviousScenarioIfFailed();
        state = ScenarioState.In_Scenario;
        this.scenario = scenario;
    }

    private void recordPreviousScenarioIfFailed() {
        if (state == ScenarioState.In_Scenario && isTestFailed) {
            recordTestFailed();
            isTestFailed = false;
        }
    }

    @Override
    public void scenarioOutline(ScenarioOutline scenarioOutline) {
        recordPreviousScenarioIfFailed();
        state = ScenarioState.None;
    }

    @Override
    public void examples(Examples examples) {
    }

    @Override
    public void step(Step step) {
    }

    @Override
    public void eof() {
        recordPreviousScenarioIfFailed();
        state = ScenarioState.None;
    }

    @Override
    public void syntaxError(String state, String event, List<String> legalEvents, String uri, Integer line) {
    }

    @Override
    public void done() {
        reportFailedScenarios();
    }

    private void reportFailedScenarios() {
        Set<Map.Entry<String, LinkedHashSet<Integer>>> entries = featureAndFailedLinesMapping.entrySet();
        boolean firstFeature = true;
        for (Map.Entry<String, LinkedHashSet<Integer>> entry : entries) {
            if (entry.getValue().size() > 0) {
                if (!firstFeature) {
                    out.append(" ");
                }
                out.append(entry.getKey());
                firstFeature = false;
                for (Integer line : entry.getValue()) {
                    out.append(":").append(line.toString());
                }
            }
        }
    }

    @Override
    public void close() {
        this.out.close();
    }

    @Override
    public void startOfScenarioLifeCycle(Scenario scenario) {
        // NoOp
    }

    @Override
    public void endOfScenarioLifeCycle(Scenario scenario) {
        // NoOp
    }

    @Override
    public void before(Match match, Result result) {
        recordPreviousScenarioIfFailed();
        state = ScenarioState.In_Background_Or_Before;
        if (isTestFailed(result)) {
            isTestFailed = true;
        }
    }

    @Override
    public void result(Result result) {
        if (isTestFailed(result)) {
            isTestFailed = true;
        }
    }

    private boolean isTestFailed(Result result) {
        String status = result.getStatus();
        return Result.FAILED.equals(status) || Result.UNDEFINED.getStatus().equals(status) || "pending".equals(status);
    }

    private void recordTestFailed() {
        LinkedHashSet<Integer> failedScenarios = this.featureAndFailedLinesMapping.get(featureLocation);
        if (failedScenarios == null) {
            failedScenarios = new LinkedHashSet<Integer>();
            this.featureAndFailedLinesMapping.put(featureLocation, failedScenarios);
        }

        failedScenarios.add(scenario.getLine());
    }

    @Override
    public void after(Match match, Result result) {
        if (isTestFailed(result)) {
            isTestFailed = true;
        }
    }

    @Override
    public void match(Match match) {
    }

    @Override
    public void embedding(String mimeType, byte[] data) {
    }

    @Override
    public void write(String text) {
    }
}

@@ -899,62 +899,8 @@
 io;%0A
-    private ScenarioState state = ScenarioState.None;%0A
     
@@ -1068,91 +1068,8 @@
 );%0A%0A
-    enum ScenarioState %7B%0A        None, In_Background_Or_Before, In_Scenario%0A    %7D%0A%0A
     
@@ -1388,487 +1388,104 @@
     
-    recordPreviousScenarioIfFailed();%0A        state = ScenarioState.In_Background_Or_Before;%0A    %7D%0A%0A    @Override%0A    public void scenario(Scenario scenario) %7B%0A        recordPreviousScenarioIfFailed();%0A        state = ScenarioState.In_Scenario;%0A        this.scenario = scenario;%0A    %7D%0A%0A    private void recordPreviousScenarioIfFailed() %7B%0A        if (state == ScenarioState.In_Scenario && isTestFailed) %7B%0A            recordTestFailed();%0A            isTestFailed = false;%0A        %7D
+%7D%0A%0A    @Override%0A    public void scenario(Scenario scenario) %7B%0A        this.scenario = scenario;
 %0A   
@@ -1573,86 +1573,8 @@
 ) %7B%0A
-        recordPreviousScenarioIfFailed();%0A        state = ScenarioState.None;%0A
     
@@ -1740,86 +1740,8 @@
 ) %7B%0A
-        recordPreviousScenarioIfFailed();%0A        state = ScenarioState.None;%0A
     
@@ -2749,39 +2749,53 @@
 ario) %7B%0A        
-// NoOp
+isTestFailed = false;
 %0A    %7D%0A%0A    @Ove
@@ -2872,15 +2872,69 @@
     
-// NoOp
+if (isTestFailed) %7B%0A            recordTestFailed();%0A        %7D
 %0A   
@@ -3008,105 +3008,8 @@
 ) %7B%0A
-        recordPreviousScenarioIfFailed();%0A        state = ScenarioState.In_Background_Or_Before;%0A
     

Use the new Formatter interface methods in the RerunFormatter