package me.atam.atam4j;

import me.atam.atam4jdomain.IndividualTestResult;
import me.atam.atam4jdomain.TestsRunResult;
import org.junit.runner.Description;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

public class TestRunListener extends RunListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(TestRunListener.class);

    private volatile Map<TestIdentifier, IndividualTestResult> inProgressTestResults;
    private volatile Map<TestIdentifier, IndividualTestResult> completedTestResults;
    private volatile boolean testsFinished = false;

    @Override
    public void testRunStarted(Description description) throws Exception {
        inProgressTestResults = new HashMap<>();
    }

    @Override
    public void testStarted(Description description) throws Exception {
        inProgressTestResults.put(
                new TestIdentifier(description),
                new IndividualTestResult(
                        description.getClassName(),
                        description.getMethodName(),
                        getCategoryName(description),
                        true)
        );
    }

    @Override
    public void testFailure(Failure failure) throws Exception {
        IndividualTestResult individualTestResult = inProgressTestResults.get(new TestIdentifier(failure.getDescription()));
        individualTestResult.setPassed(false);
    }

    public TestsRunResult getTestsRunResult() {
        if (testsFinished) {
            return new TestsRunResult(completedTestResults.values());
        }
        return new TestsRunResult(Collections.emptyList(), TestsRunResult.Status.TOO_EARLY);
    }

    public TestsRunResult getTestsRunResult(String category) {
        if (testsFinished) {
            // filter out tests that don't match category
            return new TestsRunResult(completedTestResults
                    .values()
                    .stream()
                    .filter(testResult -> testResult.getCategory().equalsIgnoreCase(category))
                    .collect(Collectors.toList())
            );
        }
        return new TestsRunResult(Collections.emptyList(), TestsRunResult.Status.TOO_EARLY);
    }

    @Override
    public void testRunFinished(Result result) throws Exception {
        testsFinished = true;
        completedTestResults = inProgressTestResults;
    }

    private String getCategoryName(Description description) {
        String category = "default";

        MonitorCategory monitorCategoryAnnotation = description.getAnnotation(MonitorCategory.class);
        if (monitorCategoryAnnotation != null) {
            category = monitorCategoryAnnotation.name();
        }
        return category;
    }

    private static class TestIdentifier {
        private Class testClass;
        private String testName;

        public TestIdentifier(Description description) {
            this.testClass = description.getTestClass();
            this.testName = description.getMethodName();
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            TestIdentifier that = (TestIdentifier) o;
            return Objects.equals(testClass, that.testClass) &&
                    Objects.equals(testName, that.testName);
        }

        @Override
        public int hashCode() {
            return Objects.hash(testClass, testName);
        }
    }
}

@@ -389,93 +389,9 @@
 til.
-Collections;%0Aimport java.util.HashMap;%0Aimport java.util.Map;%0Aimport java.util.Objects
+*
 ;%0Aim
@@ -481,98 +481,8 @@
  %7B%0A%0A
-    private static final Logger LOGGER = LoggerFactory.getLogger(TestRunListener.class);%0A%0A
     
@@ -1464,24 +1464,784 @@
 iption()));%0A
+        if (individualTestResult == null) %7B%0A            // This can happen is a test class throws an error in the setup phase, before individual tests and hence%0A            // testStarted method gets a chance to run%0A            IndividualTestResult result = new IndividualTestResult(%0A                                            failure.getDescription().getClassName(),%0A                                            failure.getDescription().getMethodName(),%0A                                            getCategoryName(failure.getDescription()),%0A                                            false);%0A            TestIdentifier identifier = new TestIdentifier(failure.getDescription());%0A            inProgressTestResults.put(identifier, result);%0A        %7D else %7B%0A    
         indi
@@ -2271,24 +2271,34 @@
 sed(false);%0A
+        %7D%0A
     %7D%0A%0A    p

Fix NPE when a test class throws error in setup