import inspect
import sys

from fields import Fields

from hunter.actions import CodePrinter, VarsDumper, Action, Debugger


__version__ = "0.1.0"


def trace(*predicates, **inlined):
    if "action" not in inlined:
        inlined["action"] = CodePrinter()
    predicate = F(*predicates, **inlined)

    def tracer(frame, kind, arg):
        if predicate(Event(frame, kind, arg)):
            return tracer

    sys.settrace(tracer)


def stop():
    sys.settrace(None)


class Tracer(object):
    pass


class CachedProperty(object):
    def __init__(self, func):
        self.func = func

    def __get__(self, obj, cls):
        value = obj.__dict__[self.func.__name__] = self.func(obj)
        return value


class Event(object):
    def __init__(self, frame, kind, arg):
        self.frame = frame
        self.kind = kind
        self.arg = arg

    @CachedProperty
    def locals(self):
        return self.frame.f_locals

    @CachedProperty
    def globals(self):
        return self.frame.f_locals

    @CachedProperty
    def function(self):
        return self.frame.f_code.co_name

    @CachedProperty
    def module(self):
        return self.frame.f_globals.get('__name__', None)

    @CachedProperty
    def filename(self):
        return self.frame.f_globals.get('__file__', None)

    @CachedProperty
    def lineno(self):
        return self.frame.f_lineno

    __getitem__ = object.__getattribute__


class F(Fields.query):
    def __new__(cls, *predicates, **query):
        optional_actions = query.pop("actions", [])
        if "action" in query:
            optional_actions.append(query.pop("action"))

        if predicates:
            predicates += F(**query),
            result = Or(*predicates)
        else:
            result = F(**query) if optional_actions else super(F, cls).__new__(cls)

        if optional_actions:
            result = When(result, actions=optional_actions)

        return result

    def __init__(self, **query):
        for key in query:
            if key not in ('function', 'module', 'filename'):
                raise TypeError("Unexpected argument {!r}. Must be one of 'function', 'module' or 'filename'.".format(key))
        self.query = query

    def __str__(self):
        return "F({})".format(
            ', '.join("{}={}".format(*item) for item in self.query.items()),
        )

    def __call__(self, event):
        for key, value in self.query.items():
            if event[key] != value:
                return

        return True

    def __or__(self, other):
        return Or(self, other)

    def __and__(self, other):
        return And(self, other)


class When(Fields.condition.actions):
    def __init__(self, condition, actions):
        super(When, self).__init__(condition, [
            action() if inspect.isclass(action) and issubclass(action, Action) else action
            for action in actions
        ])
    def __call__(self, event):
        if self.condition(event):
            for action in self.actions:
                action(event)

            return True


class And(Fields.predicates):
    def __init__(self, *predicates):
        self.predicates = predicates

    def __str__(self):
        return "And({})".format(', '.join(str(p) for p in self.predicates))

    def __call__(self, event):
        for predicate in self.predicates:
            if not predicate(event):
                return
        return True


class Or(Fields.predicates):
    def __init__(self, *predicates):
        self.predicates = predicates

    def __call__(self, event):
        for predicate in self.predicates:
            if predicate(event):
                return True


if __name__ == '__main__':
    trace(actions=[CodePrinter(), VarsDumper(names=['a', 'b'])])

    def foo(*x):
        #print(x)
        a = 1 + 2
        b = 3
        try:
            raise Exception('BOOM!')
        finally:
            return x

    foo(1, 2, 3)

@@ -146,175 +146,214 @@
 %22%0A%0A%0A
-def trace(*predicates, **inlined):%0A    if %22action%22 not in inlined:%0A        inlined%5B%22action%22%5D = CodePrinter()%0A    predicate = F(*predicates, **inlined)%0A%0A    def tracer(
+class Tracer(Fields.predicate):%0A    def __init__(self):%0A        self.predicate = self.not_started%0A%0A    def not_started(self, _):%0A        raise RuntimeError(%22Tracer is not started.%22)%0A%0A    def __call__(self, 
 fram
@@ -370,32 +370,37 @@
 rg):%0A        if 
+self.
 predicate(Event(
@@ -442,110 +442,467 @@
 urn 
-tracer%0A%0A    sys.settrace(tracer)%0A%0A%0Adef stop():%0A    sys.settrace(None)%0A%0A%0Aclass Tracer(object):%0A    pass
+self%0A%0A    def trace(self, *predicates, **options):%0A        if %22action%22 not in options:%0A            options%5B%22action%22%5D = CodePrinter()%0A        self.predicate = F(*predicates, **options)%0A%0A        sys.settrace(self)%0A        return self%0A%0A    def stop(self):%0A        sys.settrace(None)%0A%0A    def __enter__(self):%0A        return self%0A%0A    def __exit__(self, exc_type, exc_val, exc_tb):%0A        self.stop()%0A%0A_tracer = Tracer()%0Atrace = _tracer.trace%0Astop = _tracer.stop
 %0A%0A%0Ac

Use a class for the tracer (should help a bit with testing).