import sys
import atexit

# todo: make written-to stream passable


class Plan(object):
    def __init__(self, plan, param=None):
        self.counter = 0
        self.expected_tests = None
        self.ended = False

        if isinstance(plan, int):
            self.expected_tests = plan
            print("1..%u" % self.expected_tests)
        elif plan == "no_plan" or plan is None:
            True
        elif plan == "skip_all":
            print("1..0 # skip %s" % param)
            raise SystemExit(0)  # ??? this is what T::B does, but sucks
        else:
            raise TestBadPlan(plan)

    def increment_counter(self):
        self.counter += 1

    def __del__(self):
        if self.ended:
            return
        self.ended = True
        if self.expected_tests is None:
            print("1..%u" % self.counter)
        elif self.counter != self.expected_tests:
            print("# Looks like you planned %u tests but ran %u."
                  % (self.expected_tests, self.counter))


class Builder(object):
    global_defaults = {
        "_plan":    None,
        "current":  1,
        "has_plan": False,
    }
    global_test_builder = global_defaults.copy()

    def __init__(self, plan=None, plan_param=None):
        self.__dict__ = self.global_test_builder
        if plan:
            self.set_plan(plan, plan_param)

    @classmethod  # XXX: why did this fail?
    def create(cls, plan=None, plan_param=None):
        # self = new.instance(cls) # ? this sucks, too
        self = Builder()
        self.__dict__ = self.global_defaults.copy()
        if plan:
            self.set_plan(plan, plan_param)
        return self

    def set_plan(self, plan, plan_param=None):
        if self.get_plan():
            raise TestPlannedAlready(plan, plan_param)
        self._plan = Plan(plan, plan_param)
        atexit.register(self._plan.__del__)

    def get_plan(self): return self._plan

    def ok(self, is_ok, desc=None, skip=None, todo=None):
        self.get_plan().increment_counter()
        if is_ok:
            report = "ok"
        else:
            report = "not ok"

        sys.stdout.write("%s %u" % (report, self.current))

        if desc:
            sys.stdout.write(" - %s" % desc)
        if skip:
            sys.stdout.write(" # SKIP %s" % skip)
        if todo:
            sys.stdout.write(" # TODO %s" % todo)

        print()

        self.current += 1

        return is_ok

    def reset(self):
        self.__dict__.clear()
        for key in self.global_defaults.keys():
            self.__dict__[key] = self.global_defaults[key]


class TestPlannedAlready(Exception):
    def __init__(self, plan, param=None):
        self.plan = plan
        self.param = param

    def __str__(self):
        return (
            "tried to plan twice; second plan: %s, %s" % self.plan, self.param
        )


class TestWithoutPlan(Exception):
    def __str__(self):
        return "tried running tests without a plan"


class TestBadPlan(Exception):
    def __init__(self, plan):
        self.plan = plan

    def __str__(self):
        return "didn't understand plan '%s'" % self.plan