doc "Contract for things needing to be informed about the execution of tests 
     by a [[TestRunner]]."
shared interface TestListener {
    
    shared default void testRunStarted(TestRunner runner) {
    }
    
    shared default void testRunFinished(TestRunner runner, TestResult result) {
    }
    
    shared default void testStarted(TestUnit test) {
    }
    
    shared default void testFinished(TestUnit test) {
    }
    
}

doc "A [[TestListener]] which prints information about test execution to the 
     standard output."
shared class PrintingTestListener() satisfies TestListener {
    
    variable SequenceBuilder<TestUnit> errAndFail = SequenceBuilder<TestUnit>();
    
    shared actual void testRunStarted(TestRunner runner) {
        print(banner("TESTS STARTED"));
    }
    
    shared actual void testRunFinished(TestRunner runner, TestResult result) {
        if( result.runCount == 0 ) {
            print(banner("NO TESTS"));
            print("There were no tests!");
        } else {
            print(banner("TESTS RESULT"));
            print("run:     `` result.runCount ``");
            print("success: `` result.successCount ``");
            print("failure: `` result.failureCount ``");
            print("error:   `` result.errorCount ``");
            
            if (errAndFail.size > 0) {
                print("errors & failures:");
                for (errorOrFailure in errAndFail.sequence) {
                    print("``errorOrFailure.state``: ``errorOrFailure.name``");
                    if (exists ex=errorOrFailure.exception) {
                        ex.printStackTrace();
                    }
                }
            }
            
            if (result.isSuccess) {
                print(banner("TESTS SUCCESS"));
            } else {
                print(banner("TESTS FAILED"));
            }
        }
        errAndFail = SequenceBuilder<TestUnit>();
    }
    
    shared actual void testStarted(TestUnit test) {
        print(test.name);
    }
    
    shared actual void testFinished(TestUnit test) {
        switch(test.state)
        case (failure, error) {
            errAndFail.append(test);
        }
        else {
            // who cares
        }
    }
    
    doc "Generates a banner with the given text, like this:
         
         \`\``
         ============ banner ============
         \`\``
         "
    String banner(String text) {
        Character ch = '=';
        StringBuilder sb = StringBuilder();
        Integer totalWith = 60;
        Integer bannerWidth = totalWith - text.size - 2;
        for (ii in 0..bannerWidth/2) {
            sb.appendCharacter(ch);
        }
        if (bannerWidth % 2 == 1) {
            sb.appendCharacter(ch);
        }
        sb.append(" ").append(text).append(" ");
        for (ii in 0..bannerWidth/2) {
            sb.appendCharacter(ch);
        }
        return sb.string;
    }    
    
}