package nl.hsac.fitnesse.slim;

import fitnesse.testsystems.TestExecutionException;
import fitnesse.testsystems.slim.SlimTestContext;
import fitnesse.testsystems.slim.Table;
import fitnesse.testsystems.slim.tables.*;
import nl.hsac.fitnesse.slimcoverage.SlimCoverageTestContextImpl;

import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * ScenarioTable that looks for in- and output parameters in all its rows, without the
 * parameters having to be specified in the first row also.
 */
public class AutoArgScenarioTable extends ScenarioTable {
    private static final Pattern ARG_PATTERN = Pattern.compile("@\\{(.+?)\\}");
    private static final Pattern OUT_PATTERN = Pattern.compile("\\$(.+?)=");

    private Set<String> inputs;
    private Set<String> outputs;

    private Map<String, String> currentCallArguments;

    public AutoArgScenarioTable(Table table, String tableId, SlimTestContext testContext) {
        super(table, tableId, testContext);
    }

    @Override
    public List<SlimAssertion> getAssertions() throws SyntaxError {
        inputs = findArguments(ARG_PATTERN);
        outputs = findArguments(OUT_PATTERN);
        return super.getAssertions();
    }

    @Override
    protected boolean determineParameterized() {
        return !inputs.isEmpty();
    }

    @Override
    protected void getScenarioArguments() {
        for (String input : inputs) {
            addInput(input);
        }
        for (String output : outputs) {
            addOutput(output);
        }
    }

    private Set<String> findArguments(Pattern pattern) {
        Set<String> found = new LinkedHashSet<String>();
        int rowCount = table.getRowCount();
        for (int row = 0; row < rowCount; row++) {
            int columnCount = table.getColumnCountInRow(row);
            for (int column = 0; column < columnCount; column++) {
                String cellContent = table.getCellContents(column, row);
                if (!addAllMatches(pattern, found, cellContent)
                        && maybeNestedScenario(columnCount, cellContent)) {
                    addNestedScenarioArguments(found, pattern == ARG_PATTERN, cellContent);
                }
            }
        }
        return found;
    }

    private void addNestedScenarioArguments(Set<String> found, boolean addInputs, String cellContent) {
        String scenarioName = getCalledScenarioName(cellContent);
        SlimTestContext testContext = getTestContext();
        ScenarioTable scenario;
        if (testContext instanceof SlimCoverageTestContextImpl) {
            scenario = ((SlimCoverageTestContextImpl) testContext).getScenarioNoCount(scenarioName);
        } else {
            scenario = testContext.getScenario(scenarioName);
        }
        if (scenario != null) {
            Set<String> scenarioArgs = addInputs ? scenario.getInputs() : scenario.getOutputs();
            found.addAll(scenarioArgs);
        }
    }

    private String getCalledScenarioName(String cellContent) {
        String scenarioName = cellContent.substring(0, cellContent.length() - 1);
        return Disgracer.disgraceClassName(scenarioName);
    }

    private boolean maybeNestedScenario(int columnCount, String cellContent) {
        return columnCount == 1 && cellContent.endsWith(";");
    }

    private boolean addAllMatches(Pattern pattern, Set<String> found, String cellContent) {
        boolean anyMatches = false;
        Matcher m = pattern.matcher(cellContent);
        while (m.find()) {
            String input = m.group(1);
            found.add(input);
            anyMatches = true;
        }
        return anyMatches;
    }

    public Map<String, String> getCurrentCallArguments() {
        return currentCallArguments;
    }

    @Override
    public List<SlimAssertion> call(Map<String, String> scenarioArguments,
                                    SlimTable parentTable, int row) throws TestExecutionException {
        try {
            currentCallArguments = scenarioArguments;
            if (scenarioArguments.isEmpty()) {
                SlimTestContext context = parentTable.getTestContext();
                if (context instanceof ScenarioTestContext) {
                    ScenarioTestContext sTestContext = (ScenarioTestContext) context;
                    AutoArgScenarioTable caller = getCallingTable(sTestContext);
                    Map<String, String> callerArgs = caller.getCurrentCallArguments();
                    for (Map.Entry<String, String> entry : callerArgs.entrySet()) {
                        String arg = entry.getKey();
                        if (inputs.contains(arg)) {
                            scenarioArguments.put(arg, entry.getValue());
                        }
                    }
                }
            }
            return super.call(scenarioArguments, parentTable, row);
        } finally {
            currentCallArguments = null;
        }
    }

    private AutoArgScenarioTable getCallingTable(ScenarioTestContext context) {
        ScenarioTable t = context.getScenarioTable();
        if (t instanceof AutoArgScenarioTable) {
            return (AutoArgScenarioTable) t;
        } else {
            return null;
        }
    }
}

@@ -1724,22 +1724,16 @@
 HashSet%3C
-String
 %3E();%0A   

Remove unneeded type parameter