import functools

class TestStatus: # FIXME should really be an Enum
    pending = -1
    failure = 0
    success = 1

class Test:
    def __init__(self, func, description):
        self.func = func
        self.description = description
        self.status = TestStatus.pending
        self._callback = None
        self.manager = None

    def callback(self, f):
        self._callback = f
        return f

    def success(self):
        if self.status == TestStatus.pending:
            self.status = TestStatus.success
            self.manager._test_complete(self)

    def failure(self):
        if self.status == TestStatus.pending:
            self.status = TestStatus.failure
            self.manager._test_complete(self)

    def succeed_if(self, condition):
        if condition:
            self.success()
        else:
            self.failure()

    def __call__(self):
        if self.func is not None:
            self.func()
        if self._callback:
            self._callback()

class test:
    def __init__(self, description):
        self.description = description

    def __call__(self, f):
        return Test(f, self.description)

class TestManager:
    def __init__(self, tests):
        self.tests = tests
        self.test_status = []
        if any(not isinstance(i, Test) for i in self.tests):
            raise TypeError("Non-test passed to TestManager")

        for t in self.tests:
            t.manager = self

    def add_test(self, t):
        if not isinstance(t, Test):
            raise TypeError("Non-test passed to TestManager")

        t.manager = self

    def _all_tests_complete(self):
        print("{} tests complete.".format(len(self.tests)))
        success = len([t for t in self.tests if t.status])
        self.successes = success
        print("There were {} successes, {} failures.".format(success, len(self.tests) - success))

    def _test_complete(self, t):
        self.test_status.append((t.description, t.status))
        print("{}: {}".format(t.description, "success" if t.status else "failure"))
        if len(self.test_status) == len(self.tests):
            self._all_tests_complete()

    def run_all(self):
        for t in self.tests:
            t()
        return sum([t.status == TestStatus.failure for t in self.tests])
