package nl.hsac.fitnesse.junit;

import org.junit.runner.Description;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunListener;

import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * JUnit RunListener so that build log is updated with progress info while tests run.
 */
public class ProgressLoggerListener extends RunListener {
    private final PrintStream out;
    private String currentTestClassName = null;
    private SimpleDateFormat formatter;
    private int totalChildCount;
    private int currentChild;

    public ProgressLoggerListener() {
        this(System.out);
    }

    public ProgressLoggerListener(final PrintStream out) {
        this.out = out;
        this.currentTestClassName = null;
        this.formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS ");
        this.totalChildCount = 0;
        this.currentChild = 0;
    }

    @Override
    public void testRunStarted(Description description) throws Exception {
        super.testRunStarted(description);
        totalChildCount = totalChildCount(description);
        currentChild = 0;
        report("testRun Started ('%s' tests)", totalChildCount);
    }

    private int totalChildCount(Description description) {
        int count = 0;
        if (description != null) {
            count = description.testCount();
        }
        return count;
    }

    @Override
    public void testStarted(Description description) throws Exception {
        final String testClassName = getTestClassName(description);
        final String testName = getTestName(description);

        if (currentTestClassName == null || !currentTestClassName.equals(testClassName)) {
            testRunFinished(null);
            report("testSuite Started '%s'", testClassName);
            currentTestClassName = testClassName;
        }
        currentChild++;
        report("test Started '%s' (%s / %s)", testName, currentChild, totalChildCount);
    }

    @Override
    public void testFinished(Description description) throws Exception {
        final String testName = getTestName(description);

        report("test Finished '%s'\n", testName);
    }

    @Override
    public void testFailure(Failure failure) throws Exception {
        String trace = "";
        if (failure.getTrace() != null && !failure.getTrace().isEmpty()) {
            trace = failure.getTrace();
        }
        report("test Failed '%s' message='%s' details='%s'",
                getTestName(failure.getDescription()),
                "failed",
                trace);
        testFinished(failure.getDescription());
    }

    @Override
    public void testIgnored(Description description) throws Exception {
        report("test Ignored '%s'", getTestName(description));
    }

    @Override
    public void testRunFinished(Result result) throws Exception {
        if (currentTestClassName != null) {
            report("testSuite Finished '%s'\n\n", currentTestClassName);
        }
    }

    protected void report(String pattern, Object... parameters) {
        out.print(formatter.format(new Date()));
        out.println(String.format(pattern, parameters));
    }

    /**
     * @param description JUnit description of test executed
     * @return name to use in report
     */
    protected String getTestName(Description description) {
        return description.getMethodName();
    }

    protected String getTestClassName(final Description description) {
        return description.getTestClass().getName();
    }

    public void setFormatter(SimpleDateFormat formatter) {
        this.formatter = formatter;
    }
}