#!/usr/bin/env python3

import argparse
import re
import sys
from collections import defaultdict


def color(text, c):
    """
    Add color on the keyword that identifies the state of the test
    """
    if sys.stdout.isatty():
        clear = "\033[0m"

        colors = {
            "red": "\033[1m\033[91m",
            "yellow": "\033[1m\033[93m",
            "green": "\033[1m\033[92m",
        }
        return colors[c] + text + clear
    else:
        return text


def parse_args():
    parser = argparse.ArgumentParser(description="Report on test results")
    parser.add_argument('--summary', action="store_true",
                        help="Display only the totals in each category")
    parser.add_argument('--details', action="store_true",
                        help="Display details in each category")
    parser.add_argument('tapfile', default="all.log", nargs="?",
                        help="File containing TAP output")
    return parser.parse_args()


def print_category(tests):
    if not cmd_args.summary:
        for key in sorted(tests):
            print("%-32s %4d" % (key, tests[key]))


def print_category_with_details(tests, details):
    if not cmd_args.summary:
        for key in sorted(tests):
            print("%s (%d):" % (key, tests[key]))
            print(details[key])


def pad(i):
    return " " * i


if __name__ == "__main__":
    cmd_args = parse_args()

    errors = defaultdict(int)
    details_errors = defaultdict(str)
    skipped = defaultdict(int)
    details_skipped = defaultdict(str)
    expected = defaultdict(int)
    details_expected = defaultdict(str)
    unexpected = defaultdict(int)
    details_unexpected = defaultdict(str)
    passed = defaultdict(int)
    details_passed = defaultdict(str)

    file = re.compile(r"^# (?:./)?(\S+\.t)(?:\.exe)?$")
    detail = re.compile("^[^:]+?: (.+)$")
    timestamp = re.compile(r"^# (\d+(?:\.\d+)?) ==>.*$")

    expected_fail = re.compile(r"^not ok.*?#\s*TODO", re.I)
    unexpected_pass = re.compile(r"^ok .*?#\s*TODO", re.I)
    skip = re.compile(r"^ok .*?#\s*skip", re.I)
    ok = re.compile(r"^ok ", re.I)
    not_ok = re.compile(r"^not ok", re.I)
    comment = re.compile(r"^#")
    plan = re.compile(r"^1..(\d+)\s*(?:#.*)?$")

    start = None
    stop = None
    filename = None
    expected_test_count = 0
    need_plan = False

    with open(cmd_args.tapfile) as fh:
        for line in fh:
            if start is None:
                # First line contains the starting timestamp
                start = float(timestamp.match(line).group(1))
                continue

            match = file.match(line)
            if match:
                if filename:
                    if expected_test_count > 0:
                        print(color("'{}' failed to run all tests.".format(filename), "red"), file=sys.stderr)
                        errors[filename] += expected_test_count
                    elif need_plan:
                        print(color("'{}' failed to run any tests.".format(filename), "red"), file=sys.stderr)
                        errors[filename] += 1
                filename = match.group(1)
                need_plan = True
                continue

            match = plan.match(line)
            if match:
                expected_test_count = int(match.group(1))
                need_plan = False
                continue

            match = expected_fail.match(line)
            if match:
                expected[filename] += 1
                details_expected[filename] += line
                expected_test_count -= 1
                continue

            match = unexpected_pass.match(line)
            if match:
                unexpected[filename] += 1
                details_unexpected[filename] += line
                expected_test_count -= 1
                continue

            match = skip.match(line)
            if match:
                skipped[filename] += 1
                details_skipped[filename] += line
                expected_test_count -= 1
                continue

            # It's important these come last, since they're sub-patterns of the above

            match = ok.match(line)
            if match:
                passed[filename] += 1
                details_passed[filename] += line
                expected_test_count -= 1
                continue

            match = not_ok.match(line)
            if match:
                errors[filename] += 1
                details_errors[filename] += " - " + detail.match(line).group(1) + "\n"
                expected_test_count -= 1
                continue

            match = comment.match(line)
            if match:
                continue

            # Uncomment if you want to see malformed things we caught as well...
            # print(color("Malformed TAP (" + filename + "): " + line, "red"))

        # Last line contains the ending timestamp
        stop = float(timestamp.match(line).group(1))

    v = "{0:>5d}"
    passed_str = "Passed:" + pad(24)
    passed_int = v.format(sum(passed.values()))
    error_str = "Failed:" + pad(24)
    error_int = v.format(sum(errors.values()))
    unexpected_str = "Unexpected successes:" + pad(10)
    unexpected_int = v.format(sum(unexpected.values()))
    skipped_str = "Skipped:" + pad(23)
    skipped_int = v.format(sum(skipped.values()))
    expected_str = "Expected failures:" + pad(13)
    expected_int = v.format(sum(expected.values()))
    runtime_str = "Runtime:" + pad(20)
    runtime_int = "{0:>8.2f} seconds".format(stop - start)

    if cmd_args.summary:
        print(color(passed_str, "green"), passed_int)
        print(color(error_str, "red"), error_int)
        print(color(unexpected_str, "red"), unexpected_int)
        print(color(skipped_str, "yellow"), skipped_int)
        print(color(expected_str, "yellow"), expected_int)
        print(runtime_str, runtime_int)

    elif cmd_args.details:
        print(color(error_str, "red"))
        print_category_with_details(errors, details_errors)
        print()
        print(color(unexpected_str, "red"))
        print_category_with_details(unexpected, details_unexpected)
        print()
        print(color(skipped_str, "yellow"))
        print_category_with_details(skipped, details_skipped)
        print()
        print(color(expected_str, "yellow"))
        print_category_with_details(expected, details_expected)

    else:
        print(color(error_str, "red"))
        print_category(errors)
        print()
        print(color(unexpected_str, "red"))
        print_category(unexpected)
        print()
        print(color(skipped_str, "yellow"))
        print_category(skipped)
        print()
        print(color(expected_str, "yellow"))
        print_category(expected)

    # If we encountered any failures, return non-zero code
    sys.exit(1 if int(error_int) or int(unexpected_int) else 0)
